list works well, basic show, add, and update functionality
authorDmitriy Morozov <morozov@cs.duke.edu>
Sat, 29 Dec 2007 02:50:00 -0500
changeset 2 9e804a85c82c
parent 1 0bbf290d6f07
child 3 0bd1e95af89f
list works well, basic show, add, and update functionality
artemis.py
--- a/artemis.py	Thu Dec 27 13:14:58 2007 -0500
+++ b/artemis.py	Sat Dec 29 02:50:00 2007 -0500
@@ -1,24 +1,60 @@
-#!/usr/bin/env python
+# Author: Dmitriy Morozov <hg@foxcub.org>, 2007
+
+"""A very simple and lightweight issue tracker for Mercurial."""
+
+from mercurial import hg, util
+from mercurial.i18n import _
+import os, time, random, mailbox, glob, socket
 
-from mercurial import hg
-from mercurial.i18n import _
-import os, time, random
+
+new_state = "new"
+default_state = new_state
+issues_dir = ".issues"
+filter_filename = ".filters"
+date_format = '%a, %d %b %Y %H:%M:%S %Z'
+
 
 def list(ui, repo, **opts):
 	"""List issues associated with the project"""
 
+	# Process options
+	show_all = False or opts['all']
+	properties = _get_properties(opts['property']) or [['state', new_state]]
+	date_match = lambda x: True
+	if opts['date']: 
+		date_match = util.matchdate(opts['date'])
+
+	# Find issues
+	issues_path = os.path.join(repo.root, issues_dir)
+	if not os.path.exists(issues_path): return
+
+	issues = glob.glob(os.path.join(issues_path, '*'))
+	
+	for issue in issues:
+		mbox = mailbox.mbox(issue)
+		property_match = True
+		for property,value in properties: 
+			property_match = property_match and (mbox[0][property] == value)
+		if not show_all and not property_match: continue
+		if not date_match(util.parsedate(mbox[0]['date'], [date_format])[0]): continue
+		print "%s [%s]: %s (%d)" % (issue[len(issues_path)+1:], # +1 for trailing /
+									mbox[0]['State'],
+									mbox[0]['Subject'], 
+									len(mbox)-1)				# number of replies (-1 for self)
+	
+
 def add(ui, repo):
 	"""Adds a new issue"""
 	
 	# First, make sure issues have a directory
-	issues_path = os.path.join(repo.root, '.issues')
+	issues_path = os.path.join(repo.root, issues_dir)
 	if not os.path.exists(issues_path): os.mkdir(issues_path)
 	
 	user = ui.username()
 
-	default_issue_text = 	"From: %s\nDate: %s\n" % (user,
-													  time.strftime('%a, %d %b %Y %H:%M:%S %Z'))
-	default_issue_text += 	"Status: new\nSubject: brief description\n\n"
+	default_issue_text  = 	"From: %s\nDate: %s\n" % (user,
+													  time.strftime(date_format))
+	default_issue_text += 	"State: %s\nSubject: brief description\n\n" % default_state
 	default_issue_text += 	"Detailed description."
 
 	issue = ui.edit(default_issue_text, user)
@@ -29,29 +65,104 @@
 		ui.warn('Unchanged issue text, ignoring\n')
 		return
 
+	# Create the message
+	msg = mailbox.mboxMessage(issue)
+	msg.set_from('artemis', True)
+	
 	# Pick random filename
 	issue_fn = issues_path
 	while os.path.exists(issue_fn):
-		issue_fn = os.path.join(issues_path, "%x" % random.randint(2**32, 2**64-1))
+		issue_id = "%x" % random.randint(2**63, 2**64-1)
+		issue_fn = os.path.join(issues_path, issue_id)
+	msg.add_header('Message-Id', "%s-0-artemis@%s" % (issue_id, socket.gethostname()))
 
-	# FIXME: replace with creating a mailbox
-	f =	file(issue_fn, "w")
-	f.write(issue)
-	f.close()
+	# Add message to the mailbox
+	mbox = mailbox.mbox(issue_fn)
+	mbox.add(msg)
+	mbox.close()
 
+	# Add the new mailbox to the repository
 	repo.add([issue_fn[(len(repo.root)+1):]])			# +1 for the trailing /
 
-def show(ui, repo, id):
-	"""Shows issue ID"""
+
+def show(ui, repo, id, comment = None):
+	"""Shows issue ID, or possibly its comment COMMENT"""
+	
+	issue = _find_issue(ui, repo, id)
+	if not issue: return
+
+	# Read the issue
+	mbox = mailbox.mbox(issue)
+	msg = mbox[0]
+	ui.write(msg.as_string())
+
+	# Walk the mailbox, and output comments
+
+
+
+def update(ui, repo, id, **opts):
+	"""Update properties of issue ID, or add a comment to it or its comment COMMENT"""
+
+	issue = _find_issue(ui, repo, id)
+	if not issue: return
+
+	properties = _get_properties(opts['property'])
+	
+	# Read the issue
+	mbox = mailbox.mbox(issue)
+	msg = mbox[0]
+
+	# Fix the properties
+	for property, value in properties:
+		msg.replace_header(property, value)
+	mbox[0] = msg
+	mbox.flush()
+
+	# Deal with comments
+
+	# Show updated message
+	ui.write(mbox[0].as_string())
+
+
+def _find_issue(ui, repo, id):
+	issues_path = os.path.join(repo.root, issues_dir)
+	if not os.path.exists(issues_path): return False
+
+	issues = glob.glob(os.path.join(issues_path, id + '*'))
+
+	if len(issues) == 0:
+		return False
+	elif len(issues) > 1:
+		ui.status("Multiple choices:\n")
+		for i in issues: ui.status('  ', i[len(issues_path)+1:], '\n')
+		return False
+	
+	return issues[0]
+
+def _get_properties(property_list):
+	return [p.split('=') for p in property_list]
+	
+
 
 cmdtable = {
-	'issues-list':	(list, 
-					 [('s', 'status', None, 'restrict status')], 
-					 _('hg issues-list')),
-	'issues-add':   (add,  
-					 [], 
-					 _('hg issues-add')),
-	'issues-show':  (show, 
-					 [], 
-					 _('hg issues-show ID'))
+	'ilist':	(list, 
+				 [('a', 'all', None, 
+				   'list all issues (by default only those with state new)'),
+				  ('p', 'property', [], 
+				   'list issues with specific field values (e.g., -p state=fixed)'),
+				  ('d', 'date', '', 'restrict to issues matching the date (e.g., -d ">12/28/2007)"'),
+				  ('f', 'filter', '', 'restrict to pre-defined filter (in %s/%s)' % (issues_dir, filter_filename))], 
+				 _('hg ilist [OPTIONS]')),
+	'iadd':   	(add,  
+				 [], 
+				 _('hg iadd')),
+	'ishow':  	(show, 
+				 [('v', 'verbose', None, 'list the comments')], 
+				 _('hg ishow ID [COMMENT]')),
+	'iupdate':	(update,
+				 [('p', 'property', [], 
+				   'update properties (e.g., -p state=fixed)'),
+				  ('c', 'comment', 0,
+				   'add a comment to issue or its comment COMMENT')],
+				 _('hg iupdate [OPTIONS] ID [COMMENT]'))
 }