PageRenderTime 64ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/mailman/commands/cli_withlist.py

https://gitlab.com/pratik4/mailman
Python | 301 lines | 264 code | 13 blank | 24 comment | 6 complexity | 0e0cd43b280769f64c654d8ac627a742 MD5 | raw file
  1. # Copyright (C) 2009-2016 by the Free Software Foundation, Inc.
  2. #
  3. # This file is part of GNU Mailman.
  4. #
  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.
  9. #
  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.
  14. #
  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/>.
  17. """The `mailman shell` subcommand."""
  18. import re
  19. import sys
  20. from contextlib import ExitStack, suppress
  21. from functools import partial
  22. from lazr.config import as_boolean
  23. from mailman import public
  24. from mailman.config import config
  25. from mailman.core.i18n import _
  26. from mailman.interfaces.command import ICLISubCommand
  27. from mailman.interfaces.listmanager import IListManager
  28. from mailman.utilities.interact import DEFAULT_BANNER, interact
  29. from mailman.utilities.modules import call_name
  30. from string import Template
  31. from traceback import print_exc
  32. from zope.component import getUtility
  33. from zope.interface import implementer
  34. # Global holding onto the open mailing list.
  35. m = None
  36. # Global holding the results of --run.
  37. r = None
  38. def _start_ipython1(overrides, banner, *, debug=False):
  39. try:
  40. from IPython.frontend.terminal.embed import InteractiveShellEmbed
  41. except ImportError:
  42. if debug:
  43. print_exc()
  44. return None
  45. return InteractiveShellEmbed.instance(banner1=banner, user_ns=overrides)
  46. def _start_ipython4(overrides, banner, *, debug=False):
  47. try:
  48. from IPython.terminal.embed import InteractiveShellEmbed
  49. shell = InteractiveShellEmbed.instance()
  50. except ImportError:
  51. if debug:
  52. print_exc()
  53. return None
  54. return partial(shell.mainloop, local_ns=overrides, display_banner=banner)
  55. @public
  56. @implementer(ICLISubCommand)
  57. class Withlist:
  58. """Operate on a mailing list.
  59. For detailed help, see --details
  60. """
  61. name = 'withlist'
  62. def add(self, parser, command_parser):
  63. """See `ICLISubCommand`."""
  64. self.parser = parser
  65. command_parser.add_argument(
  66. '-i', '--interactive',
  67. default=None, action='store_true', help=_("""\
  68. Leaves you at an interactive prompt after all other processing is
  69. complete. This is the default unless the --run option is
  70. given."""))
  71. command_parser.add_argument(
  72. '-r', '--run',
  73. help=_("""\
  74. Run a script on a mailing list. The argument is the module path
  75. to a callable. This callable will be imported and then called
  76. with the mailing list as the first argument. If additional
  77. arguments are given at the end of the command line, they are
  78. passed as subsequent positional arguments to the callable. For
  79. additional help, see --details.
  80. """))
  81. command_parser.add_argument(
  82. '--details',
  83. default=False, action='store_true',
  84. help=_('Print detailed instructions on using this command.'))
  85. # Optional positional argument.
  86. command_parser.add_argument(
  87. 'listname', metavar='LISTNAME', nargs='?',
  88. help=_("""\
  89. The 'fully qualified list name', i.e. the posting address of the
  90. mailing list to inject the message into. This can be a Python
  91. regular expression, in which case all mailing lists whose posting
  92. address matches will be processed. To use a regular expression,
  93. LISTNAME must start with a ^ (and the matching is done with
  94. re.match(). LISTNAME cannot be a regular expression unless --run
  95. is given."""))
  96. def process(self, args):
  97. """See `ICLISubCommand`."""
  98. global m, r
  99. banner = DEFAULT_BANNER
  100. # Detailed help wanted?
  101. if args.details:
  102. self._details()
  103. return
  104. # Interactive is the default unless --run was given.
  105. if args.interactive is None:
  106. interactive = (args.run is None)
  107. else:
  108. interactive = args.interactive
  109. # List name cannot be a regular expression if --run is not given.
  110. if args.listname and args.listname.startswith('^') and not args.run:
  111. self.parser.error(_('Regular expression requires --run'))
  112. return
  113. # Handle --run.
  114. list_manager = getUtility(IListManager)
  115. if args.run:
  116. # When the module and the callable have the same name, a shorthand
  117. # without the dot is allowed.
  118. dotted_name = (args.run if '.' in args.run
  119. else '{0}.{0}'.format(args.run))
  120. if args.listname is None:
  121. self.parser.error(_('--run requires a mailing list name'))
  122. return
  123. elif args.listname.startswith('^'):
  124. r = {}
  125. cre = re.compile(args.listname, re.IGNORECASE)
  126. for mailing_list in list_manager.mailing_lists:
  127. if cre.match(mailing_list.fqdn_listname):
  128. results = call_name(dotted_name, mailing_list)
  129. r[mailing_list.fqdn_listname] = results
  130. else:
  131. fqdn_listname = args.listname
  132. m = list_manager.get(fqdn_listname)
  133. if m is None:
  134. self.parser.error(_('No such list: $fqdn_listname'))
  135. return
  136. r = call_name(dotted_name, m)
  137. else:
  138. # Not --run.
  139. if args.listname is not None:
  140. fqdn_listname = args.listname
  141. m = list_manager.get(fqdn_listname)
  142. if m is None:
  143. self.parser.error(_('No such list: $fqdn_listname'))
  144. return
  145. banner = _(
  146. "The variable 'm' is the $fqdn_listname mailing list")
  147. # All other processing is finished; maybe go into interactive mode.
  148. if interactive:
  149. overrides = dict(
  150. m=m,
  151. commit=config.db.commit,
  152. abort=config.db.abort,
  153. config=config,
  154. getUtility=getUtility
  155. )
  156. # Bootstrap some useful names into the namespace, mostly to make
  157. # the component architecture and interfaces easily available.
  158. for module_name in sys.modules:
  159. if not module_name.startswith('mailman.interfaces.'):
  160. continue
  161. module = sys.modules[module_name]
  162. for name in module.__all__:
  163. overrides[name] = getattr(module, name)
  164. banner = config.shell.banner + '\n' + (
  165. banner if isinstance(banner, str) else '')
  166. try:
  167. use_ipython = as_boolean(config.shell.use_ipython)
  168. except ValueError:
  169. if config.shell.use_ipython == 'debug':
  170. use_ipython = True
  171. debug = True
  172. else:
  173. raise
  174. else:
  175. debug = False
  176. if use_ipython:
  177. self._start_ipython(overrides, banner, debug)
  178. else:
  179. self._start_python(overrides, banner)
  180. def _start_ipython(self, overrides, banner, debug):
  181. shell = None
  182. for starter in (_start_ipython4, _start_ipython1):
  183. shell = starter(overrides, banner, debug=debug)
  184. if shell is not None:
  185. shell()
  186. break
  187. else:
  188. print(_('ipython is not available, set use_ipython to no'))
  189. def _start_python(self, overrides, banner):
  190. # Set the tab completion.
  191. with ExitStack() as resources:
  192. try: # pragma: no cover
  193. import readline, rlcompleter # noqa: F401, E401
  194. except ImportError: # pragma: no cover
  195. print(_('readline not available'), file=sys.stderr)
  196. pass
  197. else:
  198. readline.parse_and_bind('tab: complete')
  199. history_file_template = config.shell.history_file.strip()
  200. if len(history_file_template) > 0:
  201. # Expand substitutions.
  202. substitutions = {
  203. key.lower(): value
  204. for key, value in config.paths.items()
  205. }
  206. history_file = Template(
  207. history_file_template).safe_substitute(substitutions)
  208. with suppress(FileNotFoundError):
  209. readline.read_history_file(history_file)
  210. resources.callback(
  211. readline.write_history_file,
  212. history_file)
  213. sys.ps1 = config.shell.prompt + ' '
  214. interact(upframe=False, banner=banner, overrides=overrides)
  215. def _details(self):
  216. """Print detailed usage."""
  217. # Split this up into paragraphs for easier translation.
  218. print(_("""\
  219. This script provides you with a general framework for interacting with a
  220. mailing list."""))
  221. print()
  222. print(_("""\
  223. There are two ways to use this script: interactively or programmatically.
  224. Using it interactively allows you to play with, examine and modify a mailing
  225. list from Python's interactive interpreter. When running interactively, the
  226. variable 'm' will be available in the global namespace. It will reference the
  227. mailing list object."""))
  228. print()
  229. print(_("""\
  230. Programmatically, you can write a function to operate on a mailing list, and
  231. this script will take care of the housekeeping (see below for examples). In
  232. that case, the general usage syntax is:
  233. % mailman withlist [options] listname [args ...]"""))
  234. print()
  235. print(_("""\
  236. Here's an example of how to use the --run option. Say you have a file in the
  237. Mailman installation directory called 'listaddr.py', with the following two
  238. functions:
  239. def listaddr(mlist):
  240. print mlist.posting_address
  241. def requestaddr(mlist):
  242. print mlist.request_address"""))
  243. print()
  244. print(_("""\
  245. You can print the list's posting address by running the following from the
  246. command line:
  247. % mailman withlist -r listaddr mylist@example.com
  248. Importing listaddr ...
  249. Running listaddr.listaddr() ...
  250. mylist@example.com"""))
  251. print()
  252. print(_("""\
  253. And you can print the list's request address by running:
  254. % mailman withlist -r listaddr.requestaddr mylist
  255. Importing listaddr ...
  256. Running listaddr.requestaddr() ...
  257. mylist-request@example.com"""))
  258. print()
  259. print(_("""\
  260. As another example, say you wanted to change the display name for a particular
  261. mailing list. You could put the following function in a file called
  262. 'change.pw':
  263. def change(mlist, display_name):
  264. mlist.display_name = display_name
  265. # Required to save changes to the database.
  266. commit()
  267. and run this from the command line:
  268. % mailman withlist -r change mylist@example.com 'My List'"""))
  269. @public
  270. class Shell(Withlist):
  271. """An alias for `withlist`."""
  272. name = 'shell'