X-Git-Url: https://git.notmuchmail.org/git?a=blobdiff_plain;f=devel%2Fnmbug%2Fnmbug-status;h=94be6717c5f6fbbf012309f50b5a12771b8154ad;hb=88171f34ed140e0701c2101e978d993673b423d2;hp=1c390e6d391de3a71c510a759d9e11e8e90be9dc;hpb=aaa7f0d92ee9c876c38da43be5c49e8d5c73a99b;p=notmuch diff --git a/devel/nmbug/nmbug-status b/devel/nmbug/nmbug-status index 1c390e6d..94be6717 100755 --- a/devel/nmbug/nmbug-status +++ b/devel/nmbug/nmbug-status @@ -1,10 +1,30 @@ #!/usr/bin/python # # Copyright (c) 2011-2012 David Bremner -# License: Same as notmuch +# # dependencies # - python 2.6 for json # - argparse; either python 2.7, or install separately +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/ . + +"""Generate text and/or HTML for one or more notmuch searches. + +Messages matching each search are grouped by thread. Each message +that contains both a subject and message-id will have the displayed +subject link to an archive view of the message (defaulting to Gmane). +""" from __future__ import print_function from __future__ import unicode_literals @@ -13,7 +33,6 @@ import codecs import collections import datetime import email.utils -import locale try: # Python 3 from urllib.parse import quote except ImportError: # Python 2 @@ -27,7 +46,7 @@ import subprocess import xml.sax.saxutils -_ENCODING = locale.getpreferredencoding() or sys.getdefaultencoding() +_ENCODING = 'UTF-8' _PAGES = {} @@ -42,7 +61,7 @@ if not hasattr(collections, 'OrderedDict'): # Python 2.6 or earlier super(_OrderedDict, self).__setitem__(key, value) self._keys.append(key) - def __values__(self): + def values(self): for key in self._keys: yield self[key] @@ -50,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 {filename} in branch '{branch}' of {nmbgit}. " + 'Add the file or explicitly set --config.' + ).format(filename=filename, 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): @@ -90,7 +141,7 @@ class Page (object): byte_stream = sys.stdout.buffer except AttributeError: # Python 2 byte_stream = sys.stdout - stream = codecs.getwriter(encoding='UTF-8')(stream=byte_stream) + stream = codecs.getwriter(encoding=_ENCODING)(stream=byte_stream) self._write_header(views=views, stream=stream) for view in views: self._write_view(database=database, view=view, stream=stream) @@ -105,11 +156,21 @@ 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) + view['query-string'] = ' and '.join( + '( {} )'.format(q) for q in 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) @@ -172,6 +233,10 @@ class Page (object): class HtmlPage (Page): _slug_regexp = re.compile('\W+') + def __init__(self, message_url_template, **kwargs): + self.message_url_template = message_url_template + super(HtmlPage, self).__init__(**kwargs) + def _write_header(self, views, stream): super(HtmlPage, self)._write_header(views=views, stream=stream) stream.write('