]> git.madduck.net Git - code/mailplate.git/blobdiff - mailplate

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

fix encoding of manpage
[code/mailplate.git] / mailplate
index 6410670f73dd4833424578f3d3343bf0896a5336..c7a02b98a603ca17dcb18d45e22de834239467a4 100755 (executable)
--- a/mailplate
+++ b/mailplate
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 # -*- coding: utf-8 -*-
 #
 #!/usr/bin/python
 # -*- coding: utf-8 -*-
 #
-# mailplate — apply templates to mail drafts.
+# mailplate — reformat mail drafts according to templates
 #
 # This script reformats mail drafts according to a given template. The
 # template may be specified on the command line, but mailplate can also use
 #
 # This script reformats mail drafts according to a given template. The
 # template may be specified on the command line, but mailplate can also use
 # TODO: if headers like From are absent from the mail, they should not be kept
 # but replaced with a default.
 #
 # TODO: if headers like From are absent from the mail, they should not be kept
 # but replaced with a default.
 #
-# Copyright © martin f. krafft <madduck@debian.org>
+# Copyright © martin f. krafft <madduck@madduck.net>
 # Released under the terms of the Artistic Licence 2.0
 #
 
 # Released under the terms of the Artistic Licence 2.0
 #
 
+__name__ = 'mailplate'
+__description__ = 'reformat mail drafts according to templates'
+__version__ = '0.1'
+__author__ = 'martin f. krafft <madduck@madduck.net>'
+__copyright__ = 'Copyright © ' + __author__
+__licence__ = 'Artistic Licence 2.0'
+
 import email
 import os
 import posix
 import email
 import os
 import posix
@@ -79,6 +86,7 @@ HELPER_SLOT_TRAILER = ')'   # character ending a helper slot
 STD_HEADERS = ('From', 'To', 'Cc', 'Bcc', 'Subject', 'Reply-To', 'In-Reply-To')
 KEEP_HEADERS = { 'KEEP_FROM_HEADER' : STD_HEADERS[:1]
                , 'KEEP_STD_HEADERS' : STD_HEADERS[1:]
 STD_HEADERS = ('From', 'To', 'Cc', 'Bcc', 'Subject', 'Reply-To', 'In-Reply-To')
 KEEP_HEADERS = { 'KEEP_FROM_HEADER' : STD_HEADERS[:1]
                , 'KEEP_STD_HEADERS' : STD_HEADERS[1:]
+               , 'KEEP_ALL_HEADERS' : STD_HEADERS
                }
 
 SIG_DELIM='\n-- \n'
                }
 
 SIG_DELIM='\n-- \n'
@@ -88,7 +96,14 @@ SIG_DELIM='\n-- \n'
 ###
 
 def err(s):
 ###
 
 def err(s):
-    sys.stderr.write(s + '\n')
+    sys.stderr.write('E: ' + s + '\n')
+
+def warn(s):
+    sys.stderr.write('W: ' + s + '\n')
+
+def info(s):
+    if not options.verbose: return
+    sys.stderr.write('I: ' + s + '\n')
 
 # obtain a regexp from a line, run it, return score/dict if matched
 def exec_regexp(line, rawmsg, name):
 
 # obtain a regexp from a line, run it, return score/dict if matched
 def exec_regexp(line, rawmsg, name):
@@ -110,7 +125,7 @@ def exec_command(line, rawmsg, name):
         else:
             return 0
     except OSError:
         else:
             return 0
     except OSError:
-        err("W: command '%s' (template '%s') failed to run." % (r, name))
+        warn("command '%s' (template '%s') failed to run." % (r, name))
         return 0
 
 def interpolate_helpers(s):
         return 0
 
 def interpolate_helpers(s):
@@ -125,7 +140,7 @@ def interpolate_helpers(s):
             out = proc.communicate()[0]
             s = s[:helper_begin] + out.strip() + s[helper_end+1:]
         except KeyError:
             out = proc.communicate()[0]
             s = s[:helper_begin] + out.strip() + s[helper_end+1:]
         except KeyError:
-            err('E: unknown helper: ' + helper)
+            err('unknown helper: ' + helper)
             sys.exit(posix.EX_DATAERR)
     return s
 
             sys.exit(posix.EX_DATAERR)
     return s
 
@@ -162,6 +177,50 @@ vars = {}
 headers = {}
 payload = None
 
 headers = {}
 payload = None
 
+###
+### COMMAND LINE PARSING
+###
+
+parser = OptionParser()
+parser.prog = __name__
+parser.version = __version__
+parser.description = __description__
+parser.usage = '%prog [options] <message>'
+parser.add_option('-a', '--auto', dest='auto',
+        default=False, action='store_true',
+        help='turn on template auto-discovery')
+parser.add_option('-m', '--menu', dest='menu',
+        default=False, action='store_true',
+        help='choose from a list of templates (not yet implemented)')
+parser.add_option('-n', '--new', dest='new',
+        default=False, action='store_true',
+        help='create a new message')
+parser.add_option('-e', '--editor', dest='edit',
+        default=False, action='store_true',
+        help='spawn editor once template is applied')
+parser.add_option('-k', '--keep-unknown', dest='keep_unknown',
+        default=False, action='store_true',
+        help='preserve mail headers not specified in template')
+parser.add_option('-v', '--verbose', dest='verbose',
+        default=False, action='store_true',
+        help='write informational messages to stderr')
+parser.add_option('-d', '--debug', dest='debug',
+        default=False, action='store_true',
+        help='start a debugger after initialisation')
+parser.add_option('-V', '--version', dest='version',
+        default=False, action='store_true',
+        help='display version information')
+
+options, args = parser.parse_args()
+
+if options.version:
+    print __name__, __version__ + ' — ' + __description__
+    print
+    print 'Written by ' + __author__
+    print __copyright__
+    print 'Released under the ' + __licence__
+    sys.exit(posix.EX_OK)
+
 ###
 ### CONFIGURATION FILE PARSING
 ###
 ###
 ### CONFIGURATION FILE PARSING
 ###
@@ -177,15 +236,18 @@ helpers = { 'get_quote' : 'fortune -s' }
 
 if not os.path.exists(CONFFILE):
     # conffile does not exist, let's create it with defaults.
 
 if not os.path.exists(CONFFILE):
     # conffile does not exist, let's create it with defaults.
+    options.verbose = True
 
     if not os.path.isdir(MAILPLATEDIR):
 
     if not os.path.isdir(MAILPLATEDIR):
+        info('configuration directory not found, creating: ' + MAILPLATEDIR)
         os.mkdir(MAILPLATEDIR, 0700)
 
     if not os.path.isfile(CONFFILE):
         os.mkdir(MAILPLATEDIR, 0700)
 
     if not os.path.isfile(CONFFILE):
+        info('creating a default configuration file: ' + CONFFILE)
         f = file(CONFFILE, 'w')
         f.write('# mailplate configuration\n[%s]\n' % SECTION_GENERAL)
         for kvpair in config.iteritems():
         f = file(CONFFILE, 'w')
         f.write('# mailplate configuration\n[%s]\n' % SECTION_GENERAL)
         for kvpair in config.iteritems():
-            if kvpair[1] and len(kvpair[1]) > 0:
+            if len(kvpair[1]) > 0:
                 f.write('%s = %s\n' % kvpair)
 
         if len(helpers) > 0:
                 f.write('%s = %s\n' % kvpair)
 
         if len(helpers) > 0:
@@ -196,7 +258,7 @@ if not os.path.exists(CONFFILE):
         f.close()
 
 if not os.access(CONFFILE, os.R_OK):
         f.close()
 
 if not os.access(CONFFILE, os.R_OK):
-    err('E: cannot read configuration file: %s' % CONFFILE)
+    err('cannot read configuration file: %s' % CONFFILE)
     sys.exit(posix.EX_OSFILE)
 
 # now parse
     sys.exit(posix.EX_OSFILE)
 
 # now parse
@@ -208,12 +270,12 @@ for key in config.keys():
     try:
         config[key] = parser.get(SECTION_GENERAL, key)
     except ConfigParser.NoSectionError, ConfigParser.MissingSectionHeaderError:
     try:
         config[key] = parser.get(SECTION_GENERAL, key)
     except ConfigParser.NoSectionError, ConfigParser.MissingSectionHeaderError:
-        err("E: no section '%s' in %s" % (SECTION_GENERAL, CONFFILE))
+        err("no section '%s' in %s" % (SECTION_GENERAL, CONFFILE))
         sys.exit(posix.EX_CONFIG)
     except ConfigParser.NoOptionError:
         continue
     except ConfigParser.DuplicateSectionError, ConfigParser.ParseError:
         sys.exit(posix.EX_CONFIG)
     except ConfigParser.NoOptionError:
         continue
     except ConfigParser.DuplicateSectionError, ConfigParser.ParseError:
-        err('E: parse error on %s' % CONFFILE)
+        err('parse error on %s' % CONFFILE)
         sys.exit(posix.EX_CONFIG)
 
 # all HELPERS into the helpers dict
         sys.exit(posix.EX_CONFIG)
 
 # all HELPERS into the helpers dict
@@ -221,43 +283,18 @@ helpers.update(parser.items(SECTION_HELPERS))
 
 TPATH = os.path.expanduser(config['template_path'])
 if not os.path.isdir(TPATH):
 
 TPATH = os.path.expanduser(config['template_path'])
 if not os.path.isdir(TPATH):
+    info('creating template directory: ' + TPATH)
     os.mkdir(TPATH, 0700)
 
 default_templname = config['default_template']
 if default_templname is not None:
     default_templpath = os.path.join(TPATH, default_templname)
     if not os.path.isfile(default_templpath):
     os.mkdir(TPATH, 0700)
 
 default_templname = config['default_template']
 if default_templname is not None:
     default_templpath = os.path.join(TPATH, default_templname)
     if not os.path.isfile(default_templpath):
+        info('creating the default template: ' + default_templpath)
         f = file(default_templpath, 'w')
         f.write('@KEEP_STD_HEADERS\n\n@KEEP_BODY\n')
         f.close()
 
         f = file(default_templpath, 'w')
         f.write('@KEEP_STD_HEADERS\n\n@KEEP_BODY\n')
         f.close()
 
-###
-### COMMAND LINE PARSING
-###
-
-parser = OptionParser()
-parser.usage = '%prog [options] <message>'
-parser.add_option('-a', '--auto', dest='auto',
-        default=False, action='store_true',
-        help='turn on template auto-discovery')
-parser.add_option('-m', '--menu', dest='menu',
-        default=False, action='store_true',
-        help='choose from a list of template')
-parser.add_option('-n', '--new', dest='new',
-        default=False, action='store_true',
-        help='create a new message')
-parser.add_option('-e', '--editor', dest='edit',
-        default=False, action='store_true',
-        help='spawn editor once template is applied')
-parser.add_option('-k', '--keep-unknown', dest='keep_unknown',
-        default=False, action='store_true',
-        help='preserve mail headers not specified in template')
-parser.add_option('-d', '--debug', dest='debug',
-        default=False, action='store_true',
-        help='start a debugger after initialisation')
-
-options, args = parser.parse_args()
-
 if options.debug:
     import pdb
     pdb.set_trace()
 if options.debug:
     import pdb
     pdb.set_trace()
@@ -277,24 +314,27 @@ for arg in args:
         # argument referenced an existing template
         templname = arg
     else:
         # argument referenced an existing template
         templname = arg
     else:
-        err('E: unknown argument: %s' % arg)
+        err('unknown argument, and cannot find a template by this name: %s' % arg)
         sys.exit(posix.EX_USAGE)
 
 # sanity checks
 if options.auto and options.menu:
         sys.exit(posix.EX_USAGE)
 
 # sanity checks
 if options.auto and options.menu:
-    err('E: cannot combine --auto and --menu')
+    err('cannot combine --auto and --menu')
     sys.exit(posix.EX_USAGE)
 
 elif (options.auto or options.menu) and templname:
     sys.exit(posix.EX_USAGE)
 
 elif (options.auto or options.menu) and templname:
-    err('E: cannot specify a template with --auto or --menu')
+    err('cannot specify a template with --auto or --menu')
     sys.exit(posix.EX_USAGE)
 
 elif not templname and not (options.auto or options.menu):
     sys.exit(posix.EX_USAGE)
 
 elif not templname and not (options.auto or options.menu):
-    err('E: no template specified')
-    sys.exit(posix.EX_USAGE)
+    if default_templname is not None:
+        templname = default_templname
+    else:
+        err('no template specified')
+        sys.exit(posix.EX_USAGE)
 
 elif options.menu:
 
 elif options.menu:
-    err('E: --menu mode not yet implemented')
+    err('--menu mode not yet implemented')
     sys.exit(posix.EX_USAGE)
 
 ###
     sys.exit(posix.EX_USAGE)
 
 ###
@@ -339,22 +379,21 @@ if options.auto:
     templname = best_score[1]
 
     if templname is None:
     templname = best_score[1]
 
     if templname is None:
-        err('E: could not determine a template to use and no default is set')
+        err('could not determine a template to use and no default is set')
         sys.exit(posix.EX_CONFIG)
 
         sys.exit(posix.EX_CONFIG)
 
-    print >>sys.stderr, \
-            'I: Chose profile %s with score %d.' % (templname, best_score[0])
+    info('chose profile %s with score %d.' % (templname, best_score[0]))
     vars = best_score[2]
 
 # now read in the template
 templpath = os.path.join(TPATH, templname)
 
 if not os.path.isfile(templpath):
     vars = best_score[2]
 
 # now read in the template
 templpath = os.path.join(TPATH, templname)
 
 if not os.path.isfile(templpath):
-    err('E: not a template: ' + templpath)
+    err('not a template: ' + templpath)
     sys.exit(posix.EX_OSFILE)
 
 elif not os.access(templpath, os.R_OK):
     sys.exit(posix.EX_OSFILE)
 
 elif not os.access(templpath, os.R_OK):
-    err('E: template ' + templpath + ' could not be read.')
+    err('template ' + templpath + ' could not be read.')
     sys.exit(posix.EX_OSFILE)
 
 templ = file(templpath, 'r', 1)
     sys.exit(posix.EX_OSFILE)
 
 templ = file(templpath, 'r', 1)
@@ -376,10 +415,14 @@ for line in templ:
         l = line[:-1]
         if len(l) == 0:
             payload = '' # end of headers
         l = line[:-1]
         if len(l) == 0:
             payload = '' # end of headers
-        elif l[0] == KEEP_SLOT_LEADER and KEEP_HEADERS.has_key(l[1:]):
-            # found predefined header slot keyword
-            for header in KEEP_HEADERS[l[1:]]:
-                headers[header.lower()] = (header, _keep_header)
+        elif l[0] == KEEP_SLOT_LEADER:
+            if KEEP_HEADERS.has_key(l[1:]):
+                # found predefined header slot keyword
+                for header in KEEP_HEADERS[l[1:]]:
+                    headers[header.lower()] = (header, _keep_header)
+            else:
+                err('unknown header slot ' + l + ' found')
+                sys.exit(posix.EX_CONFIG)
         else:
             header, content = l.split(':', 1)
             content = content.strip()
         else:
             header, content = l.split(':', 1)
             content = content.strip()
@@ -453,7 +496,8 @@ outf.close()
 if options.edit:
     # finally, spawn the editor, if we wrote into a file
     if outfname is None:
 if options.edit:
     # finally, spawn the editor, if we wrote into a file
     if outfname is None:
-        err('E: cannot use --edit without an output file.')
+        err('cannot use --edit without an output file.')
         sys.exit(posix.EX_USAGE)
 
     os.execlp('sensible-editor', 'sensible-editor', outfname)
         sys.exit(posix.EX_USAGE)
 
     os.execlp('sensible-editor', 'sensible-editor', outfname)
+