pidgin2adium 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/Manifest.txt +10 -0
- data/README.rdoc +106 -0
- data/Rakefile.rb +26 -0
- data/bin/pidgin2adium +72 -0
- data/lib/pidgin2adium.rb +120 -0
- data/lib/pidgin2adium/{balance-tags.rb → balance_tags.rb} +32 -29
- data/lib/pidgin2adium/log_converter.rb +68 -0
- data/lib/pidgin2adium/log_file.rb +101 -0
- data/lib/pidgin2adium/log_parser.rb +590 -0
- metadata +39 -19
- data/bin/pidgin2adium_logs +0 -67
- data/bin/pidgin2adium_status +0 -15
- data/lib/pidgin2adium/ChatFileGenerator.rb +0 -59
- data/lib/pidgin2adium/SrcFileParse.rb +0 -485
- data/lib/pidgin2adium/logs.rb +0 -250
- data/lib/pidgin2adium/status.rb +0 -113
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
== pidgin2adium
|
2
|
+
* http://rubyforge.org/projects/pidgin2adium/
|
3
|
+
|
4
|
+
== DESCRIPTION:
|
5
|
+
Pidgin2Adium is a fast, easy way to convert Pidgin (formerly gaim) logs to the
|
6
|
+
Adium format.
|
7
|
+
Note that it assumes a Mac OS X environment with Adium installed.
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
* No problems (well, hopefully).
|
11
|
+
|
12
|
+
== SYNOPSIS:
|
13
|
+
|
14
|
+
There are two ways you can use this gem: as a script or as a library.
|
15
|
+
Both require you to provide aliases, which may require a bit of explanation.
|
16
|
+
Adium and Pidgin allow you to set aliases for buddies as well as for yourself,
|
17
|
+
so that you show up in chats as (for example) "Me" instead of as
|
18
|
+
"best_screen_name_ever_018845".
|
19
|
+
|
20
|
+
However, Pidgin then uses aliases in the log file instead of the actual screen
|
21
|
+
name, which complicates things. To parse properly, this gem needs to know which
|
22
|
+
aliases belong to you so it can map them to the correct screen name.
|
23
|
+
If it encounters an alias that you did not list, it assumes that it belongs to
|
24
|
+
the person to whom you are chatting.
|
25
|
+
Note that aliases are lower-cased and space is removed, so providing "Gabe B-W,
|
26
|
+
GBW" is the same as providing "gabeb-w,gbw".
|
27
|
+
|
28
|
+
===Example (using script)
|
29
|
+
Assuming that:
|
30
|
+
* your Pidgin log files are in the "pidgin-logs" folder
|
31
|
+
* your various aliases in your chats are "Gabe", "Gabe B-W", and "gbw"
|
32
|
+
Then run:
|
33
|
+
pidgin2adium -i pidgin-logs -a "Gabe, Gabe B-W, gbw"
|
34
|
+
|
35
|
+
===Example (using library)
|
36
|
+
The library style allows you to parse a log file and get back a
|
37
|
+
LogFile instance for easy reading, manipulation, etc.
|
38
|
+
You can also create log files yourself using Pidgin2Adium.parse_and_generate.
|
39
|
+
|
40
|
+
require 'pidgin2adium'
|
41
|
+
logfile = Pidgin2Adium.parse("/path/to/log/file.html", "gabe,gbw,gabeb-w")
|
42
|
+
if logfile == false
|
43
|
+
puts "Oh no! Could not parse!"
|
44
|
+
else
|
45
|
+
logfile.each do |message|
|
46
|
+
# Every message has these properties
|
47
|
+
puts "Sender's screen name: #{message.sender}"
|
48
|
+
puts "Time message was sent: #{message.time}"
|
49
|
+
puts "Sender's alias: #{message.buddy_alias}"
|
50
|
+
if [Pidgin2Adium::XMLMessage, Pidgin2Adium::AutoReplyMessage, Pidgin2Adium::Event].include?(message.class)
|
51
|
+
# All of these have a body
|
52
|
+
puts "Message body: #{message.body}"
|
53
|
+
if message.class == Pidgin2Adium::Event
|
54
|
+
puts "Event type: #{message.event_type}"
|
55
|
+
end
|
56
|
+
elsif message.class == Pidgin2Adium::StatusMessage
|
57
|
+
# Only StatusMessage has status
|
58
|
+
puts "Status: #{message.status}"
|
59
|
+
end
|
60
|
+
# Prints out the message in Adium log format
|
61
|
+
puts message.to_s
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
===Example 2 (using library)
|
66
|
+
If you want to output the logfile to an output dir instead of just parsing it, use Pidgin2Adium.parse_and_generate:
|
67
|
+
|
68
|
+
require 'pidgin2adium'
|
69
|
+
opts = {:overwrite => true, :output_dir => "/my/output/dir"}
|
70
|
+
path_to_converted_log = Pidgin2Adium.parse_and_generate("/path/to/log/file.html", "gabe,gbw,gabeb-w", opts)
|
71
|
+
|
72
|
+
== REQUIREMENTS:
|
73
|
+
* None
|
74
|
+
|
75
|
+
== INSTALL:
|
76
|
+
* sudo gem install pidgin2adium
|
77
|
+
|
78
|
+
== THANKS
|
79
|
+
With thanks to Li Ma, whose blog post at
|
80
|
+
http://li-ma.blogspot.com/2008/10/pidgin-log-file-to-adium-log-converter.html
|
81
|
+
helped tremendously.
|
82
|
+
|
83
|
+
== LICENSE:
|
84
|
+
|
85
|
+
(The MIT License)
|
86
|
+
|
87
|
+
Copyright (c) 2009 Gabriel Berke-Williams
|
88
|
+
|
89
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
90
|
+
a copy of this software and associated documentation files (the
|
91
|
+
'Software'), to deal in the Software without restriction, including
|
92
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
93
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
94
|
+
permit persons to whom the Software is furnished to do so, subject to
|
95
|
+
the following conditions:
|
96
|
+
|
97
|
+
The above copyright notice and this permission notice shall be
|
98
|
+
included in all copies or substantial portions of the Software.
|
99
|
+
|
100
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
101
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
102
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
103
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
104
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
105
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
106
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe', '>= 2.1.0'
|
3
|
+
require 'hoe'
|
4
|
+
require 'fileutils'
|
5
|
+
require './lib/pidgin2adium.rb'
|
6
|
+
|
7
|
+
Hoe.plugin :newgem
|
8
|
+
# Hoe.plugin :website
|
9
|
+
# Hoe.plugin :cucumberfeatures
|
10
|
+
|
11
|
+
# Generate all the Rake tasks
|
12
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
13
|
+
$hoe = Hoe.spec 'pidgin2adium' do
|
14
|
+
self.developer('Gabe B-W', 'gbw@brandeis.edu')
|
15
|
+
self.extra_rdoc_files = %w{README.rdoc}
|
16
|
+
#self.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
|
17
|
+
self.rubyforge_name = self.name # this is default value
|
18
|
+
# self.extra_deps = [['activesupport','>= 2.0.2']]
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'newgem/tasks'
|
22
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
23
|
+
|
24
|
+
# TODO - want other tests/tasks run by default? Add them to the list
|
25
|
+
# remove_task :default
|
26
|
+
# task :default => [:spec, :features]
|
data/bin/pidgin2adium
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
|
3
|
+
=begin
|
4
|
+
Author: Gabe Berke-Williams, 2008
|
5
|
+
Usage:
|
6
|
+
This is the shell script, which is a wrapper around Pidgin2Adium::LogConverter.
|
7
|
+
Call it like so:
|
8
|
+
<tt>pidgin2adium -i ~/in_logs/ -a "me,my_pidgin_alias,other_pidgin_alias"</tt>
|
9
|
+
For <tt>-a/--aliases</tt>, there is no need to use spaces or capitalization,
|
10
|
+
since spaces will be stripped out and the aliases will be lowercased anyway.
|
11
|
+
Aliases doesn't have to include screennames, either, since these are
|
12
|
+
automatically recognized.
|
13
|
+
=end
|
14
|
+
|
15
|
+
require 'pidgin2adium/log_converter'
|
16
|
+
require 'optparse'
|
17
|
+
|
18
|
+
options = {}
|
19
|
+
oparser = OptionParser.new do |opts|
|
20
|
+
opts.banner = "Usage: #{File.basename($0)} [options]"
|
21
|
+
opts.on('-i', '--in=IN_DIR', String, 'Specify directory where pidgin logs are stored') do |i|
|
22
|
+
options[:in] = i
|
23
|
+
end
|
24
|
+
opts.on('-a alias1,alias2', "--aliases alias1,alias2",
|
25
|
+
"A comma-separated list of your alias(es) so this script knows
|
26
|
+
which person in a chat is you.", "Whitespace and case do not matter.") do |aliases|
|
27
|
+
options[:aliases] = aliases
|
28
|
+
end
|
29
|
+
opts.on('-f', '--force', 'If this is set, then logs in the Adium log folder that have the same name as converted logs will be overwritten.') do |f|
|
30
|
+
options[:force] = f
|
31
|
+
end
|
32
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
33
|
+
puts opts
|
34
|
+
exit
|
35
|
+
end
|
36
|
+
end
|
37
|
+
begin
|
38
|
+
oparser.parse!
|
39
|
+
rescue => bang
|
40
|
+
if bang.class == OptionParser::MissingArgument
|
41
|
+
# No argument provided for a switch that requires an argument.
|
42
|
+
puts '"%s" requires an argument.' % bang.args[0]
|
43
|
+
exit 1
|
44
|
+
elsif bang.class == OptionParser::InvalidOption
|
45
|
+
# Provided a switch that we don't handle.
|
46
|
+
puts '"%s" is not a valid switch.' % bang.args[0]
|
47
|
+
elsif bang.class == OptionParser::NeedlessArgument
|
48
|
+
# Raised when argument provided for a switch that doesn't take an argument.
|
49
|
+
puts bang.message
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
need_opts = false
|
54
|
+
required_opts = [[:i, :in], [:a, :aliases]]
|
55
|
+
required_opts.each do |short, long|
|
56
|
+
if options.has_key?(long)
|
57
|
+
next
|
58
|
+
else
|
59
|
+
need_opts = true
|
60
|
+
puts "Required option -#{short}/--#{long} missing."
|
61
|
+
end
|
62
|
+
end
|
63
|
+
if need_opts
|
64
|
+
puts oparser.to_s
|
65
|
+
exit 1
|
66
|
+
end
|
67
|
+
|
68
|
+
log_converter = Pidgin2Adium::LogConverter.new(options[:in],
|
69
|
+
options[:aliases],
|
70
|
+
{:overwrite => options[:force]}
|
71
|
+
)
|
72
|
+
log_converter.start
|
data/lib/pidgin2adium.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
# Author: Gabe Berke-Williams, 2008
|
2
|
+
#
|
3
|
+
# A ruby program to convert Pidgin log files to Adium log files, then place
|
4
|
+
# them in the Adium log directory.
|
5
|
+
|
6
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
7
|
+
|
8
|
+
require 'fileutils'
|
9
|
+
require 'pidgin2adium/log_parser'
|
10
|
+
|
11
|
+
module Pidgin2Adium
|
12
|
+
# Returned by LogFile.write_out if the output logfile already exists.
|
13
|
+
FILE_EXISTS = 42
|
14
|
+
ADIUM_LOG_DIR = File.expand_path('~/Library/Application Support/Adium 2.0/Users/Default/Logs/') << '/'
|
15
|
+
# These files/directories show up in Dir.entries()
|
16
|
+
BAD_DIRS = %w{. .. .DS_Store Thumbs.db .system}
|
17
|
+
VERSION = "2.0.0"
|
18
|
+
|
19
|
+
def log_msg(str) #:nodoc
|
20
|
+
puts str.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
def oops(str) #:nodoc
|
24
|
+
warn("Oops: #{str}")
|
25
|
+
end
|
26
|
+
|
27
|
+
def error(str) #:nodoc
|
28
|
+
warn("Error: #{str}")
|
29
|
+
end
|
30
|
+
|
31
|
+
#######################
|
32
|
+
private :log_msg, :oops, :error
|
33
|
+
#######################
|
34
|
+
|
35
|
+
# Returns a LogFile instance or false if an error occurred.
|
36
|
+
def parse(logfile_path, my_aliases)
|
37
|
+
logfile_path = File.expand_path(logfile_path)
|
38
|
+
ext = File.extname(logfile_path).sub('.', '').downcase
|
39
|
+
|
40
|
+
if(ext == "html" || ext == "htm")
|
41
|
+
parser = HtmlLogParser.new(logfile_path, my_aliases)
|
42
|
+
elsif(ext == "txt")
|
43
|
+
parser = TextLogParser.new(logfile_path, my_aliases)
|
44
|
+
else
|
45
|
+
error("logfile (#{logfile_path}) is not a text or html file. Doing nothing.")
|
46
|
+
return false
|
47
|
+
end
|
48
|
+
|
49
|
+
return parser.parse()
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the path to the converted log, false if an error occurred, or
|
53
|
+
# Pidgin2Adium::FILE_EXISTS if file already exists AND opts[:overwrite] =
|
54
|
+
# false.
|
55
|
+
#
|
56
|
+
# You can add options using the _opts_ hash, which can have the following
|
57
|
+
# keys, all of which are optional:
|
58
|
+
# * *overwrite*: If true, then overwrite even if log is found.
|
59
|
+
# Defaults to false.
|
60
|
+
# * *output_dir*: The top-level dir to put the logs in.
|
61
|
+
# Logs under output_dir are still each in their own folders, etc.
|
62
|
+
# Defaults to Pidgin2Adium::ADIUM_LOG_DIR
|
63
|
+
def parse_and_generate(logfile_path, my_aliases, opts = {})
|
64
|
+
opts = {} unless opts.is_a?(Hash)
|
65
|
+
overwrite = !!opts[:overwrite]
|
66
|
+
if opts.key?(:output_dir)
|
67
|
+
output_dir = opts[:output_dir]
|
68
|
+
else
|
69
|
+
output_dir = ADIUM_LOG_DIR
|
70
|
+
end
|
71
|
+
|
72
|
+
unless File.directory?(output_dir)
|
73
|
+
puts "Output log directory (#{output_dir}) does not exist or is not a directory."
|
74
|
+
raise Errno::ENOENT
|
75
|
+
end
|
76
|
+
|
77
|
+
logfile_obj = parse(logfile_path, my_aliases)
|
78
|
+
return false if logfile_obj == false
|
79
|
+
dest_file_path = logfile_obj.write_out(overwrite, output_dir)
|
80
|
+
if dest_file_path == false
|
81
|
+
error("Converting #{logfile_path} failed.")
|
82
|
+
return false
|
83
|
+
elsif dest_file_path == FILE_EXISTS
|
84
|
+
log_msg("File already exists.")
|
85
|
+
return FILE_EXISTS
|
86
|
+
else
|
87
|
+
log_msg("Output to: #{dest_file_path}")
|
88
|
+
return true
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Newly-converted logs are viewable in the Adium Chat Transcript
|
93
|
+
# Viewer, but are not indexed, so a search of the logs doesn't give
|
94
|
+
# results from the converted logs. To fix this, we delete the cached log
|
95
|
+
# indexes, which forces Adium to re-index.
|
96
|
+
#
|
97
|
+
# Note: This function is run by LogConverter after converting all of its
|
98
|
+
# files. LogFile.write_out intentionally does _not_ run it in order to
|
99
|
+
# allow for batch-processing of files. Thus, you will probably want to run
|
100
|
+
# Pidgin2Adium.delete_search_indexes after running LogFile.write_out in
|
101
|
+
# your own scripts.
|
102
|
+
def delete_search_indexes()
|
103
|
+
log_msg "Deleting log search indexes in order to force re-indexing of imported logs..."
|
104
|
+
dirty_file = File.expand_path("~/Library/Caches/Adium/Default/DirtyLogs.plist")
|
105
|
+
log_index_file = File.expand_path("~/Library/Caches/Adium/Default/Logs.index")
|
106
|
+
[dirty_file, log_index_file].each do |f|
|
107
|
+
if File.exist?(f)
|
108
|
+
if File.writable?(f)
|
109
|
+
File.delete(f)
|
110
|
+
else
|
111
|
+
error("#{f} exists but is not writable. Please delete it yourself.")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
log_msg "...done."
|
116
|
+
log_msg "When you next start the Adium Chat Transcript Viewer, it will re-index the logs, which may take a while."
|
117
|
+
end
|
118
|
+
|
119
|
+
module_function :parse, :parse_and_generate, :delete_search_indexes
|
120
|
+
end
|
@@ -1,19 +1,23 @@
|
|
1
|
-
|
2
1
|
module Pidgin2Adium
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
|
2
|
+
# Balances tags of string using a modified stack. Returns a balanced
|
3
|
+
# string, but also affects the text passed into it!
|
4
|
+
# Use text = balance_tags(text).
|
5
|
+
|
6
|
+
# From Wordpress's formatting.php; rewritten in Ruby by Gabe
|
7
|
+
# Berke-Williams, 2009.
|
8
|
+
# Author:: Leonard Lin <leonard@acm.org>
|
9
|
+
# License:: GPL v2.0
|
10
|
+
# Copyright:: November 4, 2001
|
11
|
+
def balance_tags( text )
|
11
12
|
tagstack = []
|
12
13
|
stacksize = 0
|
13
14
|
tagqueue = ''
|
14
15
|
newtext = ''
|
15
|
-
single_tags =
|
16
|
-
nestable_tags =
|
16
|
+
single_tags = %w{br hr img input meta} # Known single-entity/self-closing tags
|
17
|
+
#nestable_tags = %w{blockquote div span} # Tags that can be immediately nested within themselves
|
18
|
+
nestable_tags = %w{blockquote div span font} # Tags that can be immediately nested within themselves
|
19
|
+
# 1: tagname, with possible leading "/"
|
20
|
+
# 2: attributes
|
17
21
|
tag_regex = /<(\/?\w*)\s*([^>]*)>/
|
18
22
|
|
19
23
|
# WP bug fix for comments - in case you REALLY meant to type '< !--'
|
@@ -22,24 +26,24 @@ module Pidgin2Adium
|
|
22
26
|
# WP bug fix for LOVE <3 (and other situations with '<' before a number)
|
23
27
|
text.gsub!(/<([0-9]{1})/, '<\1')
|
24
28
|
|
25
|
-
while (
|
26
|
-
regex = regex.to_a
|
29
|
+
while ( pos = (text =~ tag_regex) )
|
27
30
|
newtext << tagqueue
|
28
|
-
|
29
|
-
|
31
|
+
tag = $1.downcase
|
32
|
+
attributes = $2
|
33
|
+
matchlen = $~[0].size
|
30
34
|
|
31
35
|
# clear the shifter
|
32
36
|
tagqueue = ''
|
33
37
|
# Pop or Push
|
34
|
-
if (
|
35
|
-
tag
|
38
|
+
if (tag[0,1] == "/") # End Tag
|
39
|
+
tag.slice!(0,1)
|
36
40
|
# if too many closing tags
|
37
41
|
if(stacksize <= 0)
|
38
42
|
tag = ''
|
39
|
-
#or close to be safe tag = '/'
|
40
|
-
# if stacktop value = tag close value then pop
|
43
|
+
#or close to be safe: tag = '/' << tag
|
41
44
|
elsif (tagstack[stacksize - 1] == tag) # found closing tag
|
42
|
-
|
45
|
+
# if stacktop value == tag close value then pop
|
46
|
+
tag = '</' << tag << '>' # Close Tag
|
43
47
|
# Pop
|
44
48
|
tagstack.pop
|
45
49
|
stacksize -= 1
|
@@ -59,14 +63,13 @@ module Pidgin2Adium
|
|
59
63
|
end
|
60
64
|
else
|
61
65
|
# Begin Tag
|
62
|
-
tag = regex[1].downcase
|
63
66
|
|
64
67
|
# Tag Cleaning
|
65
|
-
if( (
|
68
|
+
if( (attributes[-1,1] == '/') || (tag == '') )
|
66
69
|
# If: self-closing or '', don't do anything.
|
67
70
|
elsif ( single_tags.include?(tag) )
|
68
71
|
# ElseIf: it's a known single-entity tag but it doesn't close itself, do so
|
69
|
-
|
72
|
+
attributes << '/'
|
70
73
|
else
|
71
74
|
# Push the tag onto the stack
|
72
75
|
# If the top of the stack is the same as the tag we want to push, close previous tag
|
@@ -76,11 +79,11 @@ module Pidgin2Adium
|
|
76
79
|
tagqueue = '</' << tagstack.pop << '>'
|
77
80
|
stacksize -= 1
|
78
81
|
end
|
79
|
-
|
82
|
+
tagstack.push(tag)
|
83
|
+
stacksize += 1
|
80
84
|
end
|
81
85
|
|
82
86
|
# Attributes
|
83
|
-
attributes = regex[2]
|
84
87
|
if(attributes != '')
|
85
88
|
attributes = ' ' << attributes
|
86
89
|
end
|
@@ -91,8 +94,8 @@ module Pidgin2Adium
|
|
91
94
|
tag = ''
|
92
95
|
end
|
93
96
|
end
|
94
|
-
newtext << text[0,
|
95
|
-
text = text[
|
97
|
+
newtext << text[0,pos] << tag
|
98
|
+
text = text[pos+matchlen, text.length - (pos+matchlen)]
|
96
99
|
end
|
97
100
|
|
98
101
|
# Clear Tag Queue
|
@@ -102,8 +105,8 @@ module Pidgin2Adium
|
|
102
105
|
newtext << text
|
103
106
|
|
104
107
|
# Empty Stack
|
105
|
-
|
106
|
-
newtext << '</' <<
|
108
|
+
tagstack.reverse_each do |t|
|
109
|
+
newtext << '</' << t << '>' # Add remaining tags to close
|
107
110
|
end
|
108
111
|
|
109
112
|
# WP fix for the bug with HTML comments
|