artemis.py
changeset 2 9e804a85c82c
parent 1 0bbf290d6f07
child 4 bf71e2069dbd
equal deleted inserted replaced
1:0bbf290d6f07 2:9e804a85c82c
     1 #!/usr/bin/env python
     1 # Author: Dmitriy Morozov <hg@foxcub.org>, 2007
     2 
     2 
     3 from mercurial import hg
     3 """A very simple and lightweight issue tracker for Mercurial."""
       
     4 
       
     5 from mercurial import hg, util
     4 from mercurial.i18n import _
     6 from mercurial.i18n import _
     5 import os, time, random
     7 import os, time, random, mailbox, glob, socket
       
     8 
       
     9 
       
    10 new_state = "new"
       
    11 default_state = new_state
       
    12 issues_dir = ".issues"
       
    13 filter_filename = ".filters"
       
    14 date_format = '%a, %d %b %Y %H:%M:%S %Z'
       
    15 
     6 
    16 
     7 def list(ui, repo, **opts):
    17 def list(ui, repo, **opts):
     8 	"""List issues associated with the project"""
    18 	"""List issues associated with the project"""
       
    19 
       
    20 	# Process options
       
    21 	show_all = False or opts['all']
       
    22 	properties = _get_properties(opts['property']) or [['state', new_state]]
       
    23 	date_match = lambda x: True
       
    24 	if opts['date']: 
       
    25 		date_match = util.matchdate(opts['date'])
       
    26 
       
    27 	# Find issues
       
    28 	issues_path = os.path.join(repo.root, issues_dir)
       
    29 	if not os.path.exists(issues_path): return
       
    30 
       
    31 	issues = glob.glob(os.path.join(issues_path, '*'))
       
    32 	
       
    33 	for issue in issues:
       
    34 		mbox = mailbox.mbox(issue)
       
    35 		property_match = True
       
    36 		for property,value in properties: 
       
    37 			property_match = property_match and (mbox[0][property] == value)
       
    38 		if not show_all and not property_match: continue
       
    39 		if not date_match(util.parsedate(mbox[0]['date'], [date_format])[0]): continue
       
    40 		print "%s [%s]: %s (%d)" % (issue[len(issues_path)+1:], # +1 for trailing /
       
    41 									mbox[0]['State'],
       
    42 									mbox[0]['Subject'], 
       
    43 									len(mbox)-1)				# number of replies (-1 for self)
       
    44 	
     9 
    45 
    10 def add(ui, repo):
    46 def add(ui, repo):
    11 	"""Adds a new issue"""
    47 	"""Adds a new issue"""
    12 	
    48 	
    13 	# First, make sure issues have a directory
    49 	# First, make sure issues have a directory
    14 	issues_path = os.path.join(repo.root, '.issues')
    50 	issues_path = os.path.join(repo.root, issues_dir)
    15 	if not os.path.exists(issues_path): os.mkdir(issues_path)
    51 	if not os.path.exists(issues_path): os.mkdir(issues_path)
    16 	
    52 	
    17 	user = ui.username()
    53 	user = ui.username()
    18 
    54 
    19 	default_issue_text = 	"From: %s\nDate: %s\n" % (user,
    55 	default_issue_text  = 	"From: %s\nDate: %s\n" % (user,
    20 													  time.strftime('%a, %d %b %Y %H:%M:%S %Z'))
    56 													  time.strftime(date_format))
    21 	default_issue_text += 	"Status: new\nSubject: brief description\n\n"
    57 	default_issue_text += 	"State: %s\nSubject: brief description\n\n" % default_state
    22 	default_issue_text += 	"Detailed description."
    58 	default_issue_text += 	"Detailed description."
    23 
    59 
    24 	issue = ui.edit(default_issue_text, user)
    60 	issue = ui.edit(default_issue_text, user)
    25 	if issue.strip() == '':
    61 	if issue.strip() == '':
    26 		ui.warn('Empty issue, ignoring\n')
    62 		ui.warn('Empty issue, ignoring\n')
    27 		return
    63 		return
    28 	if issue.strip() == default_issue_text:
    64 	if issue.strip() == default_issue_text:
    29 		ui.warn('Unchanged issue text, ignoring\n')
    65 		ui.warn('Unchanged issue text, ignoring\n')
    30 		return
    66 		return
    31 
    67 
       
    68 	# Create the message
       
    69 	msg = mailbox.mboxMessage(issue)
       
    70 	msg.set_from('artemis', True)
       
    71 	
    32 	# Pick random filename
    72 	# Pick random filename
    33 	issue_fn = issues_path
    73 	issue_fn = issues_path
    34 	while os.path.exists(issue_fn):
    74 	while os.path.exists(issue_fn):
    35 		issue_fn = os.path.join(issues_path, "%x" % random.randint(2**32, 2**64-1))
    75 		issue_id = "%x" % random.randint(2**63, 2**64-1)
       
    76 		issue_fn = os.path.join(issues_path, issue_id)
       
    77 	msg.add_header('Message-Id', "%s-0-artemis@%s" % (issue_id, socket.gethostname()))
    36 
    78 
    37 	# FIXME: replace with creating a mailbox
    79 	# Add message to the mailbox
    38 	f =	file(issue_fn, "w")
    80 	mbox = mailbox.mbox(issue_fn)
    39 	f.write(issue)
    81 	mbox.add(msg)
    40 	f.close()
    82 	mbox.close()
    41 
    83 
       
    84 	# Add the new mailbox to the repository
    42 	repo.add([issue_fn[(len(repo.root)+1):]])			# +1 for the trailing /
    85 	repo.add([issue_fn[(len(repo.root)+1):]])			# +1 for the trailing /
    43 
    86 
    44 def show(ui, repo, id):
    87 
    45 	"""Shows issue ID"""
    88 def show(ui, repo, id, comment = None):
       
    89 	"""Shows issue ID, or possibly its comment COMMENT"""
       
    90 	
       
    91 	issue = _find_issue(ui, repo, id)
       
    92 	if not issue: return
       
    93 
       
    94 	# Read the issue
       
    95 	mbox = mailbox.mbox(issue)
       
    96 	msg = mbox[0]
       
    97 	ui.write(msg.as_string())
       
    98 
       
    99 	# Walk the mailbox, and output comments
       
   100 
       
   101 
       
   102 
       
   103 def update(ui, repo, id, **opts):
       
   104 	"""Update properties of issue ID, or add a comment to it or its comment COMMENT"""
       
   105 
       
   106 	issue = _find_issue(ui, repo, id)
       
   107 	if not issue: return
       
   108 
       
   109 	properties = _get_properties(opts['property'])
       
   110 	
       
   111 	# Read the issue
       
   112 	mbox = mailbox.mbox(issue)
       
   113 	msg = mbox[0]
       
   114 
       
   115 	# Fix the properties
       
   116 	for property, value in properties:
       
   117 		msg.replace_header(property, value)
       
   118 	mbox[0] = msg
       
   119 	mbox.flush()
       
   120 
       
   121 	# Deal with comments
       
   122 
       
   123 	# Show updated message
       
   124 	ui.write(mbox[0].as_string())
       
   125 
       
   126 
       
   127 def _find_issue(ui, repo, id):
       
   128 	issues_path = os.path.join(repo.root, issues_dir)
       
   129 	if not os.path.exists(issues_path): return False
       
   130 
       
   131 	issues = glob.glob(os.path.join(issues_path, id + '*'))
       
   132 
       
   133 	if len(issues) == 0:
       
   134 		return False
       
   135 	elif len(issues) > 1:
       
   136 		ui.status("Multiple choices:\n")
       
   137 		for i in issues: ui.status('  ', i[len(issues_path)+1:], '\n')
       
   138 		return False
       
   139 	
       
   140 	return issues[0]
       
   141 
       
   142 def _get_properties(property_list):
       
   143 	return [p.split('=') for p in property_list]
       
   144 	
       
   145 
    46 
   146 
    47 cmdtable = {
   147 cmdtable = {
    48 	'issues-list':	(list, 
   148 	'ilist':	(list, 
    49 					 [('s', 'status', None, 'restrict status')], 
   149 				 [('a', 'all', None, 
    50 					 _('hg issues-list')),
   150 				   'list all issues (by default only those with state new)'),
    51 	'issues-add':   (add,  
   151 				  ('p', 'property', [], 
    52 					 [], 
   152 				   'list issues with specific field values (e.g., -p state=fixed)'),
    53 					 _('hg issues-add')),
   153 				  ('d', 'date', '', 'restrict to issues matching the date (e.g., -d ">12/28/2007)"'),
    54 	'issues-show':  (show, 
   154 				  ('f', 'filter', '', 'restrict to pre-defined filter (in %s/%s)' % (issues_dir, filter_filename))], 
    55 					 [], 
   155 				 _('hg ilist [OPTIONS]')),
    56 					 _('hg issues-show ID'))
   156 	'iadd':   	(add,  
       
   157 				 [], 
       
   158 				 _('hg iadd')),
       
   159 	'ishow':  	(show, 
       
   160 				 [('v', 'verbose', None, 'list the comments')], 
       
   161 				 _('hg ishow ID [COMMENT]')),
       
   162 	'iupdate':	(update,
       
   163 				 [('p', 'property', [], 
       
   164 				   'update properties (e.g., -p state=fixed)'),
       
   165 				  ('c', 'comment', 0,
       
   166 				   'add a comment to issue or its comment COMMENT')],
       
   167 				 _('hg iupdate [OPTIONS] ID [COMMENT]'))
    57 }
   168 }