notes-structured-text-json-messages 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +29 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/bin/notes_structured_text_json_messages +41 -0
- data/lib/notes_structured_text_json_messages.rb +228 -0
- data/spec/notes_structured_text_json_messages_spec.rb +507 -0
- data/spec/spec_helper.rb +12 -0
- metadata +139 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Trampoline Systems Ltd
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
= notes-structured-text-json-messages
|
2
|
+
|
3
|
+
A command-line tool for transforming Lotus Notes Structured Text exports into JSON message structures for SONAR import
|
4
|
+
|
5
|
+
notes_structured_text_json_messages <output_dir> <input_file> [<input_file>*]
|
6
|
+
|
7
|
+
= Dependencies
|
8
|
+
* actionmailer ~> 2.3.11
|
9
|
+
* RubyGems
|
10
|
+
|
11
|
+
= Install
|
12
|
+
|
13
|
+
gem install notes-structured-text-json-messages
|
14
|
+
|
15
|
+
== Contributing to notes-structured-text-json-messages
|
16
|
+
|
17
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
18
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
19
|
+
* Fork the project
|
20
|
+
* Start a feature/bugfix branch
|
21
|
+
* Commit and push until you are happy with your contribution
|
22
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
23
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
24
|
+
|
25
|
+
== Copyright
|
26
|
+
|
27
|
+
Copyright (c) 2011 Trampoline Systems Ltd. See LICENSE.txt for
|
28
|
+
further details.
|
29
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |gem|
|
6
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
7
|
+
gem.name = "notes-structured-text-json-messages"
|
8
|
+
gem.homepage = "http://github.com/trampoline/notes-structured-text-json-messages"
|
9
|
+
gem.license = "MIT"
|
10
|
+
gem.summary = %Q{produces json message descriptions from lotus notes structured text exports}
|
11
|
+
gem.description = %Q{parses lotus notes structured text exports, producing a json message file for each each message}
|
12
|
+
gem.email = "craig@trampolinesystems.com"
|
13
|
+
gem.authors = ["craig mcmillan"]
|
14
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
15
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
16
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
17
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
18
|
+
gem.add_runtime_dependency "actionmailer", "~> 2.3.11"
|
19
|
+
gem.add_development_dependency "rspec", "~> 1.3.0"
|
20
|
+
gem.add_development_dependency "jeweler", "~> 1.5.2"
|
21
|
+
gem.add_development_dependency "rcov", ">= 0"
|
22
|
+
end
|
23
|
+
Jeweler::RubygemsDotOrgTasks.new
|
24
|
+
|
25
|
+
require 'spec/rake/spectask'
|
26
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
27
|
+
spec.libs << 'lib' << 'spec'
|
28
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
29
|
+
end
|
30
|
+
|
31
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
32
|
+
spec.libs << 'lib' << 'spec'
|
33
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
34
|
+
spec.rcov = true
|
35
|
+
end
|
36
|
+
|
37
|
+
task :default => :spec
|
38
|
+
|
39
|
+
require 'rake/rdoctask'
|
40
|
+
Rake::RDocTask.new do |rdoc|
|
41
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
42
|
+
|
43
|
+
rdoc.rdoc_dir = 'rdoc'
|
44
|
+
rdoc.title = "notes-structured-text-json-messages #{version}"
|
45
|
+
rdoc.rdoc_files.include('README*')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$: << File.expand_path("../../lib", __FILE__)
|
3
|
+
require 'rubygems'
|
4
|
+
require 'action_mailer'
|
5
|
+
require 'notes_structured_text_json_messages'
|
6
|
+
require 'optparse'
|
7
|
+
require 'logger'
|
8
|
+
|
9
|
+
options={:verbose=>true}
|
10
|
+
|
11
|
+
USAGE = "Usage: #{File.basename(__FILE__)} <output_dir> <input_files> [<input_file>]* "
|
12
|
+
|
13
|
+
OptionParser.new do |opts|
|
14
|
+
opts.banner = "Usage: #{File.basename(__FILE__)} <output_dir> <input_files> [<input_file>]* "
|
15
|
+
|
16
|
+
opts.on("-v", "--[no-]verbose", "Run verbosely (default: true)") do |v|
|
17
|
+
options[:verbose] = v
|
18
|
+
end
|
19
|
+
end.parse!
|
20
|
+
|
21
|
+
NotesStructuredTextJsonMessages.logger = Logger.new($stderr)
|
22
|
+
if options[:verbose]
|
23
|
+
NotesStructuredTextJsonMessages.logger.level=Logger::INFO
|
24
|
+
else
|
25
|
+
NotesStructuredTextJsonMessages.logger.level=Logger::WARN
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
raise "insufficient arguments" if ARGV.length<2
|
30
|
+
|
31
|
+
output_dir = ARGV[0]
|
32
|
+
input_files = ARGV[1..-1]
|
33
|
+
|
34
|
+
NotesStructuredTextJsonMessages.json_messages(output_dir, input_files, options)
|
35
|
+
NotesStructuredTextJsonMessages.log{|logger| logger.info("complete")}
|
36
|
+
rescue Exception=>e
|
37
|
+
NotesStructuredTextJsonMessages.log{|logger| logger.error(e)}
|
38
|
+
NotesStructuredTextJsonMessages.log{|logger| logger.info(USAGE)}
|
39
|
+
exit(1)
|
40
|
+
end
|
41
|
+
exit(0)
|
@@ -0,0 +1,228 @@
|
|
1
|
+
require 'md5'
|
2
|
+
require 'tmail'
|
3
|
+
|
4
|
+
module NotesStructuredTextJsonMessages
|
5
|
+
class << self
|
6
|
+
attr_accessor :logger
|
7
|
+
attr_accessor :stats
|
8
|
+
end
|
9
|
+
|
10
|
+
module_function
|
11
|
+
|
12
|
+
def log
|
13
|
+
yield logger if logger
|
14
|
+
end
|
15
|
+
|
16
|
+
def reset_stats
|
17
|
+
self.stats={}
|
18
|
+
end
|
19
|
+
|
20
|
+
def increment_stats(key)
|
21
|
+
self.stats[key] = (self.stats[key]||0) + 1
|
22
|
+
end
|
23
|
+
|
24
|
+
def json_messages(output_dir, input_files, options={})
|
25
|
+
reset_stats
|
26
|
+
[*input_files].each do |input_file|
|
27
|
+
File.open(input_file, "r") do |input|
|
28
|
+
json_messages_from_stream(output_dir, input, options)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
stats.each do |k,v|
|
32
|
+
log{|logger| logger.info("#{k}: #{v}")}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def json_messages_from_stream(output_dir, input, options={})
|
37
|
+
block = nil
|
38
|
+
process_block(output_dir, block, options) while block=read_block(input)
|
39
|
+
end
|
40
|
+
|
41
|
+
def read_block(input)
|
42
|
+
return nil if input.eof?
|
43
|
+
block = []
|
44
|
+
begin
|
45
|
+
l = input.readline.chomp
|
46
|
+
block << l if l.length>0
|
47
|
+
end while !input.eof? && l != ""
|
48
|
+
block
|
49
|
+
end
|
50
|
+
|
51
|
+
def is_message_block?(block)
|
52
|
+
!!header_value(block, MESSAGE_ID)
|
53
|
+
end
|
54
|
+
|
55
|
+
def is_distinguished_name?(addr)
|
56
|
+
!!(addr =~ /CN=/)
|
57
|
+
end
|
58
|
+
|
59
|
+
def header_value(block, header)
|
60
|
+
patt = /^#{Regexp.quote(header)}: /i
|
61
|
+
h = block.find{|l| l =~ patt}
|
62
|
+
h.gsub(patt, '').strip if h
|
63
|
+
end
|
64
|
+
|
65
|
+
def header_values(block, header, split_on=",")
|
66
|
+
h = header_value(block, header)
|
67
|
+
if h
|
68
|
+
if split_on.is_a?(Symbol)
|
69
|
+
self.send(split_on, h)
|
70
|
+
else
|
71
|
+
h.split(split_on)
|
72
|
+
end.map(&:strip)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def split_rfc822_addresses(header)
|
77
|
+
addresses = []
|
78
|
+
quoted_pair = false
|
79
|
+
quoted_string = false
|
80
|
+
buf = ""
|
81
|
+
header.each_char do |c|
|
82
|
+
if quoted_pair
|
83
|
+
buf << c
|
84
|
+
quoted_pair = false
|
85
|
+
elsif quoted_string && c=='\\'
|
86
|
+
buf << c
|
87
|
+
quoted_pair = true
|
88
|
+
elsif !quoted_string && c==','
|
89
|
+
addresses << buf
|
90
|
+
buf = ""
|
91
|
+
elsif !quoted_string && c=='"'
|
92
|
+
buf << c
|
93
|
+
quoted_string = true
|
94
|
+
elsif quoted_string && c=='"'
|
95
|
+
buf << c
|
96
|
+
quoted_string = false
|
97
|
+
else
|
98
|
+
buf << c
|
99
|
+
end
|
100
|
+
end
|
101
|
+
addresses << buf if buf.length>0
|
102
|
+
addresses
|
103
|
+
end
|
104
|
+
|
105
|
+
def strip_angles(value)
|
106
|
+
value.gsub(/<([^>]*)>/, '\1')
|
107
|
+
end
|
108
|
+
|
109
|
+
def process_block(output_dir, block, options={})
|
110
|
+
if is_message_block?(block)
|
111
|
+
json_message = extract_json_message(block, options)
|
112
|
+
output_json_message(output_dir, json_message)
|
113
|
+
increment_stats(:message)
|
114
|
+
else
|
115
|
+
increment_stats(:non_message)
|
116
|
+
end
|
117
|
+
rescue Exception=>e
|
118
|
+
increment_stats(:failed_message)
|
119
|
+
log do |logger|
|
120
|
+
logger.error(e)
|
121
|
+
logger.error(block.join("\n"))
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
MESSAGE_ID = "$MessageID"
|
126
|
+
POSTED_DATE = "PostedDate"
|
127
|
+
IN_REPLY_TO = "in_reply_to"
|
128
|
+
REFERENCES = "references"
|
129
|
+
FROM = "From"
|
130
|
+
TO = "SendTo"
|
131
|
+
CC = "CopyTo"
|
132
|
+
BCC = "BlindCopyTo"
|
133
|
+
INET_FROM = "InetFrom"
|
134
|
+
INET_TO = "InetSendTo"
|
135
|
+
INET_CC = "InetCopyTo"
|
136
|
+
INET_BCC = "InetBlindCopyTo"
|
137
|
+
|
138
|
+
def process_address(addr)
|
139
|
+
if is_distinguished_name?(addr)
|
140
|
+
name = addr[/CN=([^\/]*)/, 1]
|
141
|
+
{ :name=>name,
|
142
|
+
:notes_dn=>addr}
|
143
|
+
else
|
144
|
+
ta = TMail::Address.parse(addr)
|
145
|
+
if ta.is_a?(TMail::Address)
|
146
|
+
{ :name=>ta.name,
|
147
|
+
:email_address=>ta.address.downcase}
|
148
|
+
else
|
149
|
+
log{|logger| logger.warn("addr does not parse to a TMail::Address: #{addr}")}
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def process_address_pair(inet_addr, notes_addr)
|
155
|
+
if inet_addr == "."
|
156
|
+
process_address(notes_addr)
|
157
|
+
else
|
158
|
+
process_address(inet_addr)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def process_addresses(block, inet_field, notes_field)
|
163
|
+
inet_h = header_values(block, inet_field, :split_rfc822_addresses)
|
164
|
+
notes_h = header_values(block, notes_field, :split_rfc822_addresses)
|
165
|
+
|
166
|
+
if inet_h && notes_h
|
167
|
+
if inet_h.length == notes_h.length
|
168
|
+
inet_h.zip(notes_h).map do |inet_addr, notes_addr|
|
169
|
+
process_address_pair(inet_addr, notes_addr)
|
170
|
+
end
|
171
|
+
else
|
172
|
+
raise "#{inet_field}: does not match #{notes_field}:"
|
173
|
+
end
|
174
|
+
elsif inet_h
|
175
|
+
inet_h.map{|addr| process_address(addr)}
|
176
|
+
elsif notes_h
|
177
|
+
notes_h.map{|addr| process_address(addr)}
|
178
|
+
else
|
179
|
+
nil
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
NOTES_US_DATE_FORMAT = "%m/%d/%Y %I:%M:%S %p"
|
184
|
+
|
185
|
+
def parse_date(date, options={})
|
186
|
+
DateTime.strptime(date, NOTES_US_DATE_FORMAT)
|
187
|
+
end
|
188
|
+
|
189
|
+
def extract_json_message(block, options={})
|
190
|
+
message_id_h = header_value(block, MESSAGE_ID)
|
191
|
+
raise "no #{MESSAGE_ID}" if !message_id_h
|
192
|
+
message_id = strip_angles(message_id_h)
|
193
|
+
|
194
|
+
posted_date_h = header_value(block, POSTED_DATE)
|
195
|
+
raise "no #{POSTED_DATE}" if !posted_date_h
|
196
|
+
posted_date = parse_date(posted_date_h, options)
|
197
|
+
|
198
|
+
in_reply_to_h = header_value(block, IN_REPLY_TO)
|
199
|
+
in_reply_to = strip_angles(in_reply_to_h) if in_reply_to_h
|
200
|
+
|
201
|
+
references_h = header_values(block, REFERENCES, " ")
|
202
|
+
references = references_h.map{|r| strip_angles(r)} if references_h
|
203
|
+
|
204
|
+
froms = process_addresses(block, INET_FROM, FROM)
|
205
|
+
raise "no From:, or more than one From:" if !froms || froms.size>1
|
206
|
+
from = froms[0]
|
207
|
+
to = process_addresses(block, INET_TO, TO)
|
208
|
+
cc = process_addresses(block, INET_CC, CC)
|
209
|
+
bcc = process_addresses(block, INET_BCC, BCC)
|
210
|
+
|
211
|
+
raise "no recipients" if (to||[]).size + (cc||[]).size + (bcc||[]).size == 0
|
212
|
+
|
213
|
+
{ :message_type=>"email",
|
214
|
+
:message_id=>message_id,
|
215
|
+
:sent_at=>posted_date,
|
216
|
+
:in_reply_to=>in_reply_to,
|
217
|
+
:references=>references,
|
218
|
+
:from=>from,
|
219
|
+
:to=>to,
|
220
|
+
:cc=>cc,
|
221
|
+
:bcc=>bcc}
|
222
|
+
end
|
223
|
+
|
224
|
+
def output_json_message(output_dir, json_message)
|
225
|
+
fname = File.join(output_dir, MD5.hexdigest(json_message[:message_id]))
|
226
|
+
File.open(fname, "w"){|out| out << json_message.to_json}
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,507 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe NotesStructuredTextJsonMessages do
|
4
|
+
describe "json_messages" do
|
5
|
+
it "should open each file and call json_messages_from_stream with it" do
|
6
|
+
output_dir = Object.new
|
7
|
+
input_files = [Object.new, Object.new]
|
8
|
+
options = Object.new
|
9
|
+
|
10
|
+
input0 = Object.new
|
11
|
+
input1 = Object.new
|
12
|
+
mock(File).open(input_files[0], "r"){|f,opts,block| block.call(input0)}
|
13
|
+
mock(File).open(input_files[1], "r"){|f,opts,block| block.call(input1)}
|
14
|
+
|
15
|
+
mock(NotesStructuredTextJsonMessages).json_messages_from_stream(output_dir, input0, options)
|
16
|
+
mock(NotesStructuredTextJsonMessages).json_messages_from_stream(output_dir, input1, options)
|
17
|
+
|
18
|
+
NotesStructuredTextJsonMessages.json_messages(output_dir, input_files, options)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "json_messages_from_stream" do
|
23
|
+
it "should call process_block for each block retrieved from the stream" do
|
24
|
+
output_dir = Object.new
|
25
|
+
input = Object.new
|
26
|
+
blocks = [nil, ["foo", "bar"], ["baz", "boo"]]
|
27
|
+
|
28
|
+
mock(NotesStructuredTextJsonMessages).read_block(input).times(3){blocks.pop}
|
29
|
+
|
30
|
+
mock(NotesStructuredTextJsonMessages).process_block(output_dir, ["baz", "boo"], anything)
|
31
|
+
mock(NotesStructuredTextJsonMessages).process_block(output_dir, ["foo", "bar"], anything)
|
32
|
+
|
33
|
+
NotesStructuredTextJsonMessages.json_messages_from_stream(output_dir, input)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "readblock" do
|
38
|
+
it "should return lines read from a stream until the first empty line" do
|
39
|
+
input = <<-EOF
|
40
|
+
foo
|
41
|
+
bar
|
42
|
+
|
43
|
+
baz
|
44
|
+
boo
|
45
|
+
EOF
|
46
|
+
io = StringIO.new(input)
|
47
|
+
NotesStructuredTextJsonMessages.read_block(io).should == ["foo", "bar"]
|
48
|
+
NotesStructuredTextJsonMessages.read_block(io).should == ["baz", "boo"]
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should return nil if the input stream is at EOF" do
|
52
|
+
io = StringIO.new("foo\nbar")
|
53
|
+
NotesStructuredTextJsonMessages.read_block(io).should == ["foo", "bar"]
|
54
|
+
NotesStructuredTextJsonMessages.read_block(io).should == nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "is_distinguished_name?" do
|
59
|
+
it "should return true if the address contains a CN=... string" do
|
60
|
+
NotesStructuredTextJsonMessages.is_distinguished_name?("CN=foo bar/OU=before/O=after").should == true
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should return false if there is no CN=... " do
|
64
|
+
NotesStructuredTextJsonMessages.is_distinguished_name?("foo@bar.com").should == false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "is_message_block?" do
|
69
|
+
it "should return true if the block contains a line which start with '$MessageID: ' " do
|
70
|
+
NotesStructuredTextJsonMessages.is_message_block?( ["foo", "$MessageID: bar", "baz"] ).should == true
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should return false if there are no lines starting with '$MessageID: ' in the block" do
|
74
|
+
NotesStructuredTextJsonMessages.is_message_block?( ["foo", "bar", "baz"] ).should == false
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should not be case-sensitive" do
|
78
|
+
NotesStructuredTextJsonMessages.is_message_block?( ["foo", "$MESSAGEID: bar", "baz"] ).should == true
|
79
|
+
NotesStructuredTextJsonMessages.is_message_block?( ["foo", "$messageID: bar", "baz"] ).should == true
|
80
|
+
NotesStructuredTextJsonMessages.is_message_block?( ["foo", "$messageid: bar", "baz"] ).should == true
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "header_value" do
|
85
|
+
it "should extract the first occurence of a header from the block" do
|
86
|
+
NotesStructuredTextJsonMessages.header_value(["Foo: foo", "Bar: bar", "Baz: baz"], "Foo").should == "foo"
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should strip whitespace" do
|
90
|
+
NotesStructuredTextJsonMessages.header_value(["Foo: foo ", "Bar: bar", "Baz: baz"], "Foo").should == "foo"
|
91
|
+
end
|
92
|
+
it "should return nil if there are no occurences of the heaer in the block" do
|
93
|
+
NotesStructuredTextJsonMessages.header_value(["Boo: foo", "Bar: bar", "Baz: baz"], "Foo").should == nil
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should not be case-sensitive" do
|
97
|
+
NotesStructuredTextJsonMessages.header_value(["fOO: foo", "Bar: bar", "Baz: baz"], "Foo").should == "foo"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "header_values" do
|
102
|
+
it "should extract an array of values for the header if present" do
|
103
|
+
NotesStructuredTextJsonMessages.header_values(["Foo: foo", "Bar: bar", "Baz: baz"], "Foo").should == ["foo"]
|
104
|
+
NotesStructuredTextJsonMessages.header_values(["Foo: a,b,c", "Bar: bar", "Baz: baz"], "Foo").should == ["a", "b", "c"]
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should return nil if there are no occurences of the header" do
|
108
|
+
NotesStructuredTextJsonMessages.header_values(["Boo: a,b,c", "Bar: bar", "Baz: baz"], "Foo").should == nil
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should strip whitespace" do
|
112
|
+
NotesStructuredTextJsonMessages.header_values(["Foo: a ,\tb , c ", "Bar: bar", "Baz: baz"], "Foo").should == ["a", "b", "c"]
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should strip on a given character" do
|
116
|
+
NotesStructuredTextJsonMessages.header_values(["Foo: a b c", "Bar: c d e", "Baz: f g h"], "Bar", " ").should == ["c", "d", "e"]
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should strip with a given method" do
|
120
|
+
NotesStructuredTextJsonMessages.header_values(['SendTo: "mcfoo, foo" <foo.mcfoo@foo.com>,"bar \\"barry\\" mcbar" <bar.mcbar@bar.com>'],
|
121
|
+
"SendTo",
|
122
|
+
:split_rfc822_addresses).should ==
|
123
|
+
['"mcfoo, foo" <foo.mcfoo@foo.com>',
|
124
|
+
'"bar \\"barry\\" mcbar" <bar.mcbar@bar.com>']
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "split_rfc822_addresses" do
|
129
|
+
it "should do nothing to a single address" do
|
130
|
+
NotesStructuredTextJsonMessages.split_rfc822_addresses('"foo mcfoo" <foo.mcfoo@foo.com>').should ==
|
131
|
+
['"foo mcfoo" <foo.mcfoo@foo.com>']
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should split a simple case" do
|
135
|
+
NotesStructuredTextJsonMessages.split_rfc822_addresses('"foo mcfoo" <foo.mcfoo@foo.com>,"bar mcbar" <bar.mcbar@bar.com>').should ==
|
136
|
+
['"foo mcfoo" <foo.mcfoo@foo.com>','"bar mcbar" <bar.mcbar@bar.com>']
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should split if there is a comma inside a quoted string" do
|
140
|
+
NotesStructuredTextJsonMessages.split_rfc822_addresses('"mcfoo, foo" <foo.mcfoo@foo.com>,"bar mcbar" <bar.mcbar@bar.com>').should ==
|
141
|
+
['"mcfoo, foo" <foo.mcfoo@foo.com>','"bar mcbar" <bar.mcbar@bar.com>']
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should split if there is a quoted double-qoute within a string" do
|
145
|
+
NotesStructuredTextJsonMessages.split_rfc822_addresses('"foo \\"foo foo\\" mcfoo" <foo.mcfoo@foo.com>,"bar mcbar" <bar.mcbar@bar.com>').should ==
|
146
|
+
['"foo \\"foo foo\\" mcfoo" <foo.mcfoo@foo.com>','"bar mcbar" <bar.mcbar@bar.com>']
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "strip_angles" do
|
151
|
+
it "should remove angle-brackets if present" do
|
152
|
+
NotesStructuredTextJsonMessages.strip_angles("<foo>").should == "foo"
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should do nothing if no angle brackets present" do
|
156
|
+
NotesStructuredTextJsonMessages.strip_angles("foo").should == "foo"
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should do nothing if a single angle bracket present" do
|
160
|
+
NotesStructuredTextJsonMessages.strip_angles("<foo").should == "<foo"
|
161
|
+
NotesStructuredTextJsonMessages.strip_angles("foo>").should == "foo>"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "process_block" do
|
166
|
+
it "should output_json_message if is_message_block?" do
|
167
|
+
output_dir = Object.new
|
168
|
+
block = Object.new
|
169
|
+
json_message = Object.new
|
170
|
+
|
171
|
+
stub(NotesStructuredTextJsonMessages).is_message_block?(block){true}
|
172
|
+
mock(NotesStructuredTextJsonMessages).extract_json_message(block, anything){json_message}
|
173
|
+
mock(NotesStructuredTextJsonMessages).output_json_message(output_dir, json_message)
|
174
|
+
|
175
|
+
NotesStructuredTextJsonMessages.process_block(output_dir, block)
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should ignore if !is_message_block?" do
|
179
|
+
output_dir = Object.new
|
180
|
+
block = Object.new
|
181
|
+
|
182
|
+
stub(NotesStructuredTextJsonMessages).is_message_block?(block){false}
|
183
|
+
dont_allow(NotesStructuredTextJsonMessages).extract_json_message.with_any_args
|
184
|
+
dont_allow(NotesStructuredTextJsonMessages).output_json_message.with_any_args
|
185
|
+
|
186
|
+
NotesStructuredTextJsonMessages.process_block(output_dir, block)
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should catch and log exceptions during processing" do
|
190
|
+
output_dir = Object.new
|
191
|
+
block = ["foo", "bar"]
|
192
|
+
logger = Object.new
|
193
|
+
|
194
|
+
stub(NotesStructuredTextJsonMessages).logger{logger}
|
195
|
+
stub(NotesStructuredTextJsonMessages).is_message_block?(block){true}
|
196
|
+
stub(NotesStructuredTextJsonMessages).extract_json_message(block, anything){raise "boo"}
|
197
|
+
|
198
|
+
mock(logger).error(anything) do |err|
|
199
|
+
err.is_a?(Exception).should == true
|
200
|
+
err.message.should =~ /boo/
|
201
|
+
end
|
202
|
+
mock(logger).error(block.join("\n"))
|
203
|
+
|
204
|
+
lambda {
|
205
|
+
NotesStructuredTextJsonMessages.process_block(output_dir, block)
|
206
|
+
}.should_not raise_error
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
describe "process_address" do
|
211
|
+
it "should produce a notes_dn hash if is_distinguished_name?" do
|
212
|
+
NotesStructuredTextJsonMessages.process_address("CN=foo bar/OU=here/O=there").should ==
|
213
|
+
{:name=>"foo bar", :notes_dn=>"CN=foo bar/OU=here/O=there"}
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should be case-preserving for distinguished names" do
|
217
|
+
NotesStructuredTextJsonMessages.process_address("CN=Foo Bar/OU=Here/O=There").should ==
|
218
|
+
{:name=>"Foo Bar", :notes_dn=>"CN=Foo Bar/OU=Here/O=There"}
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should parse with TMail::Address if !is_distinguished_name?" do
|
222
|
+
NotesStructuredTextJsonMessages.process_address('"foo bar" <foo@bar.com>').should ==
|
223
|
+
{:name=>"foo bar", :email_address=>"foo@bar.com"}
|
224
|
+
end
|
225
|
+
|
226
|
+
it "should downcase internet email addresses" do
|
227
|
+
NotesStructuredTextJsonMessages.process_address('"Foo Bar" <Foo@Bar.com>').should ==
|
228
|
+
{:name=>"Foo Bar", :email_address=>"foo@bar.com"}
|
229
|
+
end
|
230
|
+
|
231
|
+
it "should log a warning if an internet email address does not parse to a TMail::Address" do
|
232
|
+
logger = Object.new
|
233
|
+
stub(NotesStructuredTextJsonMessages).logger{logger}
|
234
|
+
|
235
|
+
mock(logger).warn(/does not parse .* TMail::Address/)
|
236
|
+
|
237
|
+
NotesStructuredTextJsonMessages.process_address('Undisclosed recipients:;').should == nil
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
describe "process_address_pair" do
|
242
|
+
it "should process_address the notes_address if the inet_address is '.'" do
|
243
|
+
NotesStructuredTextJsonMessages.process_address_pair(".", "CN=foo bar/OU=here/O=there").should ==
|
244
|
+
{:name=>"foo bar", :notes_dn=>"CN=foo bar/OU=here/O=there"}
|
245
|
+
end
|
246
|
+
|
247
|
+
it "should process_address the inet_addr if the inet_address is not '.'" do
|
248
|
+
NotesStructuredTextJsonMessages.process_address_pair('"foo bar" <foo@bar.com>', "CN=foo bar/OU=here/O=there").should ==
|
249
|
+
{:name=>"foo bar", :email_address=>"foo@bar.com"}
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
describe "process_addresses" do
|
254
|
+
it "should raise an exception if the notes and inet headers do not match" do
|
255
|
+
inet_field = Object.new
|
256
|
+
notes_field = Object.new
|
257
|
+
block = Object.new
|
258
|
+
|
259
|
+
stub(NotesStructuredTextJsonMessages).header_values(block, inet_field, :split_rfc822_addresses){["foo", "bar"]}
|
260
|
+
stub(NotesStructuredTextJsonMessages).header_values(block, notes_field, :split_rfc822_addresses){["foo"]}
|
261
|
+
|
262
|
+
lambda {
|
263
|
+
NotesStructuredTextJsonMessages.process_addresses(block, inet_field, notes_field)
|
264
|
+
}.should raise_error(/does not match/)
|
265
|
+
end
|
266
|
+
|
267
|
+
it "should call process_address_pair if notes and inet headers are both present" do
|
268
|
+
inet_field = Object.new
|
269
|
+
notes_field = Object.new
|
270
|
+
block = Object.new
|
271
|
+
|
272
|
+
stub(NotesStructuredTextJsonMessages).header_values(block, inet_field, :split_rfc822_addresses){["foo.mcfoo@foo.com", "bar.mcbar@bar.com"]}
|
273
|
+
stub(NotesStructuredTextJsonMessages).header_values(block, notes_field, :split_rfc822_addresses){["CN=foo mcfoo/OU=main/O=foo", "CN=bar mcbar/OU=main/O=bar"]}
|
274
|
+
|
275
|
+
mock(NotesStructuredTextJsonMessages).process_address_pair("foo.mcfoo@foo.com", "CN=foo mcfoo/OU=main/O=foo")
|
276
|
+
mock(NotesStructuredTextJsonMessages).process_address_pair("bar.mcbar@bar.com", "CN=bar mcbar/OU=main/O=bar")
|
277
|
+
|
278
|
+
NotesStructuredTextJsonMessages.process_addresses(block, inet_field, notes_field)
|
279
|
+
end
|
280
|
+
|
281
|
+
it "should call process_address if an inet header is present" do
|
282
|
+
inet_field = Object.new
|
283
|
+
notes_field = Object.new
|
284
|
+
block = Object.new
|
285
|
+
|
286
|
+
stub(NotesStructuredTextJsonMessages).header_values(block, inet_field, :split_rfc822_addresses){["foo.mcfoo@foo.com", "bar.mcbar@bar.com"]}
|
287
|
+
stub(NotesStructuredTextJsonMessages).header_values(block, notes_field, :split_rfc822_addresses){nil}
|
288
|
+
|
289
|
+
mock(NotesStructuredTextJsonMessages).process_address("foo.mcfoo@foo.com")
|
290
|
+
mock(NotesStructuredTextJsonMessages).process_address("bar.mcbar@bar.com")
|
291
|
+
|
292
|
+
NotesStructuredTextJsonMessages.process_addresses(block, inet_field, notes_field)
|
293
|
+
end
|
294
|
+
|
295
|
+
it "should call process_address if a notes header is present" do
|
296
|
+
inet_field = Object.new
|
297
|
+
notes_field = Object.new
|
298
|
+
block = Object.new
|
299
|
+
|
300
|
+
stub(NotesStructuredTextJsonMessages).header_values(block, inet_field, :split_rfc822_addresses){nil}
|
301
|
+
stub(NotesStructuredTextJsonMessages).header_values(block, notes_field, :split_rfc822_addresses){["CN=foo mcfoo/OU=main/O=foo", "CN=bar mcbar/OU=main/O=bar"]}
|
302
|
+
|
303
|
+
mock(NotesStructuredTextJsonMessages).process_address("CN=foo mcfoo/OU=main/O=foo")
|
304
|
+
mock(NotesStructuredTextJsonMessages).process_address("CN=bar mcbar/OU=main/O=bar")
|
305
|
+
|
306
|
+
NotesStructuredTextJsonMessages.process_addresses(block, inet_field, notes_field)
|
307
|
+
end
|
308
|
+
|
309
|
+
end
|
310
|
+
|
311
|
+
describe "output_json_message" do
|
312
|
+
it "should write the json encoded message to a file named by the MD5 of the message_id" do
|
313
|
+
output_dir = "/a/b/c/d"
|
314
|
+
json_message = Object.new
|
315
|
+
json_struct = {:message_id=>"foo123@foo.com"}
|
316
|
+
|
317
|
+
stub(json_message).[](:message_id){"foo123@foo.com"}
|
318
|
+
stub(json_message).to_json{json_struct.to_json}
|
319
|
+
|
320
|
+
output_stream = Object.new
|
321
|
+
mock(output_stream).<<(json_struct.to_json){output_stream}
|
322
|
+
|
323
|
+
mock(File).open("/a/b/c/d/#{MD5.hexdigest("foo123@foo.com")}", "w") do |fn,m,block|
|
324
|
+
block.call(output_stream)
|
325
|
+
end
|
326
|
+
|
327
|
+
NotesStructuredTextJsonMessages.output_json_message(output_dir, json_message)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
describe "parse_date" do
|
332
|
+
it "should parse a US morning date correctly" do
|
333
|
+
dt = NotesStructuredTextJsonMessages.parse_date("01/25/2011 05:21:37 AM")
|
334
|
+
dt.is_a?(DateTime).should == true
|
335
|
+
dt.mday.should == 25
|
336
|
+
dt.mon.should == 01
|
337
|
+
dt.year.should == 2011
|
338
|
+
dt.hour.should == 5
|
339
|
+
dt.min.should == 21
|
340
|
+
dt.sec.should == 37
|
341
|
+
dt.zone.should == "+00:00"
|
342
|
+
end
|
343
|
+
|
344
|
+
it "should parse a US evening date correctly" do
|
345
|
+
dt = NotesStructuredTextJsonMessages.parse_date("12/01/2011 05:21:37 PM")
|
346
|
+
dt.is_a?(DateTime).should == true
|
347
|
+
dt.mday.should == 1
|
348
|
+
dt.mon.should == 12
|
349
|
+
dt.year.should == 2011
|
350
|
+
dt.hour.should == 17
|
351
|
+
dt.min.should == 21
|
352
|
+
dt.sec.should == 37
|
353
|
+
dt.zone.should == "+00:00"
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
describe "extract_json_message" do
|
358
|
+
def notes_message(options={})
|
359
|
+
h = {
|
360
|
+
"$MessageID" => "<foo123@foo.com>",
|
361
|
+
"PostedDate" => "02/25/2011 08:06:10 PM",
|
362
|
+
"In_Reply_To" => "<bar456@bar.com>",
|
363
|
+
"References" => "<bar456@bar.com> <ear789@ear.com>",
|
364
|
+
"From" => "CN=foo mcfoo/OU=fooclub/O=foo",
|
365
|
+
"SendTo" => "CN=bar mcbar/OU=barclub/O=bar,CN=baz mcbaz/OU=bazclub/O=baz",
|
366
|
+
"CopyTo" => "CN=dar mcdar/OU=darclub/O=dar,CN=ear mcear/OU=earclub/O=ear",
|
367
|
+
"BlindCopyTo" => "CN=far mcfar/OU=farclub/O=far,CN=gar mcgar/OU=garclub/O=gar"}.merge(options)
|
368
|
+
h.map{|k,v| "#{k}: #{v}" if v}
|
369
|
+
end
|
370
|
+
|
371
|
+
it "should raise an exception if there is no message-id" do
|
372
|
+
block = notes_message("$MessageID"=>nil)
|
373
|
+
lambda {
|
374
|
+
NotesStructuredTextJsonMessages.extract_json_message(block)
|
375
|
+
}.should raise_error(/no \$MessageID/)
|
376
|
+
end
|
377
|
+
|
378
|
+
it "should raise an exception if there is no From: or InetFrom:" do
|
379
|
+
block = notes_message("From"=>nil)
|
380
|
+
|
381
|
+
lambda {
|
382
|
+
NotesStructuredTextJsonMessages.extract_json_message(block)
|
383
|
+
}.should raise_error(/no From/)
|
384
|
+
end
|
385
|
+
|
386
|
+
it "should raise an exception if there are no recipients" do
|
387
|
+
block = notes_message("SendTo"=>nil, "CopyTo"=>nil, "BlindCopyTo"=>nil)
|
388
|
+
|
389
|
+
lambda {
|
390
|
+
NotesStructuredTextJsonMessages.extract_json_message(block)
|
391
|
+
}.should raise_error(/no recipients/)
|
392
|
+
end
|
393
|
+
|
394
|
+
it "should raise an exception if there is no PostedDate" do
|
395
|
+
block = notes_message("PostedDate"=>nil)
|
396
|
+
|
397
|
+
lambda {
|
398
|
+
NotesStructuredTextJsonMessages.extract_json_message(block)
|
399
|
+
}.should raise_error(/no PostedDate/)
|
400
|
+
end
|
401
|
+
|
402
|
+
it "should remove angle-brackets from message_id, in_reply_to and references" do
|
403
|
+
block = notes_message
|
404
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
405
|
+
j[:message_id].should == "foo123@foo.com"
|
406
|
+
end
|
407
|
+
|
408
|
+
it "should parse a US date correctly" do
|
409
|
+
block = notes_message
|
410
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
411
|
+
d = j[:sent_at]
|
412
|
+
d.is_a?(DateTime).should == true
|
413
|
+
d.mday.should == 25
|
414
|
+
d.month.should == 2
|
415
|
+
d.year.should == 2011
|
416
|
+
d.hour.should == 20
|
417
|
+
d.min.should == 6
|
418
|
+
d.sec.should == 10
|
419
|
+
end
|
420
|
+
|
421
|
+
it "should parse the From / InetFrom fields" do
|
422
|
+
block = notes_message
|
423
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
424
|
+
j[:from].should == {:notes_dn=>"CN=foo mcfoo/OU=fooclub/O=foo", :name=>"foo mcfoo"}
|
425
|
+
|
426
|
+
block = notes_message("From"=>'"foo mcfoo" <foo.mcfoo@foo.com>')
|
427
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
428
|
+
j[:from].should == {:email_address=>"foo.mcfoo@foo.com", :name=>"foo mcfoo"}
|
429
|
+
|
430
|
+
block = notes_message("InetFrom"=>'"foo mcfoo" <foo.mcfoo@foo.com>', "From"=>"CN=foo mcfoo/OU=fooclub/O=foo")
|
431
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
432
|
+
j[:from].should == {:email_address=>"foo.mcfoo@foo.com", :name=>"foo mcfoo"}
|
433
|
+
|
434
|
+
block = notes_message("InetFrom"=>'.', "From"=>'"foo mcfoo" <foo.mcfoo@foo.com>')
|
435
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
436
|
+
j[:from].should == {:email_address=>"foo.mcfoo@foo.com", :name=>"foo mcfoo"}
|
437
|
+
end
|
438
|
+
|
439
|
+
it "should parse the SendTo / InetSendTo fields" do
|
440
|
+
block = notes_message
|
441
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
442
|
+
j[:to].should == [{:notes_dn=>"CN=bar mcbar/OU=barclub/O=bar", :name=>"bar mcbar"},
|
443
|
+
{:notes_dn=>"CN=baz mcbaz/OU=bazclub/O=baz", :name=>"baz mcbaz"}]
|
444
|
+
|
445
|
+
block = notes_message("SendTo"=>'"bar mcbar" <bar.mcbar@bar.com>,"baz mcbaz" <baz.mcbaz@baz.com>')
|
446
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
447
|
+
j[:to].should == [{:email_address=>"bar.mcbar@bar.com", :name=>"bar mcbar"},
|
448
|
+
{:email_address=>"baz.mcbaz@baz.com", :name=>"baz mcbaz"}]
|
449
|
+
|
450
|
+
block = notes_message("InetSendTo"=>'"bar mcbar" <bar.mcbar@bar.com>,"baz mcbaz" <baz.mcbaz@baz.com>')
|
451
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
452
|
+
j[:to].should == [{:email_address=>"bar.mcbar@bar.com", :name=>"bar mcbar"},
|
453
|
+
{:email_address=>"baz.mcbaz@baz.com", :name=>"baz mcbaz"}]
|
454
|
+
|
455
|
+
block = notes_message("InetSendTo"=>'.,"baz mcbaz" <baz.mcbaz@baz.com>', "SendTo"=>'"bar mcbar" <bar.mcbar@bar.com>,"CN=baz mcbaz/OU=bazclub/O=baz"')
|
456
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
457
|
+
j[:to].should == [{:email_address=>"bar.mcbar@bar.com", :name=>"bar mcbar"},
|
458
|
+
{:email_address=>"baz.mcbaz@baz.com", :name=>"baz mcbaz"}]
|
459
|
+
end
|
460
|
+
|
461
|
+
it "should parse the CopyTo / InetCopyTo fields" do
|
462
|
+
block = notes_message
|
463
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
464
|
+
j[:cc].should == [{:notes_dn=>"CN=dar mcdar/OU=darclub/O=dar", :name=>"dar mcdar"},
|
465
|
+
{:notes_dn=>"CN=ear mcear/OU=earclub/O=ear", :name=>"ear mcear"}]
|
466
|
+
|
467
|
+
block = notes_message("CopyTo" => '"dar mcdar" <dar.mcdar@dar.com>,"ear mcear" <ear.mcear@ear.com>')
|
468
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
469
|
+
j[:cc].should == [{:email_address=>"dar.mcdar@dar.com", :name=>"dar mcdar"},
|
470
|
+
{:email_address=>"ear.mcear@ear.com", :name=>"ear mcear"}]
|
471
|
+
|
472
|
+
block = notes_message("InetCopyTo" => '"dar mcdar" <dar.mcdar@dar.com>,"ear mcear" <ear.mcear@ear.com>')
|
473
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
474
|
+
j[:cc].should == [{:email_address=>"dar.mcdar@dar.com", :name=>"dar mcdar"},
|
475
|
+
{:email_address=>"ear.mcear@ear.com", :name=>"ear mcear"}]
|
476
|
+
|
477
|
+
block = notes_message("InetCopyTo" => '.,"ear mcear" <ear.mcear@ear.com>',
|
478
|
+
"CopyTo" => '"dar mcdar" <dar.mcdar@dar.com>,CN=ear mcear/OU=earclub/O=ear')
|
479
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
480
|
+
j[:cc].should == [{:email_address=>"dar.mcdar@dar.com", :name=>"dar mcdar"},
|
481
|
+
{:email_address=>"ear.mcear@ear.com", :name=>"ear mcear"}]
|
482
|
+
end
|
483
|
+
|
484
|
+
it "should parse the BlindCopyTo / InetBlindCopyTo fields" do
|
485
|
+
block = notes_message
|
486
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
487
|
+
j[:bcc].should == [{:notes_dn=>"CN=far mcfar/OU=farclub/O=far", :name=>"far mcfar"},
|
488
|
+
{:notes_dn=>"CN=gar mcgar/OU=garclub/O=gar", :name=>"gar mcgar"}]
|
489
|
+
|
490
|
+
block = notes_message("BlindCopyTo" => '"far mcfar" <far.mcfar@far.com>,"gar mcgar" <gar.mcgar@gar.com>')
|
491
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
492
|
+
j[:bcc].should == [{:email_address=>"far.mcfar@far.com", :name=>"far mcfar"},
|
493
|
+
{:email_address=>"gar.mcgar@gar.com", :name=>"gar mcgar"}]
|
494
|
+
|
495
|
+
block = notes_message("InetBlindCopyTo" => '"far mcfar" <far.mcfar@far.com>,"gar mcgar" <gar.mcgar@gar.com>')
|
496
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
497
|
+
j[:bcc].should == [{:email_address=>"far.mcfar@far.com", :name=>"far mcfar"},
|
498
|
+
{:email_address=>"gar.mcgar@gar.com", :name=>"gar mcgar"}]
|
499
|
+
|
500
|
+
block = notes_message("InetBlindCopyTo" => '.,"gar mcgar" <gar.mcgar@gar.com>',
|
501
|
+
"BlindCopyTo" => '"far mcfar" <far.mcfar@far.com>,CN=gar mcgar/OU=garclub/O=gar')
|
502
|
+
j = NotesStructuredTextJsonMessages.extract_json_message(block)
|
503
|
+
j[:bcc].should == [{:email_address=>"far.mcfar@far.com", :name=>"far mcfar"},
|
504
|
+
{:email_address=>"gar.mcgar@gar.com", :name=>"gar mcgar"}]
|
505
|
+
end
|
506
|
+
end
|
507
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rubygems'
|
4
|
+
require 'spec'
|
5
|
+
require 'spec/autorun'
|
6
|
+
require 'rr'
|
7
|
+
require 'action_mailer'
|
8
|
+
require 'notes_structured_text_json_messages'
|
9
|
+
|
10
|
+
Spec::Runner.configure do |config|
|
11
|
+
config.mock_with RR::Adapters::Rspec
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: notes-structured-text-json-messages
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- craig mcmillan
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-04-26 00:00:00 +01:00
|
19
|
+
default_executable: notes_structured_text_json_messages
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: actionmailer
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 21
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 3
|
33
|
+
- 11
|
34
|
+
version: 2.3.11
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rspec
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 27
|
46
|
+
segments:
|
47
|
+
- 1
|
48
|
+
- 3
|
49
|
+
- 0
|
50
|
+
version: 1.3.0
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: jeweler
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 7
|
62
|
+
segments:
|
63
|
+
- 1
|
64
|
+
- 5
|
65
|
+
- 2
|
66
|
+
version: 1.5.2
|
67
|
+
type: :development
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rcov
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 3
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
version: "0"
|
81
|
+
type: :development
|
82
|
+
version_requirements: *id004
|
83
|
+
description: parses lotus notes structured text exports, producing a json message file for each each message
|
84
|
+
email: craig@trampolinesystems.com
|
85
|
+
executables:
|
86
|
+
- notes_structured_text_json_messages
|
87
|
+
extensions: []
|
88
|
+
|
89
|
+
extra_rdoc_files:
|
90
|
+
- LICENSE.txt
|
91
|
+
- README.rdoc
|
92
|
+
files:
|
93
|
+
- .document
|
94
|
+
- .rspec
|
95
|
+
- LICENSE.txt
|
96
|
+
- README.rdoc
|
97
|
+
- Rakefile
|
98
|
+
- VERSION
|
99
|
+
- bin/notes_structured_text_json_messages
|
100
|
+
- lib/notes_structured_text_json_messages.rb
|
101
|
+
- spec/notes_structured_text_json_messages_spec.rb
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
has_rdoc: true
|
104
|
+
homepage: http://github.com/trampoline/notes-structured-text-json-messages
|
105
|
+
licenses:
|
106
|
+
- MIT
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
hash: 3
|
118
|
+
segments:
|
119
|
+
- 0
|
120
|
+
version: "0"
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
hash: 3
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
version: "0"
|
130
|
+
requirements: []
|
131
|
+
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 1.6.2
|
134
|
+
signing_key:
|
135
|
+
specification_version: 3
|
136
|
+
summary: produces json message descriptions from lotus notes structured text exports
|
137
|
+
test_files:
|
138
|
+
- spec/notes_structured_text_json_messages_spec.rb
|
139
|
+
- spec/spec_helper.rb
|