mbox 0.0.4

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,91 @@
1
+ #! /usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'json'
4
+ require 'eventmachine'
5
+ require 'mbox'
6
+
7
+ options = {}
8
+
9
+ OptionParser.new do |o|
10
+ options[:host] = 'localhost'
11
+ options[:port] = 9001
12
+ options[:every] = (ENV['MBOX_DAEMON_EVERY'] || 120).to_f
13
+ options[:mail] = {
14
+ directory: ENV['MBOX_DAEMON_DIR'] || "#{ENV['HOME']}/mail",
15
+ boxes: (ENV['MBOX_DAEMON_BOXES'] || 'inbox').split(/\s*[;,]\s*/)
16
+ }
17
+
18
+ o.on '-h', '--host HOST', 'the host to bind to' do |value|
19
+ options[:host] = value
20
+ end
21
+
22
+ o.on '-p', '--port PORT', 'the port to bind to' do |value|
23
+ options[:port] = value.to_i
24
+ end
25
+
26
+ o.on '-e', '--every SECONDS', 'the seconds to check the email every' do |value|
27
+ options[:every] = value.to_f
28
+ end
29
+
30
+ o.on '-m', '--mail-directory PATH', 'the path where the mailboxes are at' do |value|
31
+ options[:mail][:directory] = File.realpath(File.expand_path(value))
32
+ end
33
+
34
+ o.on '-b', '--mail-boxes BOX...', Array, 'the mailboxes to check' do |value|
35
+ options[:mail][:boxes].push(*value)
36
+ end
37
+ end.parse!
38
+
39
+ %w[INT KILL].each {|sig|
40
+ trap sig do
41
+ EM.stop_event_loop
42
+ end
43
+ }
44
+
45
+ class Connection < EventMachine::Protocols::LineAndTextProtocol
46
+ @@unread = {}
47
+
48
+ attr_accessor :boxes
49
+
50
+ def receive_line (line)
51
+ whole, target, command, rest = line.match(/^(.*?)\s+(.*?)(?:\s+(.+))?$/).to_a
52
+
53
+ boxes = target == '*' ? @boxes : @boxes.select { |box| target.include? box.name }
54
+
55
+ if command == 'list'
56
+ command = rest
57
+
58
+ if command == 'unread'
59
+ send_response boxes.select { |box|
60
+ if @@unread[box] && @@unread[box].last_check < [File.ctime(box.path), File.mtime(box.path)].max
61
+ @@unread[box] = Struct.new(:status, :last_check).new(box.has_unread?, Time.new)
62
+ else
63
+ @@unread[box] ||= Struct.new(:status, :last_check).new(box.has_unread?, Time.new)
64
+ end
65
+
66
+ @@unread[box].status
67
+ }.map(&:name)
68
+ end
69
+ end
70
+ rescue Exception => e
71
+ send_data %{{"error":#{e.to_s.inspect}}}
72
+ end
73
+
74
+ def send_response (data)
75
+ send_data "#{data.to_json}\n"
76
+ end
77
+ end
78
+
79
+ EM.run {
80
+ boxes = options[:mail][:boxes].map {|name|
81
+ Mbox.open("#{options[:mail][:directory]}/#{name}")
82
+ }
83
+
84
+ EM.start_server options[:host], options[:port], Connection do |c|
85
+ c.boxes = boxes
86
+ end
87
+
88
+ EM.add_periodic_timer options[:every] do
89
+ `fetchmail`
90
+ end
91
+ }
@@ -0,0 +1,38 @@
1
+ #! /usr/bin/env ruby
2
+ require 'socket'
3
+ require 'json'
4
+ require 'optparse'
5
+
6
+ options = {}
7
+
8
+ OptionParser.new do |o|
9
+ options[:host] = 'localhost'
10
+ options[:port] = 9001
11
+ options[:target] = '*'
12
+
13
+ o.on '-h', '--host HOST', 'the host to connect to' do |value|
14
+ options[:host] = value
15
+ end
16
+
17
+ o.on '-p', '--port PORT', 'the port to connect to' do |value|
18
+ options[:port] = value.to_i
19
+ end
20
+
21
+ o.on '-t', '--target TARGET', 'the mailboxes to query' do |value|
22
+ options[:target] = value
23
+ end
24
+ end.parse!
25
+
26
+ socket = TCPSocket.new(options[:host], options[:port]) rescue abort('could not connect')
27
+ command = ARGV.shift
28
+
29
+ if command == 'list'
30
+ command = ARGV.shift
31
+
32
+ if command == 'unread'
33
+ socket.puts("#{options[:target]} list unread")
34
+ puts JSON.parse(socket.gets).join("\n")
35
+ end
36
+ end
37
+
38
+ socket.close
@@ -0,0 +1,20 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.doesntexist.org | meh@paranoici.org]
3
+ #
4
+ # This file is part of ruby-mbox.
5
+ #
6
+ # ruby-mbox is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ruby-mbox is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with ruby-mbox. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ require 'mbox/mbox'
@@ -0,0 +1,126 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.doesntexist.org | meh@paranoici.org]
3
+ #
4
+ # This file is part of ruby-mbox.
5
+ #
6
+ # ruby-mbox is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ruby-mbox is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with ruby-mbox. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ require 'mbox/mail/metadata'
21
+ require 'mbox/mail/headers'
22
+ require 'mbox/mail/content'
23
+
24
+ class Mbox
25
+
26
+ class Mail
27
+ def self.parse (input, options = {})
28
+ metadata = Mbox::Mail::Metadata.new
29
+ headers = Mbox::Mail::Headers.new
30
+ content = Mbox::Mail::Content.new(headers)
31
+
32
+ inside = {
33
+ metadata: true,
34
+ headers: false,
35
+ content: false
36
+ }
37
+
38
+ last = {
39
+ line: '',
40
+ stuff: ''
41
+ }
42
+
43
+ next until input.eof? || (line = input.readline).match(options[:separator])
44
+
45
+ return if !line || line.empty?
46
+
47
+ metadata.parse_from line
48
+
49
+ until input.eof? || ((line = input.readline).match(options[:separator]) && last[:line].empty?)
50
+ if inside[:metadata]
51
+ if line.match(/^>+/)
52
+ metadata.parse_from line
53
+ else
54
+ inside[:metadata] = false
55
+ inside[:headers] = true
56
+
57
+ last[:line] = line.chomp
58
+
59
+ next
60
+ end
61
+ elsif inside[:headers]
62
+ if line.strip.empty?
63
+ inside[:headers] = false
64
+ inside[:content] = true
65
+
66
+ headers.parse(last[:stuff])
67
+
68
+ last[:line] = line.chomp
69
+ last[:stuff] = ''
70
+
71
+ next
72
+ end
73
+
74
+ last[:stuff] << line
75
+ elsif inside[:content]
76
+ if options[:headers_only]
77
+ last[:line] = line.chomp
78
+
79
+ next
80
+ end
81
+
82
+ last[:stuff] << line
83
+ end
84
+
85
+ last[:line] = line.chomp
86
+ end
87
+
88
+ unless last[:stuff].empty?
89
+ content.parse(last[:stuff])
90
+ end
91
+
92
+ if !input.eof? && line
93
+ input.seek(-line.length, IO::SEEK_CUR)
94
+ end
95
+
96
+ Mail.new(metadata, headers, content)
97
+ end
98
+
99
+ attr_reader :metadata, :headers, :content
100
+
101
+ def initialize (metadata, headers, content)
102
+ @metadata = metadata
103
+ @headers = headers
104
+ @content = content
105
+ end
106
+
107
+ def save_to (path)
108
+ File.open(path, 'w') {|f|
109
+ f.write to_s
110
+ }
111
+ end
112
+
113
+ def unread?
114
+ !headers[:status].read? rescue true
115
+ end
116
+
117
+ def to_s
118
+ "#{headers}\n#{content}"
119
+ end
120
+
121
+ def inspect
122
+ "#<Mail:#{headers['From']}>"
123
+ end
124
+ end
125
+
126
+ end
@@ -0,0 +1,78 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.doesntexist.org | meh@paranoici.org]
3
+ #
4
+ # This file is part of ruby-mbox.
5
+ #
6
+ # ruby-mbox is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ruby-mbox is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with ruby-mbox. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ require 'mbox/mail/file'
21
+
22
+ class Mbox; class Mail
23
+
24
+ class Content < Array
25
+ attr_reader :headers, :attachments
26
+
27
+ def initialize (headers, content = [], attachments = [])
28
+ @headers = headers
29
+ @attachments = attachments
30
+
31
+ push *content
32
+ end
33
+
34
+ def parse (text, headers = {})
35
+ headers = @headers.merge(headers)
36
+ type = headers[:content_type]
37
+
38
+ if matches = type.mime.match(%r{multipart/(\w+)})
39
+ text.sub(/^.*?--#{type.boundary}\n/m, '').sub(/--#{type.boundary}--$/m, '').split("--#{type.boundary}\n").each {|part|
40
+ stream = StringIO.new(part)
41
+
42
+ headers = ''
43
+ until stream.eof? || (line = stream.readline).chomp.empty?
44
+ headers << line
45
+ end
46
+ headers = Headers.parse(headers)
47
+
48
+ content = !stream.eof? ? stream.readline : ''
49
+ until stream.eof? || line = stream.readline
50
+ content << line
51
+ end
52
+ content.chomp!
53
+
54
+ file = File.new(headers, content)
55
+
56
+ if (headers[:content_disposition] || '').match(/^attachment/)
57
+ attachments << file
58
+ else
59
+ self << file
60
+ end
61
+ }
62
+ else
63
+ stream = StringIO.new(text)
64
+
65
+ content = (!stream.eof?) ? stream.readline : ''
66
+ until stream.eof? || line = stream.readline
67
+ content << line
68
+ end
69
+ content.chomp!
70
+
71
+ self << File.new(Headers.new, content)
72
+ end
73
+
74
+ self
75
+ end
76
+ end
77
+
78
+ end; end
@@ -0,0 +1,55 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.doesntexist.org | meh@paranoici.org]
3
+ #
4
+ # This file is part of ruby-mbox.
5
+ #
6
+ # ruby-mbox is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ruby-mbox is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with ruby-mbox. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ require 'base64'
21
+
22
+ class Mbox; class Mail
23
+
24
+ class File
25
+ attr_reader :name, :headers, :content
26
+
27
+ def initialize (headers, content)
28
+ if headers[:content_type]
29
+ content.force_encoding headers[:content_type].charset
30
+ end
31
+
32
+ if headers[:content_transfer_encoding] == 'base64'
33
+ content = Base64.decode64(content)
34
+ end
35
+
36
+ if matches = headers[:content_disposition].match(/filename="(.*?)"/) rescue nil
37
+ @name = matches[1]
38
+ end
39
+
40
+ @headers = headers
41
+ @content = content
42
+ end
43
+
44
+ def to_s
45
+ @content
46
+ end
47
+
48
+ alias to_str to_s
49
+
50
+ def inspect
51
+ "#<File:#{name}>"
52
+ end
53
+ end
54
+
55
+ end; end
@@ -0,0 +1,146 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.doesntexist.org | meh@paranoici.org]
3
+ #
4
+ # This file is part of ruby-mbox.
5
+ #
6
+ # ruby-mbox is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ruby-mbox is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with ruby-mbox. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ require 'stringio'
21
+
22
+ require 'mbox/mail/headers/status'
23
+ require 'mbox/mail/headers/content_type'
24
+
25
+ class Mbox; class Mail
26
+
27
+ class Headers
28
+ def self.name_to_symbol (name)
29
+ return name if name.is_a? Symbol
30
+
31
+ name = name.to_s.downcase.gsub('-', '_').to_sym
32
+
33
+ if name.empty?
34
+ raise ArgumentError, 'cannot pass empty name'
35
+ end
36
+
37
+ name
38
+ end
39
+
40
+ def self.symbol_to_name (name)
41
+ name.to_s.downcase.gsub('_', '-').gsub(/(\A|-)(.)/) {|match|
42
+ match.upcase
43
+ }
44
+ end
45
+
46
+ def self.parse (input)
47
+ new.parse(input)
48
+ end
49
+
50
+ include Enumerable
51
+
52
+ def initialize (start = {})
53
+ @data = {}
54
+
55
+ merge! start
56
+ end
57
+
58
+ def each (&block)
59
+ @data.each(&block)
60
+ end
61
+
62
+ def [] (name)
63
+ @data[Headers.name_to_symbol(name)]
64
+ end
65
+
66
+ def []= (name, value)
67
+ name = Headers.name_to_symbol(name)
68
+
69
+ value = case name
70
+ when :status then Status.parse(value)
71
+ when :content_type then ContentType.parse(value)
72
+ else value
73
+ end
74
+
75
+ if tmp = @data[name] && !tmp.is_a?(Array)
76
+ @data[name] = [tmp]
77
+ end
78
+
79
+ if @data[name].is_a?(Array)
80
+ @data[name] << value
81
+ else
82
+ @data[name] = value
83
+ end
84
+ end
85
+
86
+ def delete (name)
87
+ @data.delete(Headers.name_to_symbol(name))
88
+ end
89
+
90
+ def merge! (other)
91
+ other.each {|name, value|
92
+ self[name] = value
93
+ }
94
+
95
+ self
96
+ end
97
+
98
+ def merge (other)
99
+ clone.merge!(other)
100
+ end
101
+
102
+ def parse (input)
103
+ input = if input.respond_to? :to_io
104
+ input.to_io
105
+ elsif input.is_a? String
106
+ StringIO.new(input)
107
+ else
108
+ raise ArgumentError, 'I do not know what to do.'
109
+ end
110
+
111
+ last = nil
112
+
113
+ until input.eof? || (line = input.readline).chomp.empty?
114
+ if !line.match(/^\s/)
115
+ next unless matches = line.match(/^([^:]+):\s*(.+)$/)
116
+
117
+ whole, name, value = matches.to_a
118
+
119
+ self[name] = value
120
+ last = name
121
+ elsif self[last]
122
+ if self[last].is_a?(String)
123
+ self[last] << " #{line}"
124
+ elsif self[last].is_a?(Array)
125
+ self[last].last << " #{line}"
126
+ end
127
+ end
128
+ end
129
+
130
+ self
131
+ end
132
+
133
+ def to_s
134
+ result = ''
135
+
136
+ each {|name, values|
137
+ [values].flatten.each {|value|
138
+ result << "#{Headers.symbol_to_name(name)}: #{value}\n"
139
+ }
140
+ }
141
+
142
+ result
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,56 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.doesntexist.org | meh@paranoici.org]
3
+ #
4
+ # This file is part of ruby-mbox.
5
+ #
6
+ # ruby-mbox is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ruby-mbox is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with ruby-mbox. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ class Mbox; class Mail; class Headers
21
+
22
+ class ContentType
23
+ def self.parse (text)
24
+ return text if text.is_a?(ContentType)
25
+
26
+ return ContentType.new unless text && text.is_a?(String)
27
+
28
+ stuff = text.gsub(/\n\r/, '').split(/\s*;\s*/)
29
+ type = stuff.shift
30
+
31
+ ContentType.new(Hash[stuff.map {|stuff|
32
+ stuff = stuff.strip.split(/=/)
33
+ stuff[0] = stuff[0].to_sym
34
+
35
+ if stuff[1][0] == '"' && stuff[1][stuff[1].length-1] == '"'
36
+ stuff[1] = stuff[1][1, stuff[1].length-2]
37
+ end
38
+
39
+ stuff
40
+ }].merge(mime: type))
41
+ end
42
+
43
+ attr_accessor :mime, :charset, :boundary
44
+
45
+ def initialize (data = {})
46
+ @mime = data[:mime] || 'text/plain'
47
+ @charset = data[:charset]
48
+ @boundary = data[:boundary]
49
+ end
50
+
51
+ def to_s
52
+ "#{mime}#{"; charset=#{charset}" if charset}#{"; boundary=#{boundary}" if boundary}"
53
+ end
54
+ end
55
+
56
+ end; end; end
@@ -0,0 +1,46 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.doesntexist.org | meh@paranoici.org]
3
+ #
4
+ # This file is part of ruby-mbox.
5
+ #
6
+ # ruby-mbox is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ruby-mbox is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with ruby-mbox. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ class Mbox; class Mail; class Headers
21
+
22
+ class Status
23
+ def self.parse (text)
24
+ return text if text.is_a?(self)
25
+
26
+ return Status.new unless text && text.is_a?(String)
27
+
28
+ Status.new(text.include?('R'), text.include?('O'))
29
+ end
30
+
31
+ def initialize (read = false, old = false)
32
+ @read = read
33
+ @old = old
34
+ end
35
+
36
+ def read?; @read; end
37
+ def old?; @old; end
38
+
39
+ def unread?; !read?; end
40
+
41
+ def to_s
42
+ "#{'R' if read?}#{'O' if old?}"
43
+ end
44
+ end
45
+
46
+ end; end; end
@@ -0,0 +1,36 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.doesntexist.org | meh@paranoici.org]
3
+ #
4
+ # This file is part of ruby-mbox.
5
+ #
6
+ # ruby-mbox is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ruby-mbox is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with ruby-mbox. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ class Mbox; class Mail
21
+
22
+ class Metadata
23
+ attr_reader :from
24
+
25
+ def initialize
26
+ @from = []
27
+ end
28
+
29
+ def parse_from (line)
30
+ line.match /^>*From ([^\s]+) (.{24})/ do |m|
31
+ @from << Struct.new(:name, :date).new(m[1], m[2])
32
+ end
33
+ end
34
+ end
35
+
36
+ end; end
@@ -0,0 +1,140 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.doesntexist.org | meh@paranoici.org]
3
+ #
4
+ # This file is part of ruby-mbox.
5
+ #
6
+ # ruby-mbox is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ruby-mbox is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with ruby-mbox. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ require 'stringio'
21
+
22
+ require 'mbox/mail'
23
+
24
+ class Mbox
25
+ def self.open (path, options = {})
26
+ input = File.open(File.expand_path(path), 'r+:ASCII-8BIT')
27
+
28
+ Mbox.new(input, options).tap {|mbox|
29
+ mbox.path = path
30
+ mbox.name = File.basename(path)
31
+
32
+ if block_given?
33
+ yield mbox
34
+
35
+ mbox.close
36
+ else
37
+ ObjectSpace.define_finalizer mbox, finalizer(input)
38
+ end
39
+ }
40
+ end
41
+
42
+ def self.finalizer (io)
43
+ proc { io.close }
44
+ end
45
+
46
+ include Enumerable
47
+
48
+ attr_reader :options
49
+ attr_accessor :name, :path
50
+
51
+ def initialize (what, options = {})
52
+ @input = if what.respond_to? :to_io
53
+ what.to_io
54
+ elsif what.is_a? String
55
+ StringIO.new(what)
56
+ else
57
+ raise ArgumentError, 'I do not know what to do.'
58
+ end
59
+
60
+ @options = { separator: /^From [^\s]+ .{24}/ }.merge(options)
61
+ end
62
+
63
+ def close
64
+ @input.close
65
+ end
66
+
67
+ def each (opts = {})
68
+ @input.seek 0
69
+
70
+ while mail = Mail.parse(@input, options.merge(opts))
71
+ yield mail
72
+ end
73
+ end
74
+
75
+ def [] (index, opts = {})
76
+ seek index
77
+
78
+ if @input.eof?
79
+ raise IndexError, "#{index} is out of range"
80
+ end
81
+
82
+ Mail.parse(@input, options.merge(opts))
83
+ end
84
+
85
+ def seek (to, whence = IO::SEEK_SET)
86
+ if whence == IO::SEEK_SET
87
+ @input.seek 0
88
+ end
89
+
90
+ last = ''
91
+ index = -1
92
+
93
+ while line = @input.readline rescue nil
94
+ if line.match(options[:separator]) && last.chomp.empty?
95
+ index += 1
96
+
97
+ if index >= to
98
+ @input.seek(-line.length, IO::SEEK_CUR)
99
+
100
+ break
101
+ end
102
+ end
103
+
104
+ last = line
105
+ end
106
+
107
+ self
108
+ end
109
+
110
+ def length
111
+ @input.seek(0)
112
+
113
+ last = ''
114
+ length = 0
115
+
116
+ while line = @input.readline rescue nil
117
+ if line.match(options[:separator]) && last.chomp.empty?
118
+ length += 1
119
+ end
120
+
121
+ last = line
122
+ end
123
+
124
+ length
125
+ end
126
+
127
+ alias size length
128
+
129
+ def has_unread?
130
+ each headers_only: true do |mail|
131
+ return true if mail.unread?
132
+ end
133
+
134
+ false
135
+ end
136
+
137
+ def inspect
138
+ "#<Mbox:#{name} length=#{length}>"
139
+ end
140
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mbox
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - meh.
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-06 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: A simple library to read mbox files.
15
+ email: meh@paranoici.org
16
+ executables:
17
+ - mbox-do
18
+ - mbox-daemon
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - lib/mbox.rb
23
+ - lib/mbox/mbox.rb
24
+ - lib/mbox/mail.rb
25
+ - lib/mbox/mail/headers.rb
26
+ - lib/mbox/mail/metadata.rb
27
+ - lib/mbox/mail/file.rb
28
+ - lib/mbox/mail/content.rb
29
+ - lib/mbox/mail/headers/status.rb
30
+ - lib/mbox/mail/headers/content_type.rb
31
+ - bin/mbox-do
32
+ - bin/mbox-daemon
33
+ homepage: http://github.com/meh/ruby-mbox
34
+ licenses: []
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ! '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubyforge_project:
53
+ rubygems_version: 1.8.23
54
+ signing_key:
55
+ specification_version: 3
56
+ summary: A simple library to read mbox files.
57
+ test_files: []