Fixed #af5 (summary formats provided in the config file)
authorDmitriy Morozov <dmitriy@mrzv.org>
Mon, 18 Apr 2011 22:51:20 -0700
changeset 64 4129876c8b86
parent 63 0bfd48368c17
child 65 30bd40ef9165
Fixed #af5 (summary formats provided in the config file)
.issues/af5b63a7d86cf9e3/new/1303167129.M622016P25911Q1.vine
.issues/af5b63a7d86cf9e3/new/1303177038.M916213P1224Q1.vine
.issues/af5b63a7d86cf9e3/new/1303189206.M66056P5702Q1.vine
.issues/c257b7f62fae9087/new/1302933431.M193154P8736Q1.vine
.issues/c257b7f62fae9087/new/1303189598.M796344P5917Q1.vine
README
artemis/artemis.py
artemis/termcolor.py
--- 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 <dmitriy@mrzv.org>
 Date: Mon, 18 Apr 2011 15:41:53 -0700
-State: new
+State: resolved
 Subject: Configurable format
 Message-Id: <af5b63a7d86cf9e3-0-artemis@vine>
+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:
--- /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 <dmitriy@mrzv.org>
+Date: Mon, 18 Apr 2011 18:37:18 -0700
+Subject: changed properties (state=in-progress)
+Message-Id: <af5b63a7d86cf9e3-eebc359fa41d3fb5-artemis@vine>
+References: <af5b63a7d86cf9e3-0-artemis@vine>
+In-Reply-To: <af5b63a7d86cf9e3-0-artemis@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 <dmitriy@mrzv.org>
+Date: Mon, 18 Apr 2011 22:00:05 -0700
+Subject: Fixed (with changes in the format)
+Message-Id: <af5b63a7d86cf9e3-80e747b5d9f0b2ed-artemis@vine>
+References: <af5b63a7d86cf9e3-0-artemis@vine>
+In-Reply-To: <af5b63a7d86cf9e3-0-artemis@vine>
+
+Equal sign wouldn't work in the config file variable name, so instead the format
+is:
+
+  format:property1*value1&propert2*value2 = ...
--- 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 <dmitriy@mrzv.org>
 Date: Fri, 15 Apr 2011 22:54:20 -0700
-State: new
+State: resolved
 Subject: State annotations
 Message-Id: <c257b7f62fae9087-0-artemis@vine>
+resolution: wontfix
 
 Make state annotations more generic. Right now, only "resolved" state lists
 the contents of the property resolution, i.e.
--- /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 <dmitriy@mrzv.org>
+Date: Mon, 18 Apr 2011 22:04:40 -0700
+Subject: Irrelevant after #af5
+Message-Id: <c257b7f62fae9087-cdca9c416bd790fa-artemis@vine>
+References: <c257b7f62fae9087-0-artemis@vine>
+In-Reply-To: <c257b7f62fae9087-0-artemis@vine>
+
+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.
--- 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
--- 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,
--- 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 <konstantin.lepa@gmail.com>
-
-"""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'])
-