artemis.py
changeset 4 bf71e2069dbd
parent 2 9e804a85c82c
child 5 cef66aa31468
equal deleted inserted replaced
3:0bd1e95af89f 4:bf71e2069dbd
    35 		property_match = True
    35 		property_match = True
    36 		for property,value in properties: 
    36 		for property,value in properties: 
    37 			property_match = property_match and (mbox[0][property] == value)
    37 			property_match = property_match and (mbox[0][property] == value)
    38 		if not show_all and not property_match: continue
    38 		if not show_all and not property_match: continue
    39 		if not date_match(util.parsedate(mbox[0]['date'], [date_format])[0]): 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 /
    40 		print "%s (%d) [%s]: %s" % (issue[len(issues_path)+1:], # +1 for trailing /
       
    41 									len(mbox)-1,				# number of replies (-1 for self)
    41 									mbox[0]['State'],
    42 									mbox[0]['State'],
    42 									mbox[0]['Subject'], 
    43 									mbox[0]['Subject'])
    43 									len(mbox)-1)				# number of replies (-1 for self)
    44 	
    44 	
    45 
    45 
    46 def add(ui, repo, id = None, comment = 0):
    46 def add(ui, repo):
    47 	"""Adds a new issue, or comment to an existing issue ID or its comment COMMENT"""
    47 	"""Adds a new issue"""
    48 	
    48 	
    49 	comment = int(comment)
       
    50 
    49 	# First, make sure issues have a directory
    51 	# First, make sure issues have a directory
    50 	issues_path = os.path.join(repo.root, issues_dir)
    52 	issues_path = os.path.join(repo.root, issues_dir)
    51 	if not os.path.exists(issues_path): os.mkdir(issues_path)
    53 	if not os.path.exists(issues_path): os.mkdir(issues_path)
       
    54 
       
    55 	if id:
       
    56 		issue_fn, issue_id = _find_issue(ui, repo, id)
       
    57 		if not issue_fn: 
       
    58 			ui.warn('No such issue')
       
    59 			return
    52 	
    60 	
    53 	user = ui.username()
    61 	user = ui.username()
    54 
    62 
    55 	default_issue_text  = 	"From: %s\nDate: %s\n" % (user,
    63 	default_issue_text  = 		"From: %s\nDate: %s\n" % (user, time.strftime(date_format))
    56 													  time.strftime(date_format))
    64 	if not id: 
    57 	default_issue_text += 	"State: %s\nSubject: brief description\n\n" % default_state
    65 		default_issue_text += 	"State: %s\n" % default_state
    58 	default_issue_text += 	"Detailed description."
    66 	default_issue_text +=		"Subject: brief description\n\n"
       
    67 	default_issue_text += 		"Detailed description."
    59 
    68 
    60 	issue = ui.edit(default_issue_text, user)
    69 	issue = ui.edit(default_issue_text, user)
    61 	if issue.strip() == '':
    70 	if issue.strip() == '':
    62 		ui.warn('Empty issue, ignoring\n')
    71 		ui.warn('Empty issue, ignoring\n')
    63 		return
    72 		return
    68 	# Create the message
    77 	# Create the message
    69 	msg = mailbox.mboxMessage(issue)
    78 	msg = mailbox.mboxMessage(issue)
    70 	msg.set_from('artemis', True)
    79 	msg.set_from('artemis', True)
    71 	
    80 	
    72 	# Pick random filename
    81 	# Pick random filename
    73 	issue_fn = issues_path
    82 	if not id:
    74 	while os.path.exists(issue_fn):
    83 		issue_fn = issues_path
    75 		issue_id = "%x" % random.randint(2**63, 2**64-1)
    84 		while os.path.exists(issue_fn):
    76 		issue_fn = os.path.join(issues_path, issue_id)
    85 			issue_id = "%x" % random.randint(2**63, 2**64-1)
    77 	msg.add_header('Message-Id', "%s-0-artemis@%s" % (issue_id, socket.gethostname()))
    86 			issue_fn = os.path.join(issues_path, issue_id)
       
    87 	# else: issue_fn already set
    78 
    88 
    79 	# Add message to the mailbox
    89 	# Add message to the mailbox
    80 	mbox = mailbox.mbox(issue_fn)
    90 	mbox = mailbox.mbox(issue_fn)
       
    91 	if id and comment not in mbox: 
       
    92 		ui.warn('No such comment number in mailbox, commenting on the issue itself\n')
       
    93 	if not id:
       
    94 		msg.add_header('Message-Id', "%s-0-artemis@%s" % (issue_id, socket.gethostname()))
       
    95 	else:
       
    96 		msg.add_header('Message-Id', "%s-%d-artemis@%s" % (issue_id, len(mbox), socket.gethostname()))
       
    97 		msg.add_header('References', mbox[(comment < len(mbox) and comment) or 0]['Message-Id'])
    81 	mbox.add(msg)
    98 	mbox.add(msg)
    82 	mbox.close()
    99 	mbox.close()
    83 
   100 
    84 	# Add the new mailbox to the repository
   101 	# If adding issue, add the new mailbox to the repository
    85 	repo.add([issue_fn[(len(repo.root)+1):]])			# +1 for the trailing /
   102 	if not id: 
    86 
   103 		repo.add([issue_fn[(len(repo.root)+1):]])			# +1 for the trailing /
    87 
   104 		ui.status('Added new issue %s\n' % issue_id)
    88 def show(ui, repo, id, comment = None):
   105 
       
   106 
       
   107 def show(ui, repo, id, comment = 0, **opts):
    89 	"""Shows issue ID, or possibly its comment COMMENT"""
   108 	"""Shows issue ID, or possibly its comment COMMENT"""
    90 	
   109 	
    91 	issue = _find_issue(ui, repo, id)
   110 	comment = int(comment)
       
   111 	issue, id = _find_issue(ui, repo, id)
    92 	if not issue: return
   112 	if not issue: return
    93 
   113 	mbox = mailbox.mbox(issue)
       
   114 
       
   115 	if opts['all']:
       
   116 		ui.write('='*70 + '\n')
       
   117 		for i in xrange(len(mbox)):
       
   118 			_write_message(ui, mbox[i], i)
       
   119 			ui.write('-'*70 + '\n')
       
   120 		return
       
   121 
       
   122 	_show_mbox(ui, mbox, comment)
       
   123 
       
   124 
       
   125 def update(ui, repo, id, **opts):
       
   126 	"""Update properties of issue ID"""
       
   127 
       
   128 	issue, id = _find_issue(ui, repo, id)
       
   129 	if not issue: return
       
   130 
       
   131 	properties = _get_properties(opts['property'])
       
   132 	
    94 	# Read the issue
   133 	# Read the issue
    95 	mbox = mailbox.mbox(issue)
   134 	mbox = mailbox.mbox(issue)
    96 	msg = mbox[0]
   135 	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 
   136 
   115 	# Fix the properties
   137 	# Fix the properties
       
   138 	properties_text = ''
   116 	for property, value in properties:
   139 	for property, value in properties:
   117 		msg.replace_header(property, value)
   140 		msg.replace_header(property, value)
       
   141 		properties_text += '%s=%s\n' % (property, value)
   118 	mbox[0] = msg
   142 	mbox[0] = msg
       
   143 
       
   144 	# Write down a comment about updated properties
       
   145 	if properties and not opts['no_property_comment']:
       
   146 		user = ui.username()
       
   147 		properties_text  = 	"From: %s\nDate: %s\nSubject: properties changes %s\n\n%s" % (user, time.strftime(date_format),
       
   148 							 [property for property, value in properties], 
       
   149 							 properties_text)
       
   150 		msg = mailbox.mboxMessage(properties_text)
       
   151 		msg.add_header('Message-Id', "%s-%d-artemis@%s" % (id, len(mbox), socket.gethostname()))
       
   152 		msg.add_header('References', mbox[0]['Message-Id'])
       
   153 		mbox.add(msg)
   119 	mbox.flush()
   154 	mbox.flush()
   120 
   155 
   121 	# Deal with comments
       
   122 
       
   123 	# Show updated message
   156 	# Show updated message
   124 	ui.write(mbox[0].as_string())
   157 	_show_mbox(ui, mbox, 0)
   125 
   158 
   126 
   159 
   127 def _find_issue(ui, repo, id):
   160 def _find_issue(ui, repo, id):
   128 	issues_path = os.path.join(repo.root, issues_dir)
   161 	issues_path = os.path.join(repo.root, issues_dir)
   129 	if not os.path.exists(issues_path): return False
   162 	if not os.path.exists(issues_path): return False
   130 
   163 
   131 	issues = glob.glob(os.path.join(issues_path, id + '*'))
   164 	issues = glob.glob(os.path.join(issues_path, id + '*'))
   132 
   165 
   133 	if len(issues) == 0:
   166 	if len(issues) == 0:
   134 		return False
   167 		return False, 0
   135 	elif len(issues) > 1:
   168 	elif len(issues) > 1:
   136 		ui.status("Multiple choices:\n")
   169 		ui.status("Multiple choices:\n")
   137 		for i in issues: ui.status('  ', i[len(issues_path)+1:], '\n')
   170 		for i in issues: ui.status('  ', i[len(issues_path)+1:], '\n')
   138 		return False
   171 		return False, 0
   139 	
   172 	
   140 	return issues[0]
   173 	return issues[0], issues[0][len(issues_path)+1:]
   141 
   174 
   142 def _get_properties(property_list):
   175 def _get_properties(property_list):
   143 	return [p.split('=') for p in property_list]
   176 	return [p.split('=') for p in property_list]
   144 	
   177 	
       
   178 def _write_message(ui, message, index = 0):
       
   179 	if index: ui.write("Comment: %d\n" % index)
       
   180 	if ui.verbose:
       
   181 		ui.write(message.as_string().strip() + '\n')
       
   182 	else:
       
   183 		if 'From' in message: ui.write('From: %s\n' % message['From'])
       
   184 		if 'Date' in message: ui.write('Date: %s\n' % message['Date'])
       
   185 		if 'Subject' in message: ui.write('Subject: %s\n' % message['Subject'])
       
   186 		if 'State' in message: ui.write('State: %s\n' % message['State'])
       
   187 		ui.write('\n' + message.get_payload().strip() + '\n')
       
   188 
       
   189 def _show_mbox(ui, mbox, comment):
       
   190 	# Output the issue (or comment)
       
   191 	if comment >= len(mbox): 
       
   192 		comment = 0
       
   193 		ui.warn('Comment out of range, showing the issue itself\n')
       
   194 	msg = mbox[comment]
       
   195 	ui.write('='*70 + '\n')
       
   196 	if comment:
       
   197 		ui.write('Subject: %s\n' % mbox[0]['Subject'])
       
   198 		ui.write('State: %s\n' % mbox[0]['State'])
       
   199 		ui.write('-'*70 + '\n')
       
   200 	_write_message(ui, msg, comment)
       
   201 	ui.write('-'*70 + '\n')
       
   202 
       
   203 	# Read the mailbox into the messages and children dictionaries
       
   204 	messages = {}
       
   205 	children = {}
       
   206 	for i in xrange(len(mbox)):
       
   207 		m = mbox[i]
       
   208 		messages[m['Message-Id']] = (i,m)
       
   209 		children.setdefault(m['References'], []).append(m['Message-Id'])
       
   210 	children[None] = []				# Safeguard against infinte loop on empty Message-Id
       
   211 
       
   212 	# Iterate over children
       
   213 	id = msg['Message-Id'] 
       
   214 	id_stack = (id in children and map(lambda x: (x, 1), reversed(children[id]))) or []
       
   215 	if not id_stack: return
       
   216 	ui.write('Comments:\n')
       
   217 	while id_stack:
       
   218 		id,offset = id_stack.pop()
       
   219 		id_stack += (id in children and map(lambda x: (x, offset+1), reversed(children[id]))) or []
       
   220 		index, msg = messages[id]
       
   221 		ui.write('  '*offset + ('%d: ' % index) + msg['Subject'] + '\n')
       
   222 	ui.write('-'*70 + '\n')
   145 
   223 
   146 
   224 
   147 cmdtable = {
   225 cmdtable = {
   148 	'ilist':	(list, 
   226 	'ilist':	(list, 
   149 				 [('a', 'all', None, 
   227 				 [('a', 'all', None, 
   153 				  ('d', 'date', '', 'restrict to issues matching the date (e.g., -d ">12/28/2007)"'),
   231 				  ('d', 'date', '', 'restrict to issues matching the date (e.g., -d ">12/28/2007)"'),
   154 				  ('f', 'filter', '', 'restrict to pre-defined filter (in %s/%s)' % (issues_dir, filter_filename))], 
   232 				  ('f', 'filter', '', 'restrict to pre-defined filter (in %s/%s)' % (issues_dir, filter_filename))], 
   155 				 _('hg ilist [OPTIONS]')),
   233 				 _('hg ilist [OPTIONS]')),
   156 	'iadd':   	(add,  
   234 	'iadd':   	(add,  
   157 				 [], 
   235 				 [], 
   158 				 _('hg iadd')),
   236 				 _('hg iadd [ID] [COMMENT]')),
   159 	'ishow':  	(show, 
   237 	'ishow':  	(show, 
   160 				 [('v', 'verbose', None, 'list the comments')], 
   238 				 [('a', 'all', None, 'list all comments')], 
   161 				 _('hg ishow ID [COMMENT]')),
   239 				 _('hg ishow [OPTIONS] ID [COMMENT]')),
   162 	'iupdate':	(update,
   240 	'iupdate':	(update,
   163 				 [('p', 'property', [], 
   241 				 [('p', 'property', [], 
   164 				   'update properties (e.g., -p state=fixed)'),
   242 				   'update properties (e.g., -p state=fixed)'),
   165 				  ('c', 'comment', 0,
   243 				  ('n', 'no-property-comment', None, 
   166 				   'add a comment to issue or its comment COMMENT')],
   244 				   'do not add a comment about changed properties')],
   167 				 _('hg iupdate [OPTIONS] ID [COMMENT]'))
   245 				 _('hg iupdate [OPTIONS] ID'))
   168 }
   246 }