tam_tam 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ ideas/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ script: bundle exec rake
3
+ rvm:
4
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jimmy Cuadra
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,200 @@
1
+ # TamTam
2
+
3
+ [![Build Status](https://travis-ci.org/jimmycuadra/tam_tam.png?branch=master)](https://travis-ci.org/jimmycuadra/tam_tam)
4
+
5
+ **TamTam** is a Ruby gem to parse, filter, and analyze logs from chat clients. You can filter by various factors: date, participants, or contents.
6
+
7
+ TamTam is agnostic to operating system and chat client, and ships with adapters for:
8
+
9
+ * Adium (OS X)
10
+
11
+ Additional adapters are planned, and welcome via pull request.
12
+
13
+ ## Installation
14
+
15
+ On the command line:
16
+
17
+ ``` bash
18
+ gem install tam_tam
19
+ ```
20
+
21
+ Or via Bundler:
22
+
23
+ ``` ruby
24
+ gem "tam_tam"
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ### Instantiation
30
+
31
+ Create a new logs object with the default Adium adapter:
32
+
33
+ ``` ruby
34
+ require "tam_tam"
35
+ logs = TamTam.new
36
+ ```
37
+
38
+ Specify an adapter:
39
+
40
+ ```ruby
41
+ logs = TamTam.new(adapter: :messages)
42
+ ```
43
+
44
+ List all registered adapters:
45
+
46
+ ``` ruby
47
+ TamTam.adapters # [:adium, :messages]
48
+ ```
49
+
50
+ Use a non-standard path for log files:
51
+
52
+ ``` ruby
53
+ TamTam.new(path: "/path/to/logs")
54
+ ```
55
+
56
+ #### Limiting the loaded adapters
57
+
58
+ When you `require "tam_tam"`, all the included adapters are loaded. If you want to save memory and require only the one(s) you need, you may do so:
59
+
60
+ ``` ruby
61
+ require "tam_tam/adapters/adium"
62
+
63
+ logs = TamTam.new
64
+ ```
65
+
66
+ If an adapter is not provided when calling `.new`, TamTam defaults to the Adium adapter, or the first registered adapter if the Adium adapter has not been loaded. Details on using custom adapters are included later in this guide.
67
+
68
+ ### Filtering logs
69
+
70
+ Logs can be filtered by participants and date. All the filter methods are chainable, and return a new `TamTam::Logs` object with the filters applied.
71
+
72
+ #### #as
73
+
74
+ Limits logs to the account you were chatting as. Accepts any number of string arguments with the username of the account.
75
+
76
+ ``` ruby
77
+ logs.as("MyAIMScreenName")
78
+ logs.as("MyAIMScreenName", "google.talk@gmail.com")
79
+ ```
80
+
81
+ #### #with
82
+
83
+ Limits logs to the account you were chatting with. Like `as`, accepts any number of string arguments with the username of the account.
84
+
85
+ ``` ruby
86
+ logs.with("MyFriendJoe")
87
+ logs.with("MyFriendJoe", "my.secret.crush@gmail.com")
88
+ ```
89
+
90
+ #### #on
91
+
92
+ Limits logs to chats that occurred on a particular date. The date can be supplied as a string (any format [Chronic](https://github.com/mojombo/chronic "Chronic") accepts) or a temporal object (`Date`, `Time`, etc.)
93
+
94
+ ``` ruby
95
+ logs.on("September 23, 2013")
96
+ logs.on(Date.today)
97
+ logs.on(Time.now)
98
+ ```
99
+
100
+ #### #between
101
+
102
+ Limits logs to chats that occurred within a date range. Takes two dates, which, like `on`, can be strings or temporal objects.
103
+
104
+ ``` ruby
105
+ logs.between(5.days.ago, Date.today)
106
+ ```
107
+
108
+ ### Accessing messages
109
+
110
+ Once you have filtered logs down to the set you want, you can examine the messages themselves:
111
+
112
+ ``` ruby
113
+ messages = logs.messages
114
+ ```
115
+
116
+ At this point, the logs on disk are loaded into memory, so the first call to `messages` may take a while, depending on how many logs are being loaded. The messages object is enumerable, and can receive any of the usual iteration and transformation methods.
117
+
118
+ ### Filtering messages
119
+
120
+ Messages can be filtered by contents. All the filter methods are chainable and return a new `TamTam::MessageSet` object with the filters applied.
121
+
122
+ #### #including
123
+
124
+ Limits messages to those that include the provided substring.
125
+
126
+ ``` ruby
127
+ messages.including("how do you feel about")
128
+ ```
129
+
130
+ #### #matching
131
+
132
+ Limits messages to those that match the provided regular expression.
133
+
134
+ ``` ruby
135
+ messages.matching(/^lol,?\s+/)
136
+ ```
137
+
138
+ #### #sent_by
139
+
140
+ Limits messages to those sent by the provided username.
141
+
142
+ ``` ruby
143
+ messages.sent_by("MyFriendJoe")
144
+ ```
145
+
146
+ ### Analyzing logs
147
+
148
+ These methods return interesting data about the messages.
149
+
150
+ #### #by_count
151
+
152
+ Returns a hash of all the messages grouped by text and the number of times a message with that text was sent. Useful for seeing what messages/phrases you and your chat buddies say most often.
153
+
154
+ ``` ruby
155
+ messages.by_count # { "hi" => 4, "how's it going?" => 2 }
156
+ ```
157
+
158
+ ### Individual messages
159
+
160
+ When enumerating messages, each message is a `TamTam::Message` object with the following attributes:
161
+
162
+ #### #sender
163
+
164
+ The username of the person who sent the message.
165
+
166
+ #### #text
167
+
168
+ The body of the message.
169
+
170
+ #### #time
171
+
172
+ The date and time the message was sent, as a `Time` object.
173
+
174
+ ## Custom adapters
175
+
176
+ An adapter is a class that inherits from `TamTam::Adapter` and implements its abstract interface. Once you have defined your adapter class, register it with TamTam:
177
+
178
+ ``` ruby
179
+ TamTam.register_adapter(:crazy_chat, CrazyChatAdapter)
180
+ ```
181
+
182
+ Adapters must define the following methods:
183
+
184
+ * `.default_path`
185
+ * `#default_matches`
186
+ * `#load_messages`
187
+ * `#as`
188
+ * `#with`
189
+ * `#on`
190
+ * `#between`
191
+
192
+ See any of the included adapters for examples.
193
+
194
+ ## Contributing
195
+
196
+ Issues and pull requests are welcome! If you're opening a pull request, please make sure your branch maintains full test coverage (open `coverage/index.html` after running the specs), and that you don't get any warnings from running `cane`.
197
+
198
+ ## License
199
+
200
+ TamTam is available under the MIT license. See the provided `LICENSE.txt` for details.
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require "cane/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ Cane::RakeTask.new(:quality) do |cane|
7
+ cane.add_threshold "coverage/covered_percent", :>=, 100
8
+ end
9
+
10
+ task :default => [:spec, :quality]
@@ -0,0 +1 @@
1
+ require "tam_tam/adapters/adium"
@@ -0,0 +1,29 @@
1
+ require "tam_tam/core"
2
+ require "tam_tam/log_set"
3
+
4
+ module TamTam
5
+ # An exception to raise when abstract methods have not been implemented.
6
+ class AbstractMethodError < StandardError; end
7
+
8
+ # An abstract base class for all adapters.
9
+ class Adapter
10
+ attr_accessor :path
11
+
12
+ def initialize(path = nil)
13
+ self.path = path || self.class.default_path
14
+ end
15
+
16
+ def to_s
17
+ %{#<#{self.class.name}:0x#{object_id.to_s(16)} path="#{path}">}
18
+ end
19
+ alias_method :inspect, :to_s
20
+
21
+ %w[default_matches load_messages as with on between].each do |method|
22
+ define_method(method) do
23
+ raise AbstractMethodError.new(
24
+ "##{method} must be defined in the adapter."
25
+ )
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,58 @@
1
+ require "tam_tam/adapter"
2
+
3
+ require "nokogiri"
4
+
5
+ module TamTam
6
+ module Adapters
7
+ # Adapter for Adium. http://adium.im/
8
+ class Adium < Adapter
9
+ def self.default_path
10
+ "#{Dir.home}/Library/Application Support/Adium 2.0/Users/Default/Logs"
11
+ end
12
+
13
+ def default_matches
14
+ Dir["#{path}/**/*.xml"]
15
+ end
16
+
17
+ def load_messages(matches)
18
+ matches.map do |log_path|
19
+ xml = File.read(log_path)
20
+ doc = Nokogiri.parse(xml)
21
+
22
+ doc.css("chat message").map do |message|
23
+ {
24
+ sender: message.attribute("sender").value,
25
+ text: message.text,
26
+ time: Chronic.parse(message.attribute("time").value)
27
+ }
28
+ end
29
+ end.flatten
30
+ end
31
+
32
+ def as(accounts)
33
+ Dir["#{path}/*\.{#{accounts.join(',')}}/**/*.xml"]
34
+ end
35
+
36
+ def with(participants)
37
+ Dir["#{path}/*\.*/{#{participants.join(',')}}/**/*.xml"]
38
+ end
39
+
40
+ def on(start)
41
+ Dir["#{path}/**/*#{start.to_date.to_s}*/*.xml"]
42
+ end
43
+
44
+ def between(start, stop)
45
+ start = start.to_date
46
+ stop = stop.to_date
47
+
48
+ default_matches.select do |log|
49
+ match = log.match(/(\d{4})-(\d{2})-(\d{2})/)
50
+ log_date = Time.new(match[1], match[2], match[3]).to_date
51
+ log_date >= start && log_date <= stop
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ register_adapter :adium, Adapters::Adium
58
+ end
@@ -0,0 +1,28 @@
1
+ require "tam_tam/version"
2
+
3
+ # The primary entry point for users.
4
+ module TamTam
5
+ @adapters ||= {}
6
+
7
+ class << self
8
+ def new(options = {})
9
+ adapter_key = options[:adapter]
10
+ adapter_key = :adium if adapter_key.nil? && adapters.include?(:adium)
11
+ adapter_key = adapters.first if adapter_key.nil?
12
+ adapter = @adapters[adapter_key].new(options[:path])
13
+ LogSet.new(adapter)
14
+ end
15
+
16
+ def register_adapter(key, adapter)
17
+ @adapters[key] = adapter
18
+ end
19
+
20
+ def unregister_adapter(key)
21
+ @adapters.delete(key)
22
+ end
23
+
24
+ def adapters
25
+ @adapters.keys
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,59 @@
1
+ require "tam_tam/message_set"
2
+
3
+ require "chronic"
4
+
5
+ module TamTam
6
+ # A set of chat logs. Responsible for filtering at the chat log level.
7
+ # Delegates to the adapter for client-specific behavior.
8
+ class LogSet
9
+ attr_accessor :adapter, :matches
10
+
11
+ def initialize(adapter)
12
+ self.adapter = adapter
13
+ self.matches = adapter.default_matches
14
+ end
15
+
16
+ def messages
17
+ @messages ||= MessageSet.new(adapter.load_messages(matches))
18
+ end
19
+
20
+ def as(*accounts)
21
+ filter(adapter.as(accounts))
22
+ end
23
+
24
+ def with(*participants)
25
+ filter(adapter.with(participants))
26
+ end
27
+
28
+ def on(start)
29
+ start = Chronic.parse(start) if start.is_a?(String)
30
+ filter(adapter.on(start))
31
+ end
32
+
33
+ def between(start, stop)
34
+ start = Chronic.parse(start) if start.is_a?(String)
35
+ stop = Chronic.parse(stop) if stop.is_a?(String)
36
+ filter(adapter.between(start, stop))
37
+ end
38
+
39
+ def to_s
40
+ %{#<#{self.class.name}:0x#{object_id.to_s(16)} adapter=#{adapter.to_s}>}
41
+ end
42
+ alias_method :inspect, :to_s
43
+
44
+ protected
45
+
46
+ def clear_messages_cache
47
+ @messages = nil if defined?(@messages)
48
+ end
49
+
50
+ private
51
+
52
+ def filter(filter_matches)
53
+ copy = dup
54
+ copy.clear_messages_cache
55
+ copy.matches = matches & filter_matches
56
+ copy
57
+ end
58
+ end
59
+ end