chat_stew 0.0.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.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/README.md +47 -0
- data/Rakefile +7 -0
- data/chat_stew.gemspec +25 -0
- data/lib/chat_stew.rb +30 -0
- data/lib/chat_stew/adium_message.rb +3 -0
- data/lib/chat_stew/parsers/adium.rb +31 -0
- data/lib/chat_stew/version.rb +3 -0
- data/spec/chat_stew/adium_parser.rb +44 -0
- data/spec/chat_stew_spec.rb +75 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/logs/adium_log.xml +5 -0
- data/spec/support/logs/random_log.html +8 -0
- data/spec/support/mock_parser.rb +24 -0
- metadata +134 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Chat Stew
|
2
|
+
A pluggable parsing library for chat logs. It handles Adium out of the box.
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
|
6
|
+
require 'chat_stew'
|
7
|
+
# my_file can be anything that responds to #read. If it's a String, it's assumed
|
8
|
+
# that it's a filename.
|
9
|
+
chat = ChatStew.parse(my_file)
|
10
|
+
chat.each do |message|
|
11
|
+
message.sender
|
12
|
+
message.receiver
|
13
|
+
message.message
|
14
|
+
message.time # a DateTime
|
15
|
+
end
|
16
|
+
|
17
|
+
## Writing your own parser
|
18
|
+
A parser is anything that conforms to this specification:
|
19
|
+
|
20
|
+
* responds to #can_parse?(file) and returns truish or falsish
|
21
|
+
* responds to #parse(file) and returns an Enumerable containing
|
22
|
+
ChatStew::AdiumMessage instances
|
23
|
+
|
24
|
+
If two parsers are registered that can both parse a given file, the
|
25
|
+
most-recently-registered parser will win. I.E. if you register FooParser and
|
26
|
+
then register BarParser, and they can both parse a file called "foo.bar", then
|
27
|
+
BarParser will be used to parse it since it was registered after FooParser.
|
28
|
+
|
29
|
+
class FooParser
|
30
|
+
def can_parse?(file)
|
31
|
+
file.path =~ /foo$/
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse(file)
|
35
|
+
# Just return an empty log
|
36
|
+
[]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Register it
|
41
|
+
foo_parser = FooParser.new
|
42
|
+
ChatStew.register(foo_parser)
|
43
|
+
# Will use foo_parser, so parse_result will be an empty array.
|
44
|
+
parse_result = ChatStew.parse("whatever.foo")
|
45
|
+
|
46
|
+
## Author
|
47
|
+
Gabe Berke-Williams, 2011
|
data/Rakefile
ADDED
data/chat_stew.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "chat_stew/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "chat_stew"
|
7
|
+
s.version = ChatStew::VERSION
|
8
|
+
s.authors = ["Gabe Berke-Williams"]
|
9
|
+
s.email = ["gabe@thoughtbot.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{An Adium log parser with an easy interface. Supports custom parsers too.}
|
12
|
+
s.description = s.summary
|
13
|
+
|
14
|
+
s.rubyforge_project = "chat_stew"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency("nokogiri", "~> 1.5.0")
|
22
|
+
|
23
|
+
s.add_development_dependency("rspec", "~> 2.6.0")
|
24
|
+
s.add_development_dependency("bourne", "~> 1.0")
|
25
|
+
end
|
data/lib/chat_stew.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require "chat_stew/version"
|
2
|
+
require "chat_stew/adium_message"
|
3
|
+
require "chat_stew/parsers/adium"
|
4
|
+
|
5
|
+
module ChatStew
|
6
|
+
def self.register(parser)
|
7
|
+
parsers.unshift(parser)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.parse(input)
|
11
|
+
input = StringIO.new(input) unless input.respond_to?(:read)
|
12
|
+
|
13
|
+
valid_parser = parsers.detect {|parser| parser.can_parse?(input) }
|
14
|
+
if valid_parser.nil?
|
15
|
+
nil
|
16
|
+
else
|
17
|
+
valid_parser.parse(input)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.clear_parsers!
|
22
|
+
@parsers = []
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def self.parsers
|
28
|
+
@parsers ||= []
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'date'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module ChatStew
|
6
|
+
module Parsers
|
7
|
+
class Adium
|
8
|
+
def can_parse?(file)
|
9
|
+
contents = file.read
|
10
|
+
file.rewind
|
11
|
+
return contents.include?('<?xml version="1.0" encoding="UTF-8" ?>') &&
|
12
|
+
contents.include?('<chat xmlns="http://purl.org/net/ulf/ns/0.4-02"')
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse(file)
|
16
|
+
doc = Nokogiri.XML(file)
|
17
|
+
chat_messages = doc.search('chat message')
|
18
|
+
account = doc.at('chat')[:account]
|
19
|
+
other_account = chat_messages.detect{|m| m[:sender] != account }[:sender]
|
20
|
+
|
21
|
+
chat_messages.map do |message|
|
22
|
+
AdiumMessage.new(message[:sender],
|
23
|
+
message[:sender] == account ? other_account : account,
|
24
|
+
message[:alias],
|
25
|
+
message.text,
|
26
|
+
DateTime.parse(message[:time]))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ChatStew::Parsers::Adium, "#can_parse?" do
|
4
|
+
let(:adium_log) { File.join('spec', 'support', 'logs', 'adium_log.xml') }
|
5
|
+
let(:random_log) { File.join('spec', 'support', 'logs', 'random_log.html') }
|
6
|
+
|
7
|
+
it "returns true for an Adium log" do
|
8
|
+
subject.can_parse?(adium_log).should be_true
|
9
|
+
end
|
10
|
+
|
11
|
+
it "returns false for a non-Adium log" do
|
12
|
+
subject.can_parse?(random_log).should be_false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe ChatStew::Parsers::Adium, "#parse" do
|
17
|
+
let(:adium_log) { File.new(File.join('spec', 'support', 'logs', 'adium_log.xml')) }
|
18
|
+
let(:parsed) { subject.parse(adium_log) }
|
19
|
+
|
20
|
+
it "correctly parses the sender" do
|
21
|
+
parsed[0].sender.should == "this_is_me"
|
22
|
+
parsed[1].sender.should == "this_is_them"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "correctly parses the receiver" do
|
26
|
+
parsed[0].receiver.should == "this_is_them"
|
27
|
+
parsed[1].receiver.should == "this_is_me"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "correctly parses the alias" do
|
31
|
+
parsed[0].alias.should == "my_alias"
|
32
|
+
parsed[1].alias.should == "their_alias"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "correctly parses the message" do
|
36
|
+
parsed[0].message.should == "first message > second message"
|
37
|
+
parsed[1].message.should == "untrue! & I can prove it"
|
38
|
+
end
|
39
|
+
|
40
|
+
it "correctly parses the time" do
|
41
|
+
parsed[0].time.should == DateTime.parse("2009-01-10T10:14:28-05:00")
|
42
|
+
parsed[1].time.should == DateTime.parse("2009-01-10T20:15:30-05:00")
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ChatStew, ".register" do
|
4
|
+
let(:parser) { stub("Parser", :can_parse? => true, :parse => []) }
|
5
|
+
let(:log) { stub("Chat Log", :read => "asdf") }
|
6
|
+
|
7
|
+
before { ChatStew.clear_parsers! }
|
8
|
+
|
9
|
+
it "registers the given parser" do
|
10
|
+
ChatStew.register(parser)
|
11
|
+
ChatStew.parse(log)
|
12
|
+
|
13
|
+
parser.should have_received(:parse).with(log)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe ChatStew, ".parse" do
|
18
|
+
let(:parser_one) { stub("Parser", :can_parse? => true, :parse => ['one']) }
|
19
|
+
let(:parser_two) { stub("Parser", :can_parse? => true, :parse => ['two']) }
|
20
|
+
let(:invalid_parser) { stub("Parser", :can_parse? => false, :parse => ['two']) }
|
21
|
+
let(:log) { stub("Adium Log", :read => "asdf") }
|
22
|
+
|
23
|
+
before { ChatStew.clear_parsers! }
|
24
|
+
|
25
|
+
it "only uses parsers that can parse the file" do
|
26
|
+
ChatStew.register(parser_one)
|
27
|
+
ChatStew.register(invalid_parser)
|
28
|
+
ChatStew.parse(log)
|
29
|
+
|
30
|
+
invalid_parser.should have_received(:parse).never
|
31
|
+
parser_one.should have_received(:parse).with(log)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "parses with the most-recently-registered parser" do
|
35
|
+
ChatStew.register(parser_one)
|
36
|
+
ChatStew.register(parser_two)
|
37
|
+
ChatStew.parse(log)
|
38
|
+
|
39
|
+
parser_one.should have_received(:parse).never
|
40
|
+
parser_two.should have_received(:parse).with(log)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns nil if no parser is available" do
|
44
|
+
ChatStew.parse(log).should be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
context "input" do
|
48
|
+
let(:adium_log_path) { File.join('spec', 'support', 'logs', 'adium_log.xml') }
|
49
|
+
let(:mock_parser) { MockParser.new }
|
50
|
+
before { ChatStew.register(mock_parser) }
|
51
|
+
|
52
|
+
it "accepts a String" do
|
53
|
+
ChatStew.parse(adium_log_path)
|
54
|
+
mock_parser.should have_parsed
|
55
|
+
mock_parser.input.should respond_to(:read)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "accepts a File" do
|
59
|
+
ChatStew.parse(File.new(adium_log_path))
|
60
|
+
mock_parser.input.should respond_to(:read)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe ChatStew, ".clear_parsers!" do
|
66
|
+
let(:parser) { stub("Parser", :can_parse? => true, :parse => ['one']) }
|
67
|
+
let(:log) { stub("Adium Log", :read => "asdf") }
|
68
|
+
|
69
|
+
it "clears all parsers" do
|
70
|
+
ChatStew.register(parser)
|
71
|
+
ChatStew.clear_parsers!
|
72
|
+
ChatStew.parse(log)
|
73
|
+
parser.should have_received(:parse).never
|
74
|
+
end
|
75
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
|
4
|
+
require "rspec"
|
5
|
+
require "bourne"
|
6
|
+
|
7
|
+
require "chat_stew"
|
8
|
+
|
9
|
+
require "./spec/support/mock_parser"
|
10
|
+
|
11
|
+
RSpec.configure do |c|
|
12
|
+
c.mock_with :mocha
|
13
|
+
end
|
@@ -0,0 +1,5 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
2
|
+
<chat xmlns="http://purl.org/net/ulf/ns/0.4-02" account="this_is_me" service="AIM">
|
3
|
+
<message sender="this_is_me" time="2009-01-10T10:14:28-05:00" alias="my_alias"><div><span style="font-family: Helvetica; font-size: 12pt;">first message > second message</span></div></message>
|
4
|
+
<message sender="this_is_them" time="2009-01-10T20:15:30-05:00" alias="their_alias"><div><span style="font-family: Helvetica; font-size: 12pt;">untrue! & I can prove it</span></div></message>
|
5
|
+
</chat>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<head><meta http-equiv="content-type" content="text/html; charset=UTF-8"><title>Conversation with buddy_screen_name at 2007-06-16 13:51:36 on my screen name (aim)</title></head><h3>Conversation with buddy_screen_name at 2007-06-16 13:51:36 on my screen name (aim)</h3>
|
2
|
+
<font color="#A82F2F"><font size="2">(13:51:35)</font> <b>buddy_alias:</b></font> <font sml="AIM/ICQ">received</font><br/>
|
3
|
+
<font color="#16569E"><font size="2">(14:37:20)</font> <b>my_alias:</b></font> <font sml="AIM/ICQ"><span style='font-size: small; '>sent</span></font><br/>
|
4
|
+
<font color="#16569E"><font size="2">(14:39:05)</font> <b>my_alias:</b></font> <font sml="AIM/ICQ"><span style='font-size: small; '><a HREF="http://www.crazymonkeygames.com/Boxhead-2Play-Rooms.html">http://www.crazymonkeygames.com/Boxhead-2Play-Rooms.html</a></span></font><br/>
|
5
|
+
<font color="#6C2585"><font size="2">(14:39:20)</font> <b>***my_alias:</b></font> <font sml="AIM/ICQ"><span style='font-size: small; '>meified</span></font><br/>
|
6
|
+
<font color="#6C2585"><font size="2">(14:39:21)</font> <b>my_alias:</b></font> <font sml="AIM/ICQ"><span style='font-size: small; '>extra link<a HREF="http://www.nytimes.com/aponline/us/AP-Suspicious-Devices.html?_r=1&ref=us&oref=slogin"></a></span></font><br/>
|
7
|
+
<font size="2">(14:53:22)</font><b> buddy_screen_name logged out.</b><br/>
|
8
|
+
<font color="#FF0000"><font size="2">(14:54:00)</font><b> Unable to send message: Refused by client</b></font><br/>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class MockParser
|
2
|
+
attr_reader :input
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@did_parse = false
|
6
|
+
@input = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
def can_parse?(anything)
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse(input)
|
14
|
+
@did_parse = true
|
15
|
+
@input = input
|
16
|
+
|
17
|
+
[]
|
18
|
+
end
|
19
|
+
|
20
|
+
def has_parsed?
|
21
|
+
@did_parse == true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chat_stew
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Gabe Berke-Williams
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-10-18 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 1
|
31
|
+
- 5
|
32
|
+
- 0
|
33
|
+
version: 1.5.0
|
34
|
+
type: :runtime
|
35
|
+
name: nokogiri
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 23
|
45
|
+
segments:
|
46
|
+
- 2
|
47
|
+
- 6
|
48
|
+
- 0
|
49
|
+
version: 2.6.0
|
50
|
+
type: :development
|
51
|
+
name: rspec
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
prerelease: false
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 15
|
61
|
+
segments:
|
62
|
+
- 1
|
63
|
+
- 0
|
64
|
+
version: "1.0"
|
65
|
+
type: :development
|
66
|
+
name: bourne
|
67
|
+
version_requirements: *id003
|
68
|
+
description: An Adium log parser with an easy interface. Supports custom parsers too.
|
69
|
+
email:
|
70
|
+
- gabe@thoughtbot.com
|
71
|
+
executables: []
|
72
|
+
|
73
|
+
extensions: []
|
74
|
+
|
75
|
+
extra_rdoc_files: []
|
76
|
+
|
77
|
+
files:
|
78
|
+
- .gitignore
|
79
|
+
- .rspec
|
80
|
+
- Gemfile
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- chat_stew.gemspec
|
84
|
+
- lib/chat_stew.rb
|
85
|
+
- lib/chat_stew/adium_message.rb
|
86
|
+
- lib/chat_stew/parsers/adium.rb
|
87
|
+
- lib/chat_stew/version.rb
|
88
|
+
- spec/chat_stew/adium_parser.rb
|
89
|
+
- spec/chat_stew_spec.rb
|
90
|
+
- spec/spec_helper.rb
|
91
|
+
- spec/support/logs/adium_log.xml
|
92
|
+
- spec/support/logs/random_log.html
|
93
|
+
- spec/support/mock_parser.rb
|
94
|
+
has_rdoc: true
|
95
|
+
homepage: ""
|
96
|
+
licenses: []
|
97
|
+
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
hash: 3
|
109
|
+
segments:
|
110
|
+
- 0
|
111
|
+
version: "0"
|
112
|
+
required_rubygems_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
|
+
requirements: []
|
122
|
+
|
123
|
+
rubyforge_project: chat_stew
|
124
|
+
rubygems_version: 1.6.2
|
125
|
+
signing_key:
|
126
|
+
specification_version: 3
|
127
|
+
summary: An Adium log parser with an easy interface. Supports custom parsers too.
|
128
|
+
test_files:
|
129
|
+
- spec/chat_stew/adium_parser.rb
|
130
|
+
- spec/chat_stew_spec.rb
|
131
|
+
- spec/spec_helper.rb
|
132
|
+
- spec/support/logs/adium_log.xml
|
133
|
+
- spec/support/logs/random_log.html
|
134
|
+
- spec/support/mock_parser.rb
|