mailmanager 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Changelog ADDED
@@ -0,0 +1,8 @@
1
+ Current version is 1.0.8
2
+
3
+ changes since 1.0.7
4
+ - added documentation
5
+
6
+ 1.0.7 -- First working release
7
+
8
+ 1.0.0 - 1.0.6 -- Internal testing releases
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2011, Democratic National Committee
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+ * Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ * Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+ * Neither the name of the Democratic National Committee nor the
12
+ names of its contributors may be used to endorse or promote products
13
+ derived from this software without specific prior written permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL THE DEMOCRATIC NATIONAL COMMITTEE BE LIABLE FOR ANY
19
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.rdoc ADDED
@@ -0,0 +1,38 @@
1
+ = MailManager
2
+
3
+ MailManager is a Ruby wrapper for the GNU Mailman mailing list manager. It exposes
4
+ some administrative functions to Ruby. See the API docs for details.
5
+ It is licensed under the New BSD License (see the LICENSE file for details).
6
+
7
+ MailManager has been tested with Mailman 2.1.14. It has NOT been tested with any
8
+ release of Mailman 3 (in alpha as of 1/19/2011). It requires Python 2.6 or higher.
9
+ Note that while Mailman itself requires Python, it can work with older versions. So
10
+ check your Python version before using this.
11
+
12
+ == Installation
13
+
14
+ gem install mailmanager
15
+
16
+ == Basic Usage
17
+
18
+ mm = MailManager.init('/mailman/root')
19
+ all_lists = mm.lists
20
+ foo_list = mm.get_list('foo')
21
+ new_list = mm.create_list(:name => 'newlist', :admin_email => 'me@here.com', :admin_password => 'secret')
22
+ new_list.add_member('bar@baz.com')
23
+ new_list.members (returns ['foo@baz.com'])
24
+
25
+ == Contributing
26
+
27
+ - Fork on GitHub
28
+ - Make a new branch
29
+ - Push your changes to that branch (i.e. not master)
30
+ - Make a pull request
31
+ - Bask in your awesomeness
32
+
33
+ == Author
34
+
35
+ - Wes Morgan (cap10morgan on GitHub)
36
+
37
+ Copyright 2011 Democratic National Committee,
38
+ All Rights Reserved.
@@ -0,0 +1,104 @@
1
+ require "singleton"
2
+ require "rubygems"
3
+ require "bundler/setup"
4
+ require "json"
5
+ require "open4"
6
+
7
+ require 'mailmanager/lib'
8
+ require 'mailmanager/list'
9
+
10
+ module MailManager
11
+ @root = nil
12
+ @python = '/usr/bin/env python'
13
+ @debug = ENV['MAILMANAGER_DEBUG'] =~ /^(?:1|true|y|yes|on)$/i ? true : false
14
+
15
+ def self.root=(root) #:nodoc:
16
+ @root = root
17
+ end
18
+
19
+ def self.root #:nodoc:
20
+ @root
21
+ end
22
+
23
+ def self.python=(python) #:nodoc:
24
+ @python = python
25
+ end
26
+
27
+ def self.python #:nodoc:
28
+ @python
29
+ end
30
+
31
+ def self.debug #:nodoc:
32
+ @debug
33
+ end
34
+
35
+ # Call this method to start using MailManager. Give it the full path to your
36
+ # Mailman installation. It will return an instance of MailManager::Base.
37
+ def self.init(root)
38
+ self.root = root
39
+ Base.instance
40
+ end
41
+
42
+ # The MailManager::Base class is the root class for working with a Mailman
43
+ # installation. You get an instance of it by calling
44
+ # MailManager.init('/mailman/root').
45
+ class Base
46
+ include Singleton
47
+
48
+ REQUIRED_BIN_FILES = ['list_lists', 'newlist', 'inject'] #:nodoc:
49
+
50
+ def initialize #:nodoc:
51
+ raise "Must set MailManager.root before calling #{self.class}.instance" if MailManager.root.nil?
52
+ raise "#{root} does not exist" unless Dir.exist?(root)
53
+ raise "#{root}/bin does not exist" unless Dir.exist?("#{root}/bin")
54
+ REQUIRED_BIN_FILES.each do |bin_file|
55
+ raise "#{root}/bin/#{bin_file} not found" unless File.exist?("#{root}/bin/#{bin_file}")
56
+ end
57
+ @lib = MailManager::Lib.new
58
+ end
59
+
60
+ # If you want to use a non-default python executable to run the Python
61
+ # portions of this gem, set its full path here. Since we require Python
62
+ # 2.6+ and some distros don't ship with that version, you can point this at
63
+ # a newer Python you have installed. Defaults to /usr/bin/env python.
64
+ def python=(python)
65
+ MailManager.python = python
66
+ end
67
+
68
+ def python #:nodoc:
69
+ MailManager.python
70
+ end
71
+
72
+ def root #:nodoc:
73
+ MailManager.root
74
+ end
75
+
76
+ # Returns an array of MailManager::List instances of the lists in your
77
+ # Mailman installation.
78
+ def lists
79
+ @lib.lists
80
+ end
81
+
82
+ # Only retrieves the list names, doesn't wrap them in MailManager::List
83
+ # instances.
84
+ def list_names
85
+ lists.map { |list| list.name }
86
+ end
87
+
88
+ # Create a new list. Returns an instance of MailManager::List. Params are:
89
+ # * :name => 'new_list_name'
90
+ # * :admin_email => 'admin@domain.com'
91
+ # * :admin_password => 'supersecret'
92
+ def create_list(params)
93
+ MailManager::List.create(params)
94
+ end
95
+
96
+ # Get an existing list as a MailManager::List instance. Raises an exception if
97
+ # the list doesn't exist.
98
+ def get_list(list_name)
99
+ raise "#{list_name} list does not exist" unless list_names.include?(list_name.downcase)
100
+ MailManager::List.new(list_name)
101
+ end
102
+ end
103
+
104
+ end
@@ -0,0 +1,194 @@
1
+ module MailManager
2
+
3
+ class MailmanExecuteError < StandardError #:nodoc:
4
+ end
5
+
6
+ class Lib #:nodoc:all
7
+
8
+ def mailmanager
9
+ MailManager::Base.instance
10
+ end
11
+
12
+ def lists
13
+ cmd = :list_lists
14
+ out = command(cmd)
15
+ parse_output(cmd, out)
16
+ end
17
+
18
+ def create_list(params)
19
+ cmd = :newlist
20
+ out = command(cmd, params)
21
+ parse_output(cmd, out)
22
+ end
23
+
24
+ def list_address(list)
25
+ cmd = :withlist
26
+ out = command(cmd, :name => list.name, :wlcmd => :getListAddress)
27
+ parse_json_output(out)
28
+ end
29
+
30
+ def regular_members(list)
31
+ cmd = :withlist
32
+ out = command(cmd, :name => list.name, :wlcmd => :getRegularMemberKeys)
33
+ parse_json_output(out)
34
+ end
35
+
36
+ def digest_members(list)
37
+ cmd = :withlist
38
+ out = command(cmd, :name => list.name, :wlcmd => :getDigestMemberKeys)
39
+ parse_json_output(out)
40
+ end
41
+
42
+ def add_member(list, member)
43
+ cmd = :withlist
44
+ out = command(cmd, :name => list.name, :wlcmd => :AddMember, :arg => member)
45
+ parse_json_output(out)
46
+ end
47
+
48
+ def approved_add_member(list, member)
49
+ cmd = :withlist
50
+ out = command(cmd, :name => list.name, :wlcmd => :ApprovedAddMember,
51
+ :arg => member)
52
+ parse_json_output(out)
53
+ end
54
+
55
+ def delete_member(list, email)
56
+ cmd = :withlist
57
+ out = command(cmd, :name => list.name, :wlcmd => :DeleteMember,
58
+ :arg => email)
59
+ parse_json_output(out)
60
+ end
61
+
62
+ def approved_delete_member(list, email)
63
+ cmd = :withlist
64
+ out = command(cmd, :name => list.name, :wlcmd => :ApprovedDeleteMember,
65
+ :arg => email)
66
+ parse_json_output(out)
67
+ end
68
+
69
+ def moderators(list)
70
+ cmd = :withlist
71
+ out = command(cmd, :name => list.name, :wlcmd => :moderator)
72
+ parse_json_output(out)
73
+ end
74
+
75
+ def add_moderator(list, email)
76
+ if moderators(list)['return'].include?(email)
77
+ return {'result' => 'already_a_moderator'}
78
+ end
79
+ cmd = :withlist
80
+ out = command(cmd, :name => list.name, :wlcmd => 'moderator.append',
81
+ :arg => email)
82
+ parse_json_output(out)
83
+ end
84
+
85
+ def delete_moderator(list, email)
86
+ raise "#{email} is not a moderator" unless moderators(list)['return'].include?(email)
87
+ cmd = :withlist
88
+ out = command(cmd, :name => list.name, :wlcmd => 'moderator.remove',
89
+ :arg => email)
90
+ parse_json_output(out)
91
+ end
92
+
93
+ def inject(list, message, queue=nil)
94
+ cmd = :inject
95
+ params = {:listname => list.name, :stdin => message}
96
+ params[:queue] = queue unless queue.nil?
97
+ command(cmd, params)
98
+ end
99
+
100
+ def command(cmd, opts = {})
101
+ mailman_cmd = "#{mailmanager.root}/bin/#{cmd.to_s} "
102
+ # delete opts as we handle them explicitly
103
+ stdin = nil
104
+ stdin = opts.delete(:stdin) if opts.has_key?(:stdin)
105
+ case cmd
106
+ when :newlist
107
+ mailman_cmd += "-q "
108
+ raise ArgumentError, "Missing :name param" if opts[:name].nil?
109
+ raise ArgumentError, "Missing :admin_email param" if opts[:admin_email].nil?
110
+ raise ArgumentError, "Missing :admin_password param" if opts[:admin_password].nil?
111
+ mailman_cmd_suffix = [:name, :admin_email, :admin_password].map { |key|
112
+ escape(opts.delete(key))
113
+ }.join(' ')
114
+ mailman_cmd += "#{mailman_cmd_suffix} "
115
+ when :withlist
116
+ raise ArgumentError, "Missing :name param" if opts[:name].nil?
117
+ proxy_path = File.dirname(__FILE__)
118
+ mailman_cmd = "PYTHONPATH=#{proxy_path} #{MailManager.python} #{mailman_cmd}"
119
+ mailman_cmd += "-q -r listproxy.command #{escape(opts.delete(:name))} " +
120
+ "#{opts.delete(:wlcmd)} "
121
+ if !opts[:arg].nil? && opts[:arg].length > 0
122
+ mailman_cmd += "#{escape(opts.delete(:arg))} "
123
+ end
124
+ end
125
+
126
+ # assume any leftover opts are POSIX-style args
127
+ mailman_cmd += opts.keys.map { |k| "--#{k}=#{escape(opts[k])}" }.join(' ')
128
+ mailman_cmd += ' ' if mailman_cmd[-1,1] != ' '
129
+ mailman_cmd += "2>&1"
130
+ if MailManager.debug
131
+ puts "Running mailman command: #{mailman_cmd}"
132
+ puts " with stdin: #{stdin}" unless stdin.nil?
133
+ end
134
+ out, process = run_command(mailman_cmd, stdin)
135
+
136
+ if process.exitstatus > 0
137
+ raise MailManager::MailmanExecuteError.new(mailman_cmd + ':' + out.to_s)
138
+ end
139
+ out
140
+ end
141
+
142
+ def run_command(mailman_cmd, stdindata=nil)
143
+ output = nil
144
+ process = Open4::popen4(mailman_cmd) do |pid, stdin, stdout, stderr|
145
+ if !stdindata.nil?
146
+ stdin.puts(stdindata)
147
+ stdin.close
148
+ end
149
+ output = stdout.read
150
+ end
151
+ [output, process]
152
+ end
153
+
154
+ def escape(s)
155
+ # no idea what this does, stole it from the ruby-git gem
156
+ escaped = s.to_s.gsub('\'', '\'\\\'\'')
157
+ %Q{"#{escaped}"}
158
+ end
159
+
160
+ def parse_output(mailman_cmd, output)
161
+ case mailman_cmd
162
+ when :newlist
163
+ list_name = nil
164
+ output.split("\n").each do |line|
165
+ if match = /^##\s+(.+?)mailing\s+list\s*$/.match(line)
166
+ list_name = match[1]
167
+ end
168
+ end
169
+ raise "Error getting name of newly created list" if list_name.nil?
170
+ return_obj = MailManager::List.new(list_name)
171
+ when :list_lists
172
+ lists = []
173
+ puts "Output from Mailman:\n#{output}" if MailManager.debug
174
+ output.split("\n").each do |line|
175
+ next if line =~ /^\d+ matching mailing lists found:$/
176
+ /^\s*(.+?)\s+-\s+(.+)$/.match(line) do |m|
177
+ puts "Found list #{m[1]}" if MailManager.debug
178
+ lists << MailManager::List.new(m[1].downcase)
179
+ end
180
+ end
181
+ return_obj = lists
182
+ end
183
+ return_obj
184
+ end
185
+
186
+ def parse_json_output(json)
187
+ result = JSON.parse(json)
188
+ if result.is_a?(Hash) && !result['error'].nil?
189
+ raise MailmanExecuteError, result['error']
190
+ end
191
+ result
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,129 @@
1
+ module MailManager
2
+
3
+ # The List class represents mailing lists in Mailman.
4
+ # Typically you get them by doing one of these things:
5
+ # mm = MailManager.init('/mailman/root')
6
+ # mylist = mm.get_list('list_name')
7
+ # OR
8
+ # mylist = mm.create_list(:name => 'list_name', :admin_email =>
9
+ # 'foo@bar.com', :admin_password => 'supersecret')
10
+ #
11
+
12
+ class List
13
+ # The name of the list
14
+ attr_reader :name
15
+
16
+ # This doesn't do any checking to see whether or not the requested list
17
+ # exists or not. Better to use MailManager::Base#get_list instead.
18
+ def initialize(name)
19
+ @name = name
20
+ end
21
+
22
+ def to_s #:nodoc:
23
+ @name
24
+ end
25
+
26
+ def lib #:nodoc:
27
+ self.class.lib
28
+ end
29
+
30
+ def self.lib #:nodoc:
31
+ MailManager::Lib.new
32
+ end
33
+
34
+ def self.create(params) #:nodoc:
35
+ lib.create_list(params)
36
+ end
37
+
38
+ # Returns the list's email address
39
+ def address
40
+ result = lib.list_address(self)
41
+ result['return']
42
+ end
43
+
44
+ # Returns all list members (regular & digest) as an array
45
+ def members
46
+ regular_members + digest_members
47
+ end
48
+
49
+ # Returns just the regular list members (no digest members) as an array
50
+ def regular_members
51
+ result = lib.regular_members(self)
52
+ result['return']
53
+ end
54
+
55
+ # Returns just the digest list members (no regular members) as an array
56
+ def digest_members
57
+ result = lib.digest_members(self)
58
+ result['return']
59
+ end
60
+
61
+ # Adds a new list member, subject to the list's subscription rules
62
+ def add_member(email, name='')
63
+ add_member_using(:add_member, email, name)
64
+ end
65
+
66
+ # Adds a new list member, bypassing the list's subscription rules
67
+ def approved_add_member(email, name='')
68
+ add_member_using(:approved_add_member, email, name)
69
+ end
70
+
71
+ # Deletes a list member, subject to the list's unsubscription rules
72
+ def delete_member(email)
73
+ delete_member_using(:delete_member, email)
74
+ end
75
+
76
+ # Deletes a list member, bypassing the list's unsubscription rules
77
+ def approved_delete_member(email, name='')
78
+ delete_member_using(:approved_delete_member, email)
79
+ end
80
+
81
+ # Returns the list of moderators as an array of email addresses
82
+ def moderators
83
+ result = lib.moderators(self)
84
+ result['return']
85
+ end
86
+
87
+ # Adds a new moderator to the list
88
+ def add_moderator(email)
89
+ result = lib.add_moderator(self, email)
90
+ result['result'].to_sym
91
+ end
92
+
93
+ # Deletes a moderator from the list. Will raise an exception if the
94
+ # moderator doesn't exist yet.
95
+ def delete_moderator(email)
96
+ result = lib.delete_moderator(self, email)
97
+ result['result'].to_sym
98
+ end
99
+
100
+ # Injects a message into the list.
101
+ def inject(from, subject, message)
102
+ inject_message =<<EOF
103
+ From: #{from}
104
+ To: #{address}
105
+ Subject: #{subject}
106
+
107
+ #{message}
108
+ EOF
109
+ lib.inject(self, inject_message)
110
+ end
111
+
112
+ private
113
+
114
+ def add_member_using(method, email, name)
115
+ if name.length > 0
116
+ member = "#{name} <#{email}>"
117
+ else
118
+ member = email
119
+ end
120
+ result = lib.send(method, self, member)
121
+ result['result'].to_sym
122
+ end
123
+
124
+ def delete_member_using(method, email)
125
+ result = lib.send(method, self, email)
126
+ result['result'].to_sym
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,73 @@
1
+ import json
2
+ from email.Utils import parseaddr
3
+ from collections import Callable
4
+ from Mailman import MailList
5
+ from Mailman import Errors
6
+
7
+ class MailingListEncoder(json.JSONEncoder):
8
+ def default(self, obj):
9
+ if isinstance(obj, MailList.MailList):
10
+ return {'name': obj.internal_name()}
11
+ return json.JSONEncoder.default(self, obj)
12
+
13
+ def dumplist(mlist):
14
+ print json.dumps(mlist, True, cls=MailingListEncoder)
15
+
16
+ class UserDesc: pass
17
+ def userdesc_for(member):
18
+ userdesc = UserDesc()
19
+ userdesc.fullname, userdesc.address = parseaddr(member)
20
+ return userdesc
21
+
22
+ def unwindattrs(obj, attrs, *args):
23
+ if not attrs.count('.'):
24
+ attr = getattr(obj, attrs)
25
+ if isinstance(attr, Callable):
26
+ return attr(*args)
27
+ else:
28
+ return attr
29
+ else:
30
+ attr, nextattrs = attrs.split('.', 1)
31
+ nextobj = getattr(obj, attr)
32
+ return unwindattrs(nextobj, nextattrs, *args)
33
+
34
+ needs_userdesc = dict(AddMember=True, ApprovedAddMember=True)
35
+ needs_save = dict(AddMember=True, ApprovedAddMember=True,
36
+ DeleteMember=True, ApprovedDeleteMember=True,
37
+ moderator_append=True, moderator_remove=True)
38
+
39
+ def command(mlist, cmd, *args):
40
+ result = {}
41
+ try:
42
+ if needs_save.get(cmd.replace('.','_'), False):
43
+ mlist.Lock()
44
+ if needs_userdesc.get(cmd, False):
45
+ result['return'] = unwindattrs(mlist, cmd, userdesc_for(args[0]))
46
+ else:
47
+ result['return'] = unwindattrs(mlist, cmd, *args)
48
+ if needs_save.get(cmd.replace('.','_'), False):
49
+ mlist.Save()
50
+ except TypeError as err:
51
+ error_msg = '%s' % err
52
+ print json.dumps({'error': error_msg})
53
+ except AttributeError as err:
54
+ error_msg = 'AttributeError: %s' % err
55
+ print json.dumps({'error': error_msg})
56
+ except Errors.MMSubscribeNeedsConfirmation as err:
57
+ print json.dumps({'result': 'pending_confirmation'})
58
+ except Errors.MMAlreadyAMember as err:
59
+ print json.dumps({'result': 'already_a_member'})
60
+ except Errors.MMNeedApproval as err:
61
+ print json.dumps({'result': 'needs_approval'})
62
+ except Exception as err:
63
+ error_msg = '%s: %s' % (type(err), err)
64
+ print json.dumps({'error': error_msg})
65
+ else:
66
+ result['result'] = 'success'
67
+ print json.dumps(result)
68
+
69
+ #def loadlist(mlist, jsonlist):
70
+ #newlist = json.loads(jsonlist)
71
+ #for attr in newlist:
72
+ #print "Setting %s to %s" % (attr, newlist[attr])
73
+
@@ -0,0 +1,3 @@
1
+ module MailManager
2
+ VERSION = '1.0.8'
3
+ end
@@ -0,0 +1,160 @@
1
+ require 'spec_helper'
2
+
3
+ describe MailManager::Lib do
4
+ let(:mailmanager) { mock(MailManager) }
5
+ let(:subject) { MailManager::Lib.new }
6
+ let(:fake_root) { '/foo/bar' }
7
+ let(:process) { mock(Process::Status) }
8
+
9
+ before :each do
10
+ subject.stub(:mailmanager).and_return(mailmanager)
11
+ mailmanager.stub(:root).and_return(fake_root)
12
+ process.stub(:exitstatus).and_return(0)
13
+ end
14
+
15
+ describe "#lists" do
16
+ it "should return all existing lists" do
17
+ list_result = <<EOF
18
+ 3 matching mailing lists found:
19
+ Foo - [no description available]
20
+ BarBar - Dummy list
21
+ Mailman - Mailman site list
22
+ EOF
23
+ subject.stub(:run_command).with("#{fake_root}/bin/list_lists 2>&1", nil).
24
+ and_return([list_result,process])
25
+ subject.lists.should have(3).lists
26
+ end
27
+ end
28
+
29
+ describe "#create_list" do
30
+ it "should raise an argument error if list name is missing" do
31
+ lambda {
32
+ subject.create_list(:admin_email => 'foo@bar.baz', :admin_password => 'qux')
33
+ }.should raise_error(ArgumentError)
34
+ end
35
+ it "should raise an argument error if list admin email is missing" do
36
+ lambda {
37
+ subject.create_list(:name => 'foo', :admin_password => 'qux')
38
+ }.should raise_error(ArgumentError)
39
+ end
40
+ it "should raise an argument error if admin password is missing" do
41
+ lambda {
42
+ subject.create_list(:name => 'foo', :admin_email => 'foo@bar.baz')
43
+ }.should raise_error(ArgumentError)
44
+ end
45
+
46
+ context "with valid list params" do
47
+ let(:new_aliases) {
48
+ ['foo: "|/foo/bar/mail/mailman post foo"',
49
+ 'foo-admin: "|/foo/bar/mail/mailman admin foo"',
50
+ 'foo-bounces: "|/foo/bar/mail/mailman bounces foo"',
51
+ 'foo-confirm: "|/foo/bar/mail/mailman confirm foo"',
52
+ 'foo-join: "|/foo/bar/mail/mailman join foo"',
53
+ 'foo-leave: "|/foo/bar/mail/mailman leave foo"',
54
+ 'foo-owner: "|/foo/bar/mail/mailman owner foo"',
55
+ 'foo-request: "|/foo/bar/mail/mailman request foo"',
56
+ 'foo-subscribe: "|/foo/bar/mail/mailman subscribe foo"',
57
+ 'foo-unsubscribe: "|/foo/bar/mail/mailman unsubscribe foo"']
58
+ }
59
+ let(:new_list_return) {
60
+ prefix =<<EOF
61
+ To finish creating your mailing list, you must edit your /etc/aliases (or
62
+ equivalent) file by adding the following lines, and possibly running the
63
+ `newaliases' program:
64
+
65
+ ## foo mailing list
66
+ EOF
67
+ prefix+new_aliases.join("\n")
68
+ }
69
+ let(:fake_aliases_file) { mock(File) }
70
+
71
+ before :each do
72
+ File.stub(:open).with('/etc/aliases', 'a').and_return(fake_aliases_file)
73
+ subject.stub(:run_newaliases_command)
74
+ end
75
+
76
+ it "should create the list" do
77
+ subject.should_receive(:run_command).
78
+ with("#{fake_root}/bin/newlist -q \"foo\" \"foo@bar.baz\" \"qux\" 2>&1", nil).
79
+ and_return([new_list_return,process])
80
+ subject.create_list(:name => 'foo', :admin_email => 'foo@bar.baz',
81
+ :admin_password => 'qux')
82
+ end
83
+ end
84
+ end
85
+
86
+ context "with populated list" do
87
+ let(:list) { list = mock(MailManager::List)
88
+ list.stub(:name).and_return('foo')
89
+ list }
90
+
91
+ let(:regular_members) { ['me@here.com', 'you@there.org'] }
92
+ let(:digest_members) { ['them@that.net'] }
93
+
94
+ let(:cmd) { "PYTHONPATH=#{File.expand_path('lib/mailmanager')} " +
95
+ "#{fake_root}/bin/withlist -q -r listproxy.command \"foo\" " }
96
+
97
+ describe "#regular_members" do
98
+ it "should ask Mailman for the regular list members" do
99
+ subject.should_receive(:run_command).
100
+ with(cmd+"getRegularMemberKeys 2>&1", nil).
101
+ and_return([JSON.generate(regular_members),process])
102
+ subject.regular_members(list).should == regular_members
103
+ end
104
+ end
105
+
106
+ describe "#digest_members" do
107
+ it "should ask Mailman for the digest list members" do
108
+ subject.should_receive(:run_command).
109
+ with(cmd+"getDigestMemberKeys 2>&1", nil).
110
+ and_return([JSON.generate(digest_members),process])
111
+ subject.digest_members(list).should == digest_members
112
+ end
113
+ end
114
+
115
+ describe "#add_member" do
116
+ it "should ask Mailman to add the member to the list" do
117
+ new_member = 'newb@dnc.org'
118
+ result = {"result" => "pending_confirmation"}
119
+ subject.should_receive(:run_command).
120
+ with(cmd+"AddMember \"#{new_member}\" 2>&1", nil).
121
+ and_return([JSON.generate(result),process])
122
+ subject.add_member(list, new_member).should == result
123
+ end
124
+ end
125
+
126
+ describe "#approved_add_member" do
127
+ it "should ask Mailman to add the member to the list" do
128
+ new_member = 'newb@dnc.org'
129
+ result = {"result" => "success"}
130
+ subject.should_receive(:run_command).
131
+ with(cmd+"ApprovedAddMember \"#{new_member}\" 2>&1", nil).
132
+ and_return([JSON.generate(result),process])
133
+ subject.approved_add_member(list, new_member).should == result
134
+ end
135
+ end
136
+
137
+ describe "#delete_member" do
138
+ it "should ask Mailman to delete the member from the list" do
139
+ former_member = 'oldie@ofa.org'
140
+ result = {"result" => "success"}
141
+ subject.should_receive(:run_command).
142
+ with(cmd+"DeleteMember \"#{former_member}\" 2>&1", nil).
143
+ and_return([JSON.generate(result),process])
144
+ subject.delete_member(list, former_member).should == result
145
+ end
146
+ end
147
+
148
+ describe "#approved_delete_member" do
149
+ it "should ask Mailman to delete the member from the list" do
150
+ former_member = 'oldie@ofa.org'
151
+ result = {"result" => "success"}
152
+ subject.should_receive(:run_command).
153
+ with(cmd+"ApprovedDeleteMember \"#{former_member}\" 2>&1", nil).
154
+ and_return([JSON.generate(result),process])
155
+ subject.approved_delete_member(list, former_member).should == result
156
+ end
157
+ end
158
+ end
159
+
160
+ end
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+
3
+ describe MailManager::List do
4
+ let(:lib) { mock(MailManager::Lib) }
5
+ let(:subject) { MailManager::List.new('foo') }
6
+
7
+ before :each do
8
+ MailManager::List.stub(:lib).and_return(lib)
9
+ end
10
+
11
+ describe ".create" do
12
+ it "should require the params arg" do
13
+ lambda {
14
+ MailManager::List.create
15
+ }.should raise_error(ArgumentError)
16
+ end
17
+
18
+ it "should return the new list" do
19
+ params = {:name => 'foo', :admin_email => 'foo@bar.baz', :admin_password => 'qux'}
20
+ lib.stub(:create_list).with(params).and_return(subject)
21
+ new_list = MailManager::List.create(params)
22
+ new_list.should_not be_nil
23
+ new_list.name.should == 'foo'
24
+ end
25
+ end
26
+
27
+ describe "#initialize" do
28
+ it "should take a name parameter" do
29
+ MailManager::List.new('foo').name.should == 'foo'
30
+ end
31
+
32
+ it "should raise an error if the name arg is missing" do
33
+ lambda {
34
+ MailManager::List.new
35
+ }.should raise_error(ArgumentError)
36
+ end
37
+ end
38
+
39
+ describe "#to_s" do
40
+ it "should return its name" do
41
+ subject.to_s.should == "foo"
42
+ end
43
+ end
44
+
45
+ context "with list members" do
46
+ let(:regular_members) { ['me@here.com', 'you@there.org'] }
47
+ let(:digest_members) { ['them@that.net'] }
48
+ let(:all_members) { regular_members + digest_members }
49
+
50
+ describe "#regular_members" do
51
+ it "should return only regular members" do
52
+ lib.stub(:regular_members).with(subject).and_return({'return' => regular_members})
53
+ subject.regular_members.should == regular_members
54
+ end
55
+ end
56
+
57
+ describe "#digest_members" do
58
+ it "should return only digest members" do
59
+ lib.stub(:digest_members).with(subject).and_return({'return' => digest_members})
60
+ subject.digest_members.should == digest_members
61
+ end
62
+ end
63
+
64
+ describe "#members" do
65
+ it "should return the list of all members" do
66
+ lib.stub(:regular_members).with(subject).and_return({'return' => regular_members})
67
+ lib.stub(:digest_members).with(subject).and_return({'return' => digest_members})
68
+ subject.members.should == all_members
69
+ end
70
+ end
71
+ end
72
+
73
+ describe "#add_member" do
74
+ it "should tell lib to add the member" do
75
+ lib.should_receive(:add_member).with(subject, 'foo@bar.baz').
76
+ and_return({'result' => 'pending_confirmation'})
77
+ subject.add_member('foo@bar.baz').should == :pending_confirmation
78
+ end
79
+
80
+ it "should accept an optional name argument" do
81
+ lib.should_receive(:add_member).with(subject, 'Foo Bar <foo@bar.baz>').
82
+ and_return({'result' => 'pending_confirmation'})
83
+ subject.add_member('foo@bar.baz', 'Foo Bar').should == :pending_confirmation
84
+ end
85
+ end
86
+
87
+ describe "#approved_add_member" do
88
+ it "should tell lib to add the member" do
89
+ lib.should_receive(:approved_add_member).with(subject, 'foo@bar.baz').
90
+ and_return({'result' => 'success'})
91
+ subject.approved_add_member('foo@bar.baz').should == :success
92
+ end
93
+
94
+ it "should accept an optional name argument" do
95
+ lib.should_receive(:approved_add_member).with(subject, 'Foo Bar <foo@bar.baz>').
96
+ and_return({'result' => 'success'})
97
+ subject.approved_add_member('foo@bar.baz', 'Foo Bar').should == :success
98
+ end
99
+ end
100
+
101
+ describe "#delete_member" do
102
+ it "should tell lib to delete the member" do
103
+ lib.should_receive(:delete_member).with(subject, 'foo@bar.baz').
104
+ and_return({'result' => 'success'})
105
+ subject.delete_member('foo@bar.baz').should == :success
106
+ end
107
+ end
108
+
109
+ describe "#approved_delete_member" do
110
+ it "should tell lib to delete the member" do
111
+ lib.should_receive(:approved_delete_member).with(subject, 'foo@bar.baz').
112
+ and_return({'result' => 'success'})
113
+ subject.approved_delete_member('foo@bar.baz').should == :success
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe "MailManager" do
4
+
5
+ describe "Base" do
6
+ describe ".instance" do
7
+ it "should require setting the Mailman root directory first" do
8
+ lambda {
9
+ MailManager::Base.instance
10
+ }.should raise_error
11
+ end
12
+
13
+ it "should require that the Mailman directory exist" do
14
+ lambda {
15
+ MailManager.root = '/foo/bar'
16
+ MailManager::Base.instance
17
+ }.should raise_error
18
+ end
19
+
20
+ it "should require that the Mailman dir have a bin subdir" do
21
+ Dir.stub(:exist?).with('/foo/bar').and_return(true)
22
+ Dir.stub(:exist?).with('/foo/bar/bin').and_return(false)
23
+ lambda {
24
+ MailManager.root = '/foo/bar'
25
+ MailManager::Base.instance
26
+ }.should raise_error
27
+ end
28
+
29
+ context "with a valid Mailman dir" do
30
+ let(:mailman_path) { '/usr/local/mailman' }
31
+ let(:bin_files) { ['list_lists', 'newlist', 'inject'] }
32
+
33
+ before :each do
34
+ Dir.stub(:exist?).with(mailman_path).and_return(true)
35
+ Dir.stub(:exist?).with("#{mailman_path}/bin").and_return(true)
36
+ bin_files.each do |bf|
37
+ File.stub(:exist?).with("#{mailman_path}/bin/#{bf}").and_return(true)
38
+ end
39
+ end
40
+
41
+ it "should raise an error if one of the bin files is missing" do
42
+ File.stub(:exist?).with("#{mailman_path}/bin/inject").and_return(false)
43
+ lambda {
44
+ MailManager.root = mailman_path
45
+ MailManager::Base.instance
46
+ }.should raise_error
47
+ end
48
+
49
+ it "should succeed if the all the bin files are present" do
50
+ MailManager.root = mailman_path
51
+ MailManager::Base.instance.should_not be_nil
52
+ end
53
+
54
+ describe "#lists" do
55
+ it "should return an array of existing mailing lists" do
56
+
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ describe "List" do
64
+ describe ".initialize" do
65
+
66
+ end
67
+
68
+ describe ".create" do
69
+
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,3 @@
1
+ $:.unshift('../lib')
2
+ require 'mailmanager'
3
+
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mailmanager
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 8
9
+ version: 1.0.8
10
+ platform: ruby
11
+ authors:
12
+ - Wes Morgan
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-01-19 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: json
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 1
30
+ - 4
31
+ - 6
32
+ version: 1.4.6
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: open4
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 1
45
+ - 0
46
+ - 1
47
+ version: 1.0.1
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ description: Ruby wrapper library for GNU Mailman's admin functions
51
+ email: MorganW@dnc.org
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ extra_rdoc_files: []
57
+
58
+ files:
59
+ - lib/mailmanager/lib.rb
60
+ - lib/mailmanager/list.rb
61
+ - lib/mailmanager/version.rb
62
+ - lib/mailmanager.rb
63
+ - lib/mailmanager/listproxy.py
64
+ - spec/lib/mailmanager/lib_spec.rb
65
+ - spec/lib/mailmanager/list_spec.rb
66
+ - spec/lib/mailmanager_spec.rb
67
+ - spec/spec_helper.rb
68
+ - Changelog
69
+ - LICENSE
70
+ - README.rdoc
71
+ has_rdoc: true
72
+ homepage: http://github.com/dnclabs/mailmanager
73
+ licenses: []
74
+
75
+ post_install_message:
76
+ rdoc_options: []
77
+
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ segments:
94
+ - 0
95
+ version: "0"
96
+ requirements: []
97
+
98
+ rubyforge_project:
99
+ rubygems_version: 1.3.7
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: GNU Mailman wrapper for Ruby
103
+ test_files: []
104
+