mailmanager 1.0.8

Sign up to get free protection for your applications and to get access to all the features.
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
+