Merge lp:~rajeevs1992/mailman.client/mailmancli into lp:mailman.client

Proposed by Rajeev S
Status: Needs review
Proposed branch: lp:~rajeevs1992/mailman.client/mailmancli
Merge into: lp:mailman.client
Diff against target: 4955 lines (+4719/-1)
39 files modified
README.rst (+29/-1)
conf.py (+330/-0)
setup.py (+4/-0)
src/mailmanclient.egg-info/PKG-INFO (+14/-0)
src/mailmanclient.egg-info/SOURCES.txt (+59/-0)
src/mailmanclient.egg-info/dependency_links.txt (+1/-0)
src/mailmanclient.egg-info/requires.txt (+5/-0)
src/mailmanclient.egg-info/top_level.txt (+1/-0)
src/mailmanclient/cli/client/cmdparser.py (+365/-0)
src/mailmanclient/cli/client/parsers/README.txt (+190/-0)
src/mailmanclient/cli/client/parsers/_set.py (+64/-0)
src/mailmanclient/cli/client/parsers/base.py (+62/-0)
src/mailmanclient/cli/client/parsers/create.py (+86/-0)
src/mailmanclient/cli/client/parsers/delete.py (+101/-0)
src/mailmanclient/cli/client/parsers/disable.py (+63/-0)
src/mailmanclient/cli/client/parsers/enable.py (+63/-0)
src/mailmanclient/cli/client/parsers/show.py (+101/-0)
src/mailmanclient/cli/client/parsers/show_var.py (+63/-0)
src/mailmanclient/cli/client/parsers/subscribe.py (+84/-0)
src/mailmanclient/cli/client/parsers/unset.py (+63/-0)
src/mailmanclient/cli/client/parsers/unsubscribe.py (+83/-0)
src/mailmanclient/cli/client/parsers/update.py (+114/-0)
src/mailmanclient/cli/client/shell.py (+394/-0)
src/mailmanclient/cli/core/domains.py (+136/-0)
src/mailmanclient/cli/core/lists.py (+227/-0)
src/mailmanclient/cli/core/misc.py (+69/-0)
src/mailmanclient/cli/core/preferences.py (+96/-0)
src/mailmanclient/cli/core/users.py (+189/-0)
src/mailmanclient/cli/docs/using_cli_shell.txt (+150/-0)
src/mailmanclient/cli/docs/using_cli_tools.txt (+311/-0)
src/mailmanclient/cli/docs/writing_a_new_command.txt (+57/-0)
src/mailmanclient/cli/lib/colors.py (+32/-0)
src/mailmanclient/cli/lib/mailman_utils.py (+117/-0)
src/mailmanclient/cli/lib/utils.py (+216/-0)
src/mailmanclient/cli/mmclient (+46/-0)
src/mailmanclient/cli/tests/test_domain.py (+223/-0)
src/mailmanclient/cli/tests/test_list.py (+285/-0)
src/mailmanclient/cli/tests/test_preference.py (+99/-0)
src/mailmanclient/cli/tests/test_user.py (+127/-0)
To merge this branch: bzr merge lp:~rajeevs1992/mailman.client/mailmancli
Reviewer Review Type Date Requested Status
Mailman Coders Pending
Review via email: mp+236052@code.launchpad.net

Commit message

Adds Command line tools and command line shell for mailman.client

Description of the change

GSoC project "Mailman CLI"

The branch contains the Mailman CLI shell as well as the command line tools built as a part of the GSoC 2014, Under the mentors Stephen J Turnbull, Abhilash Raj and Barry Warsaw.

To post a comment you must log in.

Unmerged revisions

75. By Rajeev S

- fixed bug in update global preference, which is readonly
- fixed/improved a few test cases

Global preferences are readonly, which is mentioned in the mmclient docs.
Option to edit the global preferences, which were previously allowed in
CLI are removed.

74. By Rajeev S

merged with base

73. By Rajeev S

Ported CLI to Py3

72. By Rajeev S

- made the docs sphinx compatiable
- added more docs

71. By Rajeev S

- added more tests, for the preferences methods
- added a check for HTTPErrors to make sure that
  correct errors are displayed
- scrubbed more code to enhance readability
- added readme for parsers

70. By Rajeev S

- modifed the setup.py to use scripts instead of entry_points
- added acks to Google and Mentors in file headers
- improved code for connection verification
- improved the `get_listing` function; moved to utils
- added colors as a seperate file, as per Steve's suggestion

69. By Rajeev S

- adds backup and restore tool
- fixed up installation routines
- refactored code to make it installable
- modified `in list` filter to support regular
  expressions
- updated docs and existing tests

68. By Rajeev S

- added expoort to csv function for domains, users
  and lists
- updated shell to support object names with whitespaces

67. By Rajeev S

- switched command parsing to YACC and LEX
  using python PLY module

66. By Rajeev S

- added update preference command
- introduced regex based validation for all commands
- regex stored in config.ini
- added docstrings aka help for all commands
- help stings printed upon error

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README.rst'
2--- README.rst 2015-01-01 19:34:58 +0000
3+++ README.rst 2015-03-17 19:08:34 +0000
4@@ -1,4 +1,9 @@
5-Python bindings for the Mailman 3 REST API.
6+===========================================================
7+mailman.client - Python bindings for the Mailman 3 REST API
8+===========================================================
9+
10+This package is called ``mailman.client``.
11+
12
13 ..
14 This file is part of mailman.client.
15@@ -48,6 +53,15 @@
16 See the Launchpad project page for access to the Bazaar branch, bug report,
17 etc.
18
19+Documentation
20+=============
21+
22+A `simple guide`_ to using the library is available within this package, in
23+the form of doctests. The manual is also available online in the Cheeseshop
24+at:
25+
26+ http://package.python.org/mailman.client
27+
28
29 Acknowledgements
30 ================
31@@ -57,3 +71,17 @@
32
33 .. _`Cheese Shop`: http://pypi.python.org/mailman.client
34 .. _Launchpad: https://launchpad.net/mailman.client
35+
36+Table of Contents
37+=================
38+
39+.. toctree::
40+
41+ src/mailmanclient/docs/using.txt
42+ src/mailmanclient/cli/docs/using_cli_shell.txt
43+ src/mailmanclient/cli/docs/using_cli_tools.txt
44+ src/mailmanclient/cli/docs/writing_a_new_command.txt
45+ src/mailmanclient/cli/client/parsers/README.txt
46+ src/mailmanclient/NEWS.txt
47+
48+.. _`simple guide`: docs/using.html
49
50=== added file 'conf.py'
51--- conf.py 1970-01-01 00:00:00 +0000
52+++ conf.py 2015-03-17 19:08:34 +0000
53@@ -0,0 +1,330 @@
54+# -*- coding: utf-8 -*-
55+#
56+# Mailman CLI documentation build configuration file, created by
57+# sphinx-quickstart on Sun Aug 17 13:11:12 2014.
58+#
59+# This file is execfile()d with the current directory set to its
60+# containing dir.
61+#
62+# Note that not all possible configuration values are present in this
63+# autogenerated file.
64+#
65+# All configuration values have a default; values that are commented out
66+# serve to show the default.
67+
68+import sys
69+import os
70+
71+# If extensions (or modules to document with autodoc) are in another directory,
72+# add these directories to sys.path here. If the directory is relative to the
73+# documentation root, use os.path.abspath to make it absolute, like shown here.
74+#sys.path.insert(0, os.path.abspath('.'))
75+
76+# -- General configuration ------------------------------------------------
77+
78+# If your documentation needs a minimal Sphinx version, state it here.
79+#needs_sphinx = '1.0'
80+
81+# Add any Sphinx extension module names here, as strings. They can be
82+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
83+# ones.
84+extensions = [
85+ 'sphinx.ext.autodoc',
86+]
87+
88+# Add any paths that contain templates here, relative to this directory.
89+templates_path = ['_templates']
90+
91+# The suffix of source filenames.
92+source_suffix = '.txt'
93+
94+# The encoding of source files.
95+#source_encoding = 'utf-8-sig'
96+
97+# The master toctree document.
98+master_doc = 'README'
99+
100+# General information about the project.
101+project = u'Mailman CLI'
102+copyright = u'2014, Rajeev S'
103+
104+# The version info for the project you're documenting, acts as replacement for
105+# |version| and |release|, also used in various other places throughout the
106+# built documents.
107+#
108+# The short X.Y version.
109+version = '1'
110+# The full version, including alpha/beta/rc tags.
111+release = '1'
112+
113+# The language for content autogenerated by Sphinx. Refer to documentation
114+# for a list of supported languages.
115+#language = None
116+
117+# There are two options for replacing |today|: either, you set today to some
118+# non-false value, then it is used:
119+#today = ''
120+# Else, today_fmt is used as the format for a strftime call.
121+#today_fmt = '%B %d, %Y'
122+
123+# List of patterns, relative to source directory, that match files and
124+# directories to ignore when looking for source files.
125+exclude_patterns = ['_build']
126+
127+# The reST default role (used for this markup: `text`) to use for all
128+# documents.
129+#default_role = None
130+
131+# If true, '()' will be appended to :func: etc. cross-reference text.
132+#add_function_parentheses = True
133+
134+# If true, the current module name will be prepended to all description
135+# unit titles (such as .. function::).
136+#add_module_names = True
137+
138+# If true, sectionauthor and moduleauthor directives will be shown in the
139+# output. They are ignored by default.
140+#show_authors = False
141+
142+# The name of the Pygments (syntax highlighting) style to use.
143+pygments_style = 'sphinx'
144+
145+# A list of ignored prefixes for module index sorting.
146+#modindex_common_prefix = []
147+
148+# If true, keep warnings as "system message" paragraphs in the built documents.
149+#keep_warnings = False
150+
151+
152+# -- Options for HTML output ----------------------------------------------
153+
154+# The theme to use for HTML and HTML Help pages. See the documentation for
155+# a list of builtin themes.
156+html_theme = 'default'
157+
158+# Theme options are theme-specific and customize the look and feel of a theme
159+# further. For a list of options available for each theme, see the
160+# documentation.
161+#html_theme_options = {}
162+
163+# Add any paths that contain custom themes here, relative to this directory.
164+#html_theme_path = []
165+
166+# The name for this set of Sphinx documents. If None, it defaults to
167+# "<project> v<release> documentation".
168+#html_title = None
169+
170+# A shorter title for the navigation bar. Default is the same as html_title.
171+#html_short_title = None
172+
173+# The name of an image file (relative to this directory) to place at the top
174+# of the sidebar.
175+#html_logo = None
176+
177+# The name of an image file (within the static path) to use as favicon of the
178+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
179+# pixels large.
180+#html_favicon = None
181+
182+# Add any paths that contain custom static files (such as style sheets) here,
183+# relative to this directory. They are copied after the builtin static files,
184+# so a file named "default.css" will overwrite the builtin "default.css".
185+html_static_path = ['_static']
186+
187+# Add any extra paths that contain custom files (such as robots.txt or
188+# .htaccess) here, relative to this directory. These files are copied
189+# directly to the root of the documentation.
190+#html_extra_path = []
191+
192+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
193+# using the given strftime format.
194+#html_last_updated_fmt = '%b %d, %Y'
195+
196+# If true, SmartyPants will be used to convert quotes and dashes to
197+# typographically correct entities.
198+#html_use_smartypants = True
199+
200+# Custom sidebar templates, maps document names to template names.
201+#html_sidebars = {}
202+
203+# Additional templates that should be rendered to pages, maps page names to
204+# template names.
205+#html_additional_pages = {}
206+
207+# If false, no module index is generated.
208+#html_domain_indices = True
209+
210+# If false, no index is generated.
211+#html_use_index = True
212+
213+# If true, the index is split into individual pages for each letter.
214+#html_split_index = False
215+
216+# If true, links to the reST sources are added to the pages.
217+#html_show_sourcelink = True
218+
219+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
220+#html_show_sphinx = True
221+
222+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
223+#html_show_copyright = True
224+
225+# If true, an OpenSearch description file will be output, and all pages will
226+# contain a <link> tag referring to it. The value of this option must be the
227+# base URL from which the finished HTML is served.
228+#html_use_opensearch = ''
229+
230+# This is the file name suffix for HTML files (e.g. ".xhtml").
231+#html_file_suffix = None
232+
233+# Output file base name for HTML help builder.
234+htmlhelp_basename = 'MailmanCLIdoc'
235+
236+
237+# -- Options for LaTeX output ---------------------------------------------
238+
239+latex_elements = {
240+# The paper size ('letterpaper' or 'a4paper').
241+#'papersize': 'letterpaper',
242+
243+# The font size ('10pt', '11pt' or '12pt').
244+#'pointsize': '10pt',
245+
246+# Additional stuff for the LaTeX preamble.
247+#'preamble': '',
248+}
249+
250+# Grouping the document tree into LaTeX files. List of tuples
251+# (source start file, target name, title,
252+# author, documentclass [howto, manual, or own class]).
253+latex_documents = [
254+ ('README', 'MailmanCLI.tex', u'Mailman CLI Documentation',
255+ u'Rajeev S', 'manual'),
256+]
257+
258+# The name of an image file (relative to this directory) to place at the top of
259+# the title page.
260+#latex_logo = None
261+
262+# For "manual" documents, if this is true, then toplevel headings are parts,
263+# not chapters.
264+#latex_use_parts = False
265+
266+# If true, show page references after internal links.
267+#latex_show_pagerefs = False
268+
269+# If true, show URL addresses after external links.
270+#latex_show_urls = False
271+
272+# Documents to append as an appendix to all manuals.
273+#latex_appendices = []
274+
275+# If false, no module index is generated.
276+#latex_domain_indices = True
277+
278+
279+# -- Options for manual page output ---------------------------------------
280+
281+# One entry per manual page. List of tuples
282+# (source start file, name, description, authors, manual section).
283+man_pages = [
284+ ('README', 'mailmancli', u'Mailman CLI Documentation',
285+ [u'Rajeev S'], 1)
286+]
287+
288+# If true, show URL addresses after external links.
289+#man_show_urls = False
290+
291+
292+# -- Options for Texinfo output -------------------------------------------
293+
294+# Grouping the document tree into Texinfo files. List of tuples
295+# (source start file, target name, title, author,
296+# dir menu entry, description, category)
297+texinfo_documents = [
298+ ('README', 'MailmanCLI', u'Mailman CLI Documentation',
299+ u'Rajeev S', 'MailmanCLI', 'One line description of project.',
300+ 'Miscellaneous'),
301+]
302+
303+# Documents to append as an appendix to all manuals.
304+#texinfo_appendices = []
305+
306+# If false, no module index is generated.
307+#texinfo_domain_indices = True
308+
309+# How to display URL addresses: 'footnote', 'no', or 'inline'.
310+#texinfo_show_urls = 'footnote'
311+
312+# If true, do not generate a @detailmenu in the "Top" node's menu.
313+#texinfo_no_detailmenu = False
314+
315+
316+# -- Options for Epub output ----------------------------------------------
317+
318+# Bibliographic Dublin Core info.
319+epub_title = u'Mailman CLI'
320+epub_author = u'Rajeev S'
321+epub_publisher = u'Rajeev S'
322+epub_copyright = u'2014, Rajeev S'
323+
324+# The basename for the epub file. It defaults to the project name.
325+#epub_basename = u'Mailman CLI'
326+
327+# The HTML theme for the epub output. Since the default themes are not optimized
328+# for small screen space, using the same theme for HTML and epub output is
329+# usually not wise. This defaults to 'epub', a theme designed to save visual
330+# space.
331+#epub_theme = 'epub'
332+
333+# The language of the text. It defaults to the language option
334+# or en if the language is not set.
335+#epub_language = ''
336+
337+# The scheme of the identifier. Typical schemes are ISBN or URL.
338+#epub_scheme = ''
339+
340+# The unique identifier of the text. This can be a ISBN number
341+# or the project homepage.
342+#epub_identifier = ''
343+
344+# A unique identification for the text.
345+#epub_uid = ''
346+
347+# A tuple containing the cover image and cover page html template filenames.
348+#epub_cover = ()
349+
350+# A sequence of (type, uri, title) tuples for the guide element of content.opf.
351+#epub_guide = ()
352+
353+# HTML files that should be inserted before the pages created by sphinx.
354+# The format is a list of tuples containing the path and title.
355+#epub_pre_files = []
356+
357+# HTML files shat should be inserted after the pages created by sphinx.
358+# The format is a list of tuples containing the path and title.
359+#epub_post_files = []
360+
361+# A list of files that should not be packed into the epub file.
362+epub_exclude_files = ['search.html']
363+
364+# The depth of the table of contents in toc.ncx.
365+#epub_tocdepth = 3
366+
367+# Allow duplicate toc entries.
368+#epub_tocdup = True
369+
370+# Choose between 'default' and 'includehidden'.
371+#epub_tocscope = 'default'
372+
373+# Fix unsupported image types using the PIL.
374+#epub_fix_images = False
375+
376+# Scale large images.
377+#epub_max_image_width = 0
378+
379+# How to display URL addresses: 'footnote', 'no', or 'inline'.
380+#epub_show_urls = 'inline'
381+
382+# If false, no index is generated.
383+#epub_use_index = True
384
385=== modified file 'setup.py'
386--- setup.py 2015-01-01 21:26:10 +0000
387+++ setup.py 2015-03-17 19:08:34 +0000
388@@ -30,6 +30,7 @@
389 packages=find_packages('src'),
390 package_dir = {'': 'src'},
391 include_package_data=True,
392+ scripts=['src/mailmanclient/cli/mmclient'],
393 maintainer='Barry Warsaw',
394 maintainer_email='barry@list.org',
395 description=description('README.rst'),
396@@ -42,5 +43,8 @@
397 install_requires=[
398 'httplib2',
399 'six',
400+ 'mock',
401+ 'tabulate',
402+ 'ply',
403 ],
404 )
405
406=== added directory 'src/mailmanclient.egg-info'
407=== added file 'src/mailmanclient.egg-info/PKG-INFO'
408--- src/mailmanclient.egg-info/PKG-INFO 1970-01-01 00:00:00 +0000
409+++ src/mailmanclient.egg-info/PKG-INFO 2015-03-17 19:08:34 +0000
410@@ -0,0 +1,14 @@
411+Metadata-Version: 1.1
412+Name: mailmanclient
413+Version: 1.0.0
414+Summary: Python bindings for the Mailman 3 REST API.
415+Home-page: http://launchpad.net/mailman.client
416+Author: Barry Warsaw
417+Author-email: barry@list.org
418+License: LGPLv3
419+Download-URL: https://launchpad.net/mailman.client/+download
420+Description: src/mailmanclient/README.rst
421+
422+ src/mailmanclient/NEWS.rst
423+
424+Platform: UNKNOWN
425
426=== added file 'src/mailmanclient.egg-info/SOURCES.txt'
427--- src/mailmanclient.egg-info/SOURCES.txt 1970-01-01 00:00:00 +0000
428+++ src/mailmanclient.egg-info/SOURCES.txt 2015-03-17 19:08:34 +0000
429@@ -0,0 +1,59 @@
430+MANIFEST.in
431+Makefile
432+README.rst
433+README.txt
434+conf.py
435+distribute_setup.py
436+setup.cfg
437+setup.py
438+setup_helpers.py
439+template.py
440+src/mailmanclient/NEWS.txt
441+src/mailmanclient/__init__.py
442+src/mailmanclient/_client.py
443+src/mailmanclient.egg-info/PKG-INFO
444+src/mailmanclient.egg-info/SOURCES.txt
445+src/mailmanclient.egg-info/dependency_links.txt
446+src/mailmanclient.egg-info/requires.txt
447+src/mailmanclient.egg-info/top_level.txt
448+src/mailmanclient/cli/__init__.py
449+src/mailmanclient/cli/mmclient
450+src/mailmanclient/cli/client/__init__.py
451+src/mailmanclient/cli/client/cmdparser.py
452+src/mailmanclient/cli/client/shell.py
453+src/mailmanclient/cli/client/parsers/README.txt
454+src/mailmanclient/cli/client/parsers/__init__.py
455+src/mailmanclient/cli/client/parsers/_set.py
456+src/mailmanclient/cli/client/parsers/base.py
457+src/mailmanclient/cli/client/parsers/create.py
458+src/mailmanclient/cli/client/parsers/delete.py
459+src/mailmanclient/cli/client/parsers/disable.py
460+src/mailmanclient/cli/client/parsers/enable.py
461+src/mailmanclient/cli/client/parsers/show.py
462+src/mailmanclient/cli/client/parsers/show_var.py
463+src/mailmanclient/cli/client/parsers/subscribe.py
464+src/mailmanclient/cli/client/parsers/unset.py
465+src/mailmanclient/cli/client/parsers/unsubscribe.py
466+src/mailmanclient/cli/client/parsers/update.py
467+src/mailmanclient/cli/core/__init__.py
468+src/mailmanclient/cli/core/domains.py
469+src/mailmanclient/cli/core/lists.py
470+src/mailmanclient/cli/core/misc.py
471+src/mailmanclient/cli/core/preferences.py
472+src/mailmanclient/cli/core/users.py
473+src/mailmanclient/cli/docs/using_cli_shell.txt
474+src/mailmanclient/cli/docs/using_cli_tools.txt
475+src/mailmanclient/cli/docs/writing_a_new_command.txt
476+src/mailmanclient/cli/lib/__init__.py
477+src/mailmanclient/cli/lib/colors.py
478+src/mailmanclient/cli/lib/mailman_utils.py
479+src/mailmanclient/cli/lib/utils.py
480+src/mailmanclient/cli/tests/__init__.py
481+src/mailmanclient/cli/tests/test_domain.py
482+src/mailmanclient/cli/tests/test_list.py
483+src/mailmanclient/cli/tests/test_preference.py
484+src/mailmanclient/cli/tests/test_user.py
485+src/mailmanclient/docs/__init__.py
486+src/mailmanclient/docs/using.txt
487+src/mailmanclient/tests/__init__.py
488+src/mailmanclient/tests/test_docs.py
489\ No newline at end of file
490
491=== added file 'src/mailmanclient.egg-info/dependency_links.txt'
492--- src/mailmanclient.egg-info/dependency_links.txt 1970-01-01 00:00:00 +0000
493+++ src/mailmanclient.egg-info/dependency_links.txt 2015-03-17 19:08:34 +0000
494@@ -0,0 +1,1 @@
495+
496
497=== added file 'src/mailmanclient.egg-info/requires.txt'
498--- src/mailmanclient.egg-info/requires.txt 1970-01-01 00:00:00 +0000
499+++ src/mailmanclient.egg-info/requires.txt 2015-03-17 19:08:34 +0000
500@@ -0,0 +1,5 @@
501+httplib2
502+six
503+mock
504+tabulate
505+ply
506
507=== added file 'src/mailmanclient.egg-info/top_level.txt'
508--- src/mailmanclient.egg-info/top_level.txt 1970-01-01 00:00:00 +0000
509+++ src/mailmanclient.egg-info/top_level.txt 2015-03-17 19:08:34 +0000
510@@ -0,0 +1,1 @@
511+mailmanclient
512
513=== added directory 'src/mailmanclient/cli'
514=== added file 'src/mailmanclient/cli/__init__.py'
515=== added directory 'src/mailmanclient/cli/client'
516=== added file 'src/mailmanclient/cli/client/__init__.py'
517=== added file 'src/mailmanclient/cli/client/cmdparser.py'
518--- src/mailmanclient/cli/client/cmdparser.py 1970-01-01 00:00:00 +0000
519+++ src/mailmanclient/cli/client/cmdparser.py 2015-03-17 19:08:34 +0000
520@@ -0,0 +1,365 @@
521+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
522+#
523+# This file is part of mailman.client.
524+#
525+# mailman.client is free software: you can redistribute it and/or modify it
526+# under the terms of the GNU Lesser General Public License as published by the
527+# Free Software Foundation, version 3 of the License.
528+#
529+# mailman.client is distributed in the hope that it will be useful, but
530+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
531+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
532+# License for more details.
533+#
534+# You should have received a copy of the GNU Lesser General Public License
535+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
536+#
537+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
538+#
539+# Author : Rajeev S <rajeevs1992@gmail.com>
540+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
541+# Abhilash Raj <raj.abhilash1@gmail.com>
542+# Barry Warsaw <barry@list.org>
543+
544+from argparse import ArgumentParser
545+from mailmanclient.cli.core.misc import Misc
546+from mailmanclient.cli.core.users import Users
547+from mailmanclient.cli.core.lists import Lists
548+from mailmanclient.cli.core.domains import Domains
549+from mailmanclient.cli.core.preferences import Preferences
550+from mailmanclient.cli.lib.mailman_utils import MailmanUtils
551+
552+utils = MailmanUtils()
553+
554+
555+class CmdParser():
556+ def __init__(self, command):
557+ parser = ArgumentParser(description='Mailman Command Tools')
558+ self.initialize_options(parser)
559+ self.arguments = vars(parser.parse_args())
560+
561+ def initialize_options(self, parser):
562+ action = parser.add_subparsers(dest='action')
563+
564+ # Parser for the action `show`
565+ action_show = action.add_parser('show')
566+ scope = action_show.add_subparsers(dest='scope')
567+
568+ # Show lists
569+ show_list = scope.add_parser('list')
570+ show_list.add_argument('list',
571+ help='List details about LIST',
572+ nargs='?')
573+ show_list.add_argument('-d',
574+ '--domain',
575+ help='Filter by DOMAIN')
576+ show_list.add_argument('-v',
577+ '--verbose',
578+ help='Detailed listing',
579+ action='store_true')
580+ show_list.add_argument('--no-header',
581+ help='Omit headings in detailed listing',
582+ action='store_true')
583+ show_list.add_argument('--csv',
584+ help='Output as CSV, Specify filename')
585+
586+ # Show domains
587+ show_domain = scope.add_parser('domain')
588+ show_domain.add_argument('domain',
589+ help='List details about DOMAIN',
590+ nargs='?')
591+ show_domain.add_argument('-v',
592+ '--verbose',
593+ help='Detailed listing',
594+ action='store_true')
595+ show_domain.add_argument('--no-header',
596+ help='Omit headings in detailed listing',
597+ action='store_true')
598+ show_domain.add_argument('--csv',
599+ help='Output as CSV, Specify filename')
600+
601+ # Show users
602+ show_user = scope.add_parser('user')
603+ show_user.add_argument('user',
604+ help='List details about USER',
605+ nargs='?')
606+ show_user.add_argument('-v',
607+ '--verbose',
608+ help='Detailed listing',
609+ action='store_true')
610+ show_user.add_argument('--no-header',
611+ help='Omit headings in detailed listing',
612+ action='store_true')
613+ show_user.add_argument('-l',
614+ '--list',
615+ help='Specify list name',
616+ dest='list_name')
617+ show_user.add_argument('--csv',
618+ help='Output as CSV, Specify filename')
619+
620+ # Show preferences
621+ preferences = ['receive_list_copy', 'hide_address',
622+ 'preferred_language', 'acknowledge_posts',
623+ 'delivery_mode', 'receive_own_postings',
624+ 'http_etag', 'self_link', 'delivery_status']
625+ show_preference = scope.add_parser('preference')
626+ show_scope = show_preference.add_subparsers(dest='show_scope')
627+
628+ show_scope.add_parser('global')
629+
630+ user_show = show_scope.add_parser('user')
631+ user_show.add_argument('--email',
632+ help='Email of user whose '
633+ 'preference is to be shown',
634+ required=True)
635+
636+ address_show = show_scope.add_parser('address')
637+ address_show.add_argument('--email',
638+ help='Address whose preference'
639+ ' is to be shown',
640+ required=True)
641+
642+ member_show = show_scope.add_parser('member')
643+ member_show.add_argument('--email',
644+ help='Address whose preference'
645+ ' is to be shown',
646+ required=True)
647+ member_show.add_argument('--list',
648+ help='FQDN name of list',
649+ required=True)
650+
651+ show_preference.add_argument('key',
652+ help='Specify setting name',
653+ choices=preferences)
654+
655+ # Parser for the action `create`
656+ action_create = action.add_parser('create')
657+ scope = action_create.add_subparsers(dest='scope')
658+
659+ # Create list
660+ create_list = scope.add_parser('list')
661+ create_list.add_argument('list',
662+ help='List name. e.g., list@domain.org')
663+ # Create domain
664+ create_domain = scope.add_parser('domain')
665+ create_domain.add_argument('domain',
666+ help='Create domain DOMAIN')
667+ create_domain.add_argument('--contact',
668+ help='Contact address for domain')
669+ # Create users
670+ create_user = scope.add_parser('user')
671+ create_user.add_argument('email',
672+ help='Create user foo@bar.com')
673+ create_user.add_argument('--password',
674+ help='User password',
675+ required=True)
676+ create_user.add_argument('--name',
677+ help='Display name of the user',
678+ required=True)
679+
680+ # Parser for the action `delete`
681+ action_delete = action.add_parser('delete')
682+ scope = action_delete.add_subparsers(dest='scope')
683+
684+ # Delete list
685+ delete_list = scope.add_parser('list')
686+ delete_list.add_argument('list',
687+ help='List name. e.g., list@domain.org')
688+ delete_list.add_argument('--yes',
689+ help='Force delete',
690+ action='store_true')
691+ # Delete domain
692+ delete_domain = scope.add_parser('domain')
693+ delete_domain.add_argument('domain',
694+ help='Domain name. e.g., domain.org')
695+ delete_domain.add_argument('--yes',
696+ help='Force delete',
697+ action='store_true')
698+ # Delete user
699+ delete_user = scope.add_parser('user')
700+ delete_user.add_argument('user',
701+ help='User email e.g., foo@bar.com')
702+ delete_user.add_argument('--yes',
703+ help='Force delete',
704+ action='store_true')
705+ # Show Members of a list
706+ action_show_member = action.add_parser('show-members')
707+ scope = action_show_member.add_subparsers(dest='scope')
708+ show_member = scope.add_parser('list')
709+ show_member.add_argument('list',
710+ help='Show members of LIST')
711+ show_member.add_argument('-v',
712+ '--verbose',
713+ help='Detailed listing',
714+ action='store_true')
715+ show_member.add_argument('--no-header',
716+ help='Omit headings in detailed listing',
717+ action='store_true')
718+
719+ # Parser for the action `subscribe`
720+ action_subscribe = action.add_parser('subscribe')
721+ scope = action_subscribe.add_subparsers(dest='scope')
722+ subscribe_user = scope.add_parser('user')
723+ subscribe_user.add_argument('users',
724+ help='User email list',
725+ nargs='+')
726+ subscribe_user.add_argument('-l',
727+ '--list',
728+ help='Specify list name',
729+ dest='list_name',
730+ required=True)
731+ subscribe_user.add_argument('--quiet',
732+ help='Do not display feedback',
733+ action='store_true')
734+
735+ # Parser for the action `unsubscribe`
736+ action_subscribe = action.add_parser('unsubscribe')
737+ scope = action_subscribe.add_subparsers(dest='scope')
738+ subscribe_user = scope.add_parser('user')
739+ subscribe_user.add_argument('users',
740+ help='User email list',
741+ nargs='+')
742+ subscribe_user.add_argument('-l',
743+ '--list',
744+ help='Specify list name',
745+ dest='list_name',
746+ required=True)
747+ subscribe_user.add_argument('--quiet',
748+ help='Do not display feedback',
749+ action='store_true')
750+ # Moderation Tools
751+
752+ # Add Moderator
753+ action_add_moderator = action.add_parser('add-moderator')
754+ scope = action_add_moderator.add_subparsers(dest='scope')
755+ add_moderator = scope.add_parser('list')
756+ add_moderator.add_argument('list',
757+ help='Specify list name')
758+ add_moderator.add_argument('-u',
759+ '--user',
760+ help='User email list',
761+ dest='users',
762+ nargs='+',
763+ required=True)
764+ add_moderator.add_argument('--quiet',
765+ help='Do not display feedback',
766+ action='store_true')
767+ # Add Owner
768+ action_add_owner = action.add_parser('add-owner')
769+ scope = action_add_owner.add_subparsers(dest='scope')
770+ add_owner = scope.add_parser('list')
771+ add_owner.add_argument('list',
772+ help='Specify list name')
773+ add_owner.add_argument('-u',
774+ '--user',
775+ help='User email list',
776+ dest='users',
777+ nargs='+')
778+ add_owner.add_argument('--quiet',
779+ help='Do not display feedback',
780+ action='store_true')
781+ # Remove Moderator
782+ action_remove_moderator = action.add_parser('remove-moderator')
783+ scope = action_remove_moderator.add_subparsers(dest='scope')
784+ remove_moderator = scope.add_parser('list')
785+ remove_moderator.add_argument('list',
786+ help='Specify list name')
787+ remove_moderator.add_argument('-u',
788+ '--user',
789+ dest='users',
790+ help='User email list',
791+ nargs='+')
792+ remove_moderator.add_argument('--quiet',
793+ help='Do not display feedback',
794+ action='store_true')
795+ # Remove Owner
796+ action_remove_owner = action.add_parser('remove-owner')
797+ scope = action_remove_owner.add_subparsers(dest='scope')
798+ remove_owner = scope.add_parser('list')
799+ remove_owner.add_argument('list',
800+ help='Specify list name')
801+ remove_owner.add_argument('-u',
802+ '--user',
803+ help='User email list',
804+ dest='users',
805+ nargs='+')
806+ remove_owner.add_argument('--quiet',
807+ help='Do not display feedback',
808+ action='store_true')
809+ # Edit preferences
810+ action_update = action.add_parser('update')
811+ scope = action_update.add_subparsers(dest='scope')
812+ update_preference = scope.add_parser('preference')
813+ update_scope = update_preference.add_subparsers(dest='update_scope')
814+
815+ user_update = update_scope.add_parser('user')
816+ user_update.add_argument('--email',
817+ help='Email of user whose '
818+ 'preference is to be changed',
819+ required=True)
820+
821+ address_update = update_scope.add_parser('address')
822+ address_update.add_argument('--email',
823+ help='Address whose preference'
824+ ' is to be changed',
825+ required=True)
826+
827+ member_update = update_scope.add_parser('member')
828+ member_update.add_argument('--email',
829+ help='Address whose preference'
830+ ' is to be changed',
831+ required=True)
832+ member_update.add_argument('--list',
833+ help='FQDN name of list',
834+ required=True)
835+
836+ update_preference.add_argument('key',
837+ help='Specify setting name',
838+ choices=preferences)
839+ update_preference.add_argument('value',
840+ help='Specify setting value')
841+ # Backup Tool
842+ action_backup = action.add_parser('backup')
843+ action_backup.add_argument('output',
844+ help='Specify file to write backup')
845+
846+ # Restore Tool
847+ action_backup = action.add_parser('restore')
848+ action_backup.add_argument('backup',
849+ help='Specify backup file location')
850+ # Global options
851+ parser.add_argument('--host', help='REST API host address')
852+ parser.add_argument('--port', help='REST API host port')
853+ parser.add_argument('--restuser', help='REST API username')
854+ parser.add_argument('--restpass', help='REST API password')
855+
856+ def run(self):
857+ host = self.arguments['host']
858+ port = self.arguments['port']
859+ username = self.arguments['restuser']
860+ password = self.arguments['restpass']
861+ client = utils.connect(host=host, port=port,
862+ username=username, password=password)
863+ if 'scope' not in self.arguments:
864+ self.run_misc_actions()
865+ return
866+ scopes = {}
867+ scopes['user'] = Users
868+ scopes['list'] = Lists
869+ scopes['domain'] = Domains
870+ scopes['preference'] = Preferences
871+ self.arguments['action'] = self.arguments['action'].replace('-', '_')
872+ try:
873+ scope_object = scopes[self.arguments['scope']](client)
874+ action_name = self.arguments['action']
875+ action = getattr(scope_object, action_name)
876+ action(self.arguments)
877+ except Exception as e:
878+ utils.error(e)
879+ exit(1)
880+
881+ def run_misc_actions(self):
882+ action_name = self.arguments['action']
883+ misc = Misc()
884+ action = getattr(misc, action_name)
885+ action(self.arguments)
886
887=== added directory 'src/mailmanclient/cli/client/parsers'
888=== added file 'src/mailmanclient/cli/client/parsers/README.txt'
889--- src/mailmanclient/cli/client/parsers/README.txt 1970-01-01 00:00:00 +0000
890+++ src/mailmanclient/cli/client/parsers/README.txt 2015-03-17 19:08:34 +0000
891@@ -0,0 +1,190 @@
892+Mailman Shell Parsers
893+**********************
894+
895+This directory consists of the various parsers that
896+have been built for use with the mailman shell. The shell
897+parser has been built in such a way that each command has
898+a different and independent parser, so that adding new commands
899+can be done flexibly and easily, without worrying about the
900+existing code.
901+
902+The `show` command
903+=================
904+
905+The command to display the mailman objects
906+
907+Tokens
908+------
909+::
910+
911+ SHOW : 'show'
912+ SCOPE : '(user|domain|list)s?'
913+ WHERE : 'where'
914+ OP : '=|in|like'
915+ AND : 'and'
916+ STRING : '`([a-zA-Z0-9_@\.\*\-\$ ]*)`'
917+
918+Grammar
919+-------
920+::
921+
922+ S : SHOW SCOPE FILTER ;
923+ FILTER : WHERE EXP | ;
924+ EXP : STRING OP STRING CONJ ;
925+ CONJ : AND EXP | ;
926+
927+The `create` command
928+====================
929+
930+The commands to create mailman objects
931+
932+Tokens
933+------
934+::
935+
936+ CREATE : 'create'
937+ SCOPE : 'user|domain|list'
938+ WITH : 'with'
939+ AND : 'and'
940+ STRING : '`([a-zA-Z0-9_@\.\*\-\$ ]*)`'
941+
942+Grammar
943+-------
944+::
945+
946+ S : CREATE SCOPE WITH EXP ;
947+ EXP : STRING "=" STRING CONJ ;
948+ CONJ : AND EXP | ;
949+
950+The `delete` command
951+====================
952+
953+The commands to delete mailman objects
954+
955+Tokens
956+------
957+::
958+
959+ DELETE : 'delete'
960+ SCOPE : '(user|domain|list)s?'
961+ WHERE : 'where'
962+ OP : '=|in|like'
963+ AND : 'and'
964+ STRING : '`([a-zA-Z0-9_@\.\*\-\$ ]*)`'
965+
966+Grammar
967+-------
968+::
969+
970+ S : DELETE SCOPE FILTER ;
971+ FILTER : WHERE EXP | ;
972+ EXP : STRING OP STRING CONJ ;
973+ CONJ : AND EXP | ;
974+
975+The `subscribe` Command
976+=====================
977+
978+The commands to subscribe a list of users to a mailing lists
979+
980+Tokens
981+------
982+::
983+
984+ SUBSCRIBE : 'subscribe'
985+ USER : '(user)?'
986+ TO : 'to'
987+ STRING : '`([a-zA-Z0-9_@\.\*\-\$ ]*)`'
988+
989+Grammar
990+-------
991+::
992+
993+ S : SUBSCRIBE USER USERLIST TO STRING ;
994+ USERLIST : STRING NEXT ;
995+ NEXT : USERLIST | ;
996+
997+The `unsubscribe` Command
998+=========================
999+
1000+The commands to unsubscribe a list of users from a mailing lists
1001+
1002+Tokens
1003+------
1004+::
1005+
1006+ UNSUBSCRIBE : 'unsubscribe'
1007+ USER : '(user)?'
1008+ FROM : 'from'
1009+ STRING : '`([a-zA-Z0-9_@\.\*\-\$ ]*)`'
1010+
1011+Grammar
1012+-------
1013+::
1014+
1015+ S : UNSUBSCRIBE USER USERLIST TO STRING ;
1016+ USERLIST : STRING NEXT ;
1017+ NEXT : USERLIST | ;
1018+
1019+The `unset` command
1020+===================
1021+
1022+Command to unset a shell variable
1023+
1024+Tokens
1025+------
1026+::
1027+
1028+ UNSET : 'unset'
1029+ STRING : '`([a-zA-Z0-9_@\.\*\-\$ ]*)`'
1030+
1031+Grammar
1032+-------
1033+::
1034+
1035+ S : UNSET STRING ;
1036+
1037+The `set` Command
1038+=================
1039+
1040+Command to set a shell variable
1041+
1042+Tokens
1043+------
1044+::
1045+
1046+ SET : 'set'
1047+ STRING : '`([a-zA-Z0-9_@\.\*\-\$ ]*)`'
1048+
1049+Grammar
1050+-------
1051+::
1052+
1053+ S : SET STRING "=" STRING ;
1054+
1055+The `update` command
1056+====================
1057+
1058+Command to update a preference
1059+
1060+Tokens
1061+------
1062+::
1063+
1064+ UPDATE : 'update'
1065+ PREFERENCE : 'preference'
1066+ STRING : '`([a-zA-Z0-9_@\.\*\-\$ ]*)`'
1067+ TO : 'to'
1068+ WITH : 'with'
1069+ AND : 'and'
1070+ FOR : 'for'
1071+ GLOBALLY : 'globally'
1072+ DOMAIN : 'user|address|member'
1073+
1074+Grammar
1075+-------
1076+::
1077+
1078+ S : UPDATE PREFERENCE STRING TO STRING E ;
1079+ E : GLOBALLY | FOR DOMAIN WITH EXP ;
1080+ EXP : STRING "=" STRING CONJ ;
1081+ CONJ : AND EXP | ;
1082
1083=== added file 'src/mailmanclient/cli/client/parsers/__init__.py'
1084=== added file 'src/mailmanclient/cli/client/parsers/_set.py'
1085--- src/mailmanclient/cli/client/parsers/_set.py 1970-01-01 00:00:00 +0000
1086+++ src/mailmanclient/cli/client/parsers/_set.py 2015-03-17 19:08:34 +0000
1087@@ -0,0 +1,64 @@
1088+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
1089+#
1090+# This file is part of mailman.client.
1091+#
1092+# mailman.client is free software: you can redistribute it and/or modify it
1093+# under the terms of the GNU Lesser General Public License as published by the
1094+# Free Software Foundation, version 3 of the License.
1095+#
1096+# mailman.client is distributed in the hope that it will be useful, but
1097+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
1098+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
1099+# License for more details.
1100+#
1101+# You should have received a copy of the GNU Lesser General Public License
1102+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
1103+#
1104+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
1105+#
1106+# Author : Rajeev S <rajeevs1992@gmail.com>
1107+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
1108+# Abhilash Raj <raj.abhilash1@gmail.com>
1109+# Barry Warsaw <barry@list.org>
1110+
1111+import ply.yacc as yacc
1112+from mailmanclient.cli.client.parsers.base import Parser
1113+
1114+
1115+class Set(Parser):
1116+ tokens = ('SET', 'STRING')
1117+ literals = ['=', '`']
1118+ t_ignore = " \t"
1119+
1120+ def t_SET(self, t):
1121+ r'set'
1122+ return t
1123+
1124+ def t_STRING(self, t):
1125+ r'`([a-zA-Z0-9_@\.\*\-\$ ]*)`'
1126+ t.value = t.value.replace('`', '')
1127+ return t
1128+
1129+ def t_newline(self, t):
1130+ r'\n+'
1131+ t.lexer.lineno += t.value.count("\n")
1132+ return
1133+
1134+ def t_error(self, t):
1135+ raise Exception("Illegal character '%s'" % t.value[0])
1136+ t.lexer.skip(1)
1137+
1138+ def p_statement_scope(self, p):
1139+ '''S : SET STRING "=" STRING'''
1140+ self.arguments['key'] = p[2]
1141+ self.arguments['value'] = p[4]
1142+
1143+ def p_error(self, p):
1144+ if p:
1145+ raise Exception("Syntax error at '%s'" % p.value)
1146+ else:
1147+ raise Exception("Syntax error : Incomplete Command")
1148+
1149+ def parse(self, shell):
1150+ yacc.parse(shell.line)
1151+ return self.arguments
1152
1153=== added file 'src/mailmanclient/cli/client/parsers/base.py'
1154--- src/mailmanclient/cli/client/parsers/base.py 1970-01-01 00:00:00 +0000
1155+++ src/mailmanclient/cli/client/parsers/base.py 2015-03-17 19:08:34 +0000
1156@@ -0,0 +1,62 @@
1157+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
1158+#
1159+# This file is part of mailman.client.
1160+#
1161+# mailman.client is free software: you can redistribute it and/or modify it
1162+# under the terms of the GNU Lesser General Public License as published by the
1163+# Free Software Foundation, version 3 of the License.
1164+#
1165+# mailman.client is distributed in the hope that it will be useful, but
1166+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
1167+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
1168+# License for more details.
1169+#
1170+# You should have received a copy of the GNU Lesser General Public License
1171+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
1172+#
1173+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
1174+#
1175+# Author : Rajeev S <rajeevs1992@gmail.com>
1176+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
1177+# Abhilash Raj <raj.abhilash1@gmail.com>
1178+# Barry Warsaw <barry@list.org>
1179+
1180+import os
1181+import ply.lex as lex
1182+import ply.yacc as yacc
1183+
1184+
1185+class Parser:
1186+ """
1187+ Base class for a lexer/parser that has the rules defined as methods
1188+ """
1189+ tokens = ()
1190+ precedence = ()
1191+ arguments = {}
1192+
1193+ def __init__(self, **kw):
1194+ self.debug = kw.get('debug', 0)
1195+ self.names = {}
1196+ self.arguments = {}
1197+ try:
1198+ modname = os.path.split(os.path.splitext(__file__)[0])[1] + "_"
1199+ modname += self.__class__.__name__
1200+ except:
1201+ modname = "parser"+"_"+self.__class__.__name__
1202+ self.debugfile = modname + ".dbg"
1203+ self.tabmodule = modname + "_" + "parsetab"
1204+
1205+ if not os.path.exists('/tmp/parser_files'):
1206+ os.mkdir('/tmp/parser_files')
1207+
1208+ lex.lex(module=self, debug=self.debug)
1209+ yacc.yacc(module=self,
1210+ debug=self.debug,
1211+ debugfile=self.debugfile,
1212+ tabmodule=self.tabmodule,
1213+ outputdir='/tmp/parser_files')
1214+
1215+ def stem(self, token):
1216+ if token.value[-1] == 's':
1217+ token.value = token.value[:-1]
1218+ return token
1219
1220=== added file 'src/mailmanclient/cli/client/parsers/create.py'
1221--- src/mailmanclient/cli/client/parsers/create.py 1970-01-01 00:00:00 +0000
1222+++ src/mailmanclient/cli/client/parsers/create.py 2015-03-17 19:08:34 +0000
1223@@ -0,0 +1,86 @@
1224+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
1225+#
1226+# This file is part of mailman.client.
1227+#
1228+# mailman.client is free software: you can redistribute it and/or modify it
1229+# under the terms of the GNU Lesser General Public License as published by the
1230+# Free Software Foundation, version 3 of the License.
1231+#
1232+# mailman.client is distributed in the hope that it will be useful, but
1233+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
1234+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
1235+# License for more details.
1236+#
1237+# You should have received a copy of the GNU Lesser General Public License
1238+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
1239+#
1240+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
1241+#
1242+# Author : Rajeev S <rajeevs1992@gmail.com>
1243+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
1244+# Abhilash Raj <raj.abhilash1@gmail.com>
1245+# Barry Warsaw <barry@list.org>
1246+
1247+import ply.yacc as yacc
1248+from mailmanclient.cli.client.parsers.base import Parser
1249+
1250+
1251+class Create(Parser):
1252+ tokens = ('CREATE', 'SCOPE', 'STRING', 'WITH', 'AND')
1253+ literals = ['+', '`', '=']
1254+ t_ignore = " \t"
1255+
1256+ def t_CREATE(self, t):
1257+ r'create'
1258+ return t
1259+
1260+ def t_SCOPE(self, t):
1261+ 'user|domain|list'
1262+ return t
1263+
1264+ def t_WITH(self, t):
1265+ 'with'
1266+ return t
1267+
1268+ def t_AND(self, t):
1269+ 'and'
1270+ return t
1271+
1272+ def t_STRING(self, t):
1273+ r'`([a-zA-Z0-9_@\.\*\-\$ ]*)`'
1274+ t.value = t.value.replace('`', '')
1275+ return t
1276+
1277+ def t_newline(self, t):
1278+ r'\n+'
1279+ t.lexer.lineno += t.value.count("\n")
1280+ return
1281+
1282+ def t_error(self, t):
1283+ raise Exception("Illegal character '%s'" % t.value[0])
1284+ t.lexer.skip(1)
1285+
1286+ def p_start_statement(self, p):
1287+ '''S : CREATE SCOPE WITH EXP'''
1288+ self.arguments['scope'] = p[2]
1289+
1290+ def p_exp_condition(self, p):
1291+ '''EXP : STRING "=" STRING CONJ'''
1292+ if 'properties' not in self.arguments:
1293+ self.arguments['properties'] = {}
1294+ self.arguments['properties'][p[1]] = p[3]
1295+
1296+ def p_conj_exp(self, p):
1297+ ''' CONJ : AND EXP
1298+ |'''
1299+ pass
1300+
1301+ def p_error(self, p):
1302+ if p:
1303+ raise Exception("Syntax error at '%s'" % p.value)
1304+ else:
1305+ raise Exception("Syntax error : Incomplete Command")
1306+
1307+ def parse(self, shell):
1308+ yacc.parse(shell.line)
1309+ return self.arguments
1310
1311=== added file 'src/mailmanclient/cli/client/parsers/delete.py'
1312--- src/mailmanclient/cli/client/parsers/delete.py 1970-01-01 00:00:00 +0000
1313+++ src/mailmanclient/cli/client/parsers/delete.py 2015-03-17 19:08:34 +0000
1314@@ -0,0 +1,101 @@
1315+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
1316+#
1317+# This file is part of mailman.client.
1318+#
1319+# mailman.client is free software: you can redistribute it and/or modify it
1320+# under the terms of the GNU Lesser General Public License as published by the
1321+# Free Software Foundation, version 3 of the License.
1322+#
1323+# mailman.client is distributed in the hope that it will be useful, but
1324+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
1325+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
1326+# License for more details.
1327+#
1328+# You should have received a copy of the GNU Lesser General Public License
1329+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
1330+#
1331+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
1332+#
1333+# Author : Rajeev S <rajeevs1992@gmail.com>
1334+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
1335+# Abhilash Raj <raj.abhilash1@gmail.com>
1336+# Barry Warsaw <barry@list.org>
1337+
1338+import ply.yacc as yacc
1339+from mailmanclient.cli.client.parsers.base import Parser
1340+from mailmanclient.cli.lib.mailman_utils import MailmanUtils
1341+
1342+
1343+class Delete(Parser):
1344+ tokens = ('DELETE', 'SCOPE', 'STRING', 'WHERE', 'OP', 'AND')
1345+ literals = ['+', '`']
1346+ t_ignore = " \t"
1347+
1348+ def t_DELETE(self, t):
1349+ r'delete'
1350+ return t
1351+
1352+ def t_SCOPE(self, t):
1353+ '(user|domain|list)s?'
1354+ return self.stem(t)
1355+
1356+ def t_WHERE(self, t):
1357+ 'where'
1358+ return t
1359+
1360+ def t_OP(self, t):
1361+ '=|in|like'
1362+ return t
1363+
1364+ def t_AND(self, t):
1365+ 'and'
1366+ return t
1367+
1368+ def t_STRING(self, t):
1369+ r'`([a-zA-Z0-9_@\.\*\-\$ ]*)`'
1370+ t.value = t.value.replace('`', '')
1371+ return t
1372+
1373+ def t_newline(self, t):
1374+ r'\n+'
1375+ t.lexer.lineno += t.value.count("\n")
1376+ return
1377+
1378+ def t_error(self, t):
1379+ raise Exception("Illegal character '%s'" % t.value[0])
1380+ t.lexer.skip(1)
1381+
1382+ def p_statement_scope(self, p):
1383+ '''S : DELETE SCOPE FILTER'''
1384+ self.arguments['scope'] = p[2]
1385+
1386+ def p_filter(self, p):
1387+ '''FILTER : WHERE EXP
1388+ |'''
1389+ pass
1390+
1391+ def p_exp_condition(self, p):
1392+ '''EXP : STRING OP STRING CONJ'''
1393+ if 'filters' not in self.arguments:
1394+ self.arguments['filters'] = []
1395+ if p[2] == 'in':
1396+ self.arguments['filters'].append((p[3], p[2], p[1]))
1397+ else:
1398+ self.arguments['filters'].append((p[1], p[2], p[3]))
1399+
1400+ def p_conj_exp(self, p):
1401+ ''' CONJ : AND EXP
1402+ |'''
1403+ pass
1404+
1405+ def p_error(self, p):
1406+ if p:
1407+ raise Exception("Syntax error at '%s'" % p.value)
1408+ else:
1409+ raise Exception("Syntax error : Incomplete Command")
1410+
1411+ def parse(self, shell):
1412+ yacc.parse(shell.line)
1413+ utils = MailmanUtils()
1414+ self.arguments = utils.add_reserved_vars(self.arguments, shell)
1415+ return self.arguments
1416
1417=== added file 'src/mailmanclient/cli/client/parsers/disable.py'
1418--- src/mailmanclient/cli/client/parsers/disable.py 1970-01-01 00:00:00 +0000
1419+++ src/mailmanclient/cli/client/parsers/disable.py 2015-03-17 19:08:34 +0000
1420@@ -0,0 +1,63 @@
1421+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
1422+#
1423+# This file is part of mailman.client.
1424+#
1425+# mailman.client is free software: you can redistribute it and/or modify it
1426+# under the terms of the GNU Lesser General Public License as published by the
1427+# Free Software Foundation, version 3 of the License.
1428+#
1429+# mailman.client is distributed in the hope that it will be useful, but
1430+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
1431+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
1432+# License for more details.
1433+#
1434+# You should have received a copy of the GNU Lesser General Public License
1435+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
1436+#
1437+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
1438+#
1439+# Author : Rajeev S <rajeevs1992@gmail.com>
1440+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
1441+# Abhilash Raj <raj.abhilash1@gmail.com>
1442+# Barry Warsaw <barry@list.org>
1443+
1444+import ply.yacc as yacc
1445+from mailmanclient.cli.client.parsers.base import Parser
1446+
1447+
1448+class Disable(Parser):
1449+ tokens = ('DISABLE', 'WHAT')
1450+ literals = ['=', '`']
1451+ t_ignore = " \t"
1452+
1453+ def t_DISABLE(self, t):
1454+ r'disable'
1455+ return t
1456+
1457+ def t_WHAT(self, t):
1458+ r'env'
1459+ t.value = t.value.replace('`', '')
1460+ return t
1461+
1462+ def t_newline(self, t):
1463+ r'\n+'
1464+ t.lexer.lineno += t.value.count("\n")
1465+ return
1466+
1467+ def t_error(self, t):
1468+ raise Exception("Illegal character '%s'" % t.value[0])
1469+ t.lexer.skip(1)
1470+
1471+ def p_statment(self, p):
1472+ '''S : DISABLE WHAT'''
1473+ self.arguments['what'] = p[2]
1474+
1475+ def p_error(self, p):
1476+ if p:
1477+ raise Exception("Syntax error at '%s'" % p.value)
1478+ else:
1479+ raise Exception("Syntax error : Incomplete Command")
1480+
1481+ def parse(self, shell):
1482+ yacc.parse(shell.line)
1483+ return self.arguments
1484
1485=== added file 'src/mailmanclient/cli/client/parsers/enable.py'
1486--- src/mailmanclient/cli/client/parsers/enable.py 1970-01-01 00:00:00 +0000
1487+++ src/mailmanclient/cli/client/parsers/enable.py 2015-03-17 19:08:34 +0000
1488@@ -0,0 +1,63 @@
1489+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
1490+#
1491+# This file is part of mailman.client.
1492+#
1493+# mailman.client is free software: you can redistribute it and/or modify it
1494+# under the terms of the GNU Lesser General Public License as published by the
1495+# Free Software Foundation, version 3 of the License.
1496+#
1497+# mailman.client is distributed in the hope that it will be useful, but
1498+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
1499+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
1500+# License for more details.
1501+#
1502+# You should have received a copy of the GNU Lesser General Public License
1503+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
1504+#
1505+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
1506+#
1507+# Author : Rajeev S <rajeevs1992@gmail.com>
1508+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
1509+# Abhilash Raj <raj.abhilash1@gmail.com>
1510+# Barry Warsaw <barry@list.org>
1511+
1512+import ply.yacc as yacc
1513+from mailmanclient.cli.client.parsers.base import Parser
1514+
1515+
1516+class Enable(Parser):
1517+ tokens = ('ENABLE', 'WHAT')
1518+ literals = ['=', '`']
1519+ t_ignore = " \t"
1520+
1521+ def t_ENABLE(self, t):
1522+ r'enable'
1523+ return t
1524+
1525+ def t_WHAT(self, t):
1526+ r'env'
1527+ t.value = t.value.replace('`', '')
1528+ return t
1529+
1530+ def t_newline(self, t):
1531+ r'\n+'
1532+ t.lexer.lineno += t.value.count("\n")
1533+ return
1534+
1535+ def t_error(self, t):
1536+ raise Exception("Illegal character '%s'" % t.value[0])
1537+ t.lexer.skip(1)
1538+
1539+ def p_statment(self, p):
1540+ '''S : ENABLE WHAT'''
1541+ self.arguments['what'] = p[2]
1542+
1543+ def p_error(self, p):
1544+ if p:
1545+ raise Exception("Syntax error at '%s'" % p.value)
1546+ else:
1547+ raise Exception("Syntax error : Incomplete Command")
1548+
1549+ def parse(self, shell):
1550+ yacc.parse(shell.line)
1551+ return self.arguments
1552
1553=== added file 'src/mailmanclient/cli/client/parsers/show.py'
1554--- src/mailmanclient/cli/client/parsers/show.py 1970-01-01 00:00:00 +0000
1555+++ src/mailmanclient/cli/client/parsers/show.py 2015-03-17 19:08:34 +0000
1556@@ -0,0 +1,101 @@
1557+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
1558+#
1559+# This file is part of mailman.client.
1560+#
1561+# mailman.client is free software: you can redistribute it and/or modify it
1562+# under the terms of the GNU Lesser General Public License as published by the
1563+# Free Software Foundation, version 3 of the License.
1564+#
1565+# mailman.client is distributed in the hope that it will be useful, but
1566+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
1567+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
1568+# License for more details.
1569+#
1570+# You should have received a copy of the GNU Lesser General Public License
1571+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
1572+#
1573+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
1574+#
1575+# Author : Rajeev S <rajeevs1992@gmail.com>
1576+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
1577+# Abhilash Raj <raj.abhilash1@gmail.com>
1578+# Barry Warsaw <barry@list.org>
1579+
1580+import ply.yacc as yacc
1581+from mailmanclient.cli.client.parsers.base import Parser
1582+from mailmanclient.cli.lib.mailman_utils import MailmanUtils
1583+
1584+
1585+class Show(Parser):
1586+ tokens = ('SHOW', 'SCOPE', 'STRING', 'WHERE', 'OP', 'AND')
1587+ literals = ['+', '`']
1588+ t_ignore = " \t"
1589+
1590+ def t_SHOW(self, t):
1591+ r'show'
1592+ return t
1593+
1594+ def t_SCOPE(self, t):
1595+ '(user|domain|list)s?'
1596+ return self.stem(t)
1597+
1598+ def t_WHERE(self, t):
1599+ 'where'
1600+ return t
1601+
1602+ def t_OP(self, t):
1603+ '=|in|like'
1604+ return t
1605+
1606+ def t_AND(self, t):
1607+ 'and'
1608+ return t
1609+
1610+ def t_STRING(self, t):
1611+ r'`([a-zA-Z0-9_@\.\*\-\$ ]*)`'
1612+ t.value = t.value.replace('`', '')
1613+ return t
1614+
1615+ def t_newline(self, t):
1616+ r'\n+'
1617+ t.lexer.lineno += t.value.count("\n")
1618+ return
1619+
1620+ def t_error(self, t):
1621+ raise Exception("Illegal character '%s'" % t.value[0])
1622+ t.lexer.skip(1)
1623+
1624+ def p_statement_scope(self, p):
1625+ '''S : SHOW SCOPE FILTER'''
1626+ self.arguments['scope'] = p[2]
1627+
1628+ def p_filter(self, p):
1629+ '''FILTER : WHERE EXP
1630+ |'''
1631+ pass
1632+
1633+ def p_exp_condition(self, p):
1634+ '''EXP : STRING OP STRING CONJ'''
1635+ if 'filters' not in self.arguments:
1636+ self.arguments['filters'] = []
1637+ if p[2] == 'in':
1638+ self.arguments['filters'].append((p[3], p[2], p[1]))
1639+ else:
1640+ self.arguments['filters'].append((p[1], p[2], p[3]))
1641+
1642+ def p_conj_exp(self, p):
1643+ ''' CONJ : AND EXP
1644+ |'''
1645+ pass
1646+
1647+ def p_error(self, p):
1648+ if p:
1649+ raise Exception("Syntax error at '%s'" % p.value)
1650+ else:
1651+ raise Exception("Syntax error : Incomplete Command")
1652+
1653+ def parse(self, shell):
1654+ yacc.parse(shell.line)
1655+ utils = MailmanUtils()
1656+ self.arguments = utils.add_reserved_vars(self.arguments, shell)
1657+ return self.arguments
1658
1659=== added file 'src/mailmanclient/cli/client/parsers/show_var.py'
1660--- src/mailmanclient/cli/client/parsers/show_var.py 1970-01-01 00:00:00 +0000
1661+++ src/mailmanclient/cli/client/parsers/show_var.py 2015-03-17 19:08:34 +0000
1662@@ -0,0 +1,63 @@
1663+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
1664+#
1665+# This file is part of mailman.client.
1666+#
1667+# mailman.client is free software: you can redistribute it and/or modify it
1668+# under the terms of the GNU Lesser General Public License as published by the
1669+# Free Software Foundation, version 3 of the License.
1670+#
1671+# mailman.client is distributed in the hope that it will be useful, but
1672+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
1673+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
1674+# License for more details.
1675+#
1676+# You should have received a copy of the GNU Lesser General Public License
1677+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
1678+#
1679+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
1680+#
1681+# Author : Rajeev S <rajeevs1992@gmail.com>
1682+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
1683+# Abhilash Raj <raj.abhilash1@gmail.com>
1684+# Barry Warsaw <barry@list.org>
1685+
1686+import ply.yacc as yacc
1687+from mailmanclient.cli.client.parsers.base import Parser
1688+
1689+
1690+class ShowVar(Parser):
1691+ tokens = ('SHOW', 'STRING')
1692+ literals = ['=', '`']
1693+ t_ignore = " \t"
1694+
1695+ def t_SHOW(self, t):
1696+ r'show_var'
1697+ return t
1698+
1699+ def t_STRING(self, t):
1700+ r'`([a-zA-Z0-9_@\.\*\-]*)`'
1701+ t.value = t.value.replace('`', '')
1702+ return t
1703+
1704+ def t_newline(self, t):
1705+ r'\n+'
1706+ t.lexer.lineno += t.value.count("\n")
1707+ return
1708+
1709+ def t_error(self, t):
1710+ raise Exception("Illegal character '%s'" % t.value[0])
1711+ t.lexer.skip(1)
1712+
1713+ def p_statement_scope(self, p):
1714+ '''S : SHOW STRING'''
1715+ self.arguments['key'] = p[2]
1716+
1717+ def p_error(self, p):
1718+ if p:
1719+ raise Exception("Syntax error at '%s'" % p.value)
1720+ else:
1721+ raise Exception("Syntax error : Incomplete Command")
1722+
1723+ def parse(self, shell):
1724+ yacc.parse(shell.line)
1725+ return self.arguments
1726
1727=== added file 'src/mailmanclient/cli/client/parsers/subscribe.py'
1728--- src/mailmanclient/cli/client/parsers/subscribe.py 1970-01-01 00:00:00 +0000
1729+++ src/mailmanclient/cli/client/parsers/subscribe.py 2015-03-17 19:08:34 +0000
1730@@ -0,0 +1,84 @@
1731+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
1732+#
1733+# This file is part of mailman.client.
1734+#
1735+# mailman.client is free software: you can redistribute it and/or modify it
1736+# under the terms of the GNU Lesser General Public License as published by the
1737+# Free Software Foundation, version 3 of the License.
1738+#
1739+# mailman.client is distributed in the hope that it will be useful, but
1740+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
1741+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
1742+# License for more details.
1743+#
1744+# You should have received a copy of the GNU Lesser General Public License
1745+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
1746+#
1747+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
1748+#
1749+# Author : Rajeev S <rajeevs1992@gmail.com>
1750+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
1751+# Abhilash Raj <raj.abhilash1@gmail.com>
1752+# Barry Warsaw <barry@list.org>
1753+
1754+import ply.yacc as yacc
1755+from mailmanclient.cli.client.parsers.base import Parser
1756+
1757+
1758+class Subscribe(Parser):
1759+ tokens = ('SUBSCRIBE', 'USER', 'STRING', 'TO')
1760+ literals = ['+', '`', ',']
1761+ t_ignore = " \t"
1762+
1763+ def t_SUBSCRIBE(self, t):
1764+ r'subscribe'
1765+ return t
1766+
1767+ def t_USER(self, t):
1768+ '(user)s?'
1769+ return self.stem(t)
1770+ return t
1771+
1772+ def t_TO(self, t):
1773+ 'to'
1774+ return t
1775+
1776+ def t_STRING(self, t):
1777+ r'`([a-zA-Z0-9_@\.\*\-\$ ]*)`'
1778+ t.value = t.value.replace('`', '')
1779+ return t
1780+
1781+ def t_newline(self, t):
1782+ r'\n+'
1783+ t.lexer.lineno += t.value.count("\n")
1784+ return
1785+
1786+ def t_error(self, t):
1787+ raise Exception("Illegal character '%s'" % t.value[0])
1788+ t.lexer.skip(1)
1789+
1790+ def p_statement_subscribe(self, p):
1791+ '''S : SUBSCRIBE USER USERLIST TO STRING'''
1792+ self.arguments['list'] = p[5]
1793+ self.arguments['scope'] = p[2]
1794+
1795+ def p_get_users(self, p):
1796+ '''USERLIST : STRING NEXT'''
1797+ if 'users' not in self.arguments:
1798+ self.arguments['users'] = []
1799+ self.arguments['users'].append(p[1])
1800+
1801+ def p_next(self, p):
1802+ '''NEXT : USERLIST
1803+ |'''
1804+ pass
1805+
1806+ def p_error(self, p):
1807+ if p:
1808+ raise Exception("Syntax error at '%s'" % p.value)
1809+ else:
1810+ raise Exception("Syntax error : Incomplete Command")
1811+
1812+ def parse(self, shell):
1813+ yacc.parse(shell.line)
1814+ return self.arguments
1815
1816=== added file 'src/mailmanclient/cli/client/parsers/unset.py'
1817--- src/mailmanclient/cli/client/parsers/unset.py 1970-01-01 00:00:00 +0000
1818+++ src/mailmanclient/cli/client/parsers/unset.py 2015-03-17 19:08:34 +0000
1819@@ -0,0 +1,63 @@
1820+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
1821+#
1822+# This file is part of mailman.client.
1823+#
1824+# mailman.client is free software: you can redistribute it and/or modify it
1825+# under the terms of the GNU Lesser General Public License as published by the
1826+# Free Software Foundation, version 3 of the License.
1827+#
1828+# mailman.client is distributed in the hope that it will be useful, but
1829+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
1830+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
1831+# License for more details.
1832+#
1833+# You should have received a copy of the GNU Lesser General Public License
1834+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
1835+#
1836+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
1837+#
1838+# Author : Rajeev S <rajeevs1992@gmail.com>
1839+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
1840+# Abhilash Raj <raj.abhilash1@gmail.com>
1841+# Barry Warsaw <barry@list.org>
1842+
1843+import ply.yacc as yacc
1844+from mailmanclient.cli.client.parsers.base import Parser
1845+
1846+
1847+class Unset(Parser):
1848+ tokens = ('UNSET', 'STRING')
1849+ literals = ['=', '`']
1850+ t_ignore = " \t"
1851+
1852+ def t_UNSET(self, t):
1853+ r'unset'
1854+ return t
1855+
1856+ def t_STRING(self, t):
1857+ r'`([a-zA-Z0-9_@\.\*\-\$ ]*)`'
1858+ t.value = t.value.replace('`', '')
1859+ return t
1860+
1861+ def t_newline(self, t):
1862+ r'\n+'
1863+ t.lexer.lineno += t.value.count("\n")
1864+ return
1865+
1866+ def t_error(self, t):
1867+ raise Exception("Illegal character '%s'" % t.value[0])
1868+ t.lexer.skip(1)
1869+
1870+ def p_statement_scope(self, p):
1871+ '''S : UNSET STRING'''
1872+ self.arguments['key'] = p[2]
1873+
1874+ def p_error(self, p):
1875+ if p:
1876+ raise Exception("Syntax error at '%s'" % p.value)
1877+ else:
1878+ raise Exception("Syntax error : Incomplete Command")
1879+
1880+ def parse(self, shell):
1881+ yacc.parse(shell.line)
1882+ return self.arguments
1883
1884=== added file 'src/mailmanclient/cli/client/parsers/unsubscribe.py'
1885--- src/mailmanclient/cli/client/parsers/unsubscribe.py 1970-01-01 00:00:00 +0000
1886+++ src/mailmanclient/cli/client/parsers/unsubscribe.py 2015-03-17 19:08:34 +0000
1887@@ -0,0 +1,83 @@
1888+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
1889+#
1890+# This file is part of mailman.client.
1891+#
1892+# mailman.client is free software: you can redistribute it and/or modify it
1893+# under the terms of the GNU Lesser General Public License as published by the
1894+# Free Software Foundation, version 3 of the License.
1895+#
1896+# mailman.client is distributed in the hope that it will be useful, but
1897+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
1898+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
1899+# License for more details.
1900+#
1901+# You should have received a copy of the GNU Lesser General Public License
1902+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
1903+#
1904+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
1905+#
1906+# Author : Rajeev S <rajeevs1992@gmail.com>
1907+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
1908+# Abhilash Raj <raj.abhilash1@gmail.com>
1909+# Barry Warsaw <barry@list.org>
1910+
1911+import ply.yacc as yacc
1912+from mailmanclient.cli.client.parsers.base import Parser
1913+
1914+
1915+class Unsubscribe(Parser):
1916+ tokens = ('UNSUBSCRIBE', 'USER', 'STRING', 'FROM')
1917+ literals = ['+', '`', ',']
1918+ t_ignore = " \t"
1919+
1920+ def t_UNSUBSCRIBE(self, t):
1921+ r'unsubscribe'
1922+ return t
1923+
1924+ def t_USER(self, t):
1925+ '(user)s?'
1926+ return self.stem(t)
1927+
1928+ def t_FROM(self, t):
1929+ 'from'
1930+ return t
1931+
1932+ def t_STRING(self, t):
1933+ r'`([a-zA-Z0-9_@\.\*\-\$ ]*)`'
1934+ t.value = t.value.replace('`', '')
1935+ return t
1936+
1937+ def t_newline(self, t):
1938+ r'\n+'
1939+ t.lexer.lineno += t.value.count("\n")
1940+ return
1941+
1942+ def t_error(self, t):
1943+ raise Exception("Illegal character '%s'" % t.value[0])
1944+ t.lexer.skip(1)
1945+
1946+ def p_statement_unsubscribe(self, p):
1947+ '''S : UNSUBSCRIBE USER USERLIST FROM STRING'''
1948+ self.arguments['list'] = p[5]
1949+ self.arguments['scope'] = p[2]
1950+
1951+ def p_get_users(self, p):
1952+ '''USERLIST : STRING NEXT'''
1953+ if 'users' not in self.arguments:
1954+ self.arguments['users'] = []
1955+ self.arguments['users'].append(p[1])
1956+
1957+ def p_next(self, p):
1958+ '''NEXT : USERLIST
1959+ |'''
1960+ pass
1961+
1962+ def p_error(self, p):
1963+ if p:
1964+ raise Exception("Syntax error at '%s'" % p.value)
1965+ else:
1966+ raise Exception("Syntax error : Incomplete Command")
1967+
1968+ def parse(self, shell):
1969+ yacc.parse(shell.line)
1970+ return self.arguments
1971
1972=== added file 'src/mailmanclient/cli/client/parsers/update.py'
1973--- src/mailmanclient/cli/client/parsers/update.py 1970-01-01 00:00:00 +0000
1974+++ src/mailmanclient/cli/client/parsers/update.py 2015-03-17 19:08:34 +0000
1975@@ -0,0 +1,114 @@
1976+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
1977+#
1978+# This file is part of mailman.client.
1979+#
1980+# mailman.client is free software: you can redistribute it and/or modify it
1981+# under the terms of the GNU Lesser General Public License as published by the
1982+# Free Software Foundation, version 3 of the License.
1983+#
1984+# mailman.client is distributed in the hope that it will be useful, but
1985+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
1986+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
1987+# License for more details.
1988+#
1989+# You should have received a copy of the GNU Lesser General Public License
1990+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
1991+#
1992+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
1993+#
1994+# Author : Rajeev S <rajeevs1992@gmail.com>
1995+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
1996+# Abhilash Raj <raj.abhilash1@gmail.com>
1997+# Barry Warsaw <barry@list.org>
1998+
1999+import ply.yacc as yacc
2000+from mailmanclient.cli.client.parsers.base import Parser
2001+
2002+
2003+class Update(Parser):
2004+ tokens = ('UPDATE', 'PREFERENCE', 'STRING', 'TO', 'WITH',
2005+ 'AND', 'FOR', 'GLOBALLY', 'DOMAIN')
2006+ literals = ['=', '`']
2007+ t_ignore = " \t"
2008+
2009+ def t_UPDATE(self, t):
2010+ r'update'
2011+ return t
2012+
2013+ def t_PREFERENCE(self, t):
2014+ 'preference'
2015+ return t
2016+
2017+ def t_WITH(self, t):
2018+ 'with'
2019+ return t
2020+
2021+ def t_GLOBALLY(self, t):
2022+ 'globally'
2023+ return t
2024+
2025+ def t_DOMAIN(self, t):
2026+ 'user|address|member'
2027+ return t
2028+
2029+ def t_FOR(self, t):
2030+ 'for'
2031+ return t
2032+
2033+ def t_TO(self, t):
2034+ 'to'
2035+ return t
2036+
2037+ def t_AND(self, t):
2038+ 'and'
2039+ return t
2040+
2041+ def t_STRING(self, t):
2042+ r'`([a-zA-Z0-9_@\.\*\-\$ ]*)`'
2043+ t.value = t.value.replace('`', '')
2044+ return t
2045+
2046+ def t_newline(self, t):
2047+ r'\n+'
2048+ t.lexer.lineno += t.value.count("\n")
2049+ return
2050+
2051+ def t_error(self, t):
2052+ raise Exception("Illegal character '%s'" % t.value[0])
2053+ t.lexer.skip(1)
2054+
2055+ def p_statement_update(self, p):
2056+ '''S : UPDATE PREFERENCE STRING TO STRING E'''
2057+ self.arguments['key'] = p[3]
2058+ self.arguments['value'] = p[5]
2059+
2060+ def p_domain(self, p):
2061+ '''E : GLOBALLY
2062+ | FOR DOMAIN WITH EXP'''
2063+ try:
2064+ self.arguments['scope'] = p[2]
2065+ except IndexError:
2066+ self.arguments['scope'] = p[1]
2067+
2068+ def p_exp_condition(self, p):
2069+ '''EXP : STRING "=" STRING CONJ'''
2070+ if 'filters' not in self.arguments:
2071+ self.arguments['filters'] = {}
2072+ self.arguments['filters'][p[1]] = p[3]
2073+
2074+ def p_conj_exp(self, p):
2075+ ''' CONJ : AND EXP
2076+ |'''
2077+ pass
2078+
2079+ def p_error(self, p):
2080+ if p:
2081+ raise Exception("Syntax error at '%s'" % p.value)
2082+ else:
2083+ raise Exception("Syntax error : Incomplete Command")
2084+
2085+ def parse(self, shell):
2086+ yacc.parse(shell.line)
2087+ if 'filters' not in self.arguments:
2088+ self.arguments['filters'] = {}
2089+ return self.arguments
2090
2091=== added file 'src/mailmanclient/cli/client/shell.py'
2092--- src/mailmanclient/cli/client/shell.py 1970-01-01 00:00:00 +0000
2093+++ src/mailmanclient/cli/client/shell.py 2015-03-17 19:08:34 +0000
2094@@ -0,0 +1,394 @@
2095+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
2096+#
2097+# This file is part of mailman.client.
2098+#
2099+# mailman.client is free software: you can redistribute it and/or modify it
2100+# under the terms of the GNU Lesser General Public License as published by the
2101+# Free Software Foundation, version 3 of the License.
2102+#
2103+# mailman.client is distributed in the hope that it will be useful, but
2104+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
2105+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
2106+# License for more details.
2107+#
2108+# You should have received a copy of the GNU Lesser General Public License
2109+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
2110+#
2111+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
2112+#
2113+# Author : Rajeev S <rajeevs1992@gmail.com>
2114+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
2115+# Abhilash Raj <raj.abhilash1@gmail.com>
2116+# Barry Warsaw <barry@list.org>
2117+
2118+from cmd import Cmd
2119+from mailmanclient.cli.core.lists import Lists
2120+from mailmanclient.cli.core.domains import Domains
2121+from mailmanclient.cli.core.users import Users
2122+from mailmanclient.cli.core.preferences import Preferences
2123+from mailmanclient.cli.lib.mailman_utils import MailmanUtils
2124+from mailmanclient.cli.lib.utils import Filter
2125+
2126+utils = MailmanUtils()
2127+
2128+
2129+class Shell(Cmd):
2130+ intro = 'Mailman Command Line Interface'
2131+ prompt = '>>>'
2132+ env = {}
2133+ env_on = True
2134+ scope_classes = {}
2135+ scope_listing = {}
2136+ mmclient = None
2137+ scopes = ['list', 'user', 'domain']
2138+ line = None
2139+
2140+ def parseline(self, line):
2141+ """ This function sets the line attribute with the complete
2142+ command, which is used at the preprocessing stage"""
2143+ self.line = line
2144+ return Cmd.parseline(self, line)
2145+
2146+ def onecmd(self, s):
2147+ """ This method overides the Cmd.onecmd method, but eventully
2148+ calls the Cmd.onecmd function. The purpose of this function
2149+ is to catch the errors at a single point, rather than all
2150+ over the code.
2151+ """
2152+ try:
2153+ return Cmd.onecmd(self, s)
2154+ except Exception as e:
2155+ utils.error(e)
2156+ command = s.split()[0]
2157+ self.do_help(command)
2158+ return False
2159+
2160+ def emptyline(self):
2161+ """ Action on empty line entry. If not overridden, the shell executes last
2162+ command on encountering an empty line.
2163+ """
2164+ return False
2165+
2166+ def initialize(self):
2167+ """ Method to initialize the shell. Two hash tables are initialised, one
2168+ for storing the class signatures of the mailman Objects and the
2169+ other to store the lists of each object.
2170+ """
2171+ try:
2172+ self.mmclient = utils.connect()
2173+ self.scope_classes['list'] = Lists
2174+ self.scope_classes['domain'] = Domains
2175+ self.scope_classes['user'] = Users
2176+ self.scope_classes['preference'] = Preferences
2177+ self.refresh_lists()
2178+ except Exception as e:
2179+ utils.error(e)
2180+ exit(1)
2181+
2182+ def refresh_lists(self):
2183+ """ Refreshes the Mailman object list hash tables.
2184+ Invoke this method explicitly whenever the list contents might
2185+ get modified.
2186+ """
2187+ self.scope_listing['list'] = self.mmclient.lists
2188+ self.scope_listing['domain'] = self.mmclient.domains
2189+ self.scope_listing['user'] = self.mmclient.users
2190+
2191+ def do_EOF(self, args):
2192+ print()
2193+ print('Bye!')
2194+ exit(0)
2195+
2196+ def do_set(self, args):
2197+ """ Sets a variable in the environment
2198+ Usage:
2199+ set `<variable>` = `<value>`
2200+ """
2201+ from mailmanclient.cli.client.parsers._set import Set
2202+ parser = Set()
2203+ arguments = parser.parse(self)
2204+ key = arguments['key']
2205+ value = utils.add_shell_vars(arguments['value'], self)
2206+ self.env[key] = value
2207+ utils.warn('`%s` set to value `%s`' % (key, value))
2208+
2209+ def do_unset(self, args):
2210+ """ Delete a shell environment variable
2211+ Usage:
2212+ unset `<var_name>`"""
2213+ from mailmanclient.cli.client.parsers.unset import Unset
2214+ parser = Unset()
2215+ arguments = parser.parse(self)
2216+ key = arguments['key']
2217+ if key in self.env:
2218+ del self.env[key]
2219+ utils.warn('Shell Variable `%s` Deleted' % key)
2220+ else:
2221+ raise Exception('Invalid Argument `%s`' % key)
2222+
2223+ def do_show_var(self, args):
2224+ """ Show a shell environment variable
2225+ Usage:
2226+ show_var `<var_name>`"""
2227+ from mailmanclient.cli.client.parsers.show_var import ShowVar
2228+ parser = ShowVar()
2229+ arguments = parser.parse(self)
2230+ key = arguments['key']
2231+ if key in self.env:
2232+ utils.emphasize('Value of %s : %s' % (key, self.env[key]))
2233+ else:
2234+ raise Exception('Invalid Argument %s' % key)
2235+
2236+ def do_disable(self, args):
2237+ """ Disable the shell environment
2238+ Usage:
2239+ disable env"""
2240+ from mailmanclient.cli.client.parsers.disable import Disable
2241+ parser = Disable()
2242+ parser.parse(self)
2243+ self.env_on = False
2244+ utils.emphasize('Environment variables disabled')
2245+
2246+ def do_enable(self, args):
2247+ """ Enable the shell environment
2248+ Usage:
2249+ enable env"""
2250+ from mailmanclient.cli.client.parsers.enable import Enable
2251+ parser = Enable()
2252+ parser.parse(self)
2253+ self.env_on = True
2254+ utils.emphasize('Environment variables enabled')
2255+
2256+ def do_show(self, args):
2257+ """ Show requested mailman objects as a table
2258+ Usage:
2259+ show {domain|user|list} where `<object_attribute>` = `<value>`
2260+ show {domain|user|list} where `<object_attribute>` like `<regex>`
2261+ show {domain|user|list} where `<regex>` in `<list_attribute>`
2262+ show {domain|user|list} where <filter2> and <filter2> ..."""
2263+ from mailmanclient.cli.client.parsers.show import Show
2264+ parser = Show()
2265+ arguments = parser.parse(self)
2266+ scope = arguments['scope']
2267+ filtered_list = self.scope_listing[scope]
2268+ if 'filters' in arguments:
2269+ for i in arguments['filters']:
2270+ key, op, value = i
2271+ value = utils.add_shell_vars(value, self)
2272+ data_filter = Filter()
2273+ filtered_list = data_filter.get_results(key, value, op, filtered_list)
2274+ if not filtered_list:
2275+ return False
2276+ scope_object = self.scope_classes[scope](self.mmclient)
2277+ cmd_arguments = {}
2278+ if scope == 'list':
2279+ cmd_arguments['list'] = None
2280+ cmd_arguments['csv'] = None
2281+ cmd_arguments['domain'] = None
2282+ cmd_arguments['verbose'] = True
2283+ cmd_arguments['no_header'] = False
2284+ elif scope == 'domain':
2285+ cmd_arguments['domain'] = None
2286+ cmd_arguments['csv'] = None
2287+ cmd_arguments['verbose'] = True
2288+ cmd_arguments['no_header'] = False
2289+ elif scope == 'user':
2290+ cmd_arguments['user'] = None
2291+ cmd_arguments['csv'] = None
2292+ cmd_arguments['list_name'] = None
2293+ cmd_arguments['verbose'] = True
2294+ cmd_arguments['no_header'] = False
2295+ scope_object.show(cmd_arguments, filtered_list)
2296+
2297+ def do_create(self, args):
2298+ """ Creates mailman Objects
2299+ Usage:
2300+ create user with `email`=`EMAIL` and `password`=`PASSWD` and `name`=`NAME`
2301+ create domain with `name`=`DOMAIN` and `contact`=`CONTACT`
2302+ create list with `fqdn_listname`=`LIST`"""
2303+ from mailmanclient.cli.client.parsers.create import Create
2304+ parser = Create()
2305+ arguments = parser.parse(self)
2306+ scope = arguments['scope']
2307+ properties = arguments['properties']
2308+ scope_object = self.scope_classes[scope](self.mmclient)
2309+ cmd_arguments = {}
2310+ req_args = []
2311+ try:
2312+ if scope == 'list':
2313+ req_args = ['fqdn_listname']
2314+ cmd_arguments['list'] = utils.add_shell_vars(properties['fqdn_listname'], self)
2315+ elif scope == 'domain':
2316+ req_args = ['name', 'contact']
2317+ cmd_arguments['domain'] = utils.add_shell_vars(properties['name'], self)
2318+ cmd_arguments['contact'] = utils.add_shell_vars(properties['contact'], self)
2319+ elif scope == 'user':
2320+ req_args = ['email', 'password', 'name']
2321+ cmd_arguments['email'] = utils.add_shell_vars(properties['email'], self)
2322+ cmd_arguments['password'] = utils.add_shell_vars(properties['password'], self)
2323+ cmd_arguments['name'] = utils.add_shell_vars(properties['name'], self)
2324+ except KeyError:
2325+ utils.error('Invalid arguments')
2326+ utils.warn('The required arguments are:')
2327+ for i in req_args:
2328+ utils.warn('\t' + i)
2329+ return False
2330+ scope_object.create(cmd_arguments)
2331+ self.refresh_lists()
2332+
2333+ def do_delete(self, args):
2334+ """ Delete specified mailman objects
2335+ Usage:
2336+ delete {domain|user|list} where `<object_attribute>` = `<value>`
2337+ delete {domain|user|list} where `<object_attribute>` like `<regex>`
2338+ delete {domain|user|list} where `<key>` in `<list_attribute>`
2339+ delete {domain|user|list} where <filter1> and <filter2> ..."""
2340+ from mailmanclient.cli.client.parsers.delete import Delete
2341+ parser = Delete()
2342+ arguments = parser.parse(self)
2343+ scope = arguments['scope']
2344+ filtered_list = self.scope_listing[scope]
2345+ if 'filters' in arguments:
2346+ for i in arguments['filters']:
2347+ key, op, value = i
2348+ value = utils.add_shell_vars(value, self)
2349+ data_filter = Filter()
2350+ filtered_list = data_filter.get_results(key, value, op, filtered_list)
2351+ if 'filters' in arguments and arguments['filters'] == []:
2352+ utils.confirm('Delete all %ss?[y/n]' % scope)
2353+ ans = input()
2354+ if ans == 'n':
2355+ return False
2356+ for i in filtered_list:
2357+ if scope == 'list':
2358+ utils.warn('Deleted ' + i.fqdn_listname)
2359+ i.delete()
2360+ elif scope == 'domain':
2361+ utils.warn('Deleted ' + i.base_url)
2362+ self.mmclient.delete_domain(i.mail_host)
2363+ elif scope == 'user':
2364+ utils.warn('Deleted ' + i.display_name)
2365+ i.delete()
2366+ self.refresh_lists()
2367+
2368+ def do_subscribe(self, args):
2369+ """ Subscribes users to a list
2370+ Usage:
2371+ subscribe users `<email1>` `<email2>` ... to `<list fqdn_name>`"""
2372+ from mailmanclient.cli.client.parsers.subscribe import Subscribe
2373+ parser = Subscribe()
2374+ arguments = parser.parse(self)
2375+ users = arguments['users']
2376+ cleaned_list = []
2377+ for i in users:
2378+ cleaned_list.append(utils.add_shell_vars(i, self))
2379+ users = cleaned_list
2380+ list_name = utils.add_shell_vars(arguments['list'], self)
2381+ user_object = self.scope_classes['user'](self.mmclient)
2382+ cmd_arguments = {}
2383+ cmd_arguments['users'] = users
2384+ cmd_arguments['list_name'] = list_name
2385+ cmd_arguments['quiet'] = False
2386+ user_object.subscribe(cmd_arguments)
2387+ self.refresh_lists()
2388+
2389+ def do_unsubscribe(self, args):
2390+ """ Unsubscribes users from a list
2391+ Usage:
2392+ unsubscribe users `<email1>` [`<email2>` ...] from `<list fqdn_name>`
2393+ """
2394+ from mailmanclient.cli.client.parsers.unsubscribe import Unsubscribe
2395+ parser = Unsubscribe()
2396+ arguments = parser.parse(self)
2397+ users = arguments['users']
2398+ cleaned_list = []
2399+ for i in users:
2400+ cleaned_list.append(utils.add_shell_vars(i, self))
2401+ users = cleaned_list
2402+ list_name = utils.add_shell_vars(arguments['list'], self)
2403+ user_object = self.scope_classes['user'](self.mmclient)
2404+ cmd_arguments = {}
2405+ cmd_arguments['users'] = users
2406+ cmd_arguments['list_name'] = list_name
2407+ cmd_arguments['quiet'] = False
2408+ user_object.unsubscribe(cmd_arguments)
2409+ self.refresh_lists()
2410+
2411+ def do_update(self, args):
2412+ """ Command to set preferences
2413+ Usage:
2414+
2415+ update preference `<preference_name>` to `<value>` for member with
2416+ `email` = `foo@bar.com`
2417+ and `list` = `list@domain.org`
2418+
2419+ update preference `<preference_name>` to `<value>`
2420+ for user with `email` = `foo@bar.com`
2421+
2422+ update preference `<preference_name>` to `<value>`
2423+ for address with `email` = `foo@bar.com`"""
2424+ from mailmanclient.cli.client.parsers.update import Update
2425+ parser = Update()
2426+ arguments = parser.parse(self)
2427+ scope = arguments['scope']
2428+ preferences = self.scope_classes['preference'](self.mmclient)
2429+ cmd_arguments = {}
2430+ cmd_arguments['key'] = arguments['key']
2431+ cmd_arguments['value'] = arguments['value']
2432+ if scope == 'globally':
2433+ cmd_arguments['update_scope'] = 'global'
2434+ else:
2435+ cmd_arguments['update_scope'] = scope
2436+ for i in arguments['filters']:
2437+ cmd_arguments[i] = utils.add_shell_vars(arguments['filters'][i], self)
2438+ preferences.update(cmd_arguments)
2439+
2440+ def _complete(self, text, keys):
2441+ """ Method for computing the auto suggest suggestions
2442+ """
2443+ if not text:
2444+ completions = keys
2445+ else:
2446+ completions = [k
2447+ for k in keys
2448+ if k.startswith(text)
2449+ ]
2450+ return completions
2451+
2452+ def complete_set(self, text, line, begidx, endidx):
2453+ keys = self.env.keys()
2454+ keys.extend(self.scopes)
2455+ return self._complete(text, keys)
2456+
2457+ def complete_unset(self, text, line, begidx, endidx):
2458+ return self._complete(text, self.env.keys())
2459+
2460+ def complete_show_var(self, text, line, begidx, endidx):
2461+ return self._complete(text, self.env.keys())
2462+
2463+ def complete_delete(self, text, line, begidx, endidx):
2464+ return self._complete(text, self.scopes)
2465+
2466+ def complete_create(self, text, line, begidx, endidx):
2467+ return self._complete(text, self.scopes)
2468+
2469+ def complete_show(self, text, line, begidx, endidx):
2470+ return self._complete(text, self.scopes)
2471+
2472+ def complete_subscribe(self, text, line, begidx, endidx):
2473+ return self._complete(text, ['user'])
2474+
2475+ def complete_unsubscribe(self, text, line, begidx, endidx):
2476+ return self._complete(text, ['user'])
2477+
2478+ def complete_disable(self, text, line, begidx, endidx):
2479+ disable_list = ['env']
2480+ return self._complete(text, disable_list)
2481+
2482+ def complete_enable(self, text, line, begidx, endidx):
2483+ enable_list = ['env']
2484+ return self._complete(text, enable_list)
2485+
2486+ def complete_update(self, text, line, begidx, endidx):
2487+ _list = ['preference']
2488+ return self._complete(text, _list)
2489
2490=== added directory 'src/mailmanclient/cli/core'
2491=== added file 'src/mailmanclient/cli/core/__init__.py'
2492=== added file 'src/mailmanclient/cli/core/domains.py'
2493--- src/mailmanclient/cli/core/domains.py 1970-01-01 00:00:00 +0000
2494+++ src/mailmanclient/cli/core/domains.py 2015-03-17 19:08:34 +0000
2495@@ -0,0 +1,136 @@
2496+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
2497+#
2498+# This file is part of mailman.client.
2499+#
2500+# mailman.client is free software: you can redistribute it and/or modify it
2501+# under the terms of the GNU Lesser General Public License as published by the
2502+# Free Software Foundation, version 3 of the License.
2503+#
2504+# mailman.client is distributed in the hope that it will be useful, but
2505+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
2506+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
2507+# License for more details.
2508+#
2509+# You should have received a copy of the GNU Lesser General Public License
2510+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
2511+#
2512+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
2513+#
2514+# Author : Rajeev S <rajeevs1992@gmail.com>
2515+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
2516+# Abhilash Raj <raj.abhilash1@gmail.com>
2517+# Barry Warsaw <barry@list.org>
2518+
2519+from tabulate import tabulate
2520+from six.moves.urllib_error import HTTPError
2521+from mailmanclient.cli.lib.utils import Utils
2522+
2523+
2524+utils = Utils()
2525+
2526+
2527+class DomainException(Exception):
2528+ """ Exception on invalid domain """
2529+ pass
2530+
2531+
2532+class Domains():
2533+ """Domain related actions."""
2534+
2535+ def __init__(self, client):
2536+ self.client = client
2537+
2538+ def create(self, args):
2539+ """Create a domain name with specified domain_name.
2540+ Optionally, the contact address can also be specified.
2541+
2542+ :param args: Commandline arguments
2543+ :type args: dictionary
2544+ """
2545+ domain_name = args['domain']
2546+ contact_address = args['contact']
2547+
2548+ try:
2549+ self.client.create_domain(domain_name,
2550+ contact_address=contact_address)
2551+ except HTTPError as e:
2552+ code = e.getcode()
2553+ if code == 400:
2554+ raise DomainException('Domain already exists')
2555+ else:
2556+ raise DomainException('An unknown HTTPError has occured')
2557+
2558+ def show(self, args, domains_ext=None):
2559+ """List the domains in the system.
2560+
2561+ :param args: Commandline arguments
2562+ :type args: dictionary
2563+ """
2564+ if args['domain'] is not None:
2565+ self.describe(args)
2566+ return
2567+
2568+ headers = []
2569+ fields = ['base_url']
2570+ domains = []
2571+
2572+ if domains_ext:
2573+ domains = domains_ext
2574+ else:
2575+ domains = self.client.domains
2576+
2577+ if not args['no_header'] and args['verbose']:
2578+ headers = ['Base URL', 'Contact address',
2579+ 'Mail host', 'URL host']
2580+
2581+ if args['verbose']:
2582+ fields = ['base_url', 'contact_address', 'mail_host', 'url_host']
2583+
2584+ table = utils.get_listing(domains, fields)
2585+
2586+ if args['csv']:
2587+ utils.write_csv(table, headers, args['csv'])
2588+ else:
2589+ print(tabulate(table, headers=headers, tablefmt='plain'))
2590+
2591+ def describe(self, args):
2592+ try:
2593+ domain = self.client.get_domain(args['domain'])
2594+ except HTTPError as e:
2595+ code = e.getcode()
2596+ if code == 404:
2597+ raise DomainException('Domain not found')
2598+ else:
2599+ raise DomainException('An unknown HTTPError has occured')
2600+ table = []
2601+ table.append(['Base URL', domain.base_url])
2602+ table.append(['Contact Address', domain.contact_address])
2603+ table.append(['Mail Host', domain.mail_host])
2604+ table.append(['URL Host', domain.url_host])
2605+ utils.set_table_section_heading(table, 'Description')
2606+ table.append([domain.description, ''])
2607+ utils.set_table_section_heading(table, 'Lists')
2608+ for _list in domain.lists:
2609+ table.append([_list.list_id, ''])
2610+ print(tabulate(table, tablefmt='plain'))
2611+
2612+ def delete(self, args):
2613+ try:
2614+ domain = self.client.get_domain(args['domain'])
2615+ except HTTPError as e:
2616+ code = e.getcode()
2617+ if code == 404:
2618+ raise DomainException('Domain not found')
2619+ else:
2620+ raise DomainException('An unknown HTTPError has occured')
2621+ if not args['yes']:
2622+ utils.confirm('Domain `%s` has %d lists.Delete?[y/n]'
2623+ % (args['domain'], len(domain.lists)))
2624+ confirm = input()
2625+ if confirm == 'y':
2626+ args['yes'] = True
2627+ elif confirm == 'n':
2628+ return
2629+ else:
2630+ raise Exception('Invalid answer')
2631+ self.client.delete_domain(args['domain'])
2632
2633=== added file 'src/mailmanclient/cli/core/lists.py'
2634--- src/mailmanclient/cli/core/lists.py 1970-01-01 00:00:00 +0000
2635+++ src/mailmanclient/cli/core/lists.py 2015-03-17 19:08:34 +0000
2636@@ -0,0 +1,227 @@
2637+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
2638+#
2639+# This file is part of mailman.client.
2640+#
2641+# mailman.client is free software: you can redistribute it and/or modify it
2642+# under the terms of the GNU Lesser General Public License as published by the
2643+# Free Software Foundation, version 3 of the License.
2644+#
2645+# mailman.client is distributed in the hope that it will be useful, but
2646+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
2647+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
2648+# License for more details.
2649+#
2650+# You should have received a copy of the GNU Lesser General Public License
2651+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
2652+#
2653+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
2654+#
2655+# Author : Rajeev S <rajeevs1992@gmail.com>
2656+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
2657+# Abhilash Raj <raj.abhilash1@gmail.com>
2658+# Barry Warsaw <barry@list.org>
2659+
2660+from tabulate import tabulate
2661+from six.moves.urllib_error import HTTPError
2662+from mailmanclient.cli.lib.utils import Utils
2663+from mailmanclient.cli.core.domains import DomainException
2664+
2665+
2666+utils = Utils()
2667+
2668+
2669+class ListException(Exception):
2670+ """ List Exceptions """
2671+ pass
2672+
2673+
2674+class Lists():
2675+
2676+ """Mailing list related actions."""
2677+
2678+ def __init__(self, client):
2679+ self. client = client
2680+
2681+ def create(self, args):
2682+ """Create a mailing list with specified list_name
2683+ in the domain specified by domain_name.
2684+
2685+ :param args: Commandline arguments
2686+ :type args: dictionary
2687+ """
2688+ name = args['list'].split('@')
2689+
2690+ try:
2691+ list_name = name[0]
2692+ domain_name = name[1]
2693+ except IndexError:
2694+ raise ListException('Invalid FQDN list name')
2695+
2696+ if list_name.strip() == '' or domain_name.strip() == '':
2697+ raise ListException('Invalid FQDN list name')
2698+
2699+ domain = self.get_domain(domain_name)
2700+
2701+ try:
2702+ domain.create_list(list_name)
2703+ except HTTPError as e:
2704+ code = e.getcode()
2705+ if code == 400:
2706+ raise ListException('List already exists')
2707+ else:
2708+ raise Exception('An unknown HTTPError has occoured')
2709+
2710+ def show(self, args, lists_ext=None):
2711+ """List the mailing lists in the system or under a domain.
2712+
2713+ :param args: Commandline arguments
2714+ :type args: dictionary
2715+ """
2716+ if args['list'] is not None:
2717+ self.describe(args)
2718+ return
2719+
2720+ lists = []
2721+ fields = ['list_id']
2722+ headers = []
2723+
2724+ if args['domain']:
2725+ domain = self.get_domain(args['domain'])
2726+ lists = domain.lists
2727+ elif lists_ext:
2728+ lists = lists_ext
2729+ else:
2730+ lists = self.client.lists
2731+
2732+ if args['verbose']:
2733+ fields = ['list_id', 'list_name',
2734+ 'mail_host', 'display_name',
2735+ 'fqdn_listname']
2736+
2737+ if not args['no_header'] and args['verbose']:
2738+ headers = ['ID', 'Name', 'Mail host', 'Display Name', 'FQDN']
2739+
2740+ table = utils.get_listing(lists, fields)
2741+
2742+ if args['csv']:
2743+ utils.write_csv(table, headers, args['csv'])
2744+ else:
2745+ print(tabulate(table, headers=headers, tablefmt='plain'))
2746+
2747+ def describe(self, args):
2748+ _list = self.get_list(args['list'])
2749+ table = []
2750+ table.append(['List ID', _list.list_id])
2751+ table.append(['List name', _list.list_name])
2752+ table.append(['Mail host', _list.mail_host])
2753+ utils.set_table_section_heading(table, 'List Settings')
2754+ for i in _list.settings:
2755+ table.append([i, str(_list.settings[i])])
2756+ utils.set_table_section_heading(table, 'Owners')
2757+ for owner in _list.owners:
2758+ table.append([owner, ''])
2759+ utils.set_table_section_heading(table, 'Moderators')
2760+ for moderator in _list.moderators:
2761+ table.append([moderator, ''])
2762+ utils.set_table_section_heading(table, 'Members')
2763+ for member in _list.members:
2764+ email = member.address.split('/')[-1]
2765+ table.append([email, ''])
2766+ print(tabulate(table, tablefmt='plain'))
2767+
2768+ def add_moderator(self, args):
2769+ _list = self.get_list(args['list'])
2770+ users = args['users']
2771+ quiet = args['quiet']
2772+ for user in users:
2773+ try:
2774+ _list.add_moderator(user)
2775+ if not quiet:
2776+ utils.warn('Added %s as moderator' % (user))
2777+ except Exception as e:
2778+ if not quiet:
2779+ utils.error('Failed to add %s : %s ' %
2780+ (user, e))
2781+
2782+ def add_owner(self, args):
2783+ _list = self.get_list(args['list'])
2784+ users = args['users']
2785+ quiet = args['quiet']
2786+ for user in users:
2787+ try:
2788+ _list.add_owner(user)
2789+ if not quiet:
2790+ utils.warn('Added %s as owner' % (user))
2791+ except Exception as e:
2792+ if not quiet:
2793+ utils.error('Failed to add %s : %s ' %
2794+ (user, e))
2795+
2796+ def remove_moderator(self, args):
2797+ _list = self.get_list(args['list'])
2798+ users = args['users']
2799+ quiet = args['quiet']
2800+ for user in users:
2801+ try:
2802+ _list.remove_moderator(user)
2803+ if not quiet:
2804+ utils.warn('Removed %s as moderator' % (user))
2805+ except Exception as e:
2806+ if not quiet:
2807+ utils.error('Failed to remove %s : %s ' %
2808+ (user, e))
2809+
2810+ def remove_owner(self, args):
2811+ _list = self.get_list(args['list'])
2812+ users = args['users']
2813+ quiet = args['quiet']
2814+ for user in users:
2815+ try:
2816+ _list.remove_owner(user)
2817+ if not quiet:
2818+ utils.warn('Removed %s as owner' % (user))
2819+ except Exception as e:
2820+ if not quiet:
2821+ utils.error('Failed to remove %s : %s ' %
2822+ (user, e))
2823+
2824+ def show_members(self, args):
2825+ from core.users import Users
2826+ users = Users(self.client)
2827+ args['list_name'] = args['list']
2828+ args['user'] = None
2829+ users.show(args)
2830+
2831+ def delete(self, args):
2832+ _list = self.get_list(args['list'])
2833+ if not args['yes']:
2834+ utils.confirm('List %s has %d members.Delete?[y/n]'
2835+ % (args['list'], len(_list.members)))
2836+ confirm = input()
2837+ if confirm == 'y':
2838+ args['yes'] = True
2839+ elif confirm == 'n':
2840+ return
2841+ else:
2842+ raise Exception('Invalid Answer')
2843+ _list.delete()
2844+
2845+ def get_list(self, listname):
2846+ try:
2847+ return self.client.get_list(listname)
2848+ except HTTPError as e:
2849+ code = e.getcode()
2850+ if code == 404:
2851+ raise ListException('List not found')
2852+ else:
2853+ raise Exception('An unknown HTTPError has occoured')
2854+
2855+ def get_domain(self, domainname):
2856+ try:
2857+ return self.client.get_domain(domainname)
2858+ except HTTPError as e:
2859+ code = e.getcode()
2860+ if code == 404:
2861+ raise DomainException('Domain not found')
2862+ else:
2863+ raise Exception('An unknown HTTPError has occoured')
2864
2865=== added file 'src/mailmanclient/cli/core/misc.py'
2866--- src/mailmanclient/cli/core/misc.py 1970-01-01 00:00:00 +0000
2867+++ src/mailmanclient/cli/core/misc.py 2015-03-17 19:08:34 +0000
2868@@ -0,0 +1,69 @@
2869+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
2870+#
2871+# This file is part of mailman.client.
2872+#
2873+# mailman.client is free software: you can redistribute it and/or modify it
2874+# under the terms of the GNU Lesser General Public License as published by the
2875+# Free Software Foundation, version 3 of the License.
2876+#
2877+# mailman.client is distributed in the hope that it will be useful, but
2878+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
2879+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
2880+# License for more details.
2881+#
2882+# You should have received a copy of the GNU Lesser General Public License
2883+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
2884+#
2885+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
2886+#
2887+# Author : Rajeev S <rajeevs1992@gmail.com>
2888+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
2889+# Abhilash Raj <raj.abhilash1@gmail.com>
2890+# Barry Warsaw <barry@list.org>
2891+
2892+import os
2893+import zipfile
2894+from mailman.config import config
2895+from mailmanclient.cli.lib.utils import Utils
2896+
2897+
2898+utils = Utils()
2899+
2900+
2901+class MiscException(Exception):
2902+ """ Exceptions for miscellaneous actions """
2903+ pass
2904+
2905+
2906+class Misc():
2907+ """ Miscellaneous actions """
2908+
2909+ def backup(self, args):
2910+ config.load()
2911+ vardir = config.paths['VAR_DIR']
2912+ output = args['output']
2913+ if not output.endswith('.zip'):
2914+ output += '.zip'
2915+ relroot = os.path.abspath(os.path.join(vardir, os.pardir))
2916+ with zipfile.ZipFile(output, "w", zipfile.ZIP_DEFLATED) as z:
2917+ for root, dirs, files in os.walk(vardir):
2918+ z.write(root, os.path.relpath(root, relroot))
2919+ for f in files:
2920+ filename = os.path.join(root, f)
2921+ if os.path.isfile(filename):
2922+ arcname = os.path.join(os.path.relpath(root, relroot),
2923+ f)
2924+ z.write(filename, arcname)
2925+
2926+ def restore(self, args):
2927+ config.load()
2928+ vardir = config.paths['VAR_DIR']
2929+ backup = args['backup']
2930+ if os.path.exists(vardir):
2931+ utils.confirm('The existing var_dir will be replaced.Continue?[y/n]')
2932+ confirm = input()
2933+ if not confirm == 'y':
2934+ return
2935+ vardir += '/../'
2936+ with zipfile.ZipFile(backup) as zf:
2937+ zf.extractall(vardir)
2938
2939=== added file 'src/mailmanclient/cli/core/preferences.py'
2940--- src/mailmanclient/cli/core/preferences.py 1970-01-01 00:00:00 +0000
2941+++ src/mailmanclient/cli/core/preferences.py 2015-03-17 19:08:34 +0000
2942@@ -0,0 +1,96 @@
2943+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
2944+#
2945+# This file is part of mailman.client.
2946+#
2947+# mailman.client is free software: you can redistribute it and/or modify it
2948+# under the terms of the GNU Lesser General Public License as published by the
2949+# Free Software Foundation, version 3 of the License.
2950+#
2951+# mailman.client is distributed in the hope that it will be useful, but
2952+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
2953+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
2954+# License for more details.
2955+#
2956+# You should have received a copy of the GNU Lesser General Public License
2957+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
2958+#
2959+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
2960+#
2961+# Author : Rajeev S <rajeevs1992@gmail.com>
2962+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
2963+# Abhilash Raj <raj.abhilash1@gmail.com>
2964+# Barry Warsaw <barry@list.org>
2965+
2966+from mailmanclient.cli.lib.utils import Utils
2967+
2968+
2969+utils = Utils()
2970+
2971+
2972+class PreferenceException(Exception):
2973+ pass
2974+
2975+
2976+class Preferences():
2977+ """Preferences related actions."""
2978+
2979+ def __init__(self, client):
2980+ self.client = client
2981+
2982+ def get_scope_object(self, scope, args):
2983+ scope_object = None
2984+ try:
2985+ if scope == 'global':
2986+ scope_object = self.client
2987+ elif scope == 'user':
2988+ scope_object = self.client.get_user(args['email'])
2989+ elif scope == 'member':
2990+ scope_object = self.client.get_member(args['list'],
2991+ args['email'])
2992+ else:
2993+ scope_object = self.client.get_address(args['email'])
2994+ except:
2995+ raise PreferenceException('%s not found' % scope.capitalize())
2996+ return scope_object
2997+
2998+ def update(self, args):
2999+ """Update a preference specified by the `key` to `value`
3000+ Preferences can be set at a global, user, address or at
3001+ a member level.
3002+ """
3003+ scope = args['update_scope']
3004+ if scope == 'global':
3005+ raise PreferenceException('Global preferences are readonly')
3006+ scope_object = self.get_scope_object(scope, args)
3007+ preferences = None
3008+ key = args['key']
3009+ value = args['value']
3010+ preferences = scope_object.preferences
3011+ try:
3012+ preferences[key]
3013+ except Exception:
3014+ raise PreferenceException('Saving Preference Failed')
3015+ if type(preferences[key]).__name__ in ('bool', 'NoneType'):
3016+ value = value.lower().strip()
3017+ if value == 'true':
3018+ value = True
3019+ elif value == 'false':
3020+ value = False
3021+ else:
3022+ raise PreferenceException('Invalid value for preference.'
3023+ 'Expected values : True/False')
3024+ try:
3025+ preferences[key] = value
3026+ preferences.save()
3027+ except Exception:
3028+ raise PreferenceException('Saving Preference Failed')
3029+
3030+ def show(self, args):
3031+ """Given a preference key, and a specific object, print
3032+ the current value of the preference for that object."""
3033+ scope = args['show_scope']
3034+ scope_object = self.get_scope_object(scope, args)
3035+ preferences = None
3036+ key = args['key']
3037+ preferences = scope_object.preferences
3038+ print(str(preferences[key]))
3039
3040=== added file 'src/mailmanclient/cli/core/users.py'
3041--- src/mailmanclient/cli/core/users.py 1970-01-01 00:00:00 +0000
3042+++ src/mailmanclient/cli/core/users.py 2015-03-17 19:08:34 +0000
3043@@ -0,0 +1,189 @@
3044+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
3045+#
3046+# This file is part of mailman.client.
3047+#
3048+# mailman.client is free software: you can redistribute it and/or modify it
3049+# under the terms of the GNU Lesser General Public License as published by the
3050+# Free Software Foundation, version 3 of the License.
3051+#
3052+# mailman.client is distributed in the hope that it will be useful, but
3053+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
3054+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
3055+# License for more details.
3056+#
3057+# You should have received a copy of the GNU Lesser General Public License
3058+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
3059+#
3060+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
3061+#
3062+# Author : Rajeev S <rajeevs1992@gmail.com>
3063+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
3064+# Abhilash Raj <raj.abhilash1@gmail.com>
3065+# Barry Warsaw <barry@list.org>
3066+
3067+from tabulate import tabulate
3068+from six.moves.urllib_error import HTTPError
3069+from mailmanclient.cli.lib.utils import Utils
3070+from mailmanclient.cli.core.lists import ListException
3071+
3072+utils = Utils()
3073+
3074+
3075+class UserException(Exception):
3076+ """ User Exceptions """
3077+ pass
3078+
3079+
3080+class Users():
3081+
3082+ """User related actions."""
3083+
3084+ def __init__(self, client):
3085+ self.client = client
3086+
3087+ def create(self, args):
3088+ """Create a user with specified email,password and display name.
3089+
3090+ :param args: Commandline arguments
3091+ :type args: dictionary
3092+ """
3093+ email = args['email']
3094+ password = args['password']
3095+ display_name = args['name']
3096+
3097+ try:
3098+ self.client.create_user(email=email,
3099+ password=password,
3100+ display_name=display_name)
3101+ except HTTPError as e:
3102+ code = e.getcode()
3103+ if code == 400:
3104+ raise UserException('User already exists')
3105+ else:
3106+ raise UserException('An unknown HTTPError has occured')
3107+
3108+ def show(self, args, users_ext=None):
3109+ """List users in the system.
3110+
3111+
3112+ :param args: Commandline arguments
3113+ :type args: dictionary
3114+ """
3115+ if args['user'] is not None:
3116+ self.describe(args)
3117+ return
3118+
3119+ headers = []
3120+ fields = ['addresses']
3121+ users = []
3122+
3123+ if args['verbose']:
3124+ fields = ['display_name', 'addresses', 'created_on', 'user_id']
3125+
3126+ if not args['no_header'] and args['verbose']:
3127+ headers = ['Display Name', 'Address', 'Created on', 'User ID']
3128+
3129+ if args['list_name']:
3130+ users = self.get_users(args['list_name'])
3131+ elif users_ext:
3132+ users = users_ext
3133+ else:
3134+ users = self.client.users
3135+
3136+ table = utils.get_listing(users, fields)
3137+
3138+ if args['csv']:
3139+ utils.write_csv(table, headers, args['csv'])
3140+ else:
3141+ print(tabulate(table, headers=headers, tablefmt='plain'))
3142+
3143+ def get_users(self, listname):
3144+ users = []
3145+ _list = self.client.get_list(listname)
3146+ for member in _list.members:
3147+ users.append(member.user)
3148+ return users
3149+
3150+ def describe(self, args):
3151+ ''' Describes a user object '''
3152+ user = self.get_user(args['user'])
3153+ table = []
3154+ table.append(['User ID', user.user_id])
3155+ table.append(['Display Name', user.display_name])
3156+ table.append(['Created on', user.created_on])
3157+ table.append(['Self Link', user.self_link])
3158+ utils.set_table_section_heading(table, 'User Preferences')
3159+ preferences = user.preferences._preferences
3160+ for i in preferences:
3161+ table.append([i, str(preferences[i])])
3162+ utils.set_table_section_heading(table, 'Subscription List IDs')
3163+ for _list in user.subscription_list_ids:
3164+ table.append([_list, ''])
3165+ utils.set_table_section_heading(table, 'Subscriptions')
3166+ for subscription in user.subscriptions:
3167+ email = subscription.address.split('/')[-1]
3168+ table.append([email+' at '+str(subscription.list_id),
3169+ str(subscription.role)])
3170+ print(tabulate(table, tablefmt='plain'))
3171+
3172+ def delete(self, args):
3173+ ''' Deletes a User object '''
3174+ user = self.client.get_user(args['user'])
3175+ if not args['yes']:
3176+ utils.confirm('Delete user %s?[y/n]' % args['user'])
3177+ confirm = input()
3178+ if confirm == 'y':
3179+ args['yes'] = True
3180+ elif confirm == 'n':
3181+ return
3182+ else:
3183+ raise Exception('Invalid answer')
3184+ user.delete()
3185+
3186+ def subscribe(self, args):
3187+ ''' Subsribes a user or a list of users to a list '''
3188+ list_name = args['list_name']
3189+ emails = args['users']
3190+ _list = self.client.get_list(list_name)
3191+ for i in emails:
3192+ try:
3193+ _list.subscribe(i)
3194+ if not args['quiet']:
3195+ utils.warn('%s subscribed to %s' % (i, list_name))
3196+ except Exception as e:
3197+ if not args['quiet']:
3198+ utils.error('Failed to subscribe %s : %s' % (i, e))
3199+
3200+ def unsubscribe(self, args):
3201+ ''' Unsubsribes a user or a list of users from a list '''
3202+ list_name = args['list_name']
3203+ emails = args['users']
3204+ _list = self.client.get_list(list_name)
3205+ for i in emails:
3206+ try:
3207+ _list.unsubscribe(i)
3208+ if not args['quiet']:
3209+ utils.warn('%s unsubscribed from %s' % (i, list_name))
3210+ except Exception as e:
3211+ if not args['quiet']:
3212+ utils.error('Failed to unsubscribe %s : %s' % (i, e))
3213+
3214+ def get_list(self, listname):
3215+ try:
3216+ return self.client.get_list(listname)
3217+ except HTTPError as e:
3218+ code = e.getcode()
3219+ if code == 404:
3220+ raise ListException('List not found')
3221+ else:
3222+ raise ListException('An unknown HTTPError has occured')
3223+
3224+ def get_user(self, username):
3225+ try:
3226+ return self.client.get_user(username)
3227+ except HTTPError as e:
3228+ code = e.getcode()
3229+ if code == 404:
3230+ raise UserException('User not found')
3231+ else:
3232+ raise UserException('An unknown HTTPError has occured')
3233
3234=== added directory 'src/mailmanclient/cli/docs'
3235=== added file 'src/mailmanclient/cli/docs/using_cli_shell.txt'
3236--- src/mailmanclient/cli/docs/using_cli_shell.txt 1970-01-01 00:00:00 +0000
3237+++ src/mailmanclient/cli/docs/using_cli_shell.txt 2015-03-17 19:08:34 +0000
3238@@ -0,0 +1,150 @@
3239+The Mailman Command Line Shell
3240+******************************
3241+
3242+This document describes the usage of the Mailman Command line
3243+shell, using which you can query a mailman installation with ease.
3244+
3245+Firing and Exiting the Shell
3246+============================
3247+
3248+You can start the mailman shell by executing the mmclient command,
3249+without any arguments::
3250+
3251+ $ ./mmclient
3252+ Mailman Command Line Interface
3253+ >>>
3254+
3255+To exit the shell, use the ``EOF`` character, that is, ``Ctrl + d``.
3256+
3257+Displaying Mailman Objects
3258+==========================
3259+
3260+The shell can be used to display the mailman objects, using the show
3261+command.
3262+
3263+For example::
3264+
3265+ >>> show users
3266+ >>> show domains
3267+ >>> show lists
3268+
3269+Further, the CLI supports filtering of mailman objects based upon their
3270+attribute values or properties, using a `where` clause. For this, the CLI
3271+employs 3 filters, which are::
3272+
3273+ = Equality
3274+ like Case insensitive regular exp mathcing
3275+ in Search inside a property which list
3276+
3277+These filteres can be used in conjunction by using an ``and`` clause
3278+
3279+Examples: ::
3280+
3281+ >>> show users where `display_name` = `Foo`
3282+ >>> show users where `display_name` like `.*Foo*`
3283+ >>> show lists where `foo@bar.com` in `moderators`
3284+ >>> show lists where `foo@bar.com` in `moderators` and `a@b.com` in `owners`
3285+
3286+The Shell Environment
3287+======================
3288+
3289+The shell provides a facility to create variables that can be used to
3290+make the querying easier.
3291+
3292+For using the shell, two commands, ``set`` and ``unset`` are used.
3293+
3294+Example::
3295+
3296+ >>> set `useremail` = `foo@bar.com`
3297+
3298+The variables can be used in the queries as follows::
3299+
3300+ >>> show lists where `$useremail` in `moderators`
3301+
3302+The ``$username`` will be replaced with the value of ``useremail``
3303+
3304+The environment can be disabled using the `disable environemt`
3305+command, that prevents the CLI from replacing the query terms
3306+with environment variables, or appending of the specialised
3307+variables.
3308+
3309+The disabled environment can be enabled using the `enable env`
3310+command.::
3311+
3312+ >>> disable env
3313+ >>> enable env
3314+
3315+The environment supports a set of special variables, which denote
3316+the names of the scopes available in mailman. They are domain, list and
3317+user.
3318+
3319+The special environment variables are appended automatically with relevant commands
3320+
3321+For example, if the environment variable domain is set to a domain name, then the
3322+`show list ` command automatically applies a domain = <set domain> filter to
3323+the result list.::
3324+
3325+ >>> set `domain` = `domain.org`
3326+ >>> show lists //Shows lists under domain.org
3327+ >>> disable env
3328+ >>> show lists //Shows all lists
3329+ >>> enable env
3330+
3331+The value of stored variables can be viewed using the show_var command::
3332+
3333+ >>> show_var `domain`
3334+
3335+Create Objects
3336+==============
3337+
3338+The Mailman objects can be created using the `create` command
3339+
3340+The create command accepts the object properties and creates
3341+the object.
3342+
3343+If the supplied arguments are invalid or insufficient, the list
3344+of arguments that are required are displayed.
3345+
3346+The create command can be used as follows::
3347+
3348+ >>> create list where `fqdn_listname` = `list@domain.org`
3349+ >>> create domain where `domain` = `domain.org` and `contact` = `a@b.com`
3350+ >>> create user where `email` = `foo@bar.com` and `password` = `a` and `name` = `Foo`
3351+
3352+Delete Objects
3353+==============
3354+
3355+The Mailman objects can be deleted using the delete command. The
3356+delete command supports the same filters as those by the show command.
3357+
3358+For example::
3359+
3360+ >>> delete domain where `domain` like `test_.*`
3361+
3362+Subscription
3363+============
3364+
3365+The subscription commands include two commands, subscribe and
3366+unsubscribe users, which are respectively used to subscribe users to a
3367+list and unsubscribe users from a list. The commands allow applying
3368+the action on a single user or multiple users at a time.::
3369+
3370+ >>> subscribe users `a@b.com` `foo@bar.com` to `list@domain.org`
3371+ >>> unsubscribe users `a@b.com` `foo@bar.com` from `list@domain.org`
3372+
3373+Update Preferences
3374+==================
3375+
3376+Preferences can be updated using the shell for the following domains
3377+ - Users
3378+ - Members
3379+ - Addresses
3380+
3381+The actions are performed using the update command which can be used as follows::
3382+
3383+ >>> update preference `<preference_name>` to `<value>` for member with `email` = `foo@bar.com`
3384+ and `list` = `list@domain.org`
3385+ >>> update preference `<preference_name>` to `<value>` for user with `email` = `foo@bar.com`
3386+ >>> update preference `<preference_name>` to `<value>` for address with `email` = `foo@bar.com`
3387+
3388+Global preferences are readonly.
3389
3390=== added file 'src/mailmanclient/cli/docs/using_cli_tools.txt'
3391--- src/mailmanclient/cli/docs/using_cli_tools.txt 1970-01-01 00:00:00 +0000
3392+++ src/mailmanclient/cli/docs/using_cli_tools.txt 2015-03-17 19:08:34 +0000
3393@@ -0,0 +1,311 @@
3394+The Mailman Command Line Tools
3395+******************************
3396+
3397+Initialization
3398+==============
3399+
3400+The CLI can be started by running mmclient [options|arguments]
3401+
3402+If the mmclient is run without any arguments, the shell
3403+is started, else the specified action is performed. Use EOF
3404+to exit the shell.::
3405+
3406+ $> mmclient
3407+ Mailman Command Line Interface v1.0
3408+ >>>
3409+ Bye!
3410+
3411+ $> mmclient [options]
3412+
3413+If you have non-default login credentials, specify them with the
3414+following options::
3415+
3416+ --host HOSTNAME [defaults to http://127.0.0.1]
3417+ --port PORTNUMBER [defaults to 8001]
3418+ --restuser USERNAME [defaults to restadmin]
3419+ --restpass PASSWORD [defaults to restpass]
3420+
3421+Domains
3422+=======
3423+
3424+This section describes the domain related functions that can be performed
3425+with the CLI.
3426+
3427+Create a new domain
3428+-------------------
3429+
3430+To create a new domain::
3431+
3432+ $> mmclient create domain testdomain.org
3433+
3434+List Domains
3435+------------
3436+To list the domains in the system::
3437+
3438+ $> mmclient show domain
3439+ http://domain.org
3440+
3441+To obtain a detailed listing, use the `-v`/`--verbose` switch.
3442+The detailed listing of domains displays the domains as a table::
3443+
3444+ $> mmclient show domain -v
3445+ Base URL Contact address Mail host URL host
3446+ http://domain.org postmaster@domain.org domain.org domain.org
3447+
3448+In addition, the long listing has a `no-header` switch that can be used
3449+to remove the header, making it more comfortable to pipe the output.::
3450+
3451+ $> mmclient show domain -v --no-header
3452+ http://domain.org postmaster@domain.org domain.org domain.org
3453+
3454+Delete a domain
3455+---------------
3456+
3457+To delete a domian ::
3458+ $> mmclient delete domain domain.org
3459+
3460+To supress the confirmation message::
3461+
3462+ $> mmclient delete domain domain.org --yes
3463+
3464+To obtain a detailed description of a domain at one glance::
3465+
3466+ $> mmclient show domain domain.org
3467+
3468+Mailing List
3469+============
3470+
3471+This section describes the mailing list related function that can be
3472+performed with the CLI.
3473+
3474+Create a mailing list
3475+---------------------
3476+
3477+To create a mailing list::
3478+
3479+ $> mmclient create list list@domain.org
3480+
3481+Show Mailing lists
3482+-------------------
3483+
3484+Show all mailing lists in the system::
3485+
3486+ $> mmclient show list
3487+ foo.domain.org
3488+ bar.example.org
3489+
3490+To display the lists under the domain `domain.org`::
3491+
3492+ $> mmclient show list -d domain.org
3493+ foo.domain.org
3494+
3495+Further, a switch -v/--verbose can also be used to print a detailed listing::
3496+
3497+ $> mmclient show list --verbose
3498+ ID Name Mail host Display Name FQDN
3499+ list.domain.org list domain.org List list@domain.org
3500+
3501+Again, the `show list` supports a `--no-header` switch that removes
3502+the header line of the long listing::
3503+
3504+ $> mmclient show list --verbose --no-header
3505+ list.domain.org list domain.org List list@domain.org
3506+
3507+Delete Mailing list
3508+-------------------
3509+
3510+To delete a list::
3511+
3512+ $> mmclient delete list list@domain.org
3513+
3514+To supress the confirmation message, do::
3515+
3516+ $> mmclient delete list list@domain.org --yes
3517+
3518+To obtain a detailed description of a list at one glance::
3519+
3520+ $> mmclient show list list@domain.org
3521+
3522+Manage list owners, members and moderators::
3523+---------------------------------
3524+
3525+Adding and removing moderators can be performed using the CLI as follows
3526+
3527+To get a list of members of a mailing list::
3528+
3529+ $> mmclient show-members list list@domain.org
3530+
3531+The above command is equivalent to::
3532+
3533+ $> mmclient show user --list list@domain.com
3534+
3535+The command also supports flags like::
3536+
3537+ --verbose Show a detailed listing
3538+ --no-header Hide header in detailed listing
3539+
3540+Refer the `show user` command for more details
3541+
3542+Add or remove list owners and moderators
3543+-----------------------------------
3544+
3545+To add moderators or owners::
3546+
3547+ $> mmclient add-moderator list list@domain.org --user a@b.com b@c.com
3548+ $> mmclient add-owner list list@domain.org --user a@b.com b@c.com
3549+
3550+And to remove moderators or owners::
3551+
3552+ $> mmclient remove-moderator list list@domain.org --user a@b.com b@c.com
3553+ $> mmclient remove-owner list list@domain.org --user a@b.com b@c.com
3554+
3555+The add and remove commands support the action on a list of users
3556+at once. Success or failure messages are provided upon completion or
3557+failure of an action. The messages can be supressed by using a quiet flag::
3558+
3559+ $> mmclient remove-moderator list list@domain.org --user a@b.com b@c.com --quiet
3560+
3561+If the action fails for a user, the user is ignored and the action continues
3562+without stalling.
3563+
3564+User
3565+====
3566+
3567+Craete User
3568+-----------
3569+
3570+To create a new user::
3571+
3572+ $> mmclient create user foo@bar.com --name "Foo" --password "password"
3573+
3574+The `show user` command lists a single email ID of each user::
3575+
3576+ $> mmclient show user
3577+ foo@bar.com
3578+
3579+To list users who are members of a list::
3580+
3581+ $> mmclient show user --list list@domain.org
3582+ foo@bar.com
3583+
3584+The show command also supports a `--verbose` switch ::
3585+
3586+ $> mmclient show user --verbose
3587+ Display Name Address Created on User ID
3588+ Foo foo2@bar.com 2014-05-30T00:52:52.564634 220337223817757552725201672981303248133
3589+
3590+and a `--no-header` switch::
3591+
3592+ $> mmclient show user --verbose --no-header
3593+ Foo foo2@bar.com 2014-05-30T00:52:52.564634 220337223817757552725201672981303248133
3594+
3595+Delete User
3596+-----------
3597+
3598+To delete a user::
3599+
3600+ $> mmclient delete user foo@bar.com
3601+
3602+To supress the confirmation message, do::
3603+
3604+ $> mmclient delete user foo@bar.com --yes
3605+
3606+Describe user
3607+-------------
3608+
3609+To obtain a detailed description of a user at one glance::
3610+
3611+ $> mmclient show user foo@bar.com
3612+
3613+Subscription and Unsubscription
3614+--------------------------------
3615+
3616+Users can be subscribed to a mailing list by using the subscribe
3617+command::
3618+
3619+ $> mmclient subscribe user user1@bar.com user2@bar.com --list list@domain.org
3620+ user1@bar.com subscribed to list@domain.org
3621+ Failed to subscribe user2@bar.com : HTTP Error 409: Member already subscribed
3622+
3623+Multiple users can be subscribed to the list at the same time.
3624+
3625+Similarly, Users can be unsubscribed to a mailing list by using the unsubscribe
3626+command::
3627+
3628+ $> mmclient unsubscribe user user1@bar.com user2@bar.com --list list@domain.org
3629+ user1@bar.com unsubscribed from list@domain.org
3630+ user2@bar.com unsubscribed from list@domain.org
3631+
3632+The feedback for the subscribe and unsubscribe actions can be supressed by
3633+using the --quiet flag::
3634+
3635+ $> mmclient subscribe user user1@bar.com user2@bar.com --list list@domain.org --quiet
3636+ $> mmclient unsubscribe user user1@bar.com user2@bar.com --list list@domain.org --quiet
3637+
3638+The subscribe and unsubscribe actions continue even if one the users
3639+fail to subscribe/unsubscribe to a list. A relevant feedback message is
3640+provided if the --quiet flag is not enabled.
3641+
3642+Preferences
3643+===========
3644+
3645+
3646+Update Preference
3647+-----------------
3648+
3649+Preferences for user,address,member or globally can be updated and retrieved
3650+by using the commands of preference scope.
3651+
3652+To update the value of a preference of an user::
3653+
3654+ $> mmclient update preference user --email foo@bar.com [key] [value]
3655+
3656+To update the value of a preference of a member::
3657+
3658+ $> mmclient update preference member --email foo@bar.com --list list@domain.org [key] [value]
3659+
3660+To update the value of a preference of an address::
3661+
3662+ $> mmclient update preference address --email foo@bar.com [key] [value]
3663+
3664+To update the value of a preference globally::
3665+
3666+ $> mmclient update preference global [key] [value]
3667+
3668+View Setting
3669+------------
3670+To view the current value of a preference, use the `mmclient show preference` command
3671+in the same way above, obviously, without the `value` argument
3672+
3673+For eg, to view a setting of a member::
3674+
3675+ $> mmclient show preference member --email foo@bar.com --list list@domain.org [key]
3676+
3677+Both the commands try to suggest the possible preference keys upon errors while typing the
3678+keys. The commands return `1` upon an invalid key.
3679+
3680+Backup and Restore
3681+==================
3682+
3683+The CLI tools can be used to create backups and restore the backups of the
3684+Mailman data. The backup currently supports the backup for SQLite mode.
3685+
3686+Backup
3687+------
3688+
3689+The backup tools backs up the $var_dir specified by the Mailman configuration
3690+as a zip archive to a specified location::
3691+
3692+ $> mmclient backup ~/backup.zip
3693+
3694+Restore
3695+-------
3696+To restore the backup, specfiy the path to the backup file::
3697+
3698+ $> mmclient restore ~/backup.zip
3699+
3700+Please remember to stop the mailman runner before performing
3701+the backup and restore operations.
3702+
3703+The paths for backup and restore are read from the Mailman configuration
3704+file.
3705
3706=== added file 'src/mailmanclient/cli/docs/writing_a_new_command.txt'
3707--- src/mailmanclient/cli/docs/writing_a_new_command.txt 1970-01-01 00:00:00 +0000
3708+++ src/mailmanclient/cli/docs/writing_a_new_command.txt 2015-03-17 19:08:34 +0000
3709@@ -0,0 +1,57 @@
3710+Writing a new command
3711+*********************
3712+
3713+There are two types of commands in the CLI project, either add a new command
3714+to the CLI tools, that gives a normal terminal command, or build a command
3715+for the Mailman custom shell.
3716+
3717+Writing a new command for Command tools
3718+=======================================
3719+
3720+To write a new command for the command line tools, the following
3721+steps are to be followed.
3722+
3723+- Decide a command name and arguments expected
3724+- Decide whether the command comes under a scope. Currently
3725+the following scopes are supported
3726+
3727+ - users
3728+ - domains
3729+ - preferences
3730+ - lists
3731+
3732+The new command can either be under any of these scopes or
3733+can exist independently without a scope.
3734+
3735+- If the new command BAR is to be added to an existing scope ``FOO``,
3736+ the command would look like ``FOO BAR``, where ``FOO`` is the action and
3737+ BAR is the scope
3738+- Add a new parser to the `action` parser as::
3739+
3740+ new_cmd = action.add_parser('FOO')
3741+
3742+ Add the scope as follows::
3743+
3744+ scope = new_cmd.add_subparser(dest='scope')
3745+ FOO_BAR = scope.add_parser('BAR')
3746+
3747+- Add the arguments as follows::
3748+
3749+ FOO_BAR.add_argument('-x','--xxxx',help='<help text>',[required=True/False])
3750+
3751+- Add the action method in the ``core/bar.py`` file, to the Bar class. In effect, the action
3752+ method would be ``core.bar.Bar.action``. Note that the function name must be same as the
3753+ action name
3754+
3755+- If there is no specific scope, skip the scope parser and add the action the Misc class
3756+ under ``core/misc.py``. The action would be ``core.misc.Misc.action``
3757+
3758+Writing a new command for the Shell
3759+===================================
3760+
3761+To write a new shell command, first add a new parser for the command to the ``client/parsers`` directory.
3762+
3763+Add the command method to the ``client/shell.py``
3764+
3765+Import the parser in the corresponding method and parse the user input, present in the ``self.line`` attribute
3766+to obtain the arguments. Perform the action.
3767
3768=== added directory 'src/mailmanclient/cli/lib'
3769=== added file 'src/mailmanclient/cli/lib/__init__.py'
3770=== added file 'src/mailmanclient/cli/lib/colors.py'
3771--- src/mailmanclient/cli/lib/colors.py 1970-01-01 00:00:00 +0000
3772+++ src/mailmanclient/cli/lib/colors.py 2015-03-17 19:08:34 +0000
3773@@ -0,0 +1,32 @@
3774+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
3775+#
3776+# This file is part of mailman.client.
3777+#
3778+# mailman.client is free software: you can redistribute it and/or modify it
3779+# under the terms of the GNU Lesser General Public License as published by the
3780+# Free Software Foundation, version 3 of the License.
3781+#
3782+# mailman.client is distributed in the hope that it will be useful, but
3783+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
3784+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
3785+# License for more details.
3786+#
3787+# You should have received a copy of the GNU Lesser General Public License
3788+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
3789+#
3790+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
3791+#
3792+# Author : Rajeev S <rajeevs1992@gmail.com>
3793+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
3794+# Abhilash Raj <raj.abhilash1@gmail.com>
3795+# Barry Warsaw <barry@list.org>
3796+
3797+GREY = "\033[90m%s\033[0m"
3798+RED = "\033[91m%s\033[0m"
3799+GREEN = "\033[92m%s\033[0m"
3800+YELLOW = "\033[93m%s\033[0m"
3801+BLUE = "\033[94m%s\033[0m"
3802+PURPLE = "\033[95m%s\033[0m"
3803+CYAN = "\033[96m%s\033[0m"
3804+WHITE = "\033[97m%s\033[0m"
3805+BLACK = "\033[98m%s\033[0m"
3806
3807=== added file 'src/mailmanclient/cli/lib/mailman_utils.py'
3808--- src/mailmanclient/cli/lib/mailman_utils.py 1970-01-01 00:00:00 +0000
3809+++ src/mailmanclient/cli/lib/mailman_utils.py 2015-03-17 19:08:34 +0000
3810@@ -0,0 +1,117 @@
3811+#!/usr/bin/python
3812+
3813+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
3814+#
3815+# This file is part of mailman.client.
3816+#
3817+# mailman.client is free software: you can redistribute it and/or modify it
3818+# under the terms of the GNU Lesser General Public License as published by the
3819+# Free Software Foundation, version 3 of the License.
3820+#
3821+# mailman.client is distributed in the hope that it will be useful, but
3822+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
3823+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
3824+# License for more details.
3825+#
3826+# You should have received a copy of the GNU Lesser General Public License
3827+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
3828+#
3829+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
3830+#
3831+# Author : Rajeev S <rajeevs1992@gmail.com>
3832+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
3833+# Abhilash Raj <raj.abhilash1@gmail.com>
3834+# Barry Warsaw <barry@list.org>
3835+
3836+from mailmanclient import Client, MailmanConnectionError
3837+from mailman.config import config
3838+from mailmanclient.cli.lib.utils import Utils
3839+
3840+
3841+class MailmanUtils(Utils):
3842+
3843+ """ Utilities relating to Mailman
3844+ Client or the REST API
3845+ """
3846+
3847+ def __init__(self):
3848+ config.load()
3849+
3850+ def connect(self, *args, **kwargs):
3851+ """ Connect to Mailman REST API using the arguments specified.
3852+ Missing arguments are decided from the mailman.cfg file
3853+ return a client object.
3854+ """
3855+ host, port, username, password = self.get_credentials_from_config()
3856+
3857+ if 'host' in kwargs and kwargs['host']:
3858+ host = kwargs['host']
3859+ if 'port' in kwargs and kwargs['port']:
3860+ port = kwargs['port']
3861+ if 'username' in kwargs and kwargs['username']:
3862+ username = kwargs['username']
3863+ if 'password' in kwargs and kwargs['password']:
3864+ password = kwargs['password']
3865+
3866+ client = Client('%s:%s/3.0' % (host, port),
3867+ username,
3868+ password)
3869+ try:
3870+ client.system
3871+ except MailmanConnectionError as e:
3872+ self.error(e)
3873+ exit(1)
3874+ return client
3875+
3876+ def get_credentials_from_config(self):
3877+ """ Returns the credentials required for logging on to
3878+ the Mailman REST API, that are read from the Mailman
3879+ configuration.
3880+ """
3881+ host = 'http://' + config.schema['webservice']['hostname']
3882+ port = config.schema['webservice']['port']
3883+ username = config.schema['webservice']['admin_user']
3884+ password = config.schema['webservice']['admin_pass']
3885+ return host, port, username, password
3886+
3887+ def get_new_domain_name(self):
3888+ """ Generates the name of a non existent domain """
3889+ client = self.connect()
3890+ while True:
3891+ domain_name = self.get_random_string(10) + '.com'
3892+ try:
3893+ client.get_domain(domain_name)
3894+ continue
3895+ except Exception:
3896+ return domain_name
3897+
3898+ def add_shell_vars(self, arg, shell):
3899+ """ Replaces the variables used in the command with thier respective
3900+ values if the values are present in the shell environment, else
3901+ use the variable as such.
3902+ """
3903+ if not shell.env_on or not arg:
3904+ return arg
3905+ if arg[0] == '$' and arg[1:] in shell.env:
3906+ arg = shell.env[arg[1:]]
3907+ return arg
3908+
3909+ def add_reserved_vars(self, args, shell):
3910+ """ Adds the reserved variables to a filter query. The reserved variables
3911+ are domain, list and user, which are added to respective scopes and
3912+ atrribute names.
3913+ """
3914+ scope = args['scope']
3915+ if 'filters' not in args:
3916+ args['filters'] = []
3917+ if not shell.env_on:
3918+ return args
3919+ filters = args['filters']
3920+ if scope == 'list':
3921+ if 'domain' in shell.env:
3922+ filters.append(('mail_host', '=', shell.env['domain']))
3923+ elif scope == 'user':
3924+ if 'list' in shell.env:
3925+ filters.append((shell.env['list'], 'in', 'subscriptions'))
3926+ args['filters'] = filters
3927+ return args
3928
3929=== added file 'src/mailmanclient/cli/lib/utils.py'
3930--- src/mailmanclient/cli/lib/utils.py 1970-01-01 00:00:00 +0000
3931+++ src/mailmanclient/cli/lib/utils.py 2015-03-17 19:08:34 +0000
3932@@ -0,0 +1,216 @@
3933+#!/usr/bin/python
3934+
3935+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
3936+#
3937+# This file is part of mailman.client.
3938+#
3939+# mailman.client is free software: you can redistribute it and/or modify it
3940+# under the terms of the GNU Lesser General Public License as published by the
3941+# Free Software Foundation, version 3 of the License.
3942+#
3943+# mailman.client is distributed in the hope that it will be useful, but
3944+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
3945+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
3946+# License for more details.
3947+#
3948+# You should have received a copy of the GNU Lesser General Public License
3949+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
3950+#
3951+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
3952+#
3953+# Author : Rajeev S <rajeevs1992@gmail.com>
3954+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
3955+# Abhilash Raj <raj.abhilash1@gmail.com>
3956+# Barry Warsaw <barry@list.org>
3957+
3958+import re
3959+import sys
3960+import csv
3961+from hashlib import sha1
3962+from datetime import datetime
3963+from mailmanclient.cli.lib import colors
3964+
3965+
3966+class Utils():
3967+ """ General utilities to be used across the CLI """
3968+
3969+ WARN = colors.CYAN
3970+ ERROR = colors.RED
3971+ CONFIRM = colors.GREEN
3972+ EMPHASIZE = colors.PURPLE
3973+
3974+ # Give colored output on the CLI"
3975+ def warn(self, message):
3976+ print(self.WARN % message)
3977+
3978+ def error(self, message):
3979+ sys.stderr.write((self.ERROR + '\n') % message)
3980+
3981+ def confirm(self, message):
3982+ print(self.CONFIRM % message)
3983+
3984+ def emphasize(self, message):
3985+ print(self.EMPHASIZE % message)
3986+
3987+ def return_emphasize(self, message):
3988+ return self.EMPHASIZE % message
3989+ # End Colors!
3990+
3991+ def get_random_string(self, length):
3992+ """ Returns short random strings, less than 40 bytes in length.
3993+
3994+ :param length: Length of the random string to be returned
3995+ :type length: int
3996+ """
3997+ if length > 40:
3998+ raise Exception('Specify length less than 40')
3999+ return sha1(str(datetime.now()).encode('utf-8')).hexdigest()[:length]
4000+
4001+ def set_table_section_heading(self, table, heading):
4002+ table.append(['', ''])
4003+ table.append([heading, ''])
4004+ table.append(['=============', ''])
4005+
4006+ def write_csv(self, table, headers, filename):
4007+ if table == []:
4008+ return
4009+
4010+ if 'csv' not in filename:
4011+ filename += '.csv'
4012+
4013+ f = open(filename, 'wb')
4014+ writer = csv.writer(f, quoting=csv.QUOTE_ALL)
4015+ if headers:
4016+ writer.writerow(headers)
4017+ for row in table:
4018+ writer.writerow(row)
4019+ f.close()
4020+
4021+ return
4022+
4023+ def stem(self, arguments):
4024+ """ Allows the scope to be in singular or plural
4025+
4026+ :param arguments: The sys.argv, passed from mmclient
4027+ :type arguments: list
4028+ """
4029+ scope = arguments[2]
4030+ if scope[-1] == 's':
4031+ scope = scope[:-1]
4032+ arguments[2] = scope
4033+ return arguments
4034+
4035+ def get_listing(self, objects, fields):
4036+ """ Tabulates the set of objects by using the values of each of the
4037+ fileds. """
4038+ table = []
4039+ for obj in objects:
4040+ row = []
4041+ for field in fields:
4042+ try:
4043+ value = getattr(obj, field)
4044+ except:
4045+ value = 'None'
4046+
4047+ _type = type(value).__name__
4048+ if _type == '_Addresses':
4049+ row.append(str(value[0]))
4050+ else:
4051+ row.append(str(value))
4052+
4053+ table.append(row)
4054+ return table
4055+
4056+
4057+class Filter():
4058+ """ This class manages the filtering tasks for the show and delete commands
4059+ The class supports three filters, equality, regular expression search
4060+ and list search.
4061+ """
4062+
4063+ def get_results(self, *args):
4064+ op = args[2]
4065+ if op == '=':
4066+ return self.equality(*args)
4067+ elif op == 'like':
4068+ return self.like(*args)
4069+ elif op == 'in':
4070+ return self.in_list(*args)
4071+ else:
4072+ raise Exception('Invalid operator: %s ' % (op))
4073+
4074+ def equality(self, key, value, op, data_set):
4075+ copy_set = data_set[:]
4076+ for i in data_set:
4077+ try:
4078+ obj_value = getattr(i, key)
4079+ if obj_value != value:
4080+ copy_set.remove(i)
4081+ except AttributeError:
4082+ raise Exception('Invalid filter : %s' % key)
4083+ return copy_set
4084+
4085+ def in_list(self, key, value, op, data_set):
4086+ copy_set = data_set[:]
4087+ flag = False
4088+ for i in data_set:
4089+ try:
4090+ the_list = getattr(i, key)
4091+ except KeyError:
4092+ copy_set.remove(i)
4093+ continue
4094+ except AttributeError:
4095+ raise Exception('Invalid filter : %s' % key)
4096+
4097+ if key == 'members':
4098+ for j in the_list:
4099+ if self.match_pattern(j.email, value):
4100+ flag = True
4101+ break
4102+ elif key == 'lists':
4103+ for j in the_list:
4104+ if (self.match_pattern(j.list_id, value)
4105+ or self.match_pattern(j.fqdn_listname, value)):
4106+ flag = True
4107+ break
4108+ elif key == 'subscriptions':
4109+ value = value.replace('@', '.')
4110+ for j in the_list:
4111+ if self.match_pattern(j.list_id, value):
4112+ flag = True
4113+ break
4114+ else:
4115+ for j in the_list:
4116+ if self.match_pattern(j, value):
4117+ flag = True
4118+ break
4119+ if not flag:
4120+ copy_set.remove(i)
4121+ flag = False
4122+ return copy_set
4123+
4124+ def like(self, key, value, op, data_set):
4125+ copy_set = data_set[:]
4126+ for i in data_set:
4127+ obj_value = None
4128+ try:
4129+ obj_value = getattr(i, key)
4130+ except KeyError:
4131+ copy_set.remove(i)
4132+ except AttributeError:
4133+ raise Exception('Invalid filter : %s' % key)
4134+ if not self.match_pattern(obj_value, value):
4135+ copy_set.remove(i)
4136+ return copy_set
4137+
4138+ def match_pattern(self, string, value):
4139+ """ Regular expression matcher, returns the match object
4140+ or None
4141+ """
4142+ pattern = None
4143+ try:
4144+ pattern = re.compile(value.lower())
4145+ except:
4146+ raise Exception('Invalid pattern : %s' % value)
4147+ string = str(string).lower()
4148+ return pattern.match(string)
4149
4150=== added file 'src/mailmanclient/cli/mmclient'
4151--- src/mailmanclient/cli/mmclient 1970-01-01 00:00:00 +0000
4152+++ src/mailmanclient/cli/mmclient 2015-03-17 19:08:34 +0000
4153@@ -0,0 +1,46 @@
4154+#!/usr/bin/python
4155+
4156+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
4157+#
4158+# This file is part of mailman.client.
4159+#
4160+# mailman.client is free software: you can redistribute it and/or modify it
4161+# under the terms of the GNU Lesser General Public License as published by the
4162+# Free Software Foundation, version 3 of the License.
4163+#
4164+# mailman.client is distributed in the hope that it will be useful, but
4165+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
4166+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
4167+# License for more details.
4168+#
4169+# You should have received a copy of the GNU Lesser General Public License
4170+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
4171+#
4172+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
4173+#
4174+# Author : Rajeev S <rajeevs1992@gmail.com>
4175+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
4176+# Abhilash Raj <raj.abhilash1@gmail.com>
4177+# Barry Warsaw <barry@list.org>
4178+
4179+import sys
4180+from mailmanclient.cli.lib.utils import Utils
4181+from mailmanclient.cli.client.shell import Shell
4182+from mailmanclient.cli.client.cmdparser import CmdParser
4183+try:
4184+ # If the mmclient is invoked with arguments, trigger the command
4185+ # line tools, else trigger the commandline shell
4186+ sys.argv[1]
4187+ utils = Utils()
4188+ arguments = None
4189+ try:
4190+ arguments = utils.stem(sys.argv)
4191+ except IndexError:
4192+ pass
4193+ c = CmdParser(arguments)
4194+ c.run()
4195+except IndexError:
4196+ # No arguments supplied.Start the shell.
4197+ s = Shell()
4198+ s.initialize()
4199+ s.cmdloop()
4200
4201=== added directory 'src/mailmanclient/cli/tests'
4202=== added file 'src/mailmanclient/cli/tests/__init__.py'
4203=== added file 'src/mailmanclient/cli/tests/test_domain.py'
4204--- src/mailmanclient/cli/tests/test_domain.py 1970-01-01 00:00:00 +0000
4205+++ src/mailmanclient/cli/tests/test_domain.py 2015-03-17 19:08:34 +0000
4206@@ -0,0 +1,223 @@
4207+#!/usr/bin/python
4208+
4209+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
4210+#
4211+# This file is part of mailman.client.
4212+#
4213+# mailman.client is free software: you can redistribute it and/or modify it
4214+# under the terms of the GNU Lesser General Public License as published by the
4215+# Free Software Foundation, version 3 of the License.
4216+#
4217+# mailman.client is distributed in the hope that it will be useful, but
4218+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
4219+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
4220+# License for more details.
4221+#
4222+# You should have received a copy of the GNU Lesser General Public License
4223+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
4224+#
4225+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
4226+#
4227+# Author : Rajeev S <rajeevs1992@gmail.com>
4228+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
4229+# Abhilash Raj <raj.abhilash1@gmail.com>
4230+# Barry Warsaw <barry@list.org>
4231+
4232+import random
4233+import unittest
4234+from mock import patch
4235+from six.moves.urllib_error import HTTPError
4236+from io import StringIO
4237+from mailmanclient.cli.core.domains import Domains, DomainException
4238+from mailmanclient.cli.lib.mailman_utils import MailmanUtils
4239+
4240+
4241+class TestCreateDomain(unittest.TestCase):
4242+
4243+ domain_names = []
4244+ utils = MailmanUtils()
4245+
4246+ def setUp(self):
4247+ self.client = self.utils.connect()
4248+ self.domain = Domains(self.client)
4249+ self.test_domain = self.utils.get_new_domain_name()
4250+ self.domain_names.append(self.test_domain)
4251+ self.client.create_domain(self.test_domain)
4252+
4253+ def test_normal_create(self):
4254+ self.new_domain = self.utils.get_new_domain_name()
4255+ self.domain_names.append(self.new_domain)
4256+ args = {}
4257+ args['domain'] = self.new_domain
4258+ args['contact'] = 'a@b.com'
4259+ self.domain.create(args)
4260+
4261+ def test_create_existent_domain(self):
4262+ args = {}
4263+ args['domain'] = self.test_domain
4264+ args['contact'] = 'a@b.com'
4265+ self.assertRaises(DomainException, self.domain.create, args)
4266+
4267+ def test_no_postmaster(self):
4268+ self.new_domain = self.utils.get_new_domain_name()
4269+ self.domain_names.append(self.new_domain)
4270+ args = {}
4271+ args['domain'] = self.new_domain
4272+ args['contact'] = None
4273+ self.domain.create(args)
4274+
4275+ def tearDown(self):
4276+ for domain in self.domain_names:
4277+ try:
4278+ self.client.delete_domain(domain)
4279+ except Exception:
4280+ pass
4281+
4282+
4283+class TestShowDomain(unittest.TestCase):
4284+
4285+ domain_names = []
4286+ utils = MailmanUtils()
4287+
4288+ def setUp(self):
4289+ self.client = self.utils.connect()
4290+ self.domain = Domains(self.client)
4291+
4292+ @patch('sys.stdout', new_callable=StringIO)
4293+ def test_normal_show(self, output):
4294+ ndomains = len(self.client.domains)
4295+ args = {}
4296+ args['no_header'] = False
4297+ args['verbose'] = False
4298+ args['csv'] = None
4299+ args['domain'] = None
4300+ self.domain.show(args)
4301+ domain_list = output.getvalue().split('\n')
4302+ count = len(domain_list) - 1
4303+ self.assertEqual(ndomains, count)
4304+
4305+ @patch('sys.stdout', new_callable=StringIO)
4306+ def test_verbose_show(self, output):
4307+ args = {}
4308+ args['no_header'] = False
4309+ args['verbose'] = True
4310+ args['csv'] = None
4311+ args['domain'] = None
4312+ test_domain = random.randint(0, len(self.client.domains) - 1)
4313+ test_domain = self.client.domains[test_domain]
4314+ self.domain.show(args)
4315+ domain_list = output.getvalue().split('\n')
4316+ domain = ''
4317+ for domain in domain_list:
4318+ if test_domain.base_url in domain:
4319+ break
4320+ domain = domain.split()
4321+ cleaned_domain = []
4322+ for attribute in domain:
4323+ if attribute:
4324+ cleaned_domain.append(attribute)
4325+ self.assertEqual(cleaned_domain[0], test_domain.base_url)
4326+ self.assertEqual(cleaned_domain[1], test_domain.contact_address)
4327+ self.assertEqual(cleaned_domain[2], test_domain.mail_host)
4328+ self.assertEqual(cleaned_domain[3], test_domain.url_host)
4329+
4330+ @patch('sys.stdout', new_callable=StringIO)
4331+ def test_no_header(self, output):
4332+ args = {}
4333+ args['no_header'] = True
4334+ args['csv'] = None
4335+ args['verbose'] = True
4336+ args['domain'] = None
4337+ test_domain = random.randint(0, len(self.client.domains) - 1)
4338+ test_domain = self.client.domains[test_domain]
4339+ self.domain.show(args)
4340+ domain_list = output.getvalue().split('\n')
4341+ line_one = domain_list[0].split()
4342+ self.assertNotEqual(line_one[0], 'Base')
4343+ domain = ''
4344+ for domain in domain_list:
4345+ if test_domain.base_url in domain:
4346+ break
4347+ domain = domain.split()
4348+ cleaned_domain = []
4349+ for attribute in domain:
4350+ if attribute:
4351+ cleaned_domain.append(attribute)
4352+ self.assertEqual(cleaned_domain[0], test_domain.base_url)
4353+ self.assertEqual(cleaned_domain[1], test_domain.contact_address)
4354+ self.assertEqual(cleaned_domain[2], test_domain.mail_host)
4355+ self.assertEqual(cleaned_domain[3], test_domain.url_host)
4356+
4357+ def tearDown(self):
4358+ for domain in self.domain_names:
4359+ try:
4360+ self.client.delete_domain(domain)
4361+ except Exception:
4362+ pass
4363+
4364+
4365+class TestDeleteDomain(unittest.TestCase):
4366+
4367+ domain_names = []
4368+ utils = MailmanUtils()
4369+
4370+ def setUp(self):
4371+ self.client = self.utils.connect()
4372+ self.domain = Domains(self.client)
4373+
4374+ def test_normal_delete(self):
4375+ new_domain = self.utils.get_new_domain_name()
4376+ self.domain_names.append(new_domain)
4377+ self.client.create_domain(new_domain)
4378+ args = {}
4379+ args['domain'] = new_domain
4380+ with patch('builtins.input', return_value='y'):
4381+ args['yes'] = True
4382+ self.domain.delete(args)
4383+ self.assertRaises(HTTPError, self.client.get_domain, new_domain)
4384+
4385+ def test_delete_cancel(self):
4386+ new_domain = self.utils.get_new_domain_name()
4387+ self.domain_names.append(new_domain)
4388+ self.client.create_domain(new_domain)
4389+ args = {}
4390+ args['domain'] = new_domain
4391+ with patch('builtins.input', return_value='n'):
4392+ args['yes'] = False
4393+ self.domain.delete(args)
4394+ self.assertRaises(HTTPError, self.client.create_domain, new_domain)
4395+
4396+ def test_delete_invalid_confirm(self):
4397+ new_domain = self.utils.get_new_domain_name()
4398+ self.domain_names.append(new_domain)
4399+ self.client.create_domain(new_domain)
4400+ args = {}
4401+ args['domain'] = new_domain
4402+ args['yes'] = False
4403+ with patch('builtins.input', return_value='no'):
4404+ self.assertRaises(Exception, self.domain.delete, args)
4405+
4406+ def test_delete_without_confirm(self):
4407+ new_domain = self.utils.get_new_domain_name()
4408+ self.domain_names.append(new_domain)
4409+ self.client.create_domain(new_domain)
4410+ args = {}
4411+ args['domain'] = new_domain
4412+ args['yes'] = True
4413+ self.domain.delete(args)
4414+ self.assertRaises(HTTPError, self.client.get_domain, new_domain)
4415+
4416+ def test_delete_invalid_domain(self):
4417+ new_domain = self.utils.get_new_domain_name()
4418+ self.domain_names.append(new_domain)
4419+ args = {}
4420+ args['domain'] = new_domain
4421+ args['yes'] = True
4422+ self.assertRaises(DomainException, self.domain.delete, args)
4423+
4424+ def tearDown(self):
4425+ for domain in self.domain_names:
4426+ try:
4427+ self.client.delete_domain(domain)
4428+ except Exception:
4429+ pass
4430
4431=== added file 'src/mailmanclient/cli/tests/test_list.py'
4432--- src/mailmanclient/cli/tests/test_list.py 1970-01-01 00:00:00 +0000
4433+++ src/mailmanclient/cli/tests/test_list.py 2015-03-17 19:08:34 +0000
4434@@ -0,0 +1,285 @@
4435+#!/usr/bin/python
4436+
4437+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
4438+#
4439+# This file is part of mailman.client.
4440+#
4441+# mailman.client is free software: you can redistribute it and/or modify it
4442+# under the terms of the GNU Lesser General Public License as published by the
4443+# Free Software Foundation, version 3 of the License.
4444+#
4445+# mailman.client is distributed in the hope that it will be useful, but
4446+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
4447+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
4448+# License for more details.
4449+#
4450+# You should have received a copy of the GNU Lesser General Public License
4451+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
4452+#
4453+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
4454+#
4455+# Author : Rajeev S <rajeevs1992@gmail.com>
4456+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
4457+# Abhilash Raj <raj.abhilash1@gmail.com>
4458+# Barry Warsaw <barry@list.org>
4459+
4460+import unittest
4461+from mock import patch
4462+from six.moves.urllib_error import HTTPError
4463+from io import StringIO
4464+from mailmanclient.cli.core.lists import Lists, ListException
4465+from mailmanclient.cli.core.domains import DomainException
4466+from mailmanclient.cli.lib.mailman_utils import MailmanUtils
4467+
4468+
4469+class TestCreateList(unittest.TestCase):
4470+
4471+ domain_names = []
4472+ utils = MailmanUtils()
4473+
4474+ def setUp(self):
4475+ self.client = self.utils.connect()
4476+ self._list = Lists(self.client)
4477+ self.test_domain = self.utils.get_new_domain_name()
4478+ self.domain_names.append(self.test_domain)
4479+ self.client.create_domain(self.test_domain)
4480+
4481+ # Raise exception if domain create falied.
4482+ self.domain = self.client.get_domain(self.test_domain)
4483+
4484+ def test_normal_create(self):
4485+ args = {}
4486+ list_name = self.utils.get_random_string(5)
4487+ list_name += '@' + self.test_domain
4488+ args['list'] = list_name
4489+ self._list.create(args)
4490+
4491+ def test_create_existent_list(self):
4492+ list_name = self.utils.get_random_string(5)
4493+ self.domain.create_list(list_name)
4494+ args = {}
4495+ list_name += '@' + self.test_domain
4496+ args['list'] = list_name
4497+ self.assertRaises(ListException, self._list.create, args)
4498+
4499+ def test_invalid_fqdn_create(self):
4500+ test_list = self.utils.get_random_string(5)
4501+ invalid_name_1 = test_list + '@'
4502+ invalid_name_2 = '@' + self.test_domain
4503+ invalid_name_3 = (test_list + '@' +
4504+ self.utils.get_new_domain_name())
4505+ invalid_name_4 = self.test_domain
4506+ args = {}
4507+ args['list'] = invalid_name_1
4508+ self.assertRaises(ListException, self._list.create, args)
4509+ args['list'] = invalid_name_2
4510+ self.assertRaises(ListException, self._list.create, args)
4511+ args['list'] = invalid_name_3
4512+ self.assertRaises(DomainException, self._list.create, args)
4513+ args['list'] = invalid_name_4
4514+ self.assertRaises(ListException, self._list.create, args)
4515+
4516+ def tearDown(self):
4517+ for domain in self.domain_names:
4518+ try:
4519+ self.client.delete_domain(domain)
4520+ except Exception:
4521+ pass
4522+
4523+
4524+class TestShowList(unittest.TestCase):
4525+
4526+ domain_names = []
4527+ utils = MailmanUtils()
4528+
4529+ def setUp(self):
4530+ self.client = self.utils.connect()
4531+ self._list = Lists(self.client)
4532+ self.test_domain = self.utils.get_new_domain_name()
4533+ self.test_list = self.utils.get_random_string(5)
4534+ self.domain_names.append(self.test_domain)
4535+ self.client.create_domain(self.test_domain)
4536+
4537+ # Raise exception if domain create falied.
4538+ self.domain = self.client.get_domain(self.test_domain)
4539+ self.domain.create_list(self.test_list)
4540+
4541+ @patch('sys.stdout', new_callable=StringIO)
4542+ def test_normal_show(self, output):
4543+ nlists = len(self.client.lists)
4544+ args = {}
4545+ args['no_header'] = False
4546+ args['csv'] = None
4547+ args['verbose'] = False
4548+ args['domain'] = None
4549+ args['list'] = None
4550+ self._list.show(args)
4551+ mlist_list = output.getvalue().split('\n')
4552+ count = len(mlist_list) - 1
4553+ self.assertEqual(nlists, count)
4554+
4555+ @patch('sys.stdout', new_callable=StringIO)
4556+ def test_filter_show(self, output):
4557+ nlists = len(self.domain.lists)
4558+ args = {}
4559+ args['no_header'] = False
4560+ args['csv'] = None
4561+ args['verbose'] = False
4562+ args['domain'] = self.test_domain
4563+ args['list'] = None
4564+ self._list.show(args)
4565+ mlist_list = output.getvalue().split('\n')
4566+ count = len(mlist_list) - 1
4567+ self.assertEqual(nlists, count)
4568+
4569+ @patch('sys.stdout', new_callable=StringIO)
4570+ def test_verbose_show(self, output):
4571+ args = {}
4572+ args['no_header'] = False
4573+ args['csv'] = None
4574+ args['verbose'] = True
4575+ args['domain'] = None
4576+ args['list'] = None
4577+ self._list.show(args)
4578+ mlists = output.getvalue().split('\n')
4579+ test_list = '%s@%s' % (self.test_list, self.test_domain)
4580+ mlist = ''
4581+ for mlist in mlists:
4582+ if test_list in mlist:
4583+ break
4584+ mlist = mlist.split()
4585+ cleaned_list = []
4586+ for attribute in mlist:
4587+ if attribute:
4588+ cleaned_list.append(attribute)
4589+ mlist = self.client.get_list(test_list)
4590+ self.assertEqual(cleaned_list[0], mlist.list_id)
4591+ self.assertEqual(cleaned_list[1], mlist.list_name)
4592+ self.assertEqual(cleaned_list[2], mlist.mail_host)
4593+ self.assertEqual(cleaned_list[3], mlist.display_name)
4594+ self.assertEqual(cleaned_list[4], mlist.fqdn_listname)
4595+
4596+ @patch('sys.stdout', new_callable=StringIO)
4597+ def test_filter_verbose_show(self, output):
4598+ args = {}
4599+ args['no_header'] = False
4600+ args['csv'] = None
4601+ args['verbose'] = True
4602+ args['domain'] = self.test_domain
4603+ args['list'] = None
4604+ self._list.show(args)
4605+ mlists = output.getvalue().split('\n')
4606+ mlist = ''
4607+ test_list = '%s@%s' % (self.test_list, self.test_domain)
4608+ for mlist in mlists:
4609+ if test_list in mlist:
4610+ break
4611+ mlist = mlist.split()
4612+ cleaned_list = []
4613+ for attribute in mlist:
4614+ if attribute:
4615+ cleaned_list.append(attribute)
4616+ mlist = self.client.get_list(test_list)
4617+ self.assertEqual(cleaned_list[0], mlist.list_id)
4618+ self.assertEqual(cleaned_list[1], mlist.list_name)
4619+ self.assertEqual(cleaned_list[2], mlist.mail_host)
4620+ self.assertEqual(cleaned_list[3], mlist.display_name)
4621+ self.assertEqual(cleaned_list[4], mlist.fqdn_listname)
4622+
4623+ @patch('sys.stdout', new_callable=StringIO)
4624+ def test_no_header(self, output):
4625+ args = {}
4626+ args['no_header'] = False
4627+ args['csv'] = None
4628+ args['verbose'] = True
4629+ args['domain'] = None
4630+ args['list'] = None
4631+ self._list.show(args)
4632+ mlists = output.getvalue().split('\n')
4633+ line_one = mlists[0].split()
4634+ self.assertNotEqual(line_one[0], 'Base')
4635+ mlist = ''
4636+ for mlist in mlists:
4637+ if self.test_list in mlist:
4638+ break
4639+ mlist = mlist.split()
4640+ cleaned_list = []
4641+ for attribute in mlist:
4642+ if attribute:
4643+ cleaned_list.append(attribute)
4644+ mlist = self.client.get_list('%s@%s' % (self.test_list,
4645+ self.test_domain))
4646+ self.assertEqual(cleaned_list[0], mlist.list_id)
4647+ self.assertEqual(cleaned_list[1], mlist.list_name)
4648+ self.assertEqual(cleaned_list[2], mlist.mail_host)
4649+ self.assertEqual(cleaned_list[3], mlist.display_name)
4650+ self.assertEqual(cleaned_list[4], mlist.fqdn_listname)
4651+
4652+ def tearDown(self):
4653+ for domain in self.domain_names:
4654+ try:
4655+ self.client.delete_domain(domain)
4656+ except Exception:
4657+ pass
4658+
4659+
4660+class TestDeleteList(unittest.TestCase):
4661+
4662+ domain_names = []
4663+ utils = MailmanUtils()
4664+
4665+ def setUp(self):
4666+ self.client = self.utils.connect()
4667+ self._list = Lists(self.client)
4668+ self.test_domain = self.utils.get_new_domain_name()
4669+ self.test_list = self.utils.get_random_string(5)
4670+ self.domain_names.append(self.test_domain)
4671+ self.client.create_domain(self.test_domain)
4672+
4673+ # Raise exception if domain create falied.
4674+ self.domain = self.client.get_domain(self.test_domain)
4675+ self.domain.create_list(self.test_list)
4676+
4677+ def test_normal_delete(self):
4678+ new_list = self.utils.get_random_string(5)
4679+ self.domain.create_list(new_list)
4680+ args = {}
4681+ args['list'] = '%s@%s' % (new_list, self.test_domain)
4682+ with patch('builtins.input', return_value='y'):
4683+ args['yes'] = True
4684+ self._list.delete(args)
4685+ self.assertRaises(HTTPError, self.client.get_list, args['list'])
4686+
4687+ def test_delete_cancel(self):
4688+ new_list = self.utils.get_random_string(5)
4689+ self.domain.create_list(new_list)
4690+ args = {}
4691+ args['list'] = '%s@%s' % (new_list, self.test_domain)
4692+ with patch('builtins.input', return_value='n'):
4693+ args['yes'] = False
4694+ self._list.delete(args)
4695+ self.assertRaises(HTTPError, self.domain.create_list, args['list'])
4696+
4697+ def test_delete_invalid_confirm(self):
4698+ new_list = self.utils.get_random_string(5)
4699+ self.domain.create_list(new_list)
4700+ args = {}
4701+ args['list'] = '%s@%s' % (new_list, self.test_domain)
4702+ with patch('builtins.input', return_value='no'):
4703+ self.assertRaises(Exception, self._list.delete, args)
4704+
4705+ def test_delete_without_confirm(self):
4706+ new_list = self.utils.get_random_string(5)
4707+ self.domain.create_list(new_list)
4708+ args = {}
4709+ args['list'] = '%s@%s' % (new_list, self.test_domain)
4710+ args['yes'] = True
4711+ self._list.delete(args)
4712+ self.assertRaises(HTTPError, self.client.get_list, args['list'])
4713+
4714+ def tearDown(self):
4715+ for domain in self.domain_names:
4716+ try:
4717+ self.client.delete_domain(domain)
4718+ except Exception:
4719+ pass
4720
4721=== added file 'src/mailmanclient/cli/tests/test_preference.py'
4722--- src/mailmanclient/cli/tests/test_preference.py 1970-01-01 00:00:00 +0000
4723+++ src/mailmanclient/cli/tests/test_preference.py 2015-03-17 19:08:34 +0000
4724@@ -0,0 +1,99 @@
4725+#!/usr/bin/python
4726+
4727+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
4728+#
4729+# This file is part of mailman.client.
4730+#
4731+# mailman.client is free software: you can redistribute it and/or modify it
4732+# under the terms of the GNU Lesser General Public License as published by the
4733+# Free Software Foundation, version 3 of the License.
4734+#
4735+# mailman.client is distributed in the hope that it will be useful, but
4736+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
4737+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
4738+# License for more details.
4739+#
4740+# You should have received a copy of the GNU Lesser General Public License
4741+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
4742+#
4743+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
4744+#
4745+# Author : Rajeev S <rajeevs1992@gmail.com>
4746+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
4747+# Abhilash Raj <raj.abhilash1@gmail.com>
4748+# Barry Warsaw <barry@list.org>
4749+
4750+import unittest
4751+from mailmanclient.cli.core.preferences import Preferences
4752+from mailmanclient.cli.lib.mailman_utils import MailmanUtils
4753+
4754+
4755+utils = MailmanUtils()
4756+
4757+
4758+class TestUpdate(unittest.TestCase):
4759+
4760+ new_objects = []
4761+
4762+ test_user = None
4763+ test_domain = None
4764+ test_list = None
4765+ test_address = None
4766+ test_member = None
4767+
4768+ def setUp(self):
4769+ self.client = utils.connect()
4770+ self.preferences = Preferences(self.client)
4771+ self.test_user = 'a@' + utils.get_random_string(5) + '.com'
4772+ self.test_user = self.client.create_user(self.test_user,
4773+ display_name='a',
4774+ password='a')
4775+ self.new_objects.append(self.test_user)
4776+
4777+ self.test_domain = self.client.create_domain(utils.get_random_string(5)
4778+ + '.org')
4779+ self.new_objects.append(self.test_domain)
4780+ self.test_list = self.test_domain.create_list(utils.get_random_string(5))
4781+ self.new_objects.append(self.test_list)
4782+
4783+ self.test_member = self.test_list.subscribe(self.test_user.addresses[0])
4784+
4785+ self.test_address = self.test_user.addresses[0]
4786+
4787+ def test_user(self):
4788+ args = {}
4789+ args['update_scope'] = 'user'
4790+ args['email'] = self.test_user.addresses[0]
4791+ args['key'] = 'receive_list_copy'
4792+ args['value'] = 'False'
4793+ self.preferences.update(args)
4794+ self.assertFalse(self.test_user.preferences['receive_list_copy'])
4795+
4796+ def test_address(self):
4797+ args = {}
4798+ args['update_scope'] = 'address'
4799+ args['email'] = self.test_address
4800+ args['key'] = 'receive_list_copy'
4801+ args['value'] = 'False'
4802+ self.preferences.update(args)
4803+ self.assertFalse(self.test_address.preferences['receive_list_copy'])
4804+
4805+ def test_member(self):
4806+ args = {}
4807+ args['update_scope'] = 'member'
4808+ args['email'] = self.test_member.email
4809+ args['list'] = self.test_list.fqdn_listname
4810+ args['key'] = 'receive_list_copy'
4811+ args['value'] = 'False'
4812+ self.preferences.update(args)
4813+ self.assertFalse(self.test_member.preferences['receive_list_copy'])
4814+
4815+ def tearDown(self):
4816+ for obj in self.new_objects:
4817+ try:
4818+ if type(obj).__name__ == '_Domain':
4819+ self.client.delete_domain(obj.base_url)
4820+ else:
4821+ obj.delete()
4822+ except:
4823+ pass
4824
4825=== added file 'src/mailmanclient/cli/tests/test_user.py'
4826--- src/mailmanclient/cli/tests/test_user.py 1970-01-01 00:00:00 +0000
4827+++ src/mailmanclient/cli/tests/test_user.py 2015-03-17 19:08:34 +0000
4828@@ -0,0 +1,127 @@
4829+#!/usr/bin/python
4830+
4831+# Copyright (C) 2010-2014 by the Free Software Foundation, Inc.
4832+#
4833+# This file is part of mailman.client.
4834+#
4835+# mailman.client is free software: you can redistribute it and/or modify it
4836+# under the terms of the GNU Lesser General Public License as published by the
4837+# Free Software Foundation, version 3 of the License.
4838+#
4839+# mailman.client is distributed in the hope that it will be useful, but
4840+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
4841+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
4842+# License for more details.
4843+#
4844+# You should have received a copy of the GNU Lesser General Public License
4845+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
4846+#
4847+# This file is part of the Mailman CLI Project, Google Summer Of Code, 2014
4848+#
4849+# Author : Rajeev S <rajeevs1992@gmail.com>
4850+# Mentors : Stephen J. Turnbull <stephen@xemacs.org>
4851+# Abhilash Raj <raj.abhilash1@gmail.com>
4852+# Barry Warsaw <barry@list.org>
4853+
4854+import unittest
4855+from mailmanclient.cli.core.users import Users, UserException
4856+from mailmanclient.cli.lib.mailman_utils import MailmanUtils
4857+
4858+
4859+class TestCreateUser(unittest.TestCase):
4860+
4861+ utils = MailmanUtils()
4862+ new_users = []
4863+
4864+ def setUp(self):
4865+ self.client = self.utils.connect()
4866+ self.users = Users(self.client)
4867+ self.test_user = 'a@' + self.utils.get_random_string(5) + '.org'
4868+ self.new_users.append(self.test_user)
4869+ self.client.create_user(email=self.test_user,
4870+ password='abcdefgh',
4871+ display_name='abc')
4872+ self.test_list = self.client.lists[0]
4873+ self.test_list.subscribe(self.test_user)
4874+
4875+ # Test if user created else setup fails.
4876+ self.client.get_user(self.test_user)
4877+
4878+ def test_normal_create(self):
4879+ args = {}
4880+ new_user = 'a@' + self.utils.get_random_string(5) + '.org'
4881+ self.new_users.append(new_user)
4882+ args['email'] = new_user
4883+ args['password'] = 'abc'
4884+ args['name'] = 'abc'
4885+ self.users.create(args)
4886+ self.assertRaises(UserException, self.users.create, args)
4887+
4888+ def test_create_existent_user(self):
4889+ args = {}
4890+ args['email'] = self.test_user
4891+ args['password'] = 'abc'
4892+ args['name'] = 'abc'
4893+ self.assertRaises(UserException, self.users.create, args)
4894+
4895+ def tearDown(self):
4896+ for user in self.new_users:
4897+ try:
4898+ u = self.client.get_user(user)
4899+ u.delete()
4900+ except Exception as e:
4901+ print(e)
4902+
4903+
4904+class TestSubscription(unittest.TestCase):
4905+
4906+ utils = MailmanUtils()
4907+ new_users = []
4908+ domain_list = []
4909+
4910+ def setUp(self):
4911+ self.client = self.utils.connect()
4912+ self.users = Users(self.client)
4913+
4914+ self.test_domain = self.utils.get_new_domain_name()
4915+ self.domain_list.append(self.test_domain)
4916+ self.client.create_domain(self.test_domain)
4917+
4918+ self.test_user = self.utils.get_random_string(8) + '@domain.org'
4919+ self.new_users.append(self.test_user)
4920+ self.client.create_user(email=self.test_user,
4921+ password='abcdefgh',
4922+ display_name='abc')
4923+ self.client.get_user(self.test_user)
4924+ domain = self.client.get_domain(self.test_domain)
4925+ self.test_list = (self.utils.get_random_string(8) +
4926+ '@' + self.test_domain)
4927+ domain.create_list(self.test_list.split('@')[0])
4928+
4929+ # Test if user created else setup fails.
4930+ self.client.get_user(self.test_user)
4931+
4932+ def test_subscribe_to_list(self):
4933+ print(self.test_user)
4934+ _list = self.client.get_list(self.test_list)
4935+ nmembers = len(_list.members)
4936+ args = {}
4937+ args['users'] = ['a2b@b.com', 'ab4@c.com', 'a8d@e.com']
4938+ self.new_users.extend(args['users'])
4939+ args['list_name'] = self.test_list
4940+ args['quiet'] = False
4941+ self.users.subscribe(args)
4942+ self.assertEqual(nmembers + 3, len(_list.members))
4943+
4944+ def tearDown(self):
4945+ for i in self.new_users:
4946+ try:
4947+ user = self.client.get_user(i)
4948+ user.delete()
4949+ except Exception:
4950+ pass
4951+ for i in self.domain_list:
4952+ try:
4953+ self.client.delete_domain(i)
4954+ except Exception:
4955+ pass

Subscribers

People subscribed via source and target branches