]> git.notmuchmail.org Git - notmuch/blobdiff - devel/nmbug/nmbug-status
nmbug-status: make output title and blurb configurable
[notmuch] / devel / nmbug / nmbug-status
index 40e68962d111ee0ce35a5813daa544f7857e8d9b..03621bd534491b185db4dbbc63e4ad38b52b6d14 100755 (executable)
@@ -13,7 +13,6 @@ import codecs
 import collections
 import datetime
 import email.utils
-import locale
 try:  # Python 3
     from urllib.parse import quote
 except ImportError:  # Python 2
@@ -21,11 +20,13 @@ except ImportError:  # Python 2
 import json
 import argparse
 import os
+import re
 import sys
 import subprocess
+import xml.sax.saxutils
 
 
-_ENCODING = locale.getpreferredencoding() or sys.getdefaultencoding()
+_ENCODING = 'UTF-8'
 _PAGES = {}
 
 
@@ -40,7 +41,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]
 
@@ -88,7 +89,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)
@@ -168,24 +169,32 @@ class Page (object):
 
 
 class HtmlPage (Page):
+    _slug_regexp = re.compile('\W+')
+
     def _write_header(self, views, stream):
         super(HtmlPage, self)._write_header(views=views, stream=stream)
         stream.write('<ul>\n')
         for view in views:
+            if 'id' not in view:
+                view['id'] = self._slug(view['title'])
             stream.write(
-                '<li><a href="#{title}">{title}</a></li>\n'.format(**view))
+                '<li><a href="#{id}">{title}</a></li>\n'.format(**view))
         stream.write('</ul>\n')
 
     def _write_view_header(self, view, stream):
-        stream.write('<h3 id="{title}">{title}</h3>\n'.format(**view))
+        stream.write('<h3 id="{id}">{title}</h3>\n'.format(**view))
+        stream.write('<p>\n')
         if 'comment' in view:
             stream.write(view['comment'])
             stream.write('\n')
         for line in [
                 'The view is generated from the following query:',
-                '<blockquote>',
+                '</p>',
+                '<p>',
+                '  <code>',
                 view['query-string'],
-                '</blockquote>',
+                '  </code>',
+                '</p>',
                 ]:
             stream.write(line)
             stream.write('\n')
@@ -195,19 +204,22 @@ class HtmlPage (Page):
             return
         stream.write('<table>\n')
         for thread in threads:
+            stream.write('  <tbody>\n')
             for message_display_data in thread:
                 stream.write((
-                    '<tr>\n'
-                    '  <td>{date}</td>\n'
-                    '  <td>{message-id-term}</td>\n'
-                    '</tr>\n'
-                    '<tr>\n'
-                    '  <td>{from}</td>\n'
-                    '  <td>{subject}</td>\n'
-                    '</tr>\n'
+                    '    <tr class="message-first">\n'
+                    '      <td>{date}</td>\n'
+                    '      <td><code>{message-id-term}</code></td>\n'
+                    '    </tr>\n'
+                    '    <tr class="message-last">\n'
+                    '      <td>{from}</td>\n'
+                    '      <td>{subject}</td>\n'
+                    '    </tr>\n'
                     ).format(**message_display_data))
+            stream.write('  </tbody>\n')
             if thread != threads[-1]:
-                stream.write('<tr><td colspan="2"><br /></td></tr>\n')
+                stream.write(
+                    '  <tbody><tr><td colspan="2"><br /></td></tr></tbody>\n')
         stream.write('</table>\n')
 
     def _message_display_data(self, *args, **kwargs):
@@ -217,31 +229,18 @@ class HtmlPage (Page):
         if 'subject' in display_data and 'message-id' in display_data:
             d = {
                 'message-id': quote(display_data['message-id']),
-                'subject': display_data['subject'],
+                'subject': xml.sax.saxutils.escape(display_data['subject']),
                 }
             display_data['subject'] = (
                 '<a href="http://mid.gmane.org/{message-id}">{subject}</a>'
                 ).format(**d)
+        for key in ['message-id', 'from']:
+            if key in display_data:
+                display_data[key] = xml.sax.saxutils.escape(display_data[key])
         return (running_data, display_data)
 
-
-_PAGES['text'] = Page()
-_PAGES['html'] = HtmlPage(
-    header='''<!DOCTYPE html>
-<html lang="en">
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-<title>Notmuch Patches</title>
-</head>
-<body>
-<h2>Notmuch Patches</h2>
-Generated: {date}<br />
-For more infomation see <a href="http://notmuchmail.org/nmbug">nmbug</a>
-<h3>Views</h3>
-'''.format(date=datetime.datetime.utcnow().date()),
-    footer='</body>\n</html>\n',
-    )
-
+    def _slug(self, string):
+        return self._slug_regexp.sub('-', string)
 
 parser = argparse.ArgumentParser()
 parser.add_argument('--text', help='output plain text format',
@@ -257,6 +256,63 @@ args = parser.parse_args()
 
 config = read_config(path=args.config)
 
+_PAGES['text'] = Page()
+_PAGES['html'] = HtmlPage(
+    header='''<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset={encoding}" />
+  <title>{title}</title>
+  <style media="screen" type="text/css">
+    table {{
+      border-spacing: 0;
+    }}
+    tr.message-first td {{
+      padding-top: {inter_message_padding};
+    }}
+    tr.message-last td {{
+      padding-bottom: {inter_message_padding};
+    }}
+    td {{
+      padding-left: {border_radius};
+      padding-right: {border_radius};
+    }}
+    tr:first-child td:first-child {{
+      border-top-left-radius: {border_radius};
+    }}
+    tr:first-child td:last-child {{
+      border-top-right-radius: {border_radius};
+    }}
+    tr:last-child td:first-child {{
+      border-bottom-left-radius: {border_radius};
+    }}
+    tr:last-child td:last-child {{
+      border-bottom-right-radius: {border_radius};
+    }}
+    tbody:nth-child(4n+1) tr td {{
+      background-color: #ffd96e;
+    }}
+    tbody:nth-child(4n+3) tr td {{
+      background-color: #bce;
+    }}
+  </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',
+    )
+
 if args.list_views:
     for view in config['views']:
         print(view['title'])