nilclass-maildir 0.4.1

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.
@@ -0,0 +1,20 @@
1
+ module Maildir::Serializer
2
+ # The Maildir::Serializer::Base class reads & writes data to disk as a
3
+ # string. Other serializers (e.g. Maildir::Serializer::Mail) can extend this
4
+ # class to do some pre- and post-processing of the string.
5
+ #
6
+ # The Serializer API has two methods:
7
+ # load(path) # => returns data
8
+ # dump(data, path) # => returns number of bytes written
9
+ class Base
10
+ # Reads the file at path. Returns the contents of path.
11
+ def load(path)
12
+ File.read(path)
13
+ end
14
+
15
+ # Writes data to path. Returns number of bytes written.
16
+ def dump(data, path)
17
+ File.open(path, "w") {|file| file.write(data)}
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ require 'json'
2
+ # Serialize messages as JSON
3
+ class Maildir::Serializer::JSON < Maildir::Serializer::Base
4
+ # Read data from path and parse it as JSON.
5
+ def load(path)
6
+ ::JSON.load(super(path))
7
+ end
8
+
9
+ # Dump data as JSON and writes it to path.
10
+ def dump(data, path)
11
+ super(data.to_json, path)
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'mail'
2
+ # Serialize messages as a ruby Mail object
3
+ class Maildir::Serializer::Mail < Maildir::Serializer::Base
4
+ # Build a new Mail object from the data at path.
5
+ def load(path)
6
+ ::Mail.new(super(path))
7
+ end
8
+
9
+ # Write data to path as a Mail message.
10
+ def dump(data, path)
11
+ super(data.to_s, path)
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # Serialize messages as Marshalled ruby objects
2
+ class Maildir::Serializer::Marshal < Maildir::Serializer::Base
3
+ # Read data from path and unmarshal it.
4
+ def load(path)
5
+ ::Marshal.load(super(path))
6
+ end
7
+
8
+ # Marshal data and write it to path.
9
+ def dump(data, path)
10
+ super(::Marshal.dump(data), path)
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ require 'yaml'
2
+ # Serialize messages as YAML
3
+ class Maildir::Serializer::YAML < Maildir::Serializer::Base
4
+ # Read data from path and parse it as YAML.
5
+ def load(path)
6
+ ::YAML.load(super(path))
7
+ end
8
+
9
+ # Dump data as YAML and writes it to path.
10
+ def dump(data, path)
11
+ super(data.to_yaml, path)
12
+ end
13
+ end
@@ -0,0 +1,70 @@
1
+ # implements subdirs as used by the Courier Mail Server (courier-mta.org)
2
+ require 'maildir'
3
+ module Maildir::Subdirs
4
+ ROOT_NAME = 'INBOX'
5
+ DELIM = '.'
6
+
7
+ def self.included(base)
8
+ base.instance_eval do
9
+ alias_method :inspect_without_subdirs, :inspect
10
+ alias_method :inspect, :inspect_with_subdirs
11
+ end
12
+ end
13
+
14
+ def name
15
+ root? ? ROOT_NAME : subdir_parts(path).last
16
+ end
17
+
18
+ def create_subdir(name)
19
+ raise ArgumentError.new("'name' may not contain delimiter character (#{DELIM})") if name.include?(DELIM)
20
+ full_name = (root? ? [] : subdir_parts(File.basename(path))).push(name).unshift('').join(DELIM)
21
+ md = Maildir.new(File.join(path, full_name), true)
22
+ @subdirs << md if @subdirs
23
+ md
24
+ end
25
+
26
+ # returns the logical mailbox path
27
+ def mailbox_path
28
+ @mailbox_path ||= root? ? ROOT_NAME : subdir_parts(File.basename(path)).unshift(ROOT_NAME).join(DELIM)
29
+ end
30
+
31
+ # returns an array of Maildir objects representing the direct subdirectories of this Maildir
32
+ def subdirs(only_direct=true)
33
+ if root?
34
+ @subdirs ||= (Dir.entries(path) - %w(. ..)).select {|e|
35
+ e =~ /^\./ && File.directory?(File.join(path, e)) && (only_direct ? subdir_parts(e).size == 1 : true)
36
+ }.map { |e| Maildir.new(File.join(path, e), false) }
37
+ else
38
+ my_parts = subdir_parts(File.basename(path))
39
+ @subdirs ||= root.subdirs(false).select { |md| subdir_parts(File.basename(md.path))[0..-2] == my_parts }
40
+ end
41
+ end
42
+
43
+ # Friendly inspect method
44
+ def inspect_with_subdirs
45
+ "#<#{self.class} path=#{@path} mailbox_path=#{mailbox_path}>"
46
+ end
47
+
48
+ # returns the Maildir representing the root directory
49
+ def root
50
+ root? ? self : Maildir.new(File.dirname(path), false)
51
+ end
52
+
53
+ # returns true if the parent directory doesn't look like a maildir
54
+ def root?
55
+ ! ((Dir.entries(File.dirname(path)) & %w(cur new tmp)).size == 3)
56
+ end
57
+
58
+ private
59
+
60
+ def subdir_parts(path)
61
+ path.sub!(/\/$/, '') # remove trailing slash
62
+ parts = (path.split(DELIM) - [''])
63
+ # some clients (e.g. Thunderbird) mess up namespaces so subdirs
64
+ # end up looking like '.INBOX.Trash' instead of '.Trash'
65
+ parts.shift if parts.first == ROOT_NAME
66
+ parts
67
+ end
68
+ end
69
+
70
+ Maildir.send(:include, Maildir::Subdirs)
@@ -0,0 +1,72 @@
1
+ # Class for generating unique file names for new messages
2
+ class Maildir::UniqueName
3
+ require 'thread' # For mutex support
4
+ require 'socket' # For getting the hostname
5
+ class << self
6
+ # Return a thread-safe increasing counter
7
+ def counter
8
+ @counter_mutex ||= Mutex.new
9
+ @counter_mutex.synchronize do
10
+ @counter = @counter.to_i + 1
11
+ end
12
+ end
13
+
14
+ def create
15
+ self.new.to_s
16
+ end
17
+ end
18
+
19
+ # Return a unique file name based on strategy
20
+ def initialize
21
+ # Use the same time object
22
+ @now = Time.now
23
+ end
24
+
25
+ # Return the name as a string
26
+ def to_s
27
+ [left, middle, right].join(".")
28
+ end
29
+
30
+ protected
31
+ # The left part of the unique name is the number of seconds from since the
32
+ # UNIX epoch
33
+ def left
34
+ @now.to_i.to_s
35
+ end
36
+
37
+ # The middle part contains the microsecond, the process id, and a
38
+ # per-process incrementing counter
39
+ def middle
40
+ "M#{microsecond}P#{process_id}Q#{delivery_count}"
41
+ end
42
+
43
+ # The right part is the hostname
44
+ def right
45
+ Socket.gethostname
46
+ end
47
+
48
+ def secure_random(bytes=8)
49
+ # File.read("/dev/urandom", bytes).unpack("H*")[0]
50
+ raise "Not implemented"
51
+ end
52
+
53
+ def inode
54
+ raise "Not implemented"
55
+ end
56
+
57
+ def device_number
58
+ raise "Not implemented"
59
+ end
60
+
61
+ def microsecond
62
+ @now.usec.to_s
63
+ end
64
+
65
+ def process_id
66
+ Process.pid.to_s
67
+ end
68
+
69
+ def delivery_count
70
+ self.class.counter.to_s
71
+ end
72
+ end
data/maildir.gemspec ADDED
@@ -0,0 +1,81 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{maildir}
8
+ s.version = "0.4.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Aaron Suggs", "Niklas E. Cathor"]
12
+ s.date = %q{2010-01-24}
13
+ s.description = %q{A ruby library for reading and writing arbitrary messages in DJB's maildir format}
14
+ s.email = %q{aaron@ktheory.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "benchmarks/runner",
26
+ "lib/maildir.rb",
27
+ "lib/maildir/keywords.rb",
28
+ "lib/maildir/message.rb",
29
+ "lib/maildir/serializer/base.rb",
30
+ "lib/maildir/serializer/json.rb",
31
+ "lib/maildir/serializer/mail.rb",
32
+ "lib/maildir/serializer/marshal.rb",
33
+ "lib/maildir/serializer/yaml.rb",
34
+ "lib/maildir/subdirs.rb",
35
+ "lib/maildir/unique_name.rb",
36
+ "maildir.gemspec",
37
+ "test/test_helper.rb",
38
+ "test/test_keywords.rb",
39
+ "test/test_maildir.rb",
40
+ "test/test_message.rb",
41
+ "test/test_serializers.rb",
42
+ "test/test_subdirs.rb",
43
+ "test/test_unique_name.rb"
44
+ ]
45
+ s.homepage = %q{http://github.com/ktheory/maildir}
46
+ s.rdoc_options = ["--charset=UTF-8"]
47
+ s.require_paths = ["lib"]
48
+ s.rubygems_version = %q{1.3.5}
49
+ s.summary = %q{Read & write messages in the maildir format}
50
+ s.test_files = [
51
+ "test/test_serializers.rb",
52
+ "test/test_keywords.rb",
53
+ "test/test_helper.rb",
54
+ "test/test_subdirs.rb",
55
+ "test/test_message.rb",
56
+ "test/test_unique_name.rb",
57
+ "test/test_maildir.rb"
58
+ ]
59
+
60
+ if s.respond_to? :specification_version then
61
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
62
+ s.specification_version = 3
63
+
64
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
65
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
66
+ s.add_development_dependency(%q<mail>, [">= 0"])
67
+ s.add_development_dependency(%q<json>, [">= 0"])
68
+ s.add_development_dependency(%q<ktheory-fakefs>, [">= 0"])
69
+ else
70
+ s.add_dependency(%q<shoulda>, [">= 0"])
71
+ s.add_dependency(%q<mail>, [">= 0"])
72
+ s.add_dependency(%q<json>, [">= 0"])
73
+ s.add_dependency(%q<ktheory-fakefs>, [">= 0"])
74
+ end
75
+ else
76
+ s.add_dependency(%q<shoulda>, [">= 0"])
77
+ s.add_dependency(%q<mail>, [">= 0"])
78
+ s.add_dependency(%q<json>, [">= 0"])
79
+ s.add_dependency(%q<ktheory-fakefs>, [">= 0"])
80
+ end
81
+ end
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'fileutils'
5
+ require 'maildir'
6
+
7
+ # Require all the serializers
8
+ serializers = File.join(File.dirname(__FILE__), "..","lib","maildir","serializer","*")
9
+ Dir.glob(serializers).each do |serializer|
10
+ require serializer
11
+ end
12
+
13
+ # Require 'ktheory-fakefs' until issues 28, 29, and 30 are resolved in
14
+ # defunkt/fakefs. See http://github.com/defunkt/fakefs/issues
15
+ gem "ktheory-fakefs"
16
+ require 'fakefs'
17
+
18
+ # Create a reusable maildir that's cleaned up when the tests are done
19
+ def temp_maildir
20
+ Maildir.new("/tmp/maildir_test")
21
+ end
22
+
23
+ # create the subdir tree:
24
+ # | INBOX
25
+ # |-- a
26
+ # | |-- x
27
+ # | |-- y
28
+ # |-- b
29
+ def setup_subdirs(maildir)
30
+ %w(a b a.x a.y).each do |x|
31
+ Maildir.new(File.join(maildir.path, ".#{x}"))
32
+ end
33
+ end
34
+
35
+ # Useful for testing that strings defined & not empty
36
+ def assert_not_empty(obj, msg='')
37
+ assert !obj.nil? && !obj.empty?, msg
38
+ end
@@ -0,0 +1,16 @@
1
+ require 'test_helper'
2
+ require 'maildir/keywords'
3
+ class TestKeywords < Test::Unit::TestCase
4
+ context "A message" do
5
+ setup do
6
+ @data = "foo\n"
7
+ @msg = Maildir::Message.create(temp_maildir, @data)
8
+ end
9
+
10
+ should "remember keywords" do
11
+ kw = %w(Junk Seen)
12
+ @msg.keywords = kw
13
+ assert (@msg.keywords & kw).size == 2
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,73 @@
1
+ require 'test_helper'
2
+ class TestMaildir < Test::Unit::TestCase
3
+
4
+ context "A maildir" do
5
+ setup do
6
+ FakeFS::FileSystem.clear
7
+ end
8
+
9
+ should "have a path" do
10
+ assert_not_empty temp_maildir.path
11
+ end
12
+
13
+ should "create subdirectories by default" do
14
+ %w(tmp new cur).each do |subdir|
15
+ subdir_path = temp_maildir.send("#{subdir}_path")
16
+ assert File.directory?(subdir_path), "Subdir #{subdir} does not exist"
17
+ end
18
+ end
19
+
20
+ should "expand paths" do
21
+ maildir = Maildir.new("~/test_maildir/")
22
+ expanded_path = File.expand_path("~/test_maildir")
23
+ expanded_path = File.join(expanded_path, "/")
24
+ assert_equal expanded_path, maildir.path
25
+ end
26
+
27
+ should "not create directories if specified" do
28
+ maildir = Maildir.new("/maildir_without_subdirs", false)
29
+ %w(tmp new cur).each do |subdir|
30
+ subdir_path = maildir.send("#{subdir}_path")
31
+ assert !File.directory?(subdir_path), "Subdir #{subdir} exists"
32
+ end
33
+ end
34
+
35
+ should "be identical to maildirs with the same path" do
36
+ new_maildir = Maildir.new(temp_maildir.path)
37
+ assert_equal temp_maildir.path, new_maildir.path
38
+ assert_equal temp_maildir, new_maildir
39
+ end
40
+
41
+ should "list a new message" do
42
+ @message = temp_maildir.add("")
43
+ messages = temp_maildir.list(:new)
44
+ assert messages.include?(@message)
45
+ end
46
+
47
+ should "list a cur message" do
48
+ @message = temp_maildir.add("")
49
+ @message.process
50
+ messages = temp_maildir.list(:cur)
51
+ assert messages.include?(@message)
52
+ end
53
+ end
54
+
55
+ context "Maildirs with the same path" do
56
+ should "be identical" do
57
+ another_maildir = Maildir.new(temp_maildir.path, false)
58
+ assert_equal temp_maildir, another_maildir
59
+ end
60
+ end
61
+
62
+ context "Maildirs with stale messages in tmp" do
63
+ should "be found" do
64
+ stale_path = File.join(temp_maildir.path, "tmp", "stale_message")
65
+ File.open(stale_path, "w"){|f| f.write("")}
66
+ stale_time = Time.now - 30*24*60*60 # 1 month ago
67
+ File.utime(stale_time, stale_time, stale_path)
68
+
69
+ stale_tmp = [temp_maildir.get("tmp/stale_message")]
70
+ assert_equal stale_tmp, temp_maildir.get_stale_tmp
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,226 @@
1
+ require 'test_helper'
2
+ class TestMessage < Test::Unit::TestCase
3
+
4
+ context "An new, unwritten message" do
5
+ setup do
6
+ FakeFS::FileSystem.clear
7
+ @message = Maildir::Message.new(temp_maildir)
8
+ end
9
+
10
+ should "be in :tmp" do
11
+ assert_equal :tmp, @message.dir
12
+ assert_match(/tmp/, @message.path)
13
+ end
14
+
15
+ should "have a unique name" do
16
+ assert_not_empty @message.unique_name
17
+ end
18
+
19
+ should "have a file name" do
20
+ assert_not_empty @message.filename
21
+ end
22
+
23
+ should "have no info" do
24
+ assert_nil @message.info
25
+ end
26
+
27
+ should "not be able to set info" do
28
+ assert_raises RuntimeError do
29
+ @message.info= "2,FRS"
30
+ end
31
+ end
32
+ end
33
+
34
+ context "A written message" do
35
+ setup do
36
+ FakeFS::FileSystem.clear
37
+ @message = Maildir::Message.new(temp_maildir)
38
+ @data = "foo"
39
+ @message.write(@data)
40
+ end
41
+
42
+ should "not be writable" do
43
+ assert_raise RuntimeError do
44
+ @message.write("nope!")
45
+ end
46
+ end
47
+
48
+ should "have no info" do
49
+ assert_nil @message.info
50
+ end
51
+
52
+ should "not be able to set info" do
53
+ assert_raises RuntimeError do
54
+ @message.info= "2,FRS"
55
+ end
56
+ end
57
+
58
+ should "be in new" do
59
+ assert_equal :new, @message.dir
60
+ assert_match(/new/, @message.path)
61
+ end
62
+
63
+ should "have a file" do
64
+ assert File.exists?(@message.path)
65
+ end
66
+
67
+ should "have the correct data" do
68
+ assert_equal @data, @message.data
69
+ end
70
+ end
71
+
72
+ context "A processed message" do
73
+ setup do
74
+ FakeFS::FileSystem.clear
75
+ @data = "foo"
76
+ @message = Maildir::Message.create(temp_maildir, @data)
77
+ @message.process
78
+ end
79
+
80
+ should "not be writable" do
81
+ assert_raise RuntimeError do
82
+ @message.write("nope!")
83
+ end
84
+ end
85
+
86
+ should "be in cur" do
87
+ assert_equal :cur, @message.dir
88
+ end
89
+
90
+ should "have info" do
91
+ assert_equal Maildir::Message::INFO, @message.info
92
+ end
93
+
94
+ should "set info" do
95
+ info = "2,FRS"
96
+ @message.info = "2,FRS"
97
+ assert_equal @message.info, info
98
+ assert_match /#{info}$/, @message.path
99
+ end
100
+
101
+ should "add and remove flags" do
102
+ @message.add_flag('S')
103
+ assert_equal ['S'], @message.flags
104
+
105
+ # Test lowercase
106
+ @message.add_flag('r')
107
+ assert_equal ['R', 'S'], @message.flags
108
+
109
+ @message.remove_flag('S')
110
+ assert_equal ['R'], @message.flags
111
+
112
+ # Test lowercase
113
+ @message.remove_flag('r')
114
+ assert_equal [], @message.flags
115
+ end
116
+
117
+ flag_tests = {
118
+ "FRS" => ['F', 'R', 'S'],
119
+ "Sr" => ['R', 'S'], # test capitalization & sorting
120
+ '' => []
121
+ }
122
+ flag_tests.each do |arg, results|
123
+ should "set flags: #{arg}" do
124
+ @message.flags = arg
125
+ assert_equal results, @message.flags
126
+ path_suffix = "#{Maildir::Message::INFO}#{results.join('')}"
127
+ assert_match /#{path_suffix}$/, @message.path
128
+ end
129
+ end
130
+ end
131
+
132
+ context "A destroyed message" do
133
+ setup do
134
+ FakeFS::FileSystem.clear
135
+ @message = Maildir::Message.create(temp_maildir, "foo")
136
+ @message.destroy
137
+ end
138
+ should "be frozen" do
139
+ assert @message.frozen?, "Message is not frozen"
140
+ end
141
+ should "have a nonexistant path" do
142
+ assert !File.exists?(@message.path), "Message path exists"
143
+ end
144
+ end
145
+
146
+ context "A message with a bad path" do
147
+ setup do
148
+ FakeFS::FileSystem.clear
149
+ @message = temp_maildir.add("")
150
+ File.delete(@message.path)
151
+ end
152
+
153
+ should "raise error for data" do
154
+ assert_raise Errno::ENOENT do
155
+ @message.data
156
+ end
157
+ assert @message.frozen?
158
+ end
159
+
160
+ should "not be processed" do
161
+ old_key = @message.key
162
+ assert_equal false, @message.process
163
+ assert @message.frozen?
164
+ end
165
+
166
+
167
+ should "reset to the old key after attempt to process" do
168
+ old_key = @message.key
169
+ @message.process
170
+ assert_equal old_key, @message.key
171
+ end
172
+ end
173
+
174
+ context "Different messages" do
175
+ setup do
176
+ FakeFS::FileSystem.clear
177
+ end
178
+
179
+ should "differ" do
180
+ @message1 = temp_maildir.add("")
181
+ @message2 = temp_maildir.add("")
182
+ assert_equal -1, @message1 <=> @message2
183
+ assert_equal 1, @message2 <=> @message1
184
+ assert_not_equal @message1, @message2
185
+ end
186
+ end
187
+
188
+ context "Identical messages" do
189
+ setup do
190
+ FakeFS::FileSystem.clear
191
+ end
192
+
193
+ should "be identical" do
194
+ @message1 = temp_maildir.add("")
195
+ another_message1 = temp_maildir.get(@message1.key)
196
+ assert_equal @message1, another_message1
197
+ end
198
+ end
199
+
200
+ context "Message#utime" do
201
+ setup do
202
+ FakeFS::FileSystem.clear
203
+ end
204
+
205
+ should "update the messages mtime" do
206
+ @message = temp_maildir.add("")
207
+ time = Time.now - 60
208
+
209
+ @message.utime(time, time)
210
+
211
+ # Time should be within 1 second of each other
212
+ assert_in_delta time, @message.mtime, 1
213
+ end
214
+
215
+ # atime not currently supported in FakeFS
216
+ # should "update the messages atime" do
217
+ # @message = temp_maildir.add("")
218
+ # time = Time.now - 60
219
+ #
220
+ # @message.utime(time, time)
221
+ #
222
+ # # Time should be within 1 second of each other
223
+ # assert_in_delta time, @message.atime, 1
224
+ # end
225
+ end
226
+ end