]> git.notmuchmail.org Git - notmuch/blobdiff - devel/nmbug/nmbug-status
nmbug-status: add support for specifying sort order for each view
[notmuch] / devel / nmbug / nmbug-status
index e137a90ed07582dd0afe6af66aa984c3bbf7724f..a289798e3cc18559584de5cccda81d0f95c4e392 100755 (executable)
@@ -69,28 +69,60 @@ if not hasattr(collections, 'OrderedDict'):  # Python 2.6 or earlier
     collections.OrderedDict = _OrderedDict
 
 
+class ConfigError (Exception):
+    """Errors with config file usage
+    """
+    pass
+
+
 def read_config(path=None, encoding=None):
     "Read config from json file"
     if not encoding:
         encoding = _ENCODING
     if path:
-        fp = open(path)
+        try:
+            with open(path, 'rb') as f:
+                config_bytes = f.read()
+        except IOError as e:
+            raise ConfigError('Could not read config from {}'.format(path))
     else:
         nmbhome = os.getenv('NMBGIT', os.path.expanduser('~/.nmbug'))
+        branch = 'config'
+        filename = 'status-config.json'
 
         # read only the first line from the pipe
         sha1_bytes = subprocess.Popen(
-            ['git', '--git-dir', nmbhome, 'show-ref', '-s', 'config'],
+            ['git', '--git-dir', nmbhome, 'show-ref', '-s', '--heads', branch],
             stdout=subprocess.PIPE).stdout.readline()
         sha1 = sha1_bytes.decode(encoding).rstrip()
+        if not sha1:
+            raise ConfigError(
+                ("No local branch '{branch}' in {nmbgit}.  "
+                 'Checkout a local {branch} branch or explicitly set --config.'
+                ).format(branch=branch, nmbgit=nmbhome))
 
-        fp_byte_stream = subprocess.Popen(
+        p = subprocess.Popen(
             ['git', '--git-dir', nmbhome, 'cat-file', 'blob',
-             sha1+':status-config.json'],
-            stdout=subprocess.PIPE).stdout
-        fp = codecs.getreader(encoding=encoding)(stream=fp_byte_stream)
-
-    return json.load(fp)
+             '{}:{}'.format(sha1, filename)],
+            stdout=subprocess.PIPE)
+        config_bytes, err = p.communicate()
+        status = p.wait()
+        if status != 0:
+            raise ConfigError(
+                ("Missing status-config.json in branch '{branch}' of"
+                 '{nmbgit}.  Add the file or explicitly set --config.'
+                ).format(branch=branch, nmbgit=nmbhome))
+
+    config_json = config_bytes.decode(encoding)
+    try:
+        return json.loads(config_json)
+    except ValueError as e:
+        if not path:
+            path = "{} in branch '{}' of {}".format(
+                filename, branch, nmbhome)
+        raise ConfigError(
+            'Could not parse JSON from the config file {}:\n{}'.format(
+                path, e))
 
 
 class Thread (list):
@@ -124,11 +156,20 @@ class Page (object):
             stream.write(self.footer)
 
     def _write_view(self, database, view, stream):
+        # sort order, default to oldest-first
+        sort_key = view.get('sort', 'oldest-first')
+        # dynamically accept all values in Query.SORT
+        sort_attribute = sort_key.upper().replace('-', '_')
+        try:
+            sort = getattr(notmuch.Query.SORT, sort_attribute)
+        except AttributeError:
+            raise ConfigError('Invalid sort setting for {}: {!r}'.format(
+                view['title'], sort_key))
         if 'query-string' not in view:
             query = view['query']
             view['query-string'] = ' and '.join(query)
         q = notmuch.Query(database, view['query-string'])
-        q.set_sort(notmuch.Query.SORT.OLDEST_FIRST)
+        q.set_sort(sort)
         threads = self._get_threads(messages=q.search_messages())
         self._write_view_header(view=view, stream=stream)
         self._write_threads(threads=threads, stream=stream)
@@ -274,11 +315,13 @@ parser.add_argument('--get-query', help='get query for view',
 
 args = parser.parse_args()
 
-config = read_config(path=args.config)
+try:
+    config = read_config(path=args.config)
+except ConfigError as e:
+    print(e)
+    sys.exit(1)
 
-_PAGES['text'] = Page()
-_PAGES['html'] = HtmlPage(
-    header='''<!DOCTYPE html>
+header_template = config['meta'].get('header', '''<!DOCTYPE html>
 <html lang="en">
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset={encoding}" />
@@ -315,22 +358,43 @@ _PAGES['html'] = HtmlPage(
     tbody:nth-child(4n+3) tr td {{
       background-color: #bce;
     }}
+    hr {{
+      border: 0;
+      height: 1px;
+      color: #ccc;
+      background-color: #ccc;
+    }}
   </style>
 </head>
 <body>
 <h2>{title}</h2>
-<p>
-Generated: {date}<br />
 {blurb}
 </p>
 <h3>Views</h3>
-'''.format(date=datetime.datetime.utcnow().date(),
-           title=config['meta']['title'],
-           blurb=config['meta']['blurb'],
-           encoding=_ENCODING,
-           inter_message_padding='0.25em',
-           border_radius='0.5em'),
-    footer='</body>\n</html>\n',
+''')
+
+footer_template = config['meta'].get('footer', '''
+<hr>
+<p>Generated: {datetime}
+</body>
+</html>
+''')
+
+now = datetime.datetime.utcnow()
+context = {
+    'date': now,
+    'datetime': now.strftime('%Y-%m-%d %H:%M:%SZ'),
+    'title': config['meta']['title'],
+    'blurb': config['meta']['blurb'],
+    'encoding': _ENCODING,
+    'inter_message_padding': '0.25em',
+    'border_radius': '0.5em',
+    }
+
+_PAGES['text'] = Page()
+_PAGES['html'] = HtmlPage(
+    header=header_template.format(**context),
+    footer=footer_template.format(**context),
     )
 
 if args.list_views: