Bump copyright years.
[mailman.git] / src / mailman / bin / config_list.py
blobe8a5d193ee7ae8f757ee1c6fde8f5eadacf9b83c
1 # Copyright (C) 1998-2014 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
8 # any later version.
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 # more details.
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18 import re
19 import sys
20 import time
21 import optparse
23 from mailman import MailList
24 from mailman import errors
25 from mailman.configuration import config
26 from mailman.core.i18n import _
27 from mailman.initialize import initialize
28 from mailman.utilities.string import wrap
29 from mailman.version import MAILMAN_VERSION
32 NL = '\n'
33 nonasciipat = re.compile(r'[\x80-\xff]')
37 def parseargs():
38 parser = optparse.OptionParser(version=MAILMAN_VERSION,
39 usage=_("""\
40 %prog [options] listname
42 Configure a list from a text file description, or dump a list's configuration
43 settings."""))
44 parser.add_option('-i', '--inputfile',
45 metavar='FILENAME', default=None, type='string',
46 help=_("""\
47 Configure the list by assigning each module-global variable in the file to an
48 attribute on the mailing list object, then save the list. The named file is
49 loaded with execfile() and must be legal Python code. Any variable that isn't
50 already an attribute of the list object is ignored (a warning message is
51 printed). See also the -c option.
53 A special variable named 'mlist' is put into the globals during the execfile,
54 which is bound to the actual MailList object. This lets you do all manner of
55 bizarre thing to the list object, but BEWARE! Using this can severely (and
56 possibly irreparably) damage your mailing list!
58 The may not be used with the -o option."""))
59 parser.add_option('-o', '--outputfile',
60 metavar='FILENAME', default=None, type='string',
61 help=_("""\
62 Instead of configuring the list, print out a mailing list's configuration
63 variables in a format suitable for input using this script. In this way, you
64 can easily capture the configuration settings for a particular list and
65 imprint those settings on another list. FILENAME is the file to output the
66 settings to. If FILENAME is `-', standard out is used.
68 This may not be used with the -i option."""))
69 parser.add_option('-c', '--checkonly',
70 default=False, action='store_true', help=_("""\
71 With this option, the modified list is not actually changed. This is only
72 useful with the -i option."""))
73 parser.add_option('-v', '--verbose',
74 default=False, action='store_true', help=_("""\
75 Print the name of each attribute as it is being changed. This is only useful
76 with the -i option."""))
77 parser.add_option('-C', '--config',
78 help=_('Alternative configuration file to use'))
79 opts, args = parser.parse_args()
80 if len(args) > 1:
81 parser.print_help()
82 parser.error(_('Unexpected arguments'))
83 if not args:
84 parser.error(_('List name is required'))
85 return parser, opts, args
89 def do_output(listname, outfile, parser):
90 closep = False
91 try:
92 if outfile == '-':
93 outfp = sys.stdout
94 else:
95 outfp = open(outfile, 'w')
96 closep = True
97 # Open the specified list unlocked, since we're only reading it.
98 try:
99 mlist = MailList.MailList(listname, lock=False)
100 except errors.MMListError:
101 parser.error(_('No such list: $listname'))
102 # Preamble for the config info. PEP 263 charset and capture time.
103 charset = mlist.preferred_language.charset
104 # Set the system's default language.
105 _.default = mlist.preferred_language.code
106 if not charset:
107 charset = 'us-ascii'
108 when = time.ctime(time.time())
109 print >> outfp, _('''\
110 # -*- python -*-
111 # -*- coding: $charset -*-
112 ## "$listname" mailing list configuration settings
113 ## captured on $when
114 ''')
115 # Get all the list config info. All this stuff is accessible via the
116 # web interface.
117 for k in config.ADMIN_CATEGORIES:
118 subcats = mlist.GetConfigSubCategories(k)
119 if subcats is None:
120 do_list_categories(mlist, k, None, outfp)
121 else:
122 for subcat in [t[0] for t in subcats]:
123 do_list_categories(mlist, k, subcat, outfp)
124 finally:
125 if closep:
126 outfp.close()
130 def do_list_categories(mlist, k, subcat, outfp):
131 info = mlist.GetConfigInfo(k, subcat)
132 label, gui = mlist.GetConfigCategories()[k]
133 if info is None:
134 return
135 charset = mlist.preferred_language.charset
136 print >> outfp, '##', k.capitalize(), _('options')
137 print >> outfp, '#'
138 # First, massage the descripton text, which could have obnoxious
139 # leading whitespace on second and subsequent lines due to
140 # triple-quoted string nonsense in the source code.
141 desc = NL.join([s.lstrip() for s in info[0].splitlines()])
142 # Print out the category description
143 desc = wrap(desc)
144 for line in desc.splitlines():
145 print >> outfp, '#', line
146 print >> outfp
147 for data in info[1:]:
148 if not isinstance(data, tuple):
149 continue
150 varname = data[0]
151 # Variable could be volatile
152 if varname[0] == '_':
153 continue
154 vtype = data[1]
155 # First, massage the descripton text, which could have
156 # obnoxious leading whitespace on second and subsequent lines
157 # due to triple-quoted string nonsense in the source code.
158 desc = NL.join([s.lstrip() for s in data[-1].splitlines()])
159 # Now strip out all HTML tags
160 desc = re.sub('<.*?>', '', desc)
161 # And convert &lt;/&gt; to <>
162 desc = re.sub('&lt;', '<', desc)
163 desc = re.sub('&gt;', '>', desc)
164 # Print out the variable description.
165 desc = wrap(desc)
166 for line in desc.split('\n'):
167 print >> outfp, '#', line
168 # munge the value based on its type
169 value = None
170 if hasattr(gui, 'getValue'):
171 value = gui.getValue(mlist, vtype, varname, data[2])
172 if value is None and not varname.startswith('_'):
173 value = getattr(mlist, varname)
174 if vtype in (config.String, config.Text, config.FileUpload):
175 print >> outfp, varname, '=',
176 lines = value.splitlines()
177 if not lines:
178 print >> outfp, "''"
179 elif len(lines) == 1:
180 if charset != 'us-ascii' and nonasciipat.search(lines[0]):
181 # This is more readable for non-english list.
182 print >> outfp, '"' + lines[0].replace('"', '\\"') + '"'
183 else:
184 print >> outfp, repr(lines[0])
185 else:
186 if charset == 'us-ascii' and nonasciipat.search(value):
187 # Normally, an english list should not have non-ascii char.
188 print >> outfp, repr(NL.join(lines))
189 else:
190 outfp.write(' """')
191 outfp.write(NL.join(lines).replace('"', '\\"'))
192 outfp.write('"""\n')
193 elif vtype in (config.Radio, config.Toggle):
194 print >> outfp, '#'
195 print >> outfp, '#', _('legal values are:')
196 # TBD: This is disgusting, but it's special cased
197 # everywhere else anyway...
198 if varname == 'subscribe_policy' and \
199 not config.ALLOW_OPEN_SUBSCRIBE:
200 i = 1
201 else:
202 i = 0
203 for choice in data[2]:
204 print >> outfp, '# ', i, '= "%s"' % choice
205 i += 1
206 print >> outfp, varname, '=', repr(value)
207 else:
208 print >> outfp, varname, '=', repr(value)
209 print >> outfp
213 def getPropertyMap(mlist):
214 guibyprop = {}
215 categories = mlist.GetConfigCategories()
216 for category, (label, gui) in categories.items():
217 if not hasattr(gui, 'GetConfigInfo'):
218 continue
219 subcats = mlist.GetConfigSubCategories(category)
220 if subcats is None:
221 subcats = [(None, None)]
222 for subcat, sclabel in subcats:
223 for element in gui.GetConfigInfo(mlist, category, subcat):
224 if not isinstance(element, tuple):
225 continue
226 propname = element[0]
227 wtype = element[1]
228 guibyprop[propname] = (gui, wtype)
229 return guibyprop
232 class FakeDoc:
233 # Fake the error reporting API for the htmlformat.Document class
234 def addError(self, s, tag=None, *args):
235 if tag:
236 print >> sys.stderr, tag
237 print >> sys.stderr, s % args
239 def set_language(self, val):
240 pass
244 def do_input(listname, infile, checkonly, verbose, parser):
245 fakedoc = FakeDoc()
246 # Open the specified list locked, unless checkonly is set
247 try:
248 mlist = MailList.MailList(listname, lock=not checkonly)
249 except errors.MMListError as error:
250 parser.error(_('No such list "$listname"\n$error'))
251 savelist = False
252 guibyprop = getPropertyMap(mlist)
253 try:
254 globals = {'mlist': mlist}
255 # Any exception that occurs in execfile() will cause the list to not
256 # be saved, but any other problems are not save-fatal.
257 execfile(infile, globals)
258 savelist = True
259 for k, v in globals.items():
260 if k in ('mlist', '__builtins__'):
261 continue
262 if not hasattr(mlist, k):
263 print >> sys.stderr, _('attribute "$k" ignored')
264 continue
265 if verbose:
266 print >> sys.stderr, _('attribute "$k" changed')
267 missing = []
268 gui, wtype = guibyprop.get(k, (missing, missing))
269 if gui is missing:
270 # This isn't an official property of the list, but that's
271 # okay, we'll just restore it the old fashioned way
272 print >> sys.stderr, _('Non-standard property restored: $k')
273 setattr(mlist, k, v)
274 else:
275 # BAW: This uses non-public methods. This logic taken from
276 # the guts of GUIBase.handleForm().
277 try:
278 validval = gui._getValidValue(mlist, k, wtype, v)
279 except ValueError:
280 print >> sys.stderr, _('Invalid value for property: $k')
281 except errors.EmailAddressError:
282 print >> sys.stderr, _(
283 'Bad email address for option $k: $v')
284 else:
285 # BAW: Horrible hack, but then this is special cased
286 # everywhere anyway. :( Privacy._setValue() knows that
287 # when ALLOW_OPEN_SUBSCRIBE is false, the web values are
288 # 0, 1, 2 but these really should be 1, 2, 3, so it adds
289 # one. But we really do provide [0..3] so we need to undo
290 # the hack that _setValue adds. :( :(
291 if k == 'subscribe_policy' and \
292 not config.ALLOW_OPEN_SUBSCRIBE:
293 validval -= 1
294 # BAW: Another horrible hack. This one is just too hard
295 # to fix in a principled way in Mailman 2.1
296 elif k == 'new_member_options':
297 # Because this is a Checkbox, _getValidValue()
298 # transforms the value into a list of one item.
299 validval = validval[0]
300 validval = [bitfield for bitfield, bitval
301 in config.OPTINFO.items()
302 if validval & bitval]
303 gui._setValue(mlist, k, validval, fakedoc)
304 # BAW: when to do gui._postValidate()???
305 finally:
306 if savelist and not checkonly:
307 mlist.Save()
308 mlist.Unlock()
312 def main():
313 parser, opts, args = parseargs()
314 initialize(opts.config)
315 listname = args[0]
317 # Sanity check
318 if opts.inputfile and opts.outputfile:
319 parser.error(_('Only one of -i or -o is allowed'))
320 if not opts.inputfile and not opts.outputfile:
321 parser.error(_('One of -i or -o is required'))
323 if opts.outputfile:
324 do_output(listname, opts.outputfile, parser)
325 else:
326 do_input(listname, opts.inputfile, opts.checkonly,
327 opts.verbose, parser)
331 if __name__ == '__main__':
332 main()