1 # Copyright (C) 1998-2023 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)
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
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <https://www.gnu.org/licenses/>.
23 from mailman
import errors
, MailList
24 from mailman
.configuration
import config
25 from mailman
.core
.i18n
import _
26 from mailman
.initialize
import initialize
27 from mailman
.utilities
.string
import wrap
28 from mailman
.version
import MAILMAN_VERSION
32 nonasciipat
= re
.compile(r
'[\x80-\xff]')
37 parser
= optparse
.OptionParser(version
=MAILMAN_VERSION
,
39 %prog [options] listname
41 Configure a list from a text file description, or dump a list's configuration
43 parser
.add_option('-i', '--inputfile',
44 metavar
='FILENAME', default
=None, type='string',
46 Configure the list by assigning each module-global variable in the file to an
47 attribute on the mailing list object, then save the list. The named file is
48 loaded with execfile() and must be legal Python code. Any variable that isn't
49 already an attribute of the list object is ignored (a warning message is
50 printed). See also the -c option.
52 A special variable named 'mlist' is put into the globals during the execfile,
53 which is bound to the actual MailList object. This lets you do all manner of
54 bizarre thing to the list object, but BEWARE! Using this can severely (and
55 possibly irreparably) damage your mailing list!
57 The may not be used with the -o option."""))
58 parser
.add_option('-o', '--outputfile',
59 metavar
='FILENAME', default
=None, type='string',
61 Instead of configuring the list, print out a mailing list's configuration
62 variables in a format suitable for input using this script. In this way, you
63 can easily capture the configuration settings for a particular list and
64 imprint those settings on another list. FILENAME is the file to output the
65 settings to. If FILENAME is `-', standard out is used.
67 This may not be used with the -i option."""))
68 parser
.add_option('-c', '--checkonly',
69 default
=False, action
='store_true', help=_("""\
70 With this option, the modified list is not actually changed. This is only
71 useful with the -i option."""))
72 parser
.add_option('-v', '--verbose',
73 default
=False, action
='store_true', help=_("""\
74 Print the name of each attribute as it is being changed. This is only useful
75 with the -i option."""))
76 parser
.add_option('-C', '--config',
77 help=_('Alternative configuration file to use'))
78 opts
, args
= parser
.parse_args()
81 parser
.error(_('Unexpected arguments'))
83 parser
.error(_('List name is required'))
84 return parser
, opts
, args
88 def do_output(listname
, outfile
, parser
):
94 outfp
= open(outfile
, 'w')
96 # Open the specified list unlocked, since we're only reading it.
98 mlist
= MailList
.MailList(listname
, lock
=False)
99 except errors
.MMListError
:
100 parser
.error(_('No such list: $listname'))
101 # Preamble for the config info. PEP 263 charset and capture time.
102 charset
= mlist
.preferred_language
.charset
103 # Set the system's default language.
104 _
.default
= mlist
.preferred_language
.code
107 when
= time
.ctime(time
.time())
108 print >> outfp
, _('''\
110 # -*- coding: $charset -*-
111 ## "$listname" mailing list configuration settings
114 # Get all the list config info. All this stuff is accessible via the
116 for k
in config
.ADMIN_CATEGORIES
:
117 subcats
= mlist
.GetConfigSubCategories(k
)
119 do_list_categories(mlist
, k
, None, outfp
)
121 for subcat
in [t
[0] for t
in subcats
]:
122 do_list_categories(mlist
, k
, subcat
, outfp
)
129 def do_list_categories(mlist
, k
, subcat
, outfp
):
130 info
= mlist
.GetConfigInfo(k
, subcat
)
131 label
, gui
= mlist
.GetConfigCategories()[k
]
134 charset
= mlist
.preferred_language
.charset
135 print >> outfp
, '##', k
.capitalize(), _('options')
137 # First, massage the descripton text, which could have obnoxious
138 # leading whitespace on second and subsequent lines due to
139 # triple-quoted string nonsense in the source code.
140 desc
= NL
.join([s
.lstrip() for s
in info
[0].splitlines()])
141 # Print out the category description
143 for line
in desc
.splitlines():
144 print >> outfp
, '#', line
146 for data
in info
[1:]:
147 if not isinstance(data
, tuple):
150 # Variable could be volatile
151 if varname
[0] == '_':
154 # First, massage the descripton text, which could have
155 # obnoxious leading whitespace on second and subsequent lines
156 # due to triple-quoted string nonsense in the source code.
157 desc
= NL
.join([s
.lstrip() for s
in data
[-1].splitlines()])
158 # Now strip out all HTML tags
159 desc
= re
.sub('<.*?>', '', desc
)
160 # And convert </> to <>
161 desc
= re
.sub('<', '<', desc
)
162 desc
= re
.sub('>', '>', desc
)
163 # Print out the variable description.
165 for line
in desc
.split('\n'):
166 print >> outfp
, '#', line
167 # munge the value based on its type
169 if hasattr(gui
, 'getValue'):
170 value
= gui
.getValue(mlist
, vtype
, varname
, data
[2])
171 if value
is None and not varname
.startswith('_'):
172 value
= getattr(mlist
, varname
)
173 if vtype
in (config
.String
, config
.Text
, config
.FileUpload
):
174 print >> outfp
, varname
, '=',
175 lines
= value
.splitlines()
178 elif len(lines
) == 1:
179 if charset
!= 'us-ascii' and nonasciipat
.search(lines
[0]):
180 # This is more readable for non-english list.
181 print >> outfp
, '"' + lines
[0].replace('"', '\\"') + '"'
183 print >> outfp
, repr(lines
[0])
185 if charset
== 'us-ascii' and nonasciipat
.search(value
):
186 # Normally, an english list should not have non-ascii char.
187 print >> outfp
, repr(NL
.join(lines
))
190 outfp
.write(NL
.join(lines
).replace('"', '\\"'))
192 elif vtype
in (config
.Radio
, config
.Toggle
):
194 print >> outfp
, '#', _('legal values are:')
195 # TBD: This is disgusting, but it's special cased
196 # everywhere else anyway...
197 if varname
== 'subscribe_policy' and \
198 not config
.ALLOW_OPEN_SUBSCRIBE
:
202 for choice
in data
[2]:
203 print >> outfp
, '# ', i
, '= "%s"' % choice
205 print >> outfp
, varname
, '=', repr(value
)
207 print >> outfp
, varname
, '=', repr(value
)
212 def getPropertyMap(mlist
):
214 categories
= mlist
.GetConfigCategories()
215 for category
, (label
, gui
) in categories
.items():
216 if not hasattr(gui
, 'GetConfigInfo'):
218 subcats
= mlist
.GetConfigSubCategories(category
)
220 subcats
= [(None, None)]
221 for subcat
, sclabel
in subcats
:
222 for element
in gui
.GetConfigInfo(mlist
, category
, subcat
):
223 if not isinstance(element
, tuple):
225 propname
= element
[0]
227 guibyprop
[propname
] = (gui
, wtype
)
232 # Fake the error reporting API for the htmlformat.Document class
233 def addError(self
, s
, tag
=None, *args
):
235 print >> sys
.stderr
, tag
236 print >> sys
.stderr
, s
% args
238 def set_language(self
, val
):
243 def do_input(listname
, infile
, checkonly
, verbose
, parser
):
245 # Open the specified list locked, unless checkonly is set
247 mlist
= MailList
.MailList(listname
, lock
=not checkonly
)
248 except errors
.MMListError
as error
:
249 parser
.error(_('No such list "$listname"\n$error'))
251 guibyprop
= getPropertyMap(mlist
)
253 globals = {'mlist': mlist
}
254 # Any exception that occurs in execfile() will cause the list to not
255 # be saved, but any other problems are not save-fatal.
256 execfile(infile
, globals)
258 for k
, v
in globals.items():
259 if k
in ('mlist', '__builtins__'):
261 if not hasattr(mlist
, k
):
262 print >> sys
.stderr
, _('attribute "$k" ignored')
265 print >> sys
.stderr
, _('attribute "$k" changed')
267 gui
, wtype
= guibyprop
.get(k
, (missing
, missing
))
269 # This isn't an official property of the list, but that's
270 # okay, we'll just restore it the old fashioned way
271 print >> sys
.stderr
, _('Non-standard property restored: $k')
274 # BAW: This uses non-public methods. This logic taken from
275 # the guts of GUIBase.handleForm().
277 validval
= gui
._getValidValue
(mlist
, k
, wtype
, v
)
279 print >> sys
.stderr
, _('Invalid value for property: $k')
280 except errors
.EmailAddressError
:
281 print >> sys
.stderr
, _(
282 'Bad email address for option $k: $v')
284 # BAW: Horrible hack, but then this is special cased
285 # everywhere anyway. :( Privacy._setValue() knows that
286 # when ALLOW_OPEN_SUBSCRIBE is false, the web values are
287 # 0, 1, 2 but these really should be 1, 2, 3, so it adds
288 # one. But we really do provide [0..3] so we need to undo
289 # the hack that _setValue adds. :( :(
290 if k
== 'subscribe_policy' and \
291 not config
.ALLOW_OPEN_SUBSCRIBE
:
293 # BAW: Another horrible hack. This one is just too hard
294 # to fix in a principled way in Mailman 2.1
295 elif k
== 'new_member_options':
296 # Because this is a Checkbox, _getValidValue()
297 # transforms the value into a list of one item.
298 validval
= validval
[0]
299 validval
= [bitfield
for bitfield
, bitval
300 in config
.OPTINFO
.items()
301 if validval
& bitval
]
302 gui
._setValue
(mlist
, k
, validval
, fakedoc
)
303 # BAW: when to do gui._postValidate()???
305 if savelist
and not checkonly
:
312 parser
, opts
, args
= parseargs()
313 initialize(opts
.config
)
317 if opts
.inputfile
and opts
.outputfile
:
318 parser
.error(_('Only one of -i or -o is allowed'))
319 if not opts
.inputfile
and not opts
.outputfile
:
320 parser
.error(_('One of -i or -o is required'))
323 do_output(listname
, opts
.outputfile
, parser
)
325 do_input(listname
, opts
.inputfile
, opts
.checkonly
,
326 opts
.verbose
, parser
)
330 if __name__
== '__main__':