]> 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:

hacks to make it py3 compatible
[code/mailplate.git] / mailplate
index 4b03c05595b6b0cc978997e66a028ebe10b70cc1..bda187dfa7be20aac2cf71b6241f07cada0e2697 100755 (executable)
--- a/mailplate
+++ b/mailplate
@@ -1,43 +1,10 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 #
 # mailplate — reformat mail drafts according to templates
 #
 # -*- coding: utf-8 -*-
 #
 # 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
-# control information from the template files to automatically select an
-# appropriate template (--auto). A selection menu feature is planned (--menu).
-#
-# Applying a template means obtainined select data from an existing mail
-# message (unless --new is specified) and to fill it into appropriate slots in
-# the template. Messages are processed in three parts: headers, body, and
-# signature.
-#
-# The template can define two types of headers: mandatory and preservatory.
-# Mandatory headers take precedence over headers in the existing message and
-# thus overwrite them. Preservatory headers instruct mailplate to port their
-# data from the existing mail message. Headers in the existing message but not
-# defined in the template are dropped, unless --keep-unknown is given.
-#
-# Body and signature are separated by '-- '. If this sentinel is not found,
-# no signature is extracted.
-#
-# Templates can be interpolated and data filled into slots. Helper slots are
-# filled with the output of helper commands (which must be defined in the
-# configuration), environment variable slots are just that, and mail variable
-# slots can be filled with data obtained by running regexps or commands over
-# the message.
-#
-# This script can be run in multiple ways:
-#
-# As a filter, it applies a template to data from stdin and writes the result
-# to stdout.
-#
-# Given a file, it modifies the file, unless it cannot write to the file, in
-# which case it writes to stdout.
-#
-# When --editor is passed, the script spawns sensible-editor on the result. It
-# may thus be used as the editor for your mail user agent.
+# Please see the mailplate(1) manpage or the homepage for more information:
+#   http://madduck.net/code/mailplate/
 #
 # 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.
@@ -48,7 +15,7 @@
 
 __name__ = 'mailplate'
 __description__ = 'reformat mail drafts according to templates'
 
 __name__ = 'mailplate'
 __description__ = 'reformat mail drafts according to templates'
-__version__ = '0.1'
+__version__ = '0.3'
 __author__ = 'martin f. krafft <madduck@madduck.net>'
 __copyright__ = 'Copyright © ' + __author__
 __licence__ = 'Artistic Licence 2.0'
 __author__ = 'martin f. krafft <madduck@madduck.net>'
 __copyright__ = 'Copyright © ' + __author__
 __licence__ = 'Artistic Licence 2.0'
@@ -59,7 +26,7 @@ import posix
 import re
 import sys
 import subprocess
 import re
 import sys
 import subprocess
-import ConfigParser
+import configparser
 from optparse import OptionParser
 
 ###
 from optparse import OptionParser
 
 ###
@@ -86,6 +53,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'
@@ -137,7 +105,7 @@ def interpolate_helpers(s):
             proc = subprocess.Popen(helpers[helper], shell=True,
                     stdout=subprocess.PIPE, stderr=sys.stderr)
             out = proc.communicate()[0]
             proc = subprocess.Popen(helpers[helper], shell=True,
                     stdout=subprocess.PIPE, stderr=sys.stderr)
             out = proc.communicate()[0]
-            s = s[:helper_begin] + out.strip() + s[helper_end+1:]
+            s = s[:helper_begin] + out.strip().decode() + s[helper_end+1:]
         except KeyError:
             err('unknown helper: ' + helper)
             sys.exit(posix.EX_DATAERR)
         except KeyError:
             err('unknown helper: ' + helper)
             sys.exit(posix.EX_DATAERR)
@@ -213,11 +181,11 @@ parser.add_option('-V', '--version', dest='version',
 options, args = parser.parse_args()
 
 if options.version:
 options, args = parser.parse_args()
 
 if options.version:
-    print __name__, __version__ + ' — ' + __description__
-    print
-    print 'Written by ' + __author__
-    print __copyright__
-    print 'Released under the ' + __licence__
+    print(__name__, __version__ + ' — ' + __description__)
+    print('')
+    print('Written by ' + __author__)
+    print(__copyright__)
+    print('Released under the ' + __licence__)
     sys.exit(posix.EX_OK)
 
 ###
     sys.exit(posix.EX_OK)
 
 ###
@@ -239,7 +207,7 @@ if not os.path.exists(CONFFILE):
 
     if not os.path.isdir(MAILPLATEDIR):
         info('configuration directory not found, creating: ' + MAILPLATEDIR)
 
     if not os.path.isdir(MAILPLATEDIR):
         info('configuration directory not found, creating: ' + MAILPLATEDIR)
-        os.mkdir(MAILPLATEDIR, 0700)
+        os.mkdir(MAILPLATEDIR, o0700)
 
     if not os.path.isfile(CONFFILE):
         info('creating a default configuration file: ' + CONFFILE)
 
     if not os.path.isfile(CONFFILE):
         info('creating a default configuration file: ' + CONFFILE)
@@ -261,19 +229,21 @@ if not os.access(CONFFILE, os.R_OK):
     sys.exit(posix.EX_OSFILE)
 
 # now parse
     sys.exit(posix.EX_OSFILE)
 
 # now parse
-parser = ConfigParser.SafeConfigParser()
+parser = configparser.ConfigParser()
 parser.read(CONFFILE)
 
 # first the GENERAL section into the config dict for all keys with defaults
 for key in config.keys():
     try:
         config[key] = parser.get(SECTION_GENERAL, key)
 parser.read(CONFFILE)
 
 # first the GENERAL section into the config dict for all keys with defaults
 for key in config.keys():
     try:
         config[key] = parser.get(SECTION_GENERAL, key)
-    except ConfigParser.NoSectionError, ConfigParser.MissingSectionHeaderError:
+    except (configparser.NoSectionError,
+            configparser.MissingSectionHeaderError):
         err("no section '%s' in %s" % (SECTION_GENERAL, CONFFILE))
         sys.exit(posix.EX_CONFIG)
         err("no section '%s' in %s" % (SECTION_GENERAL, CONFFILE))
         sys.exit(posix.EX_CONFIG)
-    except ConfigParser.NoOptionError:
+    except configparser.NoOptionError:
         continue
         continue
-    except ConfigParser.DuplicateSectionError, ConfigParser.ParseError:
+    except (configparser.DuplicateSectionError, 
+            configparser.ParseError):
         err('parse error on %s' % CONFFILE)
         sys.exit(posix.EX_CONFIG)
 
         err('parse error on %s' % CONFFILE)
         sys.exit(posix.EX_CONFIG)
 
@@ -283,14 +253,14 @@ helpers.update(parser.items(SECTION_HELPERS))
 TPATH = os.path.expanduser(config['template_path'])
 if not os.path.isdir(TPATH):
     info('creating template directory: ' + TPATH)
 TPATH = os.path.expanduser(config['template_path'])
 if not os.path.isdir(TPATH):
     info('creating template directory: ' + TPATH)
-    os.mkdir(TPATH, 0700)
+    os.mkdir(TPATH, o0700)
 
 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)
 
 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 = open(default_templpath, 'w')
         f.write('@KEEP_STD_HEADERS\n\n@KEEP_BODY\n')
         f.close()
 
         f.write('@KEEP_STD_HEADERS\n\n@KEEP_BODY\n')
         f.close()
 
@@ -301,19 +271,27 @@ if options.debug:
 # parse the arguments
 for arg in args:
     if arg == '-':
 # parse the arguments
 for arg in args:
     if arg == '-':
-        # filename is -, so do nothing, since stdin/stdout are default
-        continue
+        infname = arg
+        outfname = arg
+    elif arg.find(os.path.sep) == -1 and os.access(os.path.join(TPATH, arg), os.R_OK):
+        if templname is not None:
+            err("template already specified (%s), unsure what to do with '%s'"
+                % (templname, arg))
+            sys.exit(posix.EX_USAGE)
+        # argument references an existing template
+        templname = arg
     elif os.path.isfile(arg):
     elif os.path.isfile(arg):
+        if infname is not None:
+            err("input file already specified (%s), unsure what to do with '%s'"
+                % (infname, arg))
+            sys.exit(posix.EX_USAGE)
         # the file exists, so use it as in/out if read/writeable
         if os.access(arg, os.R_OK):
             infname = arg
         if os.access(arg, os.W_OK):
             outfname = arg
         # the file exists, so use it as in/out if read/writeable
         if os.access(arg, os.R_OK):
             infname = arg
         if os.access(arg, os.W_OK):
             outfname = arg
-    elif os.access(os.path.join(TPATH, arg), os.R_OK):
-        # argument referenced an existing template
-        templname = arg
     else:
     else:
-        err('unknown argument, and cannot find a template by this name: %s' % arg)
+        err('unknown argument: %s' % arg)
         sys.exit(posix.EX_USAGE)
 
 # sanity checks
         sys.exit(posix.EX_USAGE)
 
 # sanity checks
@@ -342,7 +320,7 @@ elif options.menu:
 
 # read in the message from a file, if a filename is given.
 if infname is not None:
 
 # read in the message from a file, if a filename is given.
 if infname is not None:
-    inf = file(infname, 'r', 1)
+    inf = open(infname, 'r', 1)
 
 # read message into buffer, or preinitialise the buffer if --new is given
 if options.new:
 
 # read message into buffer, or preinitialise the buffer if --new is given
 if options.new:
@@ -395,7 +373,7 @@ elif not os.access(templpath, os.R_OK):
     err('template ' + templpath + ' could not be read.')
     sys.exit(posix.EX_OSFILE)
 
     err('template ' + templpath + ' could not be read.')
     sys.exit(posix.EX_OSFILE)
 
-templ = file(templpath, 'r', 1)
+templ = open(templpath, 'r', 1)
 
 for line in templ:
     if not options.auto and line[0] == REGEXPCHAR:
 
 for line in templ:
     if not options.auto and line[0] == REGEXPCHAR:
@@ -414,10 +392,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 l[1:] in KEEP_HEADERS:
+                # 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()
@@ -433,7 +415,7 @@ msg = email.message_from_string(rawmsg)
 for header, content in msg.items():
     # iterate all existing mail headers
     lheader = header.lower()
 for header, content in msg.items():
     # iterate all existing mail headers
     lheader = header.lower()
-    if headers.has_key(lheader):
+    if lheader in headers:
         # the template defines this header
         if headers[lheader][1] == _keep_header:
             # it's marked as keep, thus use content from email message
         # the template defines this header
         if headers[lheader][1] == _keep_header:
             # it's marked as keep, thus use content from email message
@@ -445,7 +427,7 @@ for header, content in msg.items():
 
 # open the output file
 if outfname is not None:
 
 # open the output file
 if outfname is not None:
-    outf = file(outfname, 'w', 0)
+    outf = open(outfname, 'w')
 
 # print the headers, starting with the standard headers in order
 for header in STD_HEADERS:
 
 # print the headers, starting with the standard headers in order
 for header in STD_HEADERS:
@@ -453,17 +435,17 @@ for header in STD_HEADERS:
     if headers.get(lheader, (None, _keep_header))[1] is not _keep_header:
         # the template header contains mandatory data, let's print it.
         hpair = headers[lheader]
     if headers.get(lheader, (None, _keep_header))[1] is not _keep_header:
         # the template header contains mandatory data, let's print it.
         hpair = headers[lheader]
-        print >>outf, ': '.join(hpair)
+        print(': '.join(hpair), file=outf)
         # and remove it from the dict
         del headers[lheader]
 
         # and remove it from the dict
         del headers[lheader]
 
-for i, (header, content) in headers.iteritems():
+for i, (header, content) in headers.items():
     # print all remaining headers
     if content == _keep_header: continue
     # print all remaining headers
     if content == _keep_header: continue
-    print >>outf, ': '.join((header, content))
+    print(': '.join((header, content)), file=outf)
 
 # print empty line to indicate end of headers.
 
 # print empty line to indicate end of headers.
-print >>outf
+print('', file=outf)
 
 # split payload of existing message into body and signature
 body = msg.get_payload().rsplit(SIG_DELIM, 1)
 
 # split payload of existing message into body and signature
 body = msg.get_payload().rsplit(SIG_DELIM, 1)
@@ -485,7 +467,7 @@ payload = payload.replace('@KEEP_BODY', body, 1)
 if keep_sig:
     payload = payload.replace('@KEEP_SIGNATURE', signature, 1)
 
 if keep_sig:
     payload = payload.replace('@KEEP_SIGNATURE', signature, 1)
 
-print >>outf, payload.rstrip()
+print(payload.rstrip(), file=outf)
 outf.close()
 
 if options.edit:
 outf.close()
 
 if options.edit: