X-Git-Url: https://git.madduck.net/code/mailplate.git/blobdiff_plain/817a7c757fc641e02a6fc13e04a61d3307b0ce8b..refs/heads/master:/mailplate diff --git a/mailplate b/mailplate index 439fc73..bda187d 100755 --- a/mailplate +++ b/mailplate @@ -1,43 +1,10 @@ -#!/usr/bin/python +#!/usr/bin/python3 # -*- 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. @@ -48,7 +15,7 @@ __name__ = 'mailplate' __description__ = 'reformat mail drafts according to templates' -__version__ = '0.1' +__version__ = '0.3' __author__ = 'martin f. krafft ' __copyright__ = 'Copyright © ' + __author__ __licence__ = 'Artistic Licence 2.0' @@ -59,7 +26,7 @@ import posix import re import sys import subprocess -import ConfigParser +import configparser 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:] + , 'KEEP_ALL_HEADERS' : STD_HEADERS } 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] - 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) @@ -213,11 +181,11 @@ parser.add_option('-V', '--version', dest='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) ### @@ -235,10 +203,11 @@ helpers = { 'get_quote' : 'fortune -s' } 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): 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) @@ -260,19 +229,21 @@ if not os.access(CONFFILE, os.R_OK): 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) - except ConfigParser.NoSectionError, ConfigParser.MissingSectionHeaderError: + except (configparser.NoSectionError, + configparser.MissingSectionHeaderError): err("no section '%s' in %s" % (SECTION_GENERAL, CONFFILE)) sys.exit(posix.EX_CONFIG) - except ConfigParser.NoOptionError: + except configparser.NoOptionError: continue - except ConfigParser.DuplicateSectionError, ConfigParser.ParseError: + except (configparser.DuplicateSectionError, + configparser.ParseError): err('parse error on %s' % CONFFILE) sys.exit(posix.EX_CONFIG) @@ -282,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) - 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) - f = file(default_templpath, 'w') + f = open(default_templpath, 'w') f.write('@KEEP_STD_HEADERS\n\n@KEEP_BODY\n') f.close() @@ -300,19 +271,27 @@ if options.debug: # 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): + 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 - elif os.access(os.path.join(TPATH, arg), os.R_OK): - # argument referenced an existing template - templname = arg 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 @@ -325,8 +304,11 @@ elif (options.auto or options.menu) and templname: sys.exit(posix.EX_USAGE) elif not templname and not (options.auto or options.menu): - err('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: err('--menu mode not yet implemented') @@ -338,7 +320,7 @@ elif options.menu: # 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: @@ -391,7 +373,7 @@ elif not os.access(templpath, os.R_OK): 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: @@ -410,10 +392,14 @@ for line in templ: 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() @@ -429,7 +415,7 @@ msg = email.message_from_string(rawmsg) 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 @@ -441,7 +427,7 @@ for header, content in msg.items(): # 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: @@ -449,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] - print >>outf, ': '.join(hpair) + print(': '.join(hpair), file=outf) # 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 >>outf, ': '.join((header, content)) + print(': '.join((header, content)), file=outf) # 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) @@ -481,7 +467,7 @@ payload = payload.replace('@KEEP_BODY', body, 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: