pax_global_header00006660000000000000000000000064110376273160014520gustar00rootroot0000000000000052 comment=a382cb10e13dc60580cddae71db029adaddef10d mailplate-0.2/000077500000000000000000000000001103762731600133315ustar00rootroot00000000000000mailplate-0.2/.gitignore000066400000000000000000000000151103762731600153150ustar00rootroot00000000000000/mailplate.1 mailplate-0.2/Makefile000066400000000000000000000003671103762731600147770ustar00rootroot00000000000000DB2MAN=/usr/share/sgml/docbook/stylesheet/xsl/nwalsh/manpages/docbook.xsl XP=xsltproc -''-nonet MANPAGE=mailplate.1 all: $(MANPAGE) %.1: %.xml $(XP) $(DB2MAN) $< man: $(MANPAGE) man -l $< .PHONY: man clean: rm -f mailplate.1 .PHONY: clean mailplate-0.2/README000066400000000000000000000031401103762731600142070ustar00rootroot00000000000000mailplate README ================ I decided that this script is fit for public consumption, so enjoy. You can find an example configuration and template files in /usr/share/doc/mailplate/examples. Much more information can be found on the mailplate homepage: http://madduck.net/code/mailplate/ The tool will install defaults in your home directory on first invocation. I have the following settings in my muttrc and vimrc: ~/.mutt/muttrc: set editor="mailplate --edit --auto --keep-unknown" ~/.mutt/keybindings: #TODO this is a hack until mailplate does not readd headers. macro compose e ':set my_editor="$editor":set editor=sensible-editor:set editor="$my_editor"' "invoke normal editor to edit message" macro index,pager e ':set my_editor="$editor":set editor=sensible-editor:set editor="$my_editor"' "invoke normal editor to edit message" ~/.vim/ftplugin/mail.vim: nmap :w:%!mailplate --keep-unknown --auto nmap :w:%!mailplate --keep-unknown private nmap :w:%!mailplate --keep-unknown debian Now when I reply to a message, mailplate automatically choses the right template, and if I later change my mind, I can press C-p, to override the choice and select the private template, or just hit to have it re-run the auto-detection. The source code is maintained in git, so if you want to contribute, use the following URL to clone: http://git.madduck.net/code/mailplate.git -- martin f. krafft Sun, 30 Sep 2007 17:37:36 +0100 mailplate-0.2/TODO000066400000000000000000000010711103762731600140200ustar00rootroot00000000000000mailplate to-dos ================ - provide a menu selector. You might be interested in this post on selection interfaces: http://blog.madduck.net/geek/2007.01.23_a-good-interface-for-selection - implement a way of overriding the From header without messing with mutt's reverse_name setting (http://www.mutt.org/doc/manual/manual-6.html#reverse_name). - figure out when we're editing an existing message and do not apply profiles. This could be done when e.g. the Message-Id header is present. - stop looking for control headers after encounter of /^$/ mailplate-0.2/config000066400000000000000000000003111103762731600145140ustar00rootroot00000000000000# mailplate configuration [general] default_template = private template_path = ~/.mailplate/templates [helpers] get_debian_version = cat /etc/debian_version get_kernel = uname -mr get_quote = fortune mailplate-0.2/mailplate000077500000000000000000000362441103762731600152400ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # # mailplate — reformat mail drafts according to templates # # 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. # # Copyright © martin f. krafft # 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 ' __copyright__ = 'Copyright © ' + __author__ __licence__ = 'Artistic Licence 2.0' import email import os import posix import re import sys import subprocess import ConfigParser from optparse import OptionParser ### ### CONSTANTS ### MAILPLATEDIR = '~/.mailplate' # settings directory CONFFILE = MAILPLATEDIR + '/config' # configuration file SECTION_GENERAL = 'general' # name of general config section SECTION_HELPERS = 'helpers' # name of helpers config section TEMPLATEDIR = MAILPLATEDIR + '/templates' # location of templates COMMENTCHAR = '#' # character commencing a comment line in the template REGEXPCHAR = '*' # character commencing a regexp line in the template COMMANDCHAR = '!' # character commencing a command line in the template KEEP_SLOT_LEADER = '@' # character commencing a keep slot ENV_SLOT_LEADER = '${' # character commencing an environment variable slot ENV_SLOT_TRAILER = '}' # character ending an environment variable slot HELPER_SLOT_LEADER = '$(' # character commencing a helper slot HELPER_SLOT_TRAILER = ')' # character ending a helper slot # headers we want to preserve most of the time, and their order 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' ### ### HELPER FUNCTION DEFINITIONS ### def err(s): 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): p, r = line[1:].strip().split(' ', 1) m = re.compile(r, re.M | re.I | re.U).search(rawmsg) if m is not None: return (int(p), m.groupdict()) return (0, {}) # obtain a command from a line, run it, return score if matched def exec_command(line, rawmsg, name): p, r = line[1:].strip().split(' ', 1) s = subprocess.Popen(r, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) try: stdout, stderr = s.communicate(rawmsg) if s.returncode == 0: return int(p) else: return 0 except OSError: warn("command '%s' (template '%s') failed to run." % (r, name)) return 0 def interpolate_helpers(s): while True: helper_begin = s.find(HELPER_SLOT_LEADER) if helper_begin < 0: break helper_end = s.find(HELPER_SLOT_TRAILER, helper_begin) helper = s[helper_begin + len(HELPER_SLOT_LEADER):helper_end] try: 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:] except KeyError: err('unknown helper: ' + helper) sys.exit(posix.EX_DATAERR) return s def interpolate_env(s): while True: envvar_begin = s.find(ENV_SLOT_LEADER) if envvar_begin < 0: break envvar_end = s.find(ENV_SLOT_TRAILER, envvar_begin) envvar = s[envvar_begin + len(ENV_SLOT_LEADER):envvar_end] value = os.getenv(envvar) or '' s = s[:envvar_begin] + value + s[envvar_end+1:] return s def interpolate_vars(s): return s % vars def interpolate(s): return interpolate_helpers(interpolate_env(interpolate_vars(s))) # sentinel to use as dict value for preserved headers class _keep_header: pass ### ### VARIABLE INITIALISATION ### infname = None inf = sys.stdin outfname = None outf = sys.stdout templname = None templ = None vars = {} headers = {} payload = None ### ### COMMAND LINE PARSING ### parser = OptionParser() parser.prog = __name__ parser.version = __version__ parser.description = __description__ parser.usage = '%prog [options] ' 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 ### CONFFILE = os.path.expanduser(CONFFILE) MAILPLATEDIR = os.path.expanduser(MAILPLATEDIR) # defaults config = { 'default_template' : 'default' , 'template_path' : TEMPLATEDIR } 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) 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(): if len(kvpair[1]) > 0: f.write('%s = %s\n' % kvpair) if len(helpers) > 0: f.write('\n[%s]\n' % SECTION_HELPERS) for kvpair in helpers.iteritems(): f.write('%s = %s\n' % kvpair) f.close() if not os.access(CONFFILE, os.R_OK): err('cannot read configuration file: %s' % CONFFILE) sys.exit(posix.EX_OSFILE) # now parse parser = ConfigParser.SafeConfigParser() 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: err("no section '%s' in %s" % (SECTION_GENERAL, CONFFILE)) sys.exit(posix.EX_CONFIG) except ConfigParser.NoOptionError: continue except ConfigParser.DuplicateSectionError, ConfigParser.ParseError: err('parse error on %s' % CONFFILE) sys.exit(posix.EX_CONFIG) # all HELPERS into the helpers dict 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) 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() if options.debug: import pdb pdb.set_trace() # parse the arguments for arg in args: if arg == '-': 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 else: err('unknown argument: %s' % arg) sys.exit(posix.EX_USAGE) # sanity checks if options.auto and options.menu: err('cannot combine --auto and --menu') sys.exit(posix.EX_USAGE) elif (options.auto or options.menu) and templname: err('cannot specify a template with --auto or --menu') sys.exit(posix.EX_USAGE) elif not templname and not (options.auto or options.menu): 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') sys.exit(posix.EX_USAGE) ### ### MAIL PROCESSING ### # read in the message from a file, if a filename is given. if infname is not None: inf = file(infname, 'r', 1) # read message into buffer, or preinitialise the buffer if --new is given if options.new: rawmsg = '\n'.join((header + ': ' for header in STD_HEADERS)) + '\n' else: rawmsg = ''.join(inf.readlines()) if options.auto: best_score = (0, default_templname, {}) for tf in os.listdir(TPATH): tp = os.path.join(TPATH, tf) if not os.path.isfile(tp) or not os.access(tp, os.R_OK): continue # we're iterating all files in the template directory # for each file, obtain and run regexps and commands and accumulate # the score (and variables) score = 0 vars = {} f = open(tp, 'r') for line in f: if line[0] == REGEXPCHAR: r = exec_regexp(line, rawmsg, tf) score += r[0] vars.update(r[1]) elif line[0] == COMMANDCHAR: score += exec_command(line, rawmsg, tf) # do we top the currently best score, if so then raise the bar if score > best_score[0]: best_score = (score, tf, vars) templname = best_score[1] if templname is None: err('could not determine a template to use and no default is set') sys.exit(posix.EX_CONFIG) 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): err('not a template: ' + templpath) sys.exit(posix.EX_OSFILE) 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) for line in templ: if not options.auto and line[0] == REGEXPCHAR: # obtain variables from the regexps vars.update(exec_regexp(line, rawmsg, templname)[1]) if line[0] in (COMMENTCHAR, REGEXPCHAR, COMMANDCHAR): continue elif payload is not None: # we're past the headers, so accumulate the payload payload += line else: #TODO multiline headers l = line[:-1] if len(l) == 0: payload = '' # end of headers 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() if content == KEEP_SLOT_LEADER + 'KEEP': # record header to be preserved content = _keep_header else: content = interpolate(content) headers[header.lower()] = (header, content) 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): # the template defines this header if headers[lheader][1] == _keep_header: # it's marked as keep, thus use content from email message headers[lheader] = (header, content) elif options.keep_unknown: # the template does not define the header, but --keep-unknown was # given, thus preserve the entire header field headers[lheader] = (header, content) # open the output file if outfname is not None: outf = file(outfname, 'w', 0) # print the headers, starting with the standard headers in order for header in STD_HEADERS: lheader = header.lower() 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) # and remove it from the dict del headers[lheader] for i, (header, content) in headers.iteritems(): # print all remaining headers if content == _keep_header: continue print >>outf, ': '.join((header, content)) # print empty line to indicate end of headers. print >>outf # split payload of existing message into body and signature body = msg.get_payload().rsplit(SIG_DELIM, 1) signature = '' if len(body) == 1: body = body[0] elif len(body) > 1: signature = body[-1] body = SIG_DELIM.join(body[:-1]).strip() # signature may now be '' # interpolate the template payload payload = interpolate(payload) # determine whether to interpolate the signature *before* inserting the body # to prevent text in the body from being interpolated keep_sig = payload.find('@KEEP_SIGNATURE') >= 0 # interpolate body and signature payload = payload.replace('@KEEP_BODY', body, 1) if keep_sig: payload = payload.replace('@KEEP_SIGNATURE', signature, 1) print >>outf, payload.rstrip() outf.close() if options.edit: # finally, spawn the editor, if we wrote into a file if outfname is None: err('cannot use --edit without an output file.') sys.exit(posix.EX_USAGE) os.execlp('sensible-editor', 'sensible-editor', outfname) mailplate-0.2/mailplate.xml000066400000000000000000000331451103762731600160310ustar00rootroot00000000000000 .
will be generated. You may view the manual page with: nroff -man .
| less'. A typical entry in a Makefile or Makefile.am is: DB2MAN=/usr/share/sgml/docbook/stylesheet/xsl/nwalsh/\ manpages/docbook.xsl XP=xsltproc -''-nonet manpage.1: manpage.dbk $(XP) $(DB2MAN) $< The xsltproc binary is found in the xsltproc package. The XSL files are in docbook-xsl. Please remember that if you create the nroff version in one of the debian/rules file targets (such as build), you will need to include xsltproc and docbook-xsl in your Build-Depends control field. --> Martin F."> Krafft"> September 30, 2007"> 1"> madduck@madduck.net"> MAILPLATE"> Debian"> GNU"> GPL"> ]>
&dhemail;
2007 &dhusername; &dhdate;
&dhucpackage; &dhsection; &dhpackage; reformat mail drafts according to templates &dhpackage; template-name message-file &dhpackage; template-name < message-data &dhpackage; message-file &dhpackage; < message data &dhpackage; &dhpackage; DESCRIPTION &dhpackage; is a programme that 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 (). A selection menu feature is planned (). Applying a template means obtainined select data from an existing mail message (unless is specified) and to fill it into appropriate slots in the template. Messages are processed in three parts: headers, body, and signature. When is given, an empty instantiation of the template is written to stdout. At the moment, cannot be combined with . 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 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. When is passed, the script spawns sensible-editor on the result. It may thus be used as the editor for your mail user agent. Given a file, it modifies the file, unless it cannot write to the file, in which case it writes to stdout. OPTIONS These programs follow the usual &gnu; command line syntax, with long options starting with two dashes (`--'): , Turn on template auto-discovery. , Choose from a list of templates (not yet implemented) , Create a new message. , Spawn editor once template is applied. , Preserve mail headers not specified in template. , Write informational messages to stderr. , Start a debugger after initialisation. , Show summary of options. , Show version of program. MUTT INTEGRATION I use mailplate as my $editor for mutt, with the following setting in my ~/.mutt/muttrc: mailplate currently has a bit of a limitation, or at least I have not figured out a way how to work around it sensibly yet. If you re-edit a message from mutt’s compose menu, it causes mailplate to reprocess the message, which you may not want. Similarly, if you find yourself editing messages from the index or pager, you probably also don’t want mailplate to get in the way. For these cases, I currently use the following two keybindings: :set editor=sensible-editor:set editor="$my_editor"' "invoke normal editor to edit message" macro index,pager e ':set my_editor="$editor":set editor=sensible-editor:set editor="$my_editor"' "invoke normal editor to edit message" ]]> VIM INTEGRATION I am often editing a message with vim and find that I need to use a different identity. For this purpose, I have the following keybindings in my ~/.vim/ftplugin/mail.vim: :w:%!mailplate --keep-unknown --auto nmap :w:%!mailplate --keep-unknown private nmap :w:%!mailplate --keep-unknown debian ]]> Now when I reply to a message, mailplate automatically choses the right template, and if I later change my mind, I can press C-p, to override the choice and select the private template, or just hit to have it re-run the auto-detection. SEE ALSO http://madduck.net/code/mailplate/ /usr/share/doc/mailplate/README AUTHOR This manual page was written by &dhusername; &dhemail; with help from Carl Fürstenberg.
mailplate-0.2/templates/000077500000000000000000000000001103762731600153275ustar00rootroot00000000000000mailplate-0.2/templates/debian000066400000000000000000000011501103762731600164710ustar00rootroot00000000000000* 10 ^(?:To|Cc|From): .+@(?:.+\.)*debian\.(org|net) * 8 ^(?:To|Cc): .+@(?:.+\.)*backports\.org * 8 ^(?:To|Cc): .+@(?:.+\.)*debconf\.org From: martin f krafft @KEEP_STD_HEADERS Organization: The Debian project X-OS: Debian GNU/Linux $(get_debian_version) kernel $(get_kernel) X-Motto: Keep the good times rollin' @KEEP_BODY -- .''`. martin f. krafft : :' : proud Debian developer, author, administrator, and user `. `'` http://people.debian.org/~madduck - http://debiansystem.info `- Debian - when you have better things to do than fixing systems $(get_quote) mailplate-0.2/templates/private000066400000000000000000000007741103762731600167340ustar00rootroot00000000000000#override From header (breaks mutt's reverse_name setting) #@KEEP_FROM_HEADER From: martin f krafft @KEEP_STD_HEADERS X-OS: Debian GNU/Linux $(get_debian_version) kernel $(get_kernel) X-Motto: Keep the good times rollin' X-Subliminal-Message: debian/rules! X-Spamtrap: madduck.bogus@madduck.net @KEEP_BODY -- martin; (greetings from the heart of the sun.) \____ echo mailto: !#^."<*>"|tr "<*> mailto:" net@madduck $(get_quote) spamtraps: madduck.bogus@madduck.net