# HG changeset patch # User Dmitriy Morozov # Date 1303192280 25200 # Node ID 4129876c8b867d8413c725f5ec23a207bfd59206 # Parent 0bfd48368c17e2a8dcabe02eaaa5f10c21668760 Fixed #af5 (summary formats provided in the config file) diff -r 0bfd48368c17 -r 4129876c8b86 .issues/af5b63a7d86cf9e3/new/1303167129.M622016P25911Q1.vine --- a/.issues/af5b63a7d86cf9e3/new/1303167129.M622016P25911Q1.vine Mon Apr 18 15:52:28 2011 -0700 +++ b/.issues/af5b63a7d86cf9e3/new/1303167129.M622016P25911Q1.vine Mon Apr 18 22:51:20 2011 -0700 @@ -1,8 +1,9 @@ From: Dmitriy Morozov Date: Mon, 18 Apr 2011 15:41:53 -0700 -State: new +State: resolved Subject: Configurable format Message-Id: +resolution: fixed The user should be able to define the format for the issue summary line (in ilist) in the config file. So, for instance, one could specify: diff -r 0bfd48368c17 -r 4129876c8b86 .issues/af5b63a7d86cf9e3/new/1303177038.M916213P1224Q1.vine --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.issues/af5b63a7d86cf9e3/new/1303177038.M916213P1224Q1.vine Mon Apr 18 22:51:20 2011 -0700 @@ -0,0 +1,7 @@ +From: Dmitriy Morozov +Date: Mon, 18 Apr 2011 18:37:18 -0700 +Subject: changed properties (state=in-progress) +Message-Id: +References: +In-Reply-To: + diff -r 0bfd48368c17 -r 4129876c8b86 .issues/af5b63a7d86cf9e3/new/1303189206.M66056P5702Q1.vine --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.issues/af5b63a7d86cf9e3/new/1303189206.M66056P5702Q1.vine Mon Apr 18 22:51:20 2011 -0700 @@ -0,0 +1,11 @@ +From: Dmitriy Morozov +Date: Mon, 18 Apr 2011 22:00:05 -0700 +Subject: Fixed (with changes in the format) +Message-Id: +References: +In-Reply-To: + +Equal sign wouldn't work in the config file variable name, so instead the format +is: + + format:property1*value1&propert2*value2 = ... diff -r 0bfd48368c17 -r 4129876c8b86 .issues/c257b7f62fae9087/new/1302933431.M193154P8736Q1.vine --- a/.issues/c257b7f62fae9087/new/1302933431.M193154P8736Q1.vine Mon Apr 18 15:52:28 2011 -0700 +++ b/.issues/c257b7f62fae9087/new/1302933431.M193154P8736Q1.vine Mon Apr 18 22:51:20 2011 -0700 @@ -1,8 +1,9 @@ From: Dmitriy Morozov Date: Fri, 15 Apr 2011 22:54:20 -0700 -State: new +State: resolved Subject: State annotations Message-Id: +resolution: wontfix Make state annotations more generic. Right now, only "resolved" state lists the contents of the property resolution, i.e. diff -r 0bfd48368c17 -r 4129876c8b86 .issues/c257b7f62fae9087/new/1303189598.M796344P5917Q1.vine --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.issues/c257b7f62fae9087/new/1303189598.M796344P5917Q1.vine Mon Apr 18 22:51:20 2011 -0700 @@ -0,0 +1,9 @@ +From: Dmitriy Morozov +Date: Mon, 18 Apr 2011 22:04:40 -0700 +Subject: Irrelevant after #af5 +Message-Id: +References: +In-Reply-To: + +The fix to #af5 makes this issue irrelevant. Annotations are too specialized +and redundant once the summary format can be set in the config file. diff -r 0bfd48368c17 -r 4129876c8b86 README --- a/README Mon Apr 18 15:52:28 2011 -0700 +++ b/README Mon Apr 18 22:51:20 2011 -0700 @@ -35,21 +35,9 @@ [artemis] issues = _issues -Additionally, one can define colors for the issue list, based on the -issues' states. For example:: +Additionally, one can specify filters_ and output formats_. - new.color = red - new.attrs = bold - resolved.color = white - in-progress.color = yellow - in-progress.attrs = bold - -will highlight issues with the state ``new`` as bold red, ``resolved`` issues -as white, and ``in-progress`` issues as bold yellow. Artemis uses termcolor_ -library for the output, see its documentation for color options. - -.. _`termcolor`: http://pypi.python.org/pypi/termcolor/ - +.. _formats: Format_ Example ------- @@ -214,3 +202,34 @@ invoked with the `ilist` command:: hg ilist -f olddoc + + +Format +------ + +One can specify the output format for the `ilist` command. The default looks +like:: + + [artemis] + format = %(id)s (%(len)3d) [%(state)s]: %(subject)s + +Artemis passes a dictionary with the issue properties to the format string. +(Plus ``id`` contains the issue id, and ``len`` contains the number of replies.) + +It's possible to specify different output formats depending on the properties of +the issue. The conditions are encoded in the config variable names as follows:: + + format:state*resolved&resolution*fixed = %(id)s (%(len)3d) [fixed]: %(Subject)s + format:state*resolved = %(id)s (%(len)3d) [%(state)s=%(resolution)s]: %(Subject)s + +The first rule matches issues with the ``state`` property set to ``resolved`` +and ``resolution`` set to ``fixed``; it abridges the output. The secod rule +matches all the ``resolved`` issues (not matched by the first rule); it annotates +the issue's state with its ``resolution``. + +Finally, the dictionary passed to the format string contains a subset of +`ANSI codes`_, so one could color the summary lines:: + + format:state*new = %(red)s%(bold)s%(id)s (%(len)3d) [%(state)s]: %(Subject)s%(reset)s + +.. _`ANSI codes`: http://en.wikipedia.org/wiki/ANSI_escape_code diff -r 0bfd48368c17 -r 4129876c8b86 artemis/artemis.py --- a/artemis/artemis.py Mon Apr 18 15:52:28 2011 -0700 +++ b/artemis/artemis.py Mon Apr 18 22:51:20 2011 -0700 @@ -13,19 +13,17 @@ from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText - -from termcolor import colored +from itertools import izip state = { 'new': ['new'], 'resolved': ['fixed', 'resolved'] } -annotation = { 'resolved': 'resolution' } default_state = 'new' default_issues_dir = ".issues" filter_prefix = ".filter" date_format = '%a, %d %b %Y %H:%M:%S %1%2' maildir_dirs = ['new','cur','tmp'] - +default_format = '%(id)s (%(len)3d) [%(state)s]: %(Subject)s' def ilist(ui, repo, **opts): """List issues associated with the project""" @@ -40,8 +38,8 @@ if opts['order']: order = opts['order'] - # Colors - colors = _read_colors(ui) + # Formats + formats = _read_formats(ui) # Find issues issues_dir = ui.config('artemis', 'issues', default = default_issues_dir) @@ -83,7 +81,7 @@ if match_date and not date_match(util.parsedate(mbox[root]['date'])[0]): continue if not list_properties: - summaries.append((_summary_line(mbox, root, issue[len(issues_path)+1:], colors), # +1 for trailing / + summaries.append((_summary_line(mbox, root, issue[len(issues_path)+1:], formats), # +1 for trailing / _find_mbox_date(mbox, root, order))) else: for lp in list_properties: @@ -92,7 +90,7 @@ if not list_properties: summaries.sort(lambda (s1,d1),(s2,d2): cmp(d2,d1)) for s,d in summaries: - ui.write(s) + ui.write(s + '\n') else: for lp in list_properties_dict.keys(): ui.write("%s:\n" % lp) @@ -418,43 +416,60 @@ outer.attach(attachment) return outer -def _status_msg(msg): - s = msg['State'] - if s in annotation: - return '%s=%s' % (s, msg[annotation[s]]) - else: - return s - -def _read_colors(ui): - colors = {} +def _read_formats(ui): + formats = [] + global default_format for k,v in ui.configitems('artemis'): - if k == 'issues': continue - k = k.split('.') - s = k[0]; t = k[1] - if s not in colors: colors[s] = {} - colors[s][t] = v + if not k.startswith('format'): continue + if k == 'format': + default_format = v + continue + formats.append((k.split(':')[1], v)) + + return formats - return colors +def _format_match(props, formats): + for k,v in formats: + eq = k.split('&') + eq = [e.split('*') for e in eq] + for e in eq: + if props[e[0]] != e[1]: + break + else: + return v + + return default_format + +def _summary_line(mbox, root, issue, formats): + props = PropertiesDictionary(mbox[root]) + props['id'] = issue + props['len'] = len(mbox)-1 # number of replies (-1 for self) -def _color_summary(line, msg, colors): - s = msg['State'] - for alias, l in state.items(): - if s in l: s = alias; break - if s in colors: - color = colors[s]['color'] if 'color' in colors[s] else None - on_color = colors[s]['on_color'] if 'on_color' in colors[s] else None - attrs = colors[s]['attrs'].split() if 'attrs' in colors[s] else None - return colored(line, color, on_color, attrs) - else: - return line + return _format_match(props, formats) % props + +class PropertiesDictionary(dict): + def __init__(self, msg): + # Borrowed from termcolor + for k,v in zip(['bold', 'dark', '', 'underline', 'blink', '', 'reverse', 'concealed'], range(1, 9)) + \ + zip(['grey', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'], range(30, 38)): + self[k] = '\033[' + str(v) + 'm' + self['reset'] = '\033[0m' + del self[''] -def _summary_line(mbox, root, issue, colors): - line = "%s (%3d) [%s]: %s\n" % (issue, - len(mbox)-1, # number of replies (-1 for self) - _status_msg(mbox[root]), - mbox[root]['Subject']) - return _color_summary(line, mbox[root], colors) + for k,v in msg.items(): + self[k] = v + + def __contains__(self, k): + return super(PropertiesDictionary, self).__contains__(k.lower()) + + def __getitem__(self, k): + if k not in self: return '' + return super(PropertiesDictionary, self).__getitem__(k.lower()) + + def __setitem__(self, k, v): + super(PropertiesDictionary, self).__setitem__(k.lower(), v) + cmdtable = { 'ilist': (ilist, diff -r 0bfd48368c17 -r 4129876c8b86 artemis/termcolor.py --- a/artemis/termcolor.py Mon Apr 18 15:52:28 2011 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,168 +0,0 @@ -# coding: utf-8 -# Copyright (c) 2008-2011 Volvox Development Team -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# Author: Konstantin Lepa - -"""ANSII Color formatting for output in terminal.""" - -from __future__ import print_function -import os - - -__ALL__ = [ 'colored', 'cprint' ] - -VERSION = (1, 1, 0) - -ATTRIBUTES = dict( - list(zip([ - 'bold', - 'dark', - '', - 'underline', - 'blink', - '', - 'reverse', - 'concealed' - ], - list(range(1, 9)) - )) - ) -del ATTRIBUTES[''] - - -HIGHLIGHTS = dict( - list(zip([ - 'on_grey', - 'on_red', - 'on_green', - 'on_yellow', - 'on_blue', - 'on_magenta', - 'on_cyan', - 'on_white' - ], - list(range(40, 48)) - )) - ) - - -COLORS = dict( - list(zip([ - 'grey', - 'red', - 'green', - 'yellow', - 'blue', - 'magenta', - 'cyan', - 'white', - ], - list(range(30, 38)) - )) - ) - - -RESET = '\033[0m' - - -def colored(text, color=None, on_color=None, attrs=None): - """Colorize text. - - Available text colors: - red, green, yellow, blue, magenta, cyan, white. - - Available text highlights: - on_red, on_green, on_yellow, on_blue, on_magenta, on_cyan, on_white. - - Available attributes: - bold, dark, underline, blink, reverse, concealed. - - Example: - colored('Hello, World!', 'red', 'on_grey', ['blue', 'blink']) - colored('Hello, World!', 'green') - """ - if os.getenv('ANSI_COLORS_DISABLED') is None: - fmt_str = '\033[%dm%s' - if color is not None: - text = fmt_str % (COLORS[color], text) - - if on_color is not None: - text = fmt_str % (HIGHLIGHTS[on_color], text) - - if attrs is not None: - for attr in attrs: - text = fmt_str % (ATTRIBUTES[attr], text) - - text += RESET - return text - - -def cprint(text, color=None, on_color=None, attrs=None, **kwargs): - """Print colorize text. - - It accepts arguments of print function. - """ - - print((colored(text, color, on_color, attrs)), **kwargs) - - -if __name__ == '__main__': - print('Current terminal type: %s' % os.getenv('TERM')) - print('Test basic colors:') - cprint('Grey color', 'grey') - cprint('Red color', 'red') - cprint('Green color', 'green') - cprint('Yellow color', 'yellow') - cprint('Blue color', 'blue') - cprint('Magenta color', 'magenta') - cprint('Cyan color', 'cyan') - cprint('White color', 'white') - print(('-' * 78)) - - print('Test highlights:') - cprint('On grey color', on_color='on_grey') - cprint('On red color', on_color='on_red') - cprint('On green color', on_color='on_green') - cprint('On yellow color', on_color='on_yellow') - cprint('On blue color', on_color='on_blue') - cprint('On magenta color', on_color='on_magenta') - cprint('On cyan color', on_color='on_cyan') - cprint('On white color', color='grey', on_color='on_white') - print('-' * 78) - - print('Test attributes:') - cprint('Bold grey color', 'grey', attrs=['bold']) - cprint('Dark red color', 'red', attrs=['dark']) - cprint('Underline green color', 'green', attrs=['underline']) - cprint('Blink yellow color', 'yellow', attrs=['blink']) - cprint('Reversed blue color', 'blue', attrs=['reverse']) - cprint('Concealed Magenta color', 'magenta', attrs=['concealed']) - cprint('Bold underline reverse cyan color', 'cyan', - attrs=['bold', 'underline', 'reverse']) - cprint('Dark blink concealed white color', 'white', - attrs=['dark', 'blink', 'concealed']) - print(('-' * 78)) - - print('Test mixing:') - cprint('Underline red on grey color', 'red', 'on_grey', - ['underline']) - cprint('Reversed green on red color', 'green', 'on_red', ['reverse']) -