diff options
Diffstat (limited to 'src/mailman/attic/bin')
| -rwxr-xr-x | src/mailman/attic/bin/clone_member | 219 | ||||
| -rw-r--r-- | src/mailman/attic/bin/discard | 120 | ||||
| -rw-r--r-- | src/mailman/attic/bin/fix_url.py | 93 | ||||
| -rw-r--r-- | src/mailman/attic/bin/list_admins | 101 | ||||
| -rw-r--r-- | src/mailman/attic/bin/msgfmt.py | 203 | ||||
| -rw-r--r-- | src/mailman/attic/bin/po2templ.py | 90 | ||||
| -rw-r--r-- | src/mailman/attic/bin/pygettext.py | 545 | ||||
| -rwxr-xr-x | src/mailman/attic/bin/remove_members | 186 | ||||
| -rw-r--r-- | src/mailman/attic/bin/reset_pw.py | 83 | ||||
| -rwxr-xr-x | src/mailman/attic/bin/sync_members | 286 | ||||
| -rw-r--r-- | src/mailman/attic/bin/templ2pot.py | 120 | ||||
| -rwxr-xr-x | src/mailman/attic/bin/transcheck | 412 |
12 files changed, 2458 insertions, 0 deletions
diff --git a/src/mailman/attic/bin/clone_member b/src/mailman/attic/bin/clone_member new file mode 100755 index 000000000..1f2a03aca --- /dev/null +++ b/src/mailman/attic/bin/clone_member @@ -0,0 +1,219 @@ +#! @PYTHON@ +# +# Copyright (C) 1998,1999,2000,2001,2002 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. + +"""Clone a member address. + +Cloning a member address means that a new member will be added who has all the +same options and passwords as the original member address. Note that this +operation is fairly trusting of the user who runs it -- it does no +verification to the new address, it does not send out a welcome message, etc. + +The existing member's subscription is usually not modified in any way. If you +want to remove the old address, use the -r flag. If you also want to change +any list admin addresses, use the -a flag. + +Usage: + clone_member [options] fromoldaddr tonewaddr + +Where: + + --listname=listname + -l listname + Check and modify only the named mailing lists. If -l is not given, + then all mailing lists are scanned from the address. Multiple -l + options can be supplied. + + --remove + -r + Remove the old address from the mailing list after it's been cloned. + + --admin + -a + Scan the list admin addresses for the old address, and clone or change + them too. + + --quiet + -q + Do the modifications quietly. + + --nomodify + -n + Print what would be done, but don't actually do it. Inhibits the + --quiet flag. + + --help + -h + Print this help message and exit. + + fromoldaddr (`from old address') is the old address of the user. tonewaddr + (`to new address') is the new address of the user. + +""" + +import sys +import getopt + +import paths +from Mailman import MailList +from Mailman import Utils +from Mailman import Errors +from Mailman.i18n import _ + + + +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 dolist(mlist, options): + SPACE = ' ' + if not options.quiet: + print _('processing mailing list:'), mlist.internal_name() + + # scan the list owners. TBD: mlist.owner keys should be lowercase? + oldowners = mlist.owner[:] + oldowners.sort() + if options.admintoo: + if not options.quiet: + print _(' scanning list owners:'), SPACE.join(oldowners) + newowners = {} + foundp = 0 + for owner in mlist.owner: + if options.lfromaddr == owner.lower(): + foundp = 1 + if options.remove: + continue + newowners[owner] = 1 + if foundp: + newowners[options.toaddr] = 1 + newowners = newowners.keys() + newowners.sort() + if options.modify: + mlist.owner = newowners + if not options.quiet: + if newowners <> oldowners: + print + print _(' new list owners:'), SPACE.join(newowners) + else: + print _('(no change)') + + # see if the fromaddr is a digest member or regular member + if options.lfromaddr in mlist.getDigestMemberKeys(): + digest = 1 + elif options.lfromaddr in mlist.getRegularMemberKeys(): + digest = 0 + else: + if not options.quiet: + print _(' address not found:'), options.fromaddr + return + + # Now change the membership address + try: + if options.modify: + mlist.changeMemberAddress(options.fromaddr, options.toaddr, + not options.remove) + if not options.quiet: + print _(' clone address added:'), options.toaddr + except Errors.MMAlreadyAMember: + if not options.quiet: + print _(' clone address is already a member:'), options.toaddr + + if options.remove: + print _(' original address removed:'), options.fromaddr + + + +def main(): + # default options + class Options: + listnames = None + remove = 0 + admintoo = 0 + quiet = 0 + modify = 1 + + # scan sysargs + try: + opts, args = getopt.getopt( + sys.argv[1:], 'arl:qnh', + ['admin', 'remove', 'listname=', 'quiet', 'nomodify', 'help']) + except getopt.error, msg: + usage(1, msg) + + options = Options() + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--quiet'): + options.quiet = 1 + elif opt in ('-n', '--nomodify'): + options.modify = 0 + elif opt in ('-a', '--admin'): + options.admintoo = 1 + elif opt in ('-r', '--remove'): + options.remove = 1 + elif opt in ('-l', '--listname'): + if options.listnames is None: + options.listnames = [] + options.listnames.append(arg.lower()) + + # further options and argument processing + if not options.modify: + options.quiet = 0 + + if len(args) <> 2: + usage(1) + fromaddr = args[0] + toaddr = args[1] + + # validate and normalize the target address + try: + Utils.ValidateEmail(toaddr) + except Errors.EmailAddressError: + usage(1, _('Not a valid email address: %(toaddr)s')) + lfromaddr = fromaddr.lower() + options.toaddr = toaddr + options.fromaddr = fromaddr + options.lfromaddr = lfromaddr + + if options.listnames is None: + options.listnames = Utils.list_names() + + for listname in options.listnames: + try: + mlist = MailList.MailList(listname) + except Errors.MMListError, e: + print _('Error opening list "%(listname)s", skipping.\n%(e)s') + continue + try: + dolist(mlist, options) + finally: + mlist.Save() + mlist.Unlock() + + +if __name__ == '__main__': + main() diff --git a/src/mailman/attic/bin/discard b/src/mailman/attic/bin/discard new file mode 100644 index 000000000..c30198441 --- /dev/null +++ b/src/mailman/attic/bin/discard @@ -0,0 +1,120 @@ +#! @PYTHON@ +# +# Copyright (C) 2003 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. + +"""Discard held messages. + +Usage: + discard [options] file ... + +Options: + --help / -h + Print this help message and exit. + + --quiet / -q + Don't print status messages. +""" + +# TODO: add command line arguments for specifying other actions than DISCARD, +# and also for specifying other __handlepost() arguments, i.e. comment, +# preserve, forward, addr + +import os +import re +import sys +import getopt + +import paths +from Mailman import mm_cfg +from Mailman.MailList import MailList +from Mailman.i18n import _ + +try: + True, False +except NameError: + True = 1 + False = 0 + +cre = re.compile(r'heldmsg-(?P<listname>.*)-(?P<id>[0-9]+)\.(pck|txt)$') + + + +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 main(): + try: + opts, args = getopt.getopt(sys.argv[1:], 'hq', ['help', 'quiet']) + except getopt.error, msg: + usage(1, msg) + + quiet = False + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--quiet'): + quiet = True + + files = args + if not files: + print _('Nothing to do.') + + # Mapping from listnames to sequence of request ids + discards = {} + + # Cruise through all the named files, collating by mailing list. We'll + # lock the list once, process all holds for that list and move on. + for f in files: + basename = os.path.basename(f) + mo = cre.match(basename) + if not mo: + print >> sys.stderr, _('Ignoring non-held message: %(f)s') + continue + listname, id = mo.group('listname', 'id') + try: + id = int(id) + except (ValueError, TypeError): + print >> sys.stderr, _('Ignoring held msg w/bad id: %(f)s') + continue + discards.setdefault(listname, []).append(id) + + # Now do the discards + for listname, ids in discards.items(): + mlist = MailList(listname) + try: + for id in ids: + # No comment, no preserve, no forward, no forwarding address + mlist.HandleRequest(id, mm_cfg.DISCARD, '', False, False, '') + if not quiet: + print _('Discarded held msg #%(id)s for list %(listname)s') + mlist.Save() + finally: + mlist.Unlock() + + + +if __name__ == '__main__': + main() diff --git a/src/mailman/attic/bin/fix_url.py b/src/mailman/attic/bin/fix_url.py new file mode 100644 index 000000000..30618a1a3 --- /dev/null +++ b/src/mailman/attic/bin/fix_url.py @@ -0,0 +1,93 @@ +#! @PYTHON@ +# +# Copyright (C) 2001-2009 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. + +"""Reset a list's web_page_url attribute to the default setting. + +This script is intended to be run as a bin/withlist script, i.e. + +% bin/withlist -l -r fix_url listname [options] + +Options: + -u urlhost + --urlhost=urlhost + Look up urlhost in the virtual host table and set the web_page_url and + host_name attributes of the list to the values found. This + essentially moves the list from one virtual domain to another. + + Without this option, the default web_page_url and host_name values are + used. + + -v / --verbose + Print what the script is doing. + +If run standalone, it prints this help text and exits. +""" + +import sys +import getopt + +import paths +from Mailman.configuration import config +from Mailman.i18n import _ + + + +def usage(code, msg=''): + print _(__doc__.replace('%', '%%')) + if msg: + print msg + sys.exit(code) + + + +def fix_url(mlist, *args): + try: + opts, args = getopt.getopt(args, 'u:v', ['urlhost=', 'verbose']) + except getopt.error, msg: + usage(1, msg) + + verbose = 0 + urlhost = mailhost = None + for opt, arg in opts: + if opt in ('-u', '--urlhost'): + urlhost = arg + elif opt in ('-v', '--verbose'): + verbose = 1 + + if urlhost: + web_page_url = config.DEFAULT_URL_PATTERN % urlhost + mailhost = config.VIRTUAL_HOSTS.get(urlhost.lower(), urlhost) + else: + web_page_url = config.DEFAULT_URL_PATTERN % config.DEFAULT_URL_HOST + mailhost = config.DEFAULT_EMAIL_HOST + + if verbose: + print _('Setting web_page_url to: %(web_page_url)s') + mlist.web_page_url = web_page_url + if verbose: + print _('Setting host_name to: %(mailhost)s') + mlist.host_name = mailhost + print _('Saving list') + mlist.Save() + mlist.Unlock() + + + +if __name__ == '__main__': + usage(0) diff --git a/src/mailman/attic/bin/list_admins b/src/mailman/attic/bin/list_admins new file mode 100644 index 000000000..c628a42dc --- /dev/null +++ b/src/mailman/attic/bin/list_admins @@ -0,0 +1,101 @@ +#! @PYTHON@ +# +# Copyright (C) 2001,2002 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. + +"""List all the owners of a mailing list. + +Usage: %(program)s [options] listname ... + +Where: + + --all-vhost=vhost + -v=vhost + List the owners of all the mailing lists for the given virtual host. + + --all + -a + List the owners of all the mailing lists on this system. + + --help + -h + Print this help message and exit. + +`listname' is the name of the mailing list to print the owners of. You can +have more than one named list on the command line. +""" + +import sys +import getopt + +import paths +from Mailman import MailList, Utils +from Mailman import Errors +from Mailman.i18n import _ + +COMMASPACE = ', ' + +program = sys.argv[0] + + + +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 main(): + try: + opts, args = getopt.getopt(sys.argv[1:], 'hv:a', + ['help', 'all-vhost=', 'all']) + except getopt.error, msg: + usage(1, msg) + + listnames = args + vhost = None + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-a', '--all'): + listnames = Utils.list_names() + elif opt in ('-v', '--all-vhost'): + listnames = Utils.list_names() + vhost = arg + + for listname in listnames: + try: + mlist = MailList.MailList(listname, lock=0) + except Errors.MMListError, e: + print _('No such list: %(listname)s') + continue + + if vhost and vhost <> mlist.host_name: + continue + + owners = COMMASPACE.join(mlist.owner) + print _('List: %(listname)s, \tOwners: %(owners)s') + + + +if __name__ == '__main__': + main() diff --git a/src/mailman/attic/bin/msgfmt.py b/src/mailman/attic/bin/msgfmt.py new file mode 100644 index 000000000..8a2d4e66e --- /dev/null +++ b/src/mailman/attic/bin/msgfmt.py @@ -0,0 +1,203 @@ +#! /usr/bin/env python +# -*- coding: iso-8859-1 -*- +# Written by Martin v. Löwis <loewis@informatik.hu-berlin.de> + +"""Generate binary message catalog from textual translation description. + +This program converts a textual Uniforum-style message catalog (.po file) into +a binary GNU catalog (.mo file). This is essentially the same function as the +GNU msgfmt program, however, it is a simpler implementation. + +Usage: msgfmt.py [OPTIONS] filename.po + +Options: + -o file + --output-file=file + Specify the output file to write to. If omitted, output will go to a + file named filename.mo (based off the input file name). + + -h + --help + Print this message and exit. + + -V + --version + Display version information and exit. +""" + +import sys +import os +import getopt +import struct +import array + +__version__ = "1.1" + +MESSAGES = {} + + + +def usage(code, msg=''): + print >> sys.stderr, __doc__ + if msg: + print >> sys.stderr, msg + sys.exit(code) + + + +def add(id, str, fuzzy): + "Add a non-fuzzy translation to the dictionary." + global MESSAGES + if not fuzzy and str: + MESSAGES[id] = str + + + +def generate(): + "Return the generated output." + global MESSAGES + keys = MESSAGES.keys() + # the keys are sorted in the .mo file + keys.sort() + offsets = [] + ids = strs = '' + for id in keys: + # For each string, we need size and file offset. Each string is NUL + # terminated; the NUL does not count into the size. + offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id]))) + ids += id + '\0' + strs += MESSAGES[id] + '\0' + output = '' + # The header is 7 32-bit unsigned integers. We don't use hash tables, so + # the keys start right after the index tables. + # translated string. + keystart = 7*4+16*len(keys) + # and the values start after the keys + valuestart = keystart + len(ids) + koffsets = [] + voffsets = [] + # The string table first has the list of keys, then the list of values. + # Each entry has first the size of the string, then the file offset. + for o1, l1, o2, l2 in offsets: + koffsets += [l1, o1+keystart] + voffsets += [l2, o2+valuestart] + offsets = koffsets + voffsets + output = struct.pack("Iiiiiii", + 0x950412deL, # Magic + 0, # Version + len(keys), # # of entries + 7*4, # start of key index + 7*4+len(keys)*8, # start of value index + 0, 0) # size and offset of hash table + output += array.array("i", offsets).tostring() + output += ids + output += strs + return output + + + +def make(filename, outfile): + ID = 1 + STR = 2 + + # Compute .mo name from .po name and arguments + if filename.endswith('.po'): + infile = filename + else: + infile = filename + '.po' + if outfile is None: + outfile = os.path.splitext(infile)[0] + '.mo' + + try: + lines = open(infile).readlines() + except IOError, msg: + print >> sys.stderr, msg + sys.exit(1) + + section = None + fuzzy = 0 + + # Parse the catalog + lno = 0 + for l in lines: + lno += 1 + # If we get a comment line after a msgstr, this is a new entry + if l[0] == '#' and section == STR: + add(msgid, msgstr, fuzzy) + section = None + fuzzy = 0 + # Record a fuzzy mark + if l[:2] == '#,' and l.find('fuzzy'): + fuzzy = 1 + # Skip comments + if l[0] == '#': + continue + # Now we are in a msgid section, output previous section + if l.startswith('msgid'): + if section == STR: + add(msgid, msgstr, fuzzy) + section = ID + l = l[5:] + msgid = msgstr = '' + # Now we are in a msgstr section + elif l.startswith('msgstr'): + section = STR + l = l[6:] + # Skip empty lines + l = l.strip() + if not l: + continue + # XXX: Does this always follow Python escape semantics? + l = eval(l) + if section == ID: + msgid += l + elif section == STR: + msgstr += l + else: + print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \ + 'before:' + print >> sys.stderr, l + sys.exit(1) + # Add last entry + if section == STR: + add(msgid, msgstr, fuzzy) + + # Compute output + output = generate() + + try: + open(outfile,"wb").write(output) + except IOError,msg: + print >> sys.stderr, msg + + + +def main(): + try: + opts, args = getopt.getopt(sys.argv[1:], 'hVo:', + ['help', 'version', 'output-file=']) + except getopt.error, msg: + usage(1, msg) + + outfile = None + # parse options + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-V', '--version'): + print >> sys.stderr, "msgfmt.py", __version__ + sys.exit(0) + elif opt in ('-o', '--output-file'): + outfile = arg + # do it + if not args: + print >> sys.stderr, 'No input file given' + print >> sys.stderr, "Try `msgfmt --help' for more information." + return + + for filename in args: + make(filename, outfile) + + +if __name__ == '__main__': + main() diff --git a/src/mailman/attic/bin/po2templ.py b/src/mailman/attic/bin/po2templ.py new file mode 100644 index 000000000..86eae96b9 --- /dev/null +++ b/src/mailman/attic/bin/po2templ.py @@ -0,0 +1,90 @@ +#! @PYTHON@ +# +# Copyright (C) 2005-2009 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. + +# Author: Tokio Kikuchi <tkikuchi@is.kochi-u.ac.jp> + + +"""po2templ.py + +Extract templates from language po file. + +Usage: po2templ.py languages +""" + +import re +import sys + +cre = re.compile('^#:\s*templates/en/(?P<filename>.*?):1') + + + +def do_lang(lang): + in_template = False + in_msg = False + msgstr = '' + fp = file('messages/%s/LC_MESSAGES/mailman.po' % lang) + try: + for line in fp: + m = cre.search(line) + if m: + in_template = True + in_msg = False + filename = m.group('filename') + outfilename = 'templates/%s/%s' % (lang, filename) + continue + if in_template and line.startswith('#,'): + if line.strip() == '#, fuzzy': + in_template = False + continue + if in_template and line.startswith('msgstr'): + line = line[7:] + in_msg = True + if in_msg: + if not line.strip(): + in_template = False + in_msg = False + if len(msgstr) > 1 and outfilename: + # exclude no translation ... 1 is for LF only + outfile = file(outfilename, 'w') + try: + outfile.write(msgstr) + outfile.write('\n') + finally: + outfile.close() + outfilename = '' + msgstr = '' + continue + msgstr += eval(line) + finally: + fp.close() + if len(msgstr) > 1 and outfilename: + # flush remaining msgstr (last template file) + outfile = file(outfilename, 'w') + try: + outfile.write(msgstr) + outfile.write('\n') + finally: + outfile.close() + + + +if __name__ == '__main__': + langs = sys.argv[1:] + for lang in langs: + do_lang(lang) diff --git a/src/mailman/attic/bin/pygettext.py b/src/mailman/attic/bin/pygettext.py new file mode 100644 index 000000000..84421ee8c --- /dev/null +++ b/src/mailman/attic/bin/pygettext.py @@ -0,0 +1,545 @@ +#! @PYTHON@ +# Originally written by Barry Warsaw <barry@zope.com> +# +# Minimally patched to make it even more xgettext compatible +# by Peter Funk <pf@artcom-gmbh.de> + +"""pygettext -- Python equivalent of xgettext(1) + +Many systems (Solaris, Linux, Gnu) provide extensive tools that ease the +internationalization of C programs. Most of these tools are independent of +the programming language and can be used from within Python programs. Martin +von Loewis' work[1] helps considerably in this regard. + +There's one problem though; xgettext is the program that scans source code +looking for message strings, but it groks only C (or C++). Python introduces +a few wrinkles, such as dual quoting characters, triple quoted strings, and +raw strings. xgettext understands none of this. + +Enter pygettext, which uses Python's standard tokenize module to scan Python +source code, generating .pot files identical to what GNU xgettext[2] generates +for C and C++ code. From there, the standard GNU tools can be used. + +A word about marking Python strings as candidates for translation. GNU +xgettext recognizes the following keywords: gettext, dgettext, dcgettext, and +gettext_noop. But those can be a lot of text to include all over your code. +C and C++ have a trick: they use the C preprocessor. Most internationalized C +source includes a #define for gettext() to _() so that what has to be written +in the source is much less. Thus these are both translatable strings: + + gettext("Translatable String") + _("Translatable String") + +Python of course has no preprocessor so this doesn't work so well. Thus, +pygettext searches only for _() by default, but see the -k/--keyword flag +below for how to augment this. + + [1] http://www.python.org/workshops/1997-10/proceedings/loewis.html + [2] http://www.gnu.org/software/gettext/gettext.html + +NOTE: pygettext attempts to be option and feature compatible with GNU xgettext +where ever possible. However some options are still missing or are not fully +implemented. Also, xgettext's use of command line switches with option +arguments is broken, and in these cases, pygettext just defines additional +switches. + +Usage: pygettext [options] inputfile ... + +Options: + + -a + --extract-all + Extract all strings. + + -d name + --default-domain=name + Rename the default output file from messages.pot to name.pot. + + -E + --escape + Replace non-ASCII characters with octal escape sequences. + + -D + --docstrings + Extract module, class, method, and function docstrings. These do not + need to be wrapped in _() markers, and in fact cannot be for Python to + consider them docstrings. (See also the -X option). + + -h + --help + Print this help message and exit. + + -k word + --keyword=word + Keywords to look for in addition to the default set, which are: + %(DEFAULTKEYWORDS)s + + You can have multiple -k flags on the command line. + + -K + --no-default-keywords + Disable the default set of keywords (see above). Any keywords + explicitly added with the -k/--keyword option are still recognized. + + --no-location + Do not write filename/lineno location comments. + + -n + --add-location + Write filename/lineno location comments indicating where each + extracted string is found in the source. These lines appear before + each msgid. The style of comments is controlled by the -S/--style + option. This is the default. + + -o filename + --output=filename + Rename the default output file from messages.pot to filename. If + filename is `-' then the output is sent to standard out. + + -p dir + --output-dir=dir + Output files will be placed in directory dir. + + -S stylename + --style stylename + Specify which style to use for location comments. Two styles are + supported: + + Solaris # File: filename, line: line-number + GNU #: filename:line + + The style name is case insensitive. GNU style is the default. + + -v + --verbose + Print the names of the files being processed. + + -V + --version + Print the version of pygettext and exit. + + -w columns + --width=columns + Set width of output to columns. + + -x filename + --exclude-file=filename + Specify a file that contains a list of strings that are not be + extracted from the input files. Each string to be excluded must + appear on a line by itself in the file. + + -X filename + --no-docstrings=filename + Specify a file that contains a list of files (one per line) that + should not have their docstrings extracted. This is only useful in + conjunction with the -D option above. + +If `inputfile' is -, standard input is read. +""" + +import os +import sys +import time +import getopt +import tokenize +import operator + +# for selftesting +try: + import fintl + _ = fintl.gettext +except ImportError: + def _(s): return s + +__version__ = '1.4' + +default_keywords = ['_'] +DEFAULTKEYWORDS = ', '.join(default_keywords) + +EMPTYSTRING = '' + + + +# The normal pot-file header. msgmerge and Emacs's po-mode work better if it's +# there. +pot_header = _('''\ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\\n" +"POT-Creation-Date: %(time)s\\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n" +"Language-Team: LANGUAGE <LL@li.org>\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=CHARSET\\n" +"Content-Transfer-Encoding: ENCODING\\n" +"Generated-By: pygettext.py %(version)s\\n" + +''') + + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print >> fd, _(__doc__) % globals() + if msg: + print >> fd, msg + sys.exit(code) + + + +escapes = [] + +def make_escapes(pass_iso8859): + global escapes + if pass_iso8859: + # Allow iso-8859 characters to pass through so that e.g. 'msgid + # "H[o-umlaut]he"' would result not result in 'msgid "H\366he"'. + # Otherwise we escape any character outside the 32..126 range. + mod = 128 + else: + mod = 256 + for i in range(256): + if 32 <= (i % mod) <= 126: + escapes.append(chr(i)) + else: + escapes.append("\\%03o" % i) + escapes[ord('\\')] = '\\\\' + escapes[ord('\t')] = '\\t' + escapes[ord('\r')] = '\\r' + escapes[ord('\n')] = '\\n' + escapes[ord('\"')] = '\\"' + + +def escape(s): + global escapes + s = list(s) + for i in range(len(s)): + s[i] = escapes[ord(s[i])] + return EMPTYSTRING.join(s) + + +def safe_eval(s): + # unwrap quotes, safely + return eval(s, {'__builtins__':{}}, {}) + + +def normalize(s): + # This converts the various Python string types into a format that is + # appropriate for .po files, namely much closer to C style. + lines = s.split('\n') + if len(lines) == 1: + s = '"' + escape(s) + '"' + else: + if not lines[-1]: + del lines[-1] + lines[-1] = lines[-1] + '\n' + for i in range(len(lines)): + lines[i] = escape(lines[i]) + lineterm = '\\n"\n"' + s = '""\n"' + lineterm.join(lines) + '"' + return s + + + +class TokenEater: + def __init__(self, options): + self.__options = options + self.__messages = {} + self.__state = self.__waiting + self.__data = [] + self.__lineno = -1 + self.__freshmodule = 1 + self.__curfile = None + + def __call__(self, ttype, tstring, stup, etup, line): + # dispatch +## import token +## print >> sys.stderr, 'ttype:', token.tok_name[ttype], \ +## 'tstring:', tstring + self.__state(ttype, tstring, stup[0]) + + def __waiting(self, ttype, tstring, lineno): + opts = self.__options + # Do docstring extractions, if enabled + if opts.docstrings and not opts.nodocstrings.get(self.__curfile): + # module docstring? + if self.__freshmodule: + if ttype == tokenize.STRING: + self.__addentry(safe_eval(tstring), lineno, isdocstring=1) + self.__freshmodule = 0 + elif ttype not in (tokenize.COMMENT, tokenize.NL): + self.__freshmodule = 0 + return + # class docstring? + if ttype == tokenize.NAME and tstring in ('class', 'def'): + self.__state = self.__suiteseen + return + if ttype == tokenize.NAME and tstring in opts.keywords: + self.__state = self.__keywordseen + + def __suiteseen(self, ttype, tstring, lineno): + # ignore anything until we see the colon + if ttype == tokenize.OP and tstring == ':': + self.__state = self.__suitedocstring + + def __suitedocstring(self, ttype, tstring, lineno): + # ignore any intervening noise + if ttype == tokenize.STRING: + self.__addentry(safe_eval(tstring), lineno, isdocstring=1) + self.__state = self.__waiting + elif ttype not in (tokenize.NEWLINE, tokenize.INDENT, + tokenize.COMMENT): + # there was no class docstring + self.__state = self.__waiting + + def __keywordseen(self, ttype, tstring, lineno): + if ttype == tokenize.OP and tstring == '(': + self.__data = [] + self.__lineno = lineno + self.__state = self.__openseen + else: + self.__state = self.__waiting + + def __openseen(self, ttype, tstring, lineno): + if ttype == tokenize.OP and tstring == ')': + # We've seen the last of the translatable strings. Record the + # line number of the first line of the strings and update the list + # of messages seen. Reset state for the next batch. If there + # were no strings inside _(), then just ignore this entry. + if self.__data: + self.__addentry(EMPTYSTRING.join(self.__data)) + self.__state = self.__waiting + elif ttype == tokenize.STRING: + self.__data.append(safe_eval(tstring)) + # TBD: should we warn if we seen anything else? + + def __addentry(self, msg, lineno=None, isdocstring=0): + if lineno is None: + lineno = self.__lineno + if not msg in self.__options.toexclude: + entry = (self.__curfile, lineno) + self.__messages.setdefault(msg, {})[entry] = isdocstring + + def set_filename(self, filename): + self.__curfile = filename + self.__freshmodule = 1 + + def write(self, fp): + options = self.__options + timestamp = time.ctime(time.time()) + # The time stamp in the header doesn't have the same format as that + # generated by xgettext... + print >> fp, pot_header % {'time': timestamp, 'version': __version__} + # Sort the entries. First sort each particular entry's keys, then + # sort all the entries by their first item. + reverse = {} + for k, v in self.__messages.items(): + keys = v.keys() + keys.sort() + reverse.setdefault(tuple(keys), []).append((k, v)) + rkeys = reverse.keys() + rkeys.sort() + for rkey in rkeys: + rentries = reverse[rkey] + rentries.sort() + for k, v in rentries: + isdocstring = 0 + # If the entry was gleaned out of a docstring, then add a + # comment stating so. This is to aid translators who may wish + # to skip translating some unimportant docstrings. + if reduce(operator.__add__, v.values()): + isdocstring = 1 + # k is the message string, v is a dictionary-set of (filename, + # lineno) tuples. We want to sort the entries in v first by + # file name and then by line number. + v = v.keys() + v.sort() + if not options.writelocations: + pass + # location comments are different b/w Solaris and GNU: + elif options.locationstyle == options.SOLARIS: + for filename, lineno in v: + d = {'filename': filename, 'lineno': lineno} + print >>fp, _( + '# File: %(filename)s, line: %(lineno)d') % d + elif options.locationstyle == options.GNU: + # fit as many locations on one line, as long as the + # resulting line length doesn't exceeds 'options.width' + locline = '#:' + for filename, lineno in v: + d = {'filename': filename, 'lineno': lineno} + s = _(' %(filename)s:%(lineno)d') % d + if len(locline) + len(s) <= options.width: + locline = locline + s + else: + print >> fp, locline + locline = "#:" + s + if len(locline) > 2: + print >> fp, locline + if isdocstring: + print >> fp, '#, docstring' + print >> fp, 'msgid', normalize(k) + print >> fp, 'msgstr ""\n' + + + +def main(): + global default_keywords + try: + opts, args = getopt.getopt( + sys.argv[1:], + 'ad:DEhk:Kno:p:S:Vvw:x:X:', + ['extract-all', 'default-domain=', 'escape', 'help', + 'keyword=', 'no-default-keywords', + 'add-location', 'no-location', 'output=', 'output-dir=', + 'style=', 'verbose', 'version', 'width=', 'exclude-file=', + 'docstrings', 'no-docstrings', + ]) + except getopt.error, msg: + usage(1, msg) + + # for holding option values + class Options: + # constants + GNU = 1 + SOLARIS = 2 + # defaults + extractall = 0 # FIXME: currently this option has no effect at all. + escape = 0 + keywords = [] + outpath = '' + outfile = 'messages.pot' + writelocations = 1 + locationstyle = GNU + verbose = 0 + width = 78 + excludefilename = '' + docstrings = 0 + nodocstrings = {} + + options = Options() + locations = {'gnu' : options.GNU, + 'solaris' : options.SOLARIS, + } + + # parse options + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-a', '--extract-all'): + options.extractall = 1 + elif opt in ('-d', '--default-domain'): + options.outfile = arg + '.pot' + elif opt in ('-E', '--escape'): + options.escape = 1 + elif opt in ('-D', '--docstrings'): + options.docstrings = 1 + elif opt in ('-k', '--keyword'): + options.keywords.append(arg) + elif opt in ('-K', '--no-default-keywords'): + default_keywords = [] + elif opt in ('-n', '--add-location'): + options.writelocations = 1 + elif opt in ('--no-location',): + options.writelocations = 0 + elif opt in ('-S', '--style'): + options.locationstyle = locations.get(arg.lower()) + if options.locationstyle is None: + usage(1, _('Invalid value for --style: %s') % arg) + elif opt in ('-o', '--output'): + options.outfile = arg + elif opt in ('-p', '--output-dir'): + options.outpath = arg + elif opt in ('-v', '--verbose'): + options.verbose = 1 + elif opt in ('-V', '--version'): + print _('pygettext.py (xgettext for Python) %s') % __version__ + sys.exit(0) + elif opt in ('-w', '--width'): + try: + options.width = int(arg) + except ValueError: + usage(1, _('--width argument must be an integer: %s') % arg) + elif opt in ('-x', '--exclude-file'): + options.excludefilename = arg + elif opt in ('-X', '--no-docstrings'): + fp = open(arg) + try: + while 1: + line = fp.readline() + if not line: + break + options.nodocstrings[line[:-1]] = 1 + finally: + fp.close() + + # calculate escapes + make_escapes(options.escape) + + # calculate all keywords + options.keywords.extend(default_keywords) + + # initialize list of strings to exclude + if options.excludefilename: + try: + fp = open(options.excludefilename) + options.toexclude = fp.readlines() + fp.close() + except IOError: + print >> sys.stderr, _( + "Can't read --exclude-file: %s") % options.excludefilename + sys.exit(1) + else: + options.toexclude = [] + + # slurp through all the files + eater = TokenEater(options) + for filename in args: + if filename == '-': + if options.verbose: + print _('Reading standard input') + fp = sys.stdin + closep = 0 + else: + if options.verbose: + print _('Working on %s') % filename + fp = open(filename) + closep = 1 + try: + eater.set_filename(filename) + try: + tokenize.tokenize(fp.readline, eater) + except tokenize.TokenError, e: + print >> sys.stderr, '%s: %s, line %d, column %d' % ( + e[0], filename, e[1][0], e[1][1]) + finally: + if closep: + fp.close() + + # write the output + if options.outfile == '-': + fp = sys.stdout + closep = 0 + else: + if options.outpath: + options.outfile = os.path.join(options.outpath, options.outfile) + fp = open(options.outfile, 'w') + closep = 1 + try: + eater.write(fp) + finally: + if closep: + fp.close() + + +if __name__ == '__main__': + main() + # some more test strings + _(u'a unicode string') diff --git a/src/mailman/attic/bin/remove_members b/src/mailman/attic/bin/remove_members new file mode 100755 index 000000000..a7b4ebb47 --- /dev/null +++ b/src/mailman/attic/bin/remove_members @@ -0,0 +1,186 @@ +#! @PYTHON@ +# +# Copyright (C) 1998-2005 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. + +"""Remove members from a list. + +Usage: + remove_members [options] [listname] [addr1 ...] + +Options: + + --file=file + -f file + Remove member addresses found in the given file. If file is + `-', read stdin. + + --all + -a + Remove all members of the mailing list. + (mutually exclusive with --fromall) + + --fromall + Removes the given addresses from all the lists on this system + regardless of virtual domains if you have any. This option cannot be + used -a/--all. Also, you should not specify a listname when using + this option. + + --nouserack + -n + Don't send the user acknowledgements. If not specified, the list + default value is used. + + --noadminack + -N + Don't send the admin acknowledgements. If not specified, the list + default value is used. + + --help + -h + Print this help message and exit. + + listname is the name of the mailing list to use. + + addr1 ... are additional addresses to remove. +""" + +import sys +import getopt + +import paths +from Mailman import MailList +from Mailman import Utils +from Mailman import Errors +from Mailman.i18n import _ + +try: + True, False +except NameError: + True = 1 + False = 0 + + + +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 ReadFile(filename): + lines = [] + if filename == "-": + fp = sys.stdin + closep = False + else: + fp = open(filename) + closep = True + lines = filter(None, [line.strip() for line in fp.readlines()]) + if closep: + fp.close() + return lines + + + +def main(): + try: + opts, args = getopt.getopt( + sys.argv[1:], 'naf:hN', + ['all', 'fromall', 'file=', 'help', 'nouserack', 'noadminack']) + except getopt.error, msg: + usage(1, msg) + + filename = None + all = False + alllists = False + # None means use list default + userack = None + admin_notif = None + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-f', '--file'): + filename = arg + elif opt in ('-a', '--all'): + all = True + elif opt == '--fromall': + alllists = True + elif opt in ('-n', '--nouserack'): + userack = False + elif opt in ('-N', '--noadminack'): + admin_notif = False + + if len(args) < 1 and not (filename and alllists): + usage(1) + + # You probably don't want to delete all the users of all the lists -- Marc + if all and alllists: + usage(1) + + if alllists: + addresses = args + else: + listname = args[0].lower().strip() + addresses = args[1:] + + if alllists: + listnames = Utils.list_names() + else: + listnames = [listname] + + if filename: + try: + addresses = addresses + ReadFile(filename) + except IOError: + print _('Could not open file for reading: %(filename)s.') + + for listname in listnames: + try: + # open locked + mlist = MailList.MailList(listname) + except Errors.MMListError: + print _('Error opening list %(listname)s... skipping.') + continue + + if all: + addresses = mlist.getMembers() + + try: + for addr in addresses: + if not mlist.isMember(addr): + if not alllists: + print _('No such member: %(addr)s') + continue + mlist.ApprovedDeleteMember(addr, 'bin/remove_members', + admin_notif, userack) + if alllists: + print _("User `%(addr)s' removed from list: %(listname)s.") + mlist.Save() + finally: + mlist.Unlock() + + + +if __name__ == '__main__': + main() diff --git a/src/mailman/attic/bin/reset_pw.py b/src/mailman/attic/bin/reset_pw.py new file mode 100644 index 000000000..453c8b849 --- /dev/null +++ b/src/mailman/attic/bin/reset_pw.py @@ -0,0 +1,83 @@ +#! @PYTHON@ +# +# Copyright (C) 2004-2009 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. + +# Inspired by Florian Weimer. + +"""Reset the passwords for members of a mailing list. + +This script resets all the passwords of a mailing list's members. It can also +be used to reset the lists of all members of all mailing lists, but it is your +responsibility to let the users know that their passwords have been changed. + +This script is intended to be run as a bin/withlist script, i.e. + +% bin/withlist -l -r reset_pw listname [options] + +Options: + -v / --verbose + Print what the script is doing. +""" + +import sys +import getopt + +import paths +from Mailman import Utils +from Mailman.i18n import _ + + + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print >> fd, _(__doc__.replace('%', '%%')) + if msg: + print >> fd, msg + sys.exit(code) + + + +def reset_pw(mlist, *args): + try: + opts, args = getopt.getopt(args, 'v', ['verbose']) + except getopt.error, msg: + usage(1, msg) + + verbose = False + for opt, args in opts: + if opt in ('-v', '--verbose'): + verbose = True + + listname = mlist.internal_name() + if verbose: + print _('Changing passwords for list: %(listname)s') + + for member in mlist.getMembers(): + randompw = Utils.MakeRandomPassword() + mlist.setMemberPassword(member, randompw) + if verbose: + print _('New password for member %(member)40s: %(randompw)s') + + mlist.Save() + + + +if __name__ == '__main__': + usage(0) diff --git a/src/mailman/attic/bin/sync_members b/src/mailman/attic/bin/sync_members new file mode 100755 index 000000000..4a21624c1 --- /dev/null +++ b/src/mailman/attic/bin/sync_members @@ -0,0 +1,286 @@ +#! @PYTHON@ +# +# Copyright (C) 1998-2003 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. + +"""Synchronize a mailing list's membership with a flat file. + +This script is useful if you have a Mailman mailing list and a sendmail +:include: style list of addresses (also as is used in Majordomo). For every +address in the file that does not appear in the mailing list, the address is +added. For every address in the mailing list that does not appear in the +file, the address is removed. Other options control what happens when an +address is added or removed. + +Usage: %(PROGRAM)s [options] -f file listname + +Where `options' are: + + --no-change + -n + Don't actually make the changes. Instead, print out what would be + done to the list. + + --welcome-msg[=<yes|no>] + -w[=<yes|no>] + Sets whether or not to send the newly added members a welcome + message, overriding whatever the list's `send_welcome_msg' setting + is. With -w=yes or -w, the welcome message is sent. With -w=no, no + message is sent. + + --goodbye-msg[=<yes|no>] + -g[=<yes|no>] + Sets whether or not to send the goodbye message to removed members, + overriding whatever the list's `send_goodbye_msg' setting is. With + -g=yes or -g, the goodbye message is sent. With -g=no, no message is + sent. + + --digest[=<yes|no>] + -d[=<yes|no>] + Selects whether to make newly added members receive messages in + digests. With -d=yes or -d, they become digest members. With -d=no + (or if no -d option given) they are added as regular members. + + --notifyadmin[=<yes|no>] + -a[=<yes|no>] + Specifies whether the admin should be notified for each subscription + or unsubscription. If you're adding a lot of addresses, you + definitely want to turn this off! With -a=yes or -a, the admin is + notified. With -a=no, the admin is not notified. With no -a option, + the default for the list is used. + + --file <filename | -> + -f <filename | -> + This option is required. It specifies the flat file to synchronize + against. Email addresses must appear one per line. If filename is + `-' then stdin is used. + + --help + -h + Print this message. + + listname + Required. This specifies the list to synchronize. +""" + +import sys + +import paths +# Import this /after/ paths so that the sys.path is properly hacked +import email.Utils + +from Mailman import MailList +from Mailman import Errors +from Mailman import Utils +from Mailman.UserDesc import UserDesc +from Mailman.i18n import _ + + + +PROGRAM = sys.argv[0] + +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 yesno(opt): + i = opt.find('=') + yesno = opt[i+1:].lower() + if yesno in ('y', 'yes'): + return 1 + elif yesno in ('n', 'no'): + return 0 + else: + usage(1, _('Bad choice: %(yesno)s')) + # no return + + +def main(): + dryrun = 0 + digest = 0 + welcome = None + goodbye = None + filename = None + listname = None + notifyadmin = None + + # TBD: can't use getopt with this command line syntax, which is broken and + # should be changed to be getopt compatible. + i = 1 + while i < len(sys.argv): + opt = sys.argv[i] + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-n', '--no-change'): + dryrun = 1 + i += 1 + print _('Dry run mode') + elif opt in ('-d', '--digest'): + digest = 1 + i += 1 + elif opt.startswith('-d=') or opt.startswith('--digest='): + digest = yesno(opt) + i += 1 + elif opt in ('-w', '--welcome-msg'): + welcome = 1 + i += 1 + elif opt.startswith('-w=') or opt.startswith('--welcome-msg='): + welcome = yesno(opt) + i += 1 + elif opt in ('-g', '--goodbye-msg'): + goodbye = 1 + i += 1 + elif opt.startswith('-g=') or opt.startswith('--goodbye-msg='): + goodbye = yesno(opt) + i += 1 + elif opt in ('-f', '--file'): + if filename is not None: + usage(1, _('Only one -f switch allowed')) + try: + filename = sys.argv[i+1] + except IndexError: + usage(1, _('No argument to -f given')) + i += 2 + elif opt in ('-a', '--notifyadmin'): + notifyadmin = 1 + i += 1 + elif opt.startswith('-a=') or opt.startswith('--notifyadmin='): + notifyadmin = yesno(opt) + i += 1 + elif opt[0] == '-': + usage(1, _('Illegal option: %(opt)s')) + else: + try: + listname = sys.argv[i].lower() + i += 1 + except IndexError: + usage(1, _('No listname given')) + break + + if listname is None or filename is None: + usage(1, _('Must have a listname and a filename')) + + # read the list of addresses to sync to from the file + if filename == '-': + filemembers = sys.stdin.readlines() + else: + try: + fp = open(filename) + except IOError, (code, msg): + usage(1, _('Cannot read address file: %(filename)s: %(msg)s')) + try: + filemembers = fp.readlines() + finally: + fp.close() + + # strip out lines we don't care about, they are comments (# in first + # non-whitespace) or are blank + for i in range(len(filemembers)-1, -1, -1): + addr = filemembers[i].strip() + if addr == '' or addr[:1] == '#': + del filemembers[i] + print _('Ignore : %(addr)30s') + + # first filter out any invalid addresses + filemembers = email.Utils.getaddresses(filemembers) + invalid = 0 + for name, addr in filemembers: + try: + Utils.ValidateEmail(addr) + except Errors.EmailAddressError: + print _('Invalid : %(addr)30s') + invalid = 1 + if invalid: + print _('You must fix the preceding invalid addresses first.') + sys.exit(1) + + # get the locked list object + try: + mlist = MailList.MailList(listname) + except Errors.MMListError, e: + print _('No such list: %(listname)s') + sys.exit(1) + + try: + # Get the list of addresses currently subscribed + addrs = {} + needsadding = {} + matches = {} + for addr in mlist.getMemberCPAddresses(mlist.getMembers()): + addrs[addr.lower()] = addr + + for name, addr in filemembers: + # Any address found in the file that is also in the list can be + # ignored. If not found in the list, it must be added later. + laddr = addr.lower() + if addrs.has_key(laddr): + del addrs[laddr] + matches[laddr] = 1 + elif not matches.has_key(laddr): + needsadding[laddr] = (name, addr) + + if not needsadding and not addrs: + print _('Nothing to do.') + sys.exit(0) + + enc = sys.getdefaultencoding() + # addrs contains now all the addresses that need removing + for laddr, (name, addr) in needsadding.items(): + pw = Utils.MakeRandomPassword() + # should not already be subscribed, otherwise our test above is + # broken. Bogosity is if the address is listed in the file more + # than once. Second and subsequent ones trigger an + # MMAlreadyAMember error. Just catch it and go on. + userdesc = UserDesc(addr, name, pw, digest) + try: + if not dryrun: + mlist.ApprovedAddMember(userdesc, welcome, notifyadmin) + s = email.Utils.formataddr((name, addr)).encode(enc, 'replace') + print _('Added : %(s)s') + except Errors.MMAlreadyAMember: + pass + + for laddr, addr in addrs.items(): + # Should be a member, otherwise our test above is broken + name = mlist.getMemberName(laddr) or '' + if not dryrun: + try: + mlist.ApprovedDeleteMember(addr, admin_notif=notifyadmin, + userack=goodbye) + except Errors.NotAMemberError: + # This can happen if the address is illegal (i.e. can't be + # parsed by email.Utils.parseaddr()) but for legacy + # reasons is in the database. Use a lower level remove to + # get rid of this member's entry + mlist.removeMember(addr) + s = email.Utils.formataddr((name, addr)).encode(enc, 'replace') + print _('Removed: %(s)s') + + mlist.Save() + finally: + mlist.Unlock() + + +if __name__ == '__main__': + main() diff --git a/src/mailman/attic/bin/templ2pot.py b/src/mailman/attic/bin/templ2pot.py new file mode 100644 index 000000000..0253cc2cd --- /dev/null +++ b/src/mailman/attic/bin/templ2pot.py @@ -0,0 +1,120 @@ +#! @PYTHON@ +# Code stolen from pygettext.py +# by Tokio Kikuchi <tkikuchi@is.kochi-u.ac.jp> + +"""templ2pot.py -- convert mailman template (en) to pot format. + +Usage: templ2pot.py inputfile ... + +Options: + + -h, --help + +Inputfiles are english templates. Outputs are written to stdout. +""" + +import sys +import getopt + + + +try: + import paths + from Mailman.i18n import _ +except ImportError: + def _(s): return s + +EMPTYSTRING = '' + + + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print >> fd, _(__doc__) % globals() + if msg: + print >> fd, msg + sys.exit(code) + + + +escapes = [] + +def make_escapes(pass_iso8859): + global escapes + if pass_iso8859: + # Allow iso-8859 characters to pass through so that e.g. 'msgid + # "H[o-umlaut]he"' would result not result in 'msgid "H\366he"'. + # Otherwise we escape any character outside the 32..126 range. + mod = 128 + else: + mod = 256 + for i in range(256): + if 32 <= (i % mod) <= 126: + escapes.append(chr(i)) + else: + escapes.append("\\%03o" % i) + escapes[ord('\\')] = '\\\\' + escapes[ord('\t')] = '\\t' + escapes[ord('\r')] = '\\r' + escapes[ord('\n')] = '\\n' + escapes[ord('\"')] = '\\"' + + +def escape(s): + global escapes + s = list(s) + for i in range(len(s)): + s[i] = escapes[ord(s[i])] + return EMPTYSTRING.join(s) + + +def normalize(s): + # This converts the various Python string types into a format that is + # appropriate for .po files, namely much closer to C style. + lines = s.splitlines() + if len(lines) == 1: + s = '"' + escape(s) + '"' + else: + if not lines[-1]: + del lines[-1] + lines[-1] = lines[-1] + '\n' + for i in range(len(lines)): + lines[i] = escape(lines[i]) + lineterm = '\\n"\n"' + s = '""\n"' + lineterm.join(lines) + '"' + return s + + + +def main(): + try: + opts, args = getopt.getopt( + sys.argv[1:], + 'h', + ['help',] + ) + except getopt.error, msg: + usage(1, msg) + + # parse options + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + + # calculate escapes + make_escapes(0) + + for filename in args: + print '#: %s:1' % filename + s = file(filename).read() + print '#, template' + print 'msgid', normalize(s) + print 'msgstr ""\n' + + + +if __name__ == '__main__': + main() diff --git a/src/mailman/attic/bin/transcheck b/src/mailman/attic/bin/transcheck new file mode 100755 index 000000000..73910e771 --- /dev/null +++ b/src/mailman/attic/bin/transcheck @@ -0,0 +1,412 @@ +#! @PYTHON@ +# +# transcheck - (c) 2002 by Simone Piunno <pioppo@ferrara.linux.it> +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the version 2.0 of the GNU General Public License as +# published by the Free Software Foundation. +# +# 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. + +""" +Check a given Mailman translation, making sure that variables and +tags referenced in translation are the same variables and tags in +the original templates and catalog. + +Usage: + +cd $MAILMAN_DIR +%(program)s [-q] <lang> + +Where <lang> is your country code (e.g. 'it' for Italy) and -q is +to ask for a brief summary. +""" + +import sys +import re +import os +import getopt + +import paths +from Mailman.i18n import _ + +program = sys.argv[0] + + + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print >> fd, _(__doc__) + if msg: + print >> fd, msg + sys.exit(code) + + + +class TransChecker: + "check a translation comparing with the original string" + def __init__(self, regexp, escaped=None): + self.dict = {} + self.errs = [] + self.regexp = re.compile(regexp) + self.escaped = None + if escaped: + self.escaped = re.compile(escaped) + + def checkin(self, string): + "scan a string from the original file" + for key in self.regexp.findall(string): + if self.escaped and self.escaped.match(key): + continue + if self.dict.has_key(key): + self.dict[key] += 1 + else: + self.dict[key] = 1 + + def checkout(self, string): + "scan a translated string" + for key in self.regexp.findall(string): + if self.escaped and self.escaped.match(key): + continue + if self.dict.has_key(key): + self.dict[key] -= 1 + else: + self.errs.append( + "%(key)s was not found" % + { 'key' : key } + ) + + def computeErrors(self): + "check for differences between checked in and checked out" + for key in self.dict.keys(): + if self.dict[key] < 0: + self.errs.append( + "Too much %(key)s" % + { 'key' : key } + ) + if self.dict[key] > 0: + self.errs.append( + "Too few %(key)s" % + { 'key' : key } + ) + return self.errs + + def status(self): + if self.errs: + return "FAILED" + else: + return "OK" + + def errorsAsString(self): + msg = "" + for err in self.errs: + msg += " - %(err)s" % { 'err': err } + return msg + + def reset(self): + self.dict = {} + self.errs = [] + + + +class POParser: + "parse a .po file extracting msgids and msgstrs" + def __init__(self, filename=""): + self.status = 0 + self.files = [] + self.msgid = "" + self.msgstr = "" + self.line = 1 + self.f = None + self.esc = { "n": "\n", "r": "\r", "t": "\t" } + if filename: + self.f = open(filename) + + def open(self, filename): + self.f = open(filename) + + def close(self): + self.f.close() + + def parse(self): + """States table for the finite-states-machine parser: + 0 idle + 1 filename-or-comment + 2 msgid + 3 msgstr + 4 end + """ + # each time we can safely re-initialize those vars + self.files = [] + self.msgid = "" + self.msgstr = "" + + + # can't continue if status == 4, this is a dead status + if self.status == 4: + return 0 + + while 1: + # continue scanning, char-by-char + c = self.f.read(1) + if not c: + # EOF -> maybe we have a msgstr to save? + self.status = 4 + if self.msgstr: + return 1 + else: + return 0 + + # keep the line count up-to-date + if c == "\n": + self.line += 1 + + # a pound was detected the previous char... + if self.status == 1: + if c == ":": + # was a line of filenames + row = self.f.readline() + self.files += row.split() + self.line += 1 + elif c == "\n": + # was a single pount on the line + pass + else: + # was a comment... discard + self.f.readline() + self.line += 1 + # in every case, we switch to idle status + self.status = 0; + continue + + # in idle status we search for a '#' or for a 'm' + if self.status == 0: + if c == "#": + # this could be a comment or a filename + self.status = 1; + continue + elif c == "m": + # this should be a msgid start... + s = self.f.read(4) + assert s == "sgid" + # so now we search for a '"' + self.status = 2 + continue + # in idle only those other chars are possibile + assert c in [ "\n", " ", "\t" ] + + # searching for the msgid string + if self.status == 2: + if c == "\n": + # a double LF is not possible here + c = self.f.read(1) + assert c != "\n" + if c == "\"": + # ok, this is the start of the string, + # now search for the end + while 1: + c = self.f.read(1) + if not c: + # EOF, bailout + self.status = 4 + return 0 + if c == "\\": + # a quoted char... + c = self.f.read(1) + if self.esc.has_key(c): + self.msgid += self.esc[c] + else: + self.msgid += c + continue + if c == "\"": + # end of string found + break + # a normal char, add it + self.msgid += c + if c == "m": + # this should be a msgstr identifier + s = self.f.read(5) + assert s == "sgstr" + # ok, now search for the msgstr string + self.status = 3 + + # searching for the msgstr string + if self.status == 3: + if c == "\n": + # a double LF is the end of the msgstr! + c = self.f.read(1) + if c == "\n": + # ok, time to go idle and return + self.status = 0 + self.line += 1 + return 1 + if c == "\"": + # start of string found + while 1: + c = self.f.read(1) + if not c: + # EOF, bail out + self.status = 4 + return 1 + if c == "\\": + # a quoted char... + c = self.f.read(1) + if self.esc.has_key(c): + self.msgid += self.esc[c] + else: + self.msgid += c + continue + if c == "\"": + # end of string + break + # a normal char, add it + self.msgstr += c + + + + +def check_file(translatedFile, originalFile, html=0, quiet=0): + """check a translated template against the original one + search also <MM-*> tags if html is not zero""" + + if html: + c = TransChecker("(%%|%\([^)]+\)[0-9]*[sd]|</?MM-[^>]+>)", "^%%$") + else: + c = TransChecker("(%%|%\([^)]+\)[0-9]*[sd])", "^%%$") + + try: + f = open(originalFile) + except IOError: + if not quiet: + print " - Can'open original file " + originalFile + return 1 + + while 1: + line = f.readline() + if not line: break + c.checkin(line) + + f.close() + + try: + f = open(translatedFile) + except IOError: + if not quiet: + print " - Can'open translated file " + translatedFile + return 1 + + while 1: + line = f.readline() + if not line: break + c.checkout(line) + + f.close() + + n = 0 + msg = "" + for desc in c.computeErrors(): + n +=1 + if not quiet: + print " - %(desc)s" % { 'desc': desc } + return n + + + +def check_po(file, quiet=0): + "scan the po file comparing msgids with msgstrs" + n = 0 + p = POParser(file) + c = TransChecker("(%%|%\([^)]+\)[0-9]*[sdu]|%[0-9]*[sdu])", "^%%$") + while p.parse(): + if p.msgstr: + c.reset() + c.checkin(p.msgid) + c.checkout(p.msgstr) + for desc in c.computeErrors(): + n += 1 + if not quiet: + print " - near line %(line)d %(file)s: %(desc)s" % { + 'line': p.line, + 'file': p.files, + 'desc': desc + } + p.close() + return n + + +def main(): + try: + opts, args = getopt.getopt(sys.argv[1:], 'qh', ['quiet', 'help']) + except getopt.error, msg: + usage(1, msg) + + quiet = 0 + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-q', '--quiet'): + quiet = 1 + + if len(args) <> 1: + usage(1) + + lang = args[0] + + isHtml = re.compile("\.html$"); + isTxt = re.compile("\.txt$"); + + numerrors = 0 + numfiles = 0 + try: + files = os.listdir("templates/" + lang + "/") + except: + print "can't open templates/%s/" % lang + for file in files: + fileEN = "templates/en/" + file + fileIT = "templates/" + lang + "/" + file + errlist = [] + if isHtml.search(file): + if not quiet: + print "HTML checking " + fileIT + "... " + n = check_file(fileIT, fileEN, html=1, quiet=quiet) + if n: + numerrors += n + numfiles += 1 + elif isTxt.search(file): + if not quiet: + print "TXT checking " + fileIT + "... " + n = check_file(fileIT, fileEN, html=0, quiet=quiet) + if n: + numerrors += n + numfiles += 1 + + else: + continue + + file = "messages/" + lang + "/LC_MESSAGES/mailman.po" + if not quiet: + print "PO checking " + file + "... " + n = check_po(file, quiet=quiet) + if n: + numerrors += n + numfiles += 1 + + if quiet: + print "%(errs)u warnings in %(files)u files" % { + 'errs': numerrors, + 'files': numfiles + } + + +if __name__ == '__main__': + main() |
