diff options
| -rw-r--r-- | Mailman/bin/config_list.py (renamed from bin/config_list) | 229 | ||||
| -rw-r--r-- | Mailman/bin/export.py | 308 | ||||
| -rw-r--r-- | Mailman/bin/list_lists.py | 4 | ||||
| -rw-r--r-- | bin/Makefile.in | 11 | ||||
| -rwxr-xr-x | configure | 3 | ||||
| -rw-r--r-- | configure.in | 3 |
6 files changed, 417 insertions, 141 deletions
diff --git a/bin/config_list b/Mailman/bin/config_list.py index 25d4fb62a..32daecc6c 100644 --- a/bin/config_list +++ b/Mailman/bin/config_list.py @@ -1,6 +1,4 @@ -#! @PYTHON@ -# -# Copyright (C) 1998-2005 by the Free Software Foundation, Inc. +# Copyright (C) 1998-2006 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -17,97 +15,92 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -"""Configure a list from a text file description. - -Usage: config_list [options] listname - -Options: - --inputfile filename - -i filename - Configure the list by assigning each module-global variable in the - file to an attribute on the list object, then saving the list. The - named file is loaded with execfile() and must be legal Python code. - Any variable that isn't already an attribute of the list object is - ignored (a warning message is printed). See also the -c option. - - A special variable named `mlist' is put into the globals during the - execfile, which is bound to the actual MailList object. This lets you - do all manner of bizarre thing to the list object, but BEWARE! Using - this can severely (and possibly irreparably) damage your mailing list! - - --outputfile filename - -o filename - Instead of configuring the list, print out a list's configuration - variables in a format suitable for input using this script. In this - way, you can easily capture the configuration settings for a - particular list and imprint those settings on another list. filename - is the file to output the settings to. If filename is `-', standard - out is used. - - --checkonly - -c - With this option, the modified list is not actually changed. Only - useful with -i. - - --verbose - -v - Print the name of each attribute as it is being changed. Only useful - with -i. - - --help - -h - Print this help message and exit. - -The options -o and -i are mutually exclusive. - -""" - -import sys import re +import sys import time -import getopt -from types import TupleType +import optparse -import paths -from Mailman import mm_cfg +from Mailman import Errors from Mailman import MailList from Mailman import Utils -from Mailman import Errors +from Mailman import Version from Mailman import i18n +from Mailman.configuration import config _ = i18n._ +__i18n_templates__ = True NL = '\n' nonasciipat = re.compile(r'[\x80-\xff]') -def usage(code, msg=''): - if code: - fd = sys.stderr - else: - fd = sys.stdout - print >> fd, _(__doc__) - if msg: - print >> fd, msg - sys.exit(code) +def parseargs(): + parser = optparse.OptionParser(version=Version.MAILMAN_VERSION, + usage=_("""\ +%prog [options] listname + +Configure a list from a text file description, or dump a list's configuration +settings.""")) + parser.add_option('-i', '--inputfile', + metavar='FILENAME', default=None, type='string', + help=_("""\ +Configure the list by assigning each module-global variable in the file to an +attribute on the mailing list object, then save the list. The named file is +loaded with execfile() and must be legal Python code. Any variable that isn't +already an attribute of the list object is ignored (a warning message is +printed). See also the -c option. + +A special variable named 'mlist' is put into the globals during the execfile, +which is bound to the actual MailList object. This lets you do all manner of +bizarre thing to the list object, but BEWARE! Using this can severely (and +possibly irreparably) damage your mailing list! + +The may not be used with the -o option.""")) + parser.add_option('-o', '--outputfile', + metavar='FILENAME', default=None, type='string', + help=_("""\ +Instead of configuring the list, print out a mailing list's configuration +variables in a format suitable for input using this script. In this way, you +can easily capture the configuration settings for a particular list and +imprint those settings on another list. FILENAME is the file to output the +settings to. If FILENAME is `-', standard out is used. + +This may not be used with the -i option.""")) + parser.add_option('-c', '--checkonly', + default=False, action='store_true', help=_("""\ +With this option, the modified list is not actually changed. This is only +useful with the -i option.""")) + parser.add_option('-v', '--verbose', + default=False, action='store_true', help=_("""\ +Print the name of each attribute as it is being changed. This is only useful +with the -i option.""")) + parser.add_option('-C', '--config', + help=_('Alternative configuration file to use')) + opts, args = parser.parse_args() + if len(args) > 1: + parser.print_help() + parser.error(_('Unexpected arguments')) + if not args: + parser.error(_('List name is required')) + return parser, opts, args -def do_output(listname, outfile): - closep = 0 +def do_output(listname, outfile, parser): + closep = False try: if outfile == '-': outfp = sys.stdout else: outfp = open(outfile, 'w') - closep = 1 + closep = True # Open the specified list unlocked, since we're only reading it. try: - mlist = MailList.MailList(listname, lock=0) + mlist = MailList.MailList(listname, lock=False) except Errors.MMListError: - usage(1, _('No such list: %(listname)s')) - # Preamble for the config info. PEP263 charset and capture time. + parser.error(_('No such list: $listname')) + # Preamble for the config info. PEP 263 charset and capture time. language = mlist.preferred_language charset = Utils.GetCharSet(language) i18n.set_language(language) @@ -116,13 +109,13 @@ def do_output(listname, outfile): when = time.ctime(time.time()) print >> outfp, _('''\ # -*- python -*- -# -*- coding: %(charset)s -*- -## "%(listname)s" mailing list configuration settings -## captured on %(when)s +# -*- coding: $charset -*- +## "$listname" mailing list configuration settings +## captured on $when ''') - # get all the list config info. all this stuff is accessible via the - # web interface - for k in mm_cfg.ADMIN_CATEGORIES: + # Get all the list config info. All this stuff is accessible via the + # web interface. + for k in config.ADMIN_CATEGORIES: subcats = mlist.GetConfigSubCategories(k) if subcats is None: do_list_categories(mlist, k, None, outfp) @@ -134,6 +127,7 @@ def do_output(listname, outfile): outfp.close() + def do_list_categories(mlist, k, subcat, outfp): info = mlist.GetConfigInfo(k, subcat) label, gui = mlist.GetConfigCategories()[k] @@ -145,14 +139,14 @@ def do_list_categories(mlist, k, subcat, outfp): # First, massage the descripton text, which could have obnoxious # leading whitespace on second and subsequent lines due to # triple-quoted string nonsense in the source code. - desc = NL.join([s.lstrip() for s in info[0].split('\n')]) + desc = NL.join([s.lstrip() for s in info[0].splitlines()]) # Print out the category description desc = Utils.wrap(desc) - for line in desc.split('\n'): + for line in desc.splitlines(): print >> outfp, '#', line print >> outfp for data in info[1:]: - if not isinstance(data, TupleType): + if not isinstance(data, tuple): continue varname = data[0] # Variable could be volatile @@ -162,7 +156,7 @@ def do_list_categories(mlist, k, subcat, outfp): # First, massage the descripton text, which could have # obnoxious leading whitespace on second and subsequent lines # due to triple-quoted string nonsense in the source code. - desc = NL.join([s.lstrip() for s in data[-1].split('\n')]) + desc = NL.join([s.lstrip() for s in data[-1].splitlines()]) # Now strip out all HTML tags desc = re.sub('<.*?>', '', desc) # And convert </> to <> @@ -178,7 +172,7 @@ def do_list_categories(mlist, k, subcat, outfp): value = gui.getValue(mlist, vtype, varname, data[2]) if value is None and not varname.startswith('_'): value = getattr(mlist, varname) - if vtype in (mm_cfg.String, mm_cfg.Text, mm_cfg.FileUpload): + if vtype in (config.String, config.Text, config.FileUpload): print >> outfp, varname, '=', lines = value.splitlines() if not lines: @@ -197,13 +191,13 @@ def do_list_categories(mlist, k, subcat, outfp): outfp.write(' """') outfp.write(NL.join(lines).replace('"', '\\"')) outfp.write('"""\n') - elif vtype in (mm_cfg.Radio, mm_cfg.Toggle): + elif vtype in (config.Radio, config.Toggle): print >> outfp, '#' print >> outfp, '#', _('legal values are:') # TBD: This is disgusting, but it's special cased # everywhere else anyway... if varname == 'subscribe_policy' and \ - not mm_cfg.ALLOW_OPEN_SUBSCRIBE: + not config.ALLOW_OPEN_SUBSCRIBE: i = 1 else: i = 0 @@ -228,7 +222,7 @@ def getPropertyMap(mlist): subcats = [(None, None)] for subcat, sclabel in subcats: for element in gui.GetConfigInfo(mlist, category, subcat): - if not isinstance(element, TupleType): + if not isinstance(element, tuple): continue propname = element[0] wtype = element[1] @@ -247,35 +241,36 @@ class FakeDoc: pass -def do_input(listname, infile, checkonly, verbose): + +def do_input(listname, infile, checkonly, verbose, parser): fakedoc = FakeDoc() - # open the specified list locked, unless checkonly is set + # Open the specified list locked, unless checkonly is set try: mlist = MailList.MailList(listname, lock=not checkonly) except Errors.MMListError, e: - usage(1, _('No such list "%(listname)s"\n%(e)s')) - savelist = 0 + parser.error(_('No such list "$listname"\n$e')) + savelist = False guibyprop = getPropertyMap(mlist) try: globals = {'mlist': mlist} # Any exception that occurs in execfile() will cause the list to not # be saved, but any other problems are not save-fatal. execfile(infile, globals) - savelist = 1 + savelist = True for k, v in globals.items(): if k in ('mlist', '__builtins__'): continue if not hasattr(mlist, k): - print >> sys.stderr, _('attribute "%(k)s" ignored') + print >> sys.stderr, _('attribute "$k" ignored') continue if verbose: - print >> sys.stderr, _('attribute "%(k)s" changed') + print >> sys.stderr, _('attribute "$k" changed') missing = [] gui, wtype = guibyprop.get(k, (missing, missing)) if gui is missing: # This isn't an official property of the list, but that's # okay, we'll just restore it the old fashioned way - print >> sys.stderr, _('Non-standard property restored: %(k)s') + print >> sys.stderr, _('Non-standard property restored: $k') setattr(mlist, k, v) else: # BAW: This uses non-public methods. This logic taken from @@ -283,10 +278,10 @@ def do_input(listname, infile, checkonly, verbose): try: validval = gui._getValidValue(mlist, k, wtype, v) except ValueError: - print >> sys.stderr, _('Invalid value for property: %(k)s') + print >> sys.stderr, _('Invalid value for property: $k') except Errors.EmailAddressError: print >> sys.stderr, _( - 'Bad email address for option %(k)s: %(v)s') + 'Bad email address for option $k: $v') else: # BAW: Horrible hack, but then this is special cased # everywhere anyway. :( Privacy._setValue() knows that @@ -316,45 +311,21 @@ def do_input(listname, infile, checkonly, verbose): def main(): - try: - opts, args = getopt.getopt( - sys.argv[1:], 'ci:o:vh', - ['checkonly', 'inputfile=', 'outputfile=', 'verbose', 'help']) - except getopt.error, msg: - usage(1, msg) - - # defaults - infile = None - outfile = None - checkonly = 0 - verbose = 0 - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-o', '--outputfile'): - outfile = arg - elif opt in ('-i', '--inputfile'): - infile = arg - elif opt in ('-c', '--checkonly'): - checkonly = 1 - elif opt in ('-v', '--verbose'): - verbose = 1 - - # sanity check - if infile is not None and outfile is not None: - usage(1, _('Only one of -i or -o is allowed')) - if infile is None and outfile is None: - usage(1, _('One of -i or -o is required')) + parser, opts, args = parseargs() + config.load(opts.config) + listname = args[0] - # get the list name - if len(args) <> 1: - usage(1, _('List name is required')) - listname = args[0].lower().strip() + # Sanity check + if opts.inputfile and opts.outputfile: + parser.error(_('Only one of -i or -o is allowed')) + if not opts.inputfile and not opts.outputfile: + parser.error(_('One of -i or -o is required')) - if outfile: - do_output(listname, outfile) + if opts.outputfile: + do_output(listname, opts.outputfile, parser) else: - do_input(listname, infile, checkonly, verbose) + do_input(listname, opts.inputfile, opts.checkonly, + opts.verbose, parser) diff --git a/Mailman/bin/export.py b/Mailman/bin/export.py new file mode 100644 index 000000000..4a72c4e82 --- /dev/null +++ b/Mailman/bin/export.py @@ -0,0 +1,308 @@ +# Copyright (C) 2006 by the Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""Export an XML representation of a mailing list.""" + +import sys +import datetime +import optparse + +from xml.sax.saxutils import escape + +from Mailman import Defaults +from Mailman import Errors +from Mailman import MemberAdaptor +from Mailman import Utils +from Mailman import Version +from Mailman.MailList import MailList +from Mailman.configuration import config +from Mailman.i18n import _ + +__i18n_templates__ = True + +SPACE = ' ' +DOLLAR_STRINGS = ('msg_header', 'msg_footer', + 'digest_header', 'digest_footer', + 'autoresponse_postings_text', + 'autoresponse_admin_text', + 'autoresponse_request_text') + + + +class Indenter: + def __init__(self, fp, indentwidth=4): + self._fp = fp + self._indent = 0 + self._width = indentwidth + + def indent(self): + self._indent += 1 + + def dedent(self): + self._indent -= 1 + assert self._indent >= 0 + + def write(self, s): + self._fp.write(self._indent * self._width * ' ') + self._fp.write(s) + + + +class XMLDumper(object): + def __init__(self, fp): + self._fp = Indenter(fp) + self._tagbuffer = None + self._stack = [] + + def _makeattrs(self, tagattrs): + # The attribute values might contain angle brackets. They might also + # be None. + attrs = [] + for k, v in tagattrs.items(): + if v is None: + v = '' + else: + v = escape(str(v)) + attrs.append('%s="%s"' % (k, v)) + return SPACE.join(attrs) + + def _flush(self, more=True): + if not self._tagbuffer: + return + name, attributes = self._tagbuffer + self._tagbuffer = None + if attributes: + attrstr = ' ' + self._makeattrs(attributes) + else: + attrstr = '' + if more: + print >> self._fp, '<%s%s>' % (name, attrstr) + self._fp.indent() + self._stack.append(name) + else: + print >> self._fp, '<%s%s/>' % (name, attrstr) + + # Use this method when you know you have sub-elements. + def _push_element(self, _name, **_tagattrs): + self._flush() + self._tagbuffer = (_name, _tagattrs) + + def _pop_element(self, _name): + buffered = bool(self._tagbuffer) + self._flush(more=False) + if not buffered: + name = self._stack.pop() + assert name == _name, 'got: %s, expected: %s' % (_name, name) + self._fp.dedent() + print >> self._fp, '</%s>' % name + + # Use this method when you do not have sub-elements + def _element(self, _name, _value=None, **_attributes): + self._flush() + if _attributes: + attrs = ' ' + self._makeattrs(_attributes) + else: + attrs = '' + if _value is None: + print >> self._fp, '<%s%s/>' % (_name, attrs) + else: + # The value might contain angle brackets. + value = escape(str(_value)) + print >> self._fp, '<%s%s>%s</%s>' % (_name, attrs, value, _name) + + def _do_list_categories(self, mlist, k, subcat=None): + is_converted = bool(getattr(mlist, 'use_dollar_strings', False)) + info = mlist.GetConfigInfo(k, subcat) + label, gui = mlist.GetConfigCategories()[k] + if info is None: + return + for data in info[1:]: + if not isinstance(data, tuple): + continue + varname = data[0] + # Variable could be volatile + if varname.startswith('_'): + continue + vtype = data[1] + # Munge the value based on its type + value = None + if hasattr(gui, 'getValue'): + value = gui.getValue(mlist, vtype, varname, data[2]) + if value is None: + value = getattr(mlist, varname) + # Do %-string to $-string conversions if the list hasn't already + # been converted. + if varname == 'use_dollar_strings': + continue + if not is_converted and varname in DOLLAR_STRINGS: + value = Utils.to_dollar(value) + if isinstance(value, list): + self._push_element('option', name=varname) + for v in value: + self._element('value', v) + self._pop_element('option') + else: + self._element('option', name=varname, value=value) + + def _dump_list(self, mlist, with_passwords): + # Write list configuration values + self._push_element('list', name=mlist.fqdn_listname) + self._element('language', mlist.preferred_language) + for k in config.ADMIN_CATEGORIES: + subcats = mlist.GetConfigSubCategories(k) + if subcats is None: + self._do_list_categories(mlist, k) + else: + for subcat in [t[0] for t in subcats]: + self._do_list_categories(mlist, k, subcat) + # Write membership + self._push_element('roster') + digesters = set(mlist.getDigestMemberKeys()) + for member in sorted(mlist.getMembers()): + attrs = dict(id=member) + cased = mlist.getMemberCPAddress(member) + if cased <> member: + dict['original'] = cased + self._push_element('member', **attrs) + self._element('realname', mlist.getMemberName(member)) + if with_passwords: + self._element('password', mlist.getMemberPassword(member)) + self._element('language', mlist.getMemberLanguage(member)) + # Delivery status, combined with the type of delivery + attrs = {} + status = mlist.getDeliveryStatus(member) + if status == MemberAdaptor.ENABLED: + attrs['status'] = 'enabled' + else: + attrs['status'] = 'disabled' + attrs['reason'] = {MemberAdaptor.BYUSER : 'byuser', + MemberAdaptor.BYADMIN : 'byadmin', + MemberAdaptor.BYBOUNCE : 'bybounce', + }.get(mlist.getDeliveryStatus(member), + 'unknown') + if member in digesters: + if mlist.getMemberOption('plain'): + attrs['delivery'] = 'plain' + else: + attrs['delivery'] = 'mime' + else: + attrs['delivery'] = 'regular' + changed = mlist.getDeliveryStatusChangeTime(member) + if changed: + when = datetime.datetime.fromtimestamp(changed) + attrs['changed'] = when.isoformat() + self._element('delivery', **attrs) + self._push_element('options') + for option, flag in Defaults.OPTINFO.items(): + # Digest/Regular delivery flag must be handled separately + if option in ('digest', 'plain'): + continue + value = mlist.getMemberOption(member, flag) + self._element(option, value) + self._pop_element('options') + topics = mlist.getMemberTopics(member) + if not topics: + self._element('topics') + else: + self._push_element('topics') + for topic in topics: + self._element('topic', topic) + self._pop_element('topics') + self._pop_element('member') + self._pop_element('roster') + self._pop_element('list') + + def dump(self, listnames, with_passwords=False): + print >> self._fp, '<?xml version="1.0" encoding="UTF-8"?>' + self._push_element('mailman', **{ + 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:noNamespaceSchemaLocation': 'ssi-1.0.xsd', + }) + for listname in sorted(listnames): + try: + mlist = MailList(listname, lock=False) + except Errors.MMUnknownListError: + print >> sys.stderr, _('No such list: $listname') + continue + self._dump_list(mlist, with_passwords) + self._pop_element('mailman') + + def close(self): + while self._stack: + self._pop_element() + + + +def parseargs(): + parser = optparse.OptionParser(version=Version.MAILMAN_VERSION, + usage=_("""\ +%prog [options] + +Export the configuration and members of a mailing list in XML format.""")) + parser.add_option('-o', '--outputfile', + metavar='FILENAME', default=None, type='string', + help=_("""\ +Output XML to FILENAME. If not given, or if FILENAME is '-', standard out is +used.""")) + parser.add_option('-p', '--with-passwords', + default=False, action='store_true', help=_("""\ +With this option, user passwords are included in cleartext. For this reason, +the default is to not include passwords.""")) + parser.add_option('-l', '--listname', + default=[], action='append', type='string', + dest='listnames', help=_("""\ +The list to include in the output. If not given, then all mailing lists are +included in the XML output. Multiple -l flags may be given.""")) + parser.add_option('-C', '--config', + help=_('Alternative configuration file to use')) + opts, args = parser.parse_args() + if args: + parser.print_help() + parser.error(_('Unexpected arguments')) + return parser, opts, args + + + +def main(): + parser, opts, args = parseargs() + config.load(opts.config) + + if opts.outputfile in (None, '-'): + fp = sys.stdout + else: + fp = open(opts.outputfile, 'w') + + try: + dumper = XMLDumper(fp) + if opts.listnames: + listnames = [] + for listname in opts.listnames: + if '@' not in listname: + listname = '%s@%s' % (listname, config.DEFAULT_EMAIL_HOST) + listnames.append(listname) + else: + listnames = Utils.list_names() + dumper.dump(listnames, opts.with_passwords) + dumper.close() + finally: + if fp is not sys.stdout: + fp.close() + + + +if __name__ == '__main__': + main() diff --git a/Mailman/bin/list_lists.py b/Mailman/bin/list_lists.py index b766f85bc..c9480217d 100644 --- a/Mailman/bin/list_lists.py +++ b/Mailman/bin/list_lists.py @@ -15,7 +15,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -import sys import optparse from Mailman import Defaults @@ -59,8 +58,7 @@ ignored when -b is given.""")) opts, args = parser.parse_args() if args: parser.print_help() - print >> sys.stderr, _('Unexpected arguments') - sys.exit(1) + parser.error(_('Unexpected arguments')) return parser, opts, args diff --git a/bin/Makefile.in b/bin/Makefile.in index 1aeb68923..2256cfd60 100644 --- a/bin/Makefile.in +++ b/bin/Makefile.in @@ -47,16 +47,17 @@ SHELL= /bin/sh SCRIPTS= mmshell \ remove_members clone_member \ sync_members check_db withlist \ - config_list dumpdb cleanarch \ + dumpdb cleanarch \ list_admins \ fix_url.py convert.py transcheck \ msgfmt.py discard \ reset_pw.py templ2pot.py po2templ.py -LN_SCRIPTS= add_members arch change_pw check_perms find_member \ - genaliases inject list_lists list_members list_owners \ - mailmanctl mmsitepass newlist qrunner rmlist show_qfiles \ - show_mm_cfg testall unshunt update version +LN_SCRIPTS= add_members arch change_pw check_perms config_list \ + export find_member genaliases inject list_lists \ + list_members list_owners mailmanctl mmsitepass newlist \ + qrunner rmlist show_qfiles show_mm_cfg testall unshunt \ + update version BUILDDIR= ../build/bin @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.in Revision: 8038 . +# From configure.in Revision: 8040 . # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.59 for GNU Mailman 2.2.0a0. # @@ -4287,7 +4287,6 @@ done SCRIPTS="build/bin/check_db:bin/check_db \ build/bin/cleanarch:bin/cleanarch \ build/bin/clone_member:bin/clone_member \ -build/bin/config_list:bin/config_list \ build/bin/convert.py:bin/convert.py \ build/bin/discard:bin/discard \ build/bin/dumpdb:bin/dumpdb \ diff --git a/configure.in b/configure.in index fd6e793dc..d5017718d 100644 --- a/configure.in +++ b/configure.in @@ -16,7 +16,7 @@ # USA. dnl Process this file with autoconf to produce a configure script. -AC_REVISION($Revision: 8040 $) +AC_REVISION($Revision: 8051 $) AC_PREREQ(2.0) AC_INIT([GNU Mailman], [2.2.0a0]) @@ -595,7 +595,6 @@ AC_DEFUN(MM_SCRIPTS, [dnl bin/check_db \ bin/cleanarch \ bin/clone_member \ -bin/config_list \ bin/convert.py \ bin/discard \ bin/dumpdb \ |
