pidgin2adium 3.0.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,97 @@
1
+ # The Message class and its subclasses, each used for holding one line of a
2
+ # chat.
3
+
4
+ module Pidgin2Adium
5
+ # A holding object for each line of the chat. It is subclassed as
6
+ # appropriate (eg AutoReplyMessage). Each subclass (but not Message
7
+ # itself) has its own to_s which prints out its information in a format
8
+ # appropriate for putting in an Adium log file.
9
+ # Subclasses: XMLMessage, AutoReplyMessage, StatusMessage, Event.
10
+ class Message
11
+ def initialize(sender, time, buddy_alias)
12
+ # The sender's screen name
13
+ @sender = sender
14
+ # The time the message was sent, in Adium format (e.g.
15
+ # "2008-10-05T22:26:20-0800")
16
+ @time = time
17
+ # The receiver's alias (NOT screen name)
18
+ @buddy_alias = buddy_alias
19
+ end
20
+ attr_accessor :sender, :time, :buddy_alias
21
+ end
22
+
23
+ # Basic message with body text (as opposed to pure status messages, which
24
+ # have no body).
25
+ class XMLMessage < Message
26
+ def initialize(sender, time, buddy_alias, body)
27
+ super(sender, time, buddy_alias)
28
+ @body = body
29
+ @styled_body = '<div><span style="font-family: Helvetica; font-size: 12pt;">%s</span></div>' % @body
30
+ normalize_body!()
31
+ end
32
+ attr_accessor :body
33
+
34
+ def to_s
35
+ return sprintf('<message sender="%s" time="%s" alias="%s">%s</message>' << "\n",
36
+ @sender, @time, @buddy_alias, @styled_body)
37
+ end
38
+
39
+ # Balances mismatched tags, normalizes body style, and fixes actions
40
+ # so they are in Adium style (Pidgin uses "***Buddy waves at you", Adium uses
41
+ # "*Buddy waves at you*").
42
+ def normalize_body!
43
+ normalize_body_entities!()
44
+ # Fix mismatched tags. Yes, it's faster to do it per-message
45
+ # than all at once.
46
+ @body = Pidgin2Adium.balance_tags_c(@body)
47
+ if @buddy_alias[0,3] == '***'
48
+ # "***<alias>" is what pidgin sets as the alias for a /me action
49
+ @buddy_alias.slice!(0,3)
50
+ @body = '*' << @body << '*'
51
+ end
52
+ end
53
+
54
+ # Escapes entities.
55
+ def normalize_body_entities!
56
+ # Convert '&' to '&amp;' only if it's not followed by an entity.
57
+ @body.gsub!(/&(?!lt|gt|amp|quot|apos)/, '&amp;')
58
+ end
59
+ end # END XMLMessage class
60
+
61
+ # An auto reply message.
62
+ class AutoReplyMessage < XMLMessage
63
+ def to_s
64
+ return sprintf('<message sender="%s" time="%s" auto="true" alias="%s">%s</message>' << "\n",
65
+ @sender, @time, @buddy_alias, @styled_body)
66
+ end
67
+ end # END AutoReplyMessage class
68
+
69
+ # A message saying e.g. "Blahblah has gone away."
70
+ class StatusMessage < Message
71
+ def initialize(sender, time, buddy_alias, status)
72
+ super(sender, time, buddy_alias)
73
+ @status = status
74
+ end
75
+ attr_accessor :status
76
+
77
+ def to_s
78
+ return sprintf('<status type="%s" sender="%s" time="%s" alias="%s"/>' << "\n", @status, @sender, @time, @buddy_alias)
79
+ end
80
+ end # END StatusMessage class
81
+
82
+ # Pidgin does not have Events, but Adium does. Pidgin mostly uses system
83
+ # messages to display what Adium calls events. These include sending a file,
84
+ # starting a Direct IM connection, or an error in chat.
85
+ class Event < XMLMessage
86
+ def initialize(sender, time, buddy_alias, body, event_type)
87
+ super(sender, time, buddy_alias, body)
88
+ @event_type = event_type
89
+ end
90
+ attr_accessor :event_type
91
+
92
+ def to_s
93
+ return sprintf('<event type="%s" sender="%s" time="%s" alias="%s">%s</event>',
94
+ @event_type, @sender, @time, @buddy_alias, @styled_body)
95
+ end
96
+ end # END Event class
97
+ end
@@ -0,0 +1,39 @@
1
+ # TextLogParser class, a subclass of BasicParser.
2
+ # Used for parse()ing text logs.
3
+
4
+ module Pidgin2Adium
5
+ class TextLogParser < BasicParser
6
+ def initialize(src_path, user_aliases)
7
+ super(src_path, user_aliases)
8
+ @timestamp_rx = '\((\d{1,2}:\d{1,2}:\d{1,2})\)'
9
+
10
+ # @line_regex matches a line in a TXT log file other than the first
11
+ # @line_regex matchdata:
12
+ # 0: timestamp
13
+ # 1: screen name or alias, if alias set
14
+ # 2: "<AUTO-REPLY>" or nil
15
+ # 3: message body
16
+ @line_regex = /#{@timestamp_rx} (.*?) ?(<AUTO-REPLY>)?: (.*)/o
17
+ # @line_regex_status matches a status line
18
+ # @line_regex_status matchdata:
19
+ # 0: timestamp
20
+ # 1: status message
21
+ @line_regex_status = /#{@timestamp_rx} ([^:]+)/o
22
+ end
23
+
24
+ def cleanup(text)
25
+ text.tr!("\r", '')
26
+ # Escape entities since this will be in XML
27
+ text.gsub!('&', '&amp;') # escape '&' first
28
+ text.gsub!('<', '&lt;')
29
+ text.gsub!('>', '&gt;')
30
+ text.gsub!('"', '&quot;')
31
+ text.gsub!("'", '&apos;')
32
+ # Replace newlines with "<br/>" unless they end a chat line.
33
+ # Add the <br/> after converting to &lt; etc so we
34
+ # don't escape the tag.
35
+ text.gsub!(/\n(?!(#{@timestamp_rx}|\Z))/, '<br/>')
36
+ return text
37
+ end
38
+ end # END TextLogParser class
39
+ end
data/pidgin2adium.gemspec CHANGED
@@ -5,45 +5,61 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{pidgin2adium}
8
- s.version = "3.0.1"
8
+ s.version = "3.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Gabe Berke-Williams"]
12
- s.date = %q{2010-08-07}
12
+ s.date = %q{2010-08-13}
13
+ s.default_executable = %q{pidgin2adium}
13
14
  s.description = %q{Pidgin2Adium is a fast, easy way to convert Pidgin (formerly gaim) logs to the Adium format.}
14
15
  s.email = %q{gbw@brandeis.edu}
15
- s.executables = ["pidgin2adium", "pidgin2adium_profiler"]
16
+ s.executables = ["pidgin2adium"]
16
17
  s.extensions = ["ext/balance_tags_c/extconf.rb"]
17
18
  s.extra_rdoc_files = [
18
- "LICENSE",
19
+ "ChangeLog",
20
+ "LICENSE",
19
21
  "README.rdoc"
20
22
  ]
21
23
  s.files = [
22
24
  ".autotest",
23
25
  ".gitignore",
24
26
  ".rspec",
27
+ "ChangeLog",
25
28
  "Gemfile",
26
- "History.txt",
27
29
  "LICENSE",
28
30
  "Manifest.txt",
29
31
  "README.rdoc",
30
32
  "Rakefile",
31
33
  "VERSION",
32
34
  "bin/pidgin2adium",
33
- "bin/pidgin2adium_profiler",
34
35
  "config/website.yml",
35
36
  "ext/balance_tags_c/balance_tags_c.c",
36
37
  "ext/balance_tags_c/extconf.rb",
37
38
  "lib/pidgin2adium.rb",
38
39
  "lib/pidgin2adium/balance_tags.rb",
40
+ "lib/pidgin2adium/basic_parser.rb",
41
+ "lib/pidgin2adium/html_log_parser.rb",
39
42
  "lib/pidgin2adium/log_converter.rb",
40
43
  "lib/pidgin2adium/log_file.rb",
41
44
  "lib/pidgin2adium/log_parser.rb",
45
+ "lib/pidgin2adium/message.rb",
46
+ "lib/pidgin2adium/text_log_parser.rb",
42
47
  "pidgin2adium.gemspec",
48
+ "spec/balance_tags_c_extn_spec.rb",
49
+ "spec/basic_parser_spec.rb",
50
+ "spec/html_log_parser_spec.rb",
51
+ "spec/log_converter_spec.rb",
52
+ "spec/log_file_spec.rb",
53
+ "spec/logfiles/2006-12-21.223606.txt",
54
+ "spec/logfiles/2008-01-15.071445-0500PST.htm",
55
+ "spec/logfiles/2008-01-15.071445-0500PST.html",
43
56
  "spec/pidgin2adium_spec.rb",
44
57
  "spec/spec.opts",
45
58
  "spec/spec_helper.rb",
46
- "tasks/build_profiler.rake",
59
+ "spec/test-output/README.md",
60
+ "spec/test-output/html_log_output.xml",
61
+ "spec/test-output/text_log_output.xml",
62
+ "spec/text_log_parser_spec.rb",
47
63
  "tasks/extconf.rake",
48
64
  "tasks/extconf/balance_tags_c.rake"
49
65
  ]
@@ -53,8 +69,14 @@ Gem::Specification.new do |s|
53
69
  s.rubygems_version = %q{1.3.7}
54
70
  s.summary = %q{Pidgin2Adium is a fast, easy way to convert Pidgin (formerly gaim) logs to the Adium format}
55
71
  s.test_files = [
56
- "spec/pidgin2adium_spec.rb",
57
- "spec/spec_helper.rb"
72
+ "spec/balance_tags_c_extn_spec.rb",
73
+ "spec/basic_parser_spec.rb",
74
+ "spec/html_log_parser_spec.rb",
75
+ "spec/log_converter_spec.rb",
76
+ "spec/log_file_spec.rb",
77
+ "spec/pidgin2adium_spec.rb",
78
+ "spec/spec_helper.rb",
79
+ "spec/text_log_parser_spec.rb"
58
80
  ]
59
81
 
60
82
  if s.respond_to? :specification_version then
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'ext', 'balance_tags_c'))
4
+ require "balance_tags_c"
5
+
6
+ describe "BalanceTagsCExtension" do
7
+ describe "text without tags" do
8
+ it "should be left untouched" do
9
+ text = "Foo bar baz, this is my excellent test text!"
10
+ Pidgin2Adium.balance_tags_c(text).should == text
11
+ end
12
+ end
13
+
14
+ describe "text with tags" do
15
+ it "should be balanced correctly" do
16
+ Pidgin2Adium.balance_tags_c('<p><b>this is unbalanced!').should == "<p><b>this is unbalanced!</b></p>"
17
+ end
18
+
19
+ # Make sure it doesn't segfault
20
+ it "should be balanced correctly when run many times" do
21
+ text = <<-LATIN
22
+ Sequi unde et nobis ipsum. Expedita temporibus aut adipisci debitis
23
+ porro ducimus. Dignissimos est tenetur vero error voluptatem quidem
24
+ ducimus. Sapiente non occaecati omnis non provident sint ut. Repellat
25
+ laudantium quis aperiam ad fugit accusantium placeat itaque. Quia
26
+ velit voluptatem sint aliquid rem quam occaecati doloremque. Eos
27
+ provident ut suscipit reprehenderit mollitia. Non vitae voluptatem
28
+ laudantium quis a et. In libero voluptas aliquam.Veniam minima
29
+ consequatur quod. Voluptatem quibusdam ut consequatur et ratione
30
+ repellat. Iusto est aspernatur consequatur ex nostrum voluptas
31
+ voluptas et. Rerum voluptas et veritatis ratione voluptates ut iusto
32
+ ut. Aspernatur sed molestiae sint eveniet asperiores mollitia qui.
33
+ Rerum laudantium architecto soluta. Earum qui ut vel corporis ullam
34
+ doloribus voluptatem. Nemo quo recusandae ut. Deleniti vel ea qui ut
35
+ perferendis. Est dolor ducimus voluptatem nemo quis et animi
36
+ reprehenderit. Laudantium voluptas adipisci alias. Ut aut soluta
37
+ repellat consequuntur quidem. Deserunt voluptatem eum eveniet cum.Quia
38
+ consectetur at ut quisquam occaecati et sint. Sint voluptatem quaerat
39
+ qui molestiae ratione voluptatem. Autem labore quos perferendis enim
40
+ fuga deleniti recusandae. Aut libero quo cum autem voluptatem.
41
+ LATIN
42
+ 2_000.times do
43
+ Pidgin2Adium.balance_tags_c("<p><b>#{text}").should == "<p><b>#{text}</b></p>"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,217 @@
1
+ require 'spec_helper'
2
+
3
+ describe "BasicParser" do
4
+ it "should include Pidgin2Adium" do
5
+ Pidgin2Adium::BasicParser.included_modules.include?(Pidgin2Adium).should be_true
6
+ end
7
+
8
+ describe "#parse" do
9
+ it "should return false" do
10
+ bp = Pidgin2Adium::BasicParser.new(@text_logfile_path,
11
+ @aliases)
12
+ bp.parse().should be_false
13
+ end
14
+ end
15
+
16
+ describe "#get_time_zone_offset" do
17
+ context "with no timezone available" do
18
+ it "should return the local time zone" do
19
+ bp = Pidgin2Adium::BasicParser.new(@text_logfile_path,
20
+ @aliases)
21
+ bp.get_time_zone_offset.should == @current_tz_offset
22
+ end
23
+ end
24
+
25
+ context "with a time zone available" do
26
+ it "should return the logfiles's time zone" do
27
+ bp = Pidgin2Adium::BasicParser.new(@html_logfile_path,
28
+ @aliases)
29
+ bp.get_time_zone_offset.should == "-0500"
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "#create_adium_time" do
35
+ before(:each) do
36
+ @first_line_time = "4/18/2007 11:02:00 AM"
37
+ @time = "2007-08-20 12:33:13"
38
+ @minimal_time = "04:22:05 AM"
39
+ @minimal_time_2 = "04:22:05"
40
+ @invalid_time = "Hammer time!"
41
+
42
+ # Use HTML logfile because it has an explicit timezone (-0500), so we
43
+ # don't have to calculate it out.
44
+ @bp = Pidgin2Adium::BasicParser.new(@html_logfile_path,
45
+ @aliases)
46
+ end
47
+
48
+ it "should parse a first line time correctly" do
49
+ time = @bp.create_adium_time(@first_line_time, true)
50
+ time.should == "2007-04-18T11.02.00-0500"
51
+ end
52
+
53
+ it "should parse a normal time correctly" do
54
+ time = @bp.create_adium_time(@time)
55
+ time.should == "2007-08-20T12:33:13-0500"
56
+ end
57
+
58
+ it "should parse a minimal time correctly" do
59
+ time = @bp.create_adium_time(@minimal_time)
60
+ time.should == "2008-01-15T04:22:05-0500"
61
+ end
62
+
63
+ it "should parse a minimal time without AM/PM correctly" do
64
+ time = @bp.create_adium_time(@minimal_time_2)
65
+ time.should == "2008-01-15T04:22:05-0500"
66
+ end
67
+
68
+ it "should return an array of nils for an invalid time" do
69
+ time = @bp.create_adium_time(@invalid_time)
70
+ time.should be_nil
71
+ end
72
+ end
73
+
74
+ describe "#pre_parse" do
75
+ it "should raise an error for an invalid first line" do
76
+ bp = Pidgin2Adium::BasicParser.new(
77
+ File.join(@current_dir,
78
+ "logfiles",
79
+ "invalid-first-line.txt"),
80
+ @aliases)
81
+ lambda do
82
+ bp.pre_parse()
83
+ end.should raise_error(Pidgin2Adium::InvalidFirstLineError)
84
+ end
85
+
86
+ it "should return correct info for an valid first line" do
87
+ bp = Pidgin2Adium::BasicParser.new(@html_logfile_path,
88
+ @aliases)
89
+ results = bp.pre_parse()
90
+ results.should be_instance_of(Array)
91
+ results.should == ['aim', # service
92
+ 'othersn', # my SN
93
+ 'aolsystemmsg', # other person's SN
94
+ {:year=>2008, :mon=>1, :mday=>15}, # basic time info
95
+ '2008-01-15T07.14.45-0500' # chat start time
96
+ ]
97
+ end
98
+ end
99
+
100
+ describe "#get_sender_by_alias" do
101
+ before(:each) do
102
+ @my_alias = "Gabe B-W"
103
+ @my_SN = "awesomesn" # normalized from "awesome SN"
104
+
105
+ @partner_alias = "Leola Farber III"
106
+ @partner_SN = "BUDDY_PERSON" # not normalized
107
+ # Use text logfile since it has aliases set up.
108
+ @bp = Pidgin2Adium::BasicParser.new(@text_logfile_path,
109
+ @my_alias)
110
+ end
111
+
112
+ it "should return my SN when passed my alias" do
113
+ @bp.get_sender_by_alias(@my_alias).should == @my_SN
114
+ end
115
+
116
+ it "should return my SN when passed my alias with an action" do
117
+ @bp.get_sender_by_alias("***#{@my_alias}").should == @my_SN
118
+ end
119
+
120
+ it "should return partner's SN when passed partner's alias" do
121
+ @bp.get_sender_by_alias(@partner_alias).should == @partner_SN
122
+ end
123
+ end
124
+
125
+ describe "#create_msg" do
126
+ before(:each) do
127
+ body = "Your screen name (otherSN) is now signed into " +
128
+ "AOL(R) Instant Messenger (TM) in 2 locations. " +
129
+ "To sign off the other location(s), reply to this message " + "with the number 1. Click " +
130
+ "<a href='http://www.aim.com/password/routing.adp'>here</a> " +
131
+ "for more information."
132
+ @matches = ['2008-01-15T07.14.45-0500', # time
133
+ 'AOL System Msg', # alias
134
+ nil, # not an auto-reply
135
+ body # message body
136
+ ]
137
+ @auto_reply_matches = @matches.dup
138
+ @auto_reply_matches[2] = '<AUTO-REPLY>'
139
+
140
+ @bp = Pidgin2Adium::BasicParser.new(@text_logfile_path,
141
+ "Gabe B-W")
142
+ end
143
+
144
+
145
+ it "should return XMLMessage class for a normal message" do
146
+ @bp.create_msg(@matches).should
147
+ be_instance_of(Pidgin2Adium::XMLMessage)
148
+ end
149
+
150
+ it "should return AutoReplyMessage class for an auto reply" do
151
+ @bp.create_msg(@auto_reply_matches).should
152
+ be_instance_of(Pidgin2Adium::AutoReplyMessage)
153
+ end
154
+
155
+ it "should return nil if the time is nil" do
156
+ @matches[0] = nil
157
+ @bp.create_msg(@matches).should be_nil
158
+ end
159
+ end
160
+
161
+ describe "#create_status_or_event_msg" do
162
+ before(:each) do
163
+ # not yet converted to Adium format
164
+ @time = "2007-08-20 12:33:13"
165
+ @alias = "Gabe B-W"
166
+ @status_map = {
167
+ "#{@alias} logged in." => 'online',
168
+ "#{@alias} logged out." => 'offline',
169
+ "#{@alias} has signed on." => 'online',
170
+ "#{@alias} has signed off." => 'offline',
171
+ "#{@alias} has gone away." => 'away',
172
+ "#{@alias} is no longer away." => 'available',
173
+ "#{@alias} has become idle." => 'idle',
174
+ "#{@alias} is no longer idle." => 'available'
175
+ }
176
+
177
+ # Small subset of all events
178
+ @libpurple_event_msg = "Starting transfer of cute kitties.jpg from Gabe B-W"
179
+ @event_msg = "You missed 8 messages from Gabe B-W because they were too large"
180
+ @event_type = 'chat-error'
181
+
182
+ @ignored_event_msg = "Gabe B-W is now known as gbw.<br/>"
183
+
184
+ @bp = Pidgin2Adium::BasicParser.new(@html_logfile_path,
185
+ @alias)
186
+ end
187
+
188
+ it "should map statuses correctly" do
189
+ @status_map.each do |message, status|
190
+ return_value = @bp.create_status_or_event_msg([@time,
191
+ message])
192
+ return_value.should be_instance_of(Pidgin2Adium::StatusMessage)
193
+ return_value.status.should == status
194
+ end
195
+ end
196
+
197
+ it "should map libpurple events correctly" do
198
+ return_val = @bp.create_status_or_event_msg([@time,
199
+ @libpurple_event_msg])
200
+ return_val.should be_instance_of(Pidgin2Adium::Event)
201
+ return_val.event_type.should == 'libpurpleEvent'
202
+ end
203
+
204
+ it "should map non-libpurple events correctly" do
205
+ return_val = @bp.create_status_or_event_msg([@time,
206
+ @event_msg])
207
+ return_val.should be_instance_of(Pidgin2Adium::Event)
208
+ return_val.event_type.should == @event_type
209
+ end
210
+
211
+ it "should return nil for ignored events" do
212
+ return_val = @bp.create_status_or_event_msg([@time,
213
+ @ignored_event_msg])
214
+ return_val.should be_nil
215
+ end
216
+ end
217
+ end