opera-contacts 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +65 -0
- data/LICENSE.txt +10 -0
- data/README.rdoc +24 -0
- data/Rakefile +44 -0
- data/lib/opera-contacts.rb +5 -0
- data/lib/opera-contacts/contacts.rb +72 -0
- data/lib/opera-contacts/parser.rb +177 -0
- data/opera-contacts.gemspec +63 -0
- data/spec/parser_spec.rb +57 -0
- data/spec/test_files/broken_indentation.adr +18 -0
- data/spec/test_files/detailed_contact.adr +18 -0
- data/spec/test_files/missing_headers.adr +15 -0
- data/spec/test_files/nested_contact.adr +46 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: be3b64235a7f5ed2796123e69d43b013b7e69ef2
|
4
|
+
data.tar.gz: a8054833e8cb16b31268a33fb59f5ed3ddcfd002
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1487b06a375a7304494d8ca08d9c841eec6216089a25afc3d9c3a737e9ff511edc1d5280005e66ac364edfa28089300d74908c7344a0d0821ac697be8eebdac6
|
7
|
+
data.tar.gz: 7462ab4b37b77a260615714ecf337f1f5902d738a2a8a7c1770b5a34235e61e8a45a7a6fe13306dccc28b2684ed5499c183aef15aa2ff3943007d228a6c9c3a8
|
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "rspec"
|
10
|
+
gem "rdoc", "~> 3.12"
|
11
|
+
gem "bundler", "~> 1.0"
|
12
|
+
gem "jeweler", "~> 2.0.0"
|
13
|
+
end
|
14
|
+
|
15
|
+
# vim: ft=ruby et sw=2 ts=2 sts=2
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.3.5)
|
5
|
+
builder (3.2.2)
|
6
|
+
descendants_tracker (0.0.3)
|
7
|
+
diff-lcs (1.2.5)
|
8
|
+
faraday (0.9.0)
|
9
|
+
multipart-post (>= 1.2, < 3)
|
10
|
+
git (1.2.6)
|
11
|
+
github_api (0.11.1)
|
12
|
+
addressable (~> 2.3)
|
13
|
+
descendants_tracker (~> 0.0.1)
|
14
|
+
faraday (~> 0.8, < 0.10)
|
15
|
+
hashie (>= 1.2)
|
16
|
+
multi_json (>= 1.7.5, < 2.0)
|
17
|
+
nokogiri (~> 1.6.0)
|
18
|
+
oauth2
|
19
|
+
hashie (2.0.5)
|
20
|
+
highline (1.6.20)
|
21
|
+
jeweler (2.0.0)
|
22
|
+
builder
|
23
|
+
bundler (>= 1.0)
|
24
|
+
git (>= 1.2.5)
|
25
|
+
github_api
|
26
|
+
highline (>= 1.6.15)
|
27
|
+
nokogiri (>= 1.5.10)
|
28
|
+
rake
|
29
|
+
rdoc
|
30
|
+
json (1.8.1)
|
31
|
+
jwt (0.1.11)
|
32
|
+
multi_json (>= 1.5)
|
33
|
+
mini_portile (0.5.2)
|
34
|
+
multi_json (1.8.4)
|
35
|
+
multi_xml (0.5.5)
|
36
|
+
multipart-post (2.0.0)
|
37
|
+
nokogiri (1.6.1)
|
38
|
+
mini_portile (~> 0.5.0)
|
39
|
+
oauth2 (0.9.3)
|
40
|
+
faraday (>= 0.8, < 0.10)
|
41
|
+
jwt (~> 0.1.8)
|
42
|
+
multi_json (~> 1.3)
|
43
|
+
multi_xml (~> 0.5)
|
44
|
+
rack (~> 1.2)
|
45
|
+
rack (1.5.2)
|
46
|
+
rake (10.1.1)
|
47
|
+
rdoc (3.12.2)
|
48
|
+
json (~> 1.4)
|
49
|
+
rspec (2.14.1)
|
50
|
+
rspec-core (~> 2.14.0)
|
51
|
+
rspec-expectations (~> 2.14.0)
|
52
|
+
rspec-mocks (~> 2.14.0)
|
53
|
+
rspec-core (2.14.7)
|
54
|
+
rspec-expectations (2.14.4)
|
55
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
56
|
+
rspec-mocks (2.14.4)
|
57
|
+
|
58
|
+
PLATFORMS
|
59
|
+
ruby
|
60
|
+
|
61
|
+
DEPENDENCIES
|
62
|
+
bundler (~> 1.0)
|
63
|
+
jeweler (~> 2.0.0)
|
64
|
+
rdoc (~> 3.12)
|
65
|
+
rspec
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
Copyright (c) 2014, Stefan Schneider-Kennedy
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
5
|
+
|
6
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
7
|
+
|
8
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
9
|
+
|
10
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
= opera-contacts
|
2
|
+
|
3
|
+
This library parses the Opera browser's contacts file (called 'contacts.adr').
|
4
|
+
|
5
|
+
Usage example:
|
6
|
+
|
7
|
+
require 'opera-contacts'
|
8
|
+
hotlist_string = File.open("contacts.adr", "r:UTF-8").read
|
9
|
+
contacts_tree = OperaContacts.parse_s(hotlist_string)
|
10
|
+
|
11
|
+
puts("== Top level items (folders or contacts)")
|
12
|
+
contacts_tree.each{|i| puts(i.name)}
|
13
|
+
|
14
|
+
puts("== Top level folders")
|
15
|
+
contacts_tree.folders.each{|f| puts(f.name)}
|
16
|
+
|
17
|
+
puts("== Top level contacts")
|
18
|
+
contacts_tree.contacts.each{|c| puts(c.name)}
|
19
|
+
|
20
|
+
== Copyright
|
21
|
+
|
22
|
+
Copyright (c) 2014 Stefan Schneider-Kennedy. See LICENSE.txt for
|
23
|
+
further details.
|
24
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "opera-contacts"
|
18
|
+
gem.homepage = "http://github.com/splondike/opera-contacts"
|
19
|
+
gem.license = "BSD 2-Clause"
|
20
|
+
gem.summary = %Q{Parse library for the Opera browser contacts file}
|
21
|
+
gem.description = %Q{Parses the opera browser contacts file (ending in .adr) to a ruby data structure. This can then be exported to vCard or whatever.}
|
22
|
+
gem.email = "code@stefansk.name"
|
23
|
+
gem.authors = ["Stefan Schneider-Kennedy"]
|
24
|
+
gem.version = "0.0.0"
|
25
|
+
# dependencies defined in Gemfile
|
26
|
+
end
|
27
|
+
Jeweler::RubygemsDotOrgTasks.new
|
28
|
+
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec)
|
31
|
+
|
32
|
+
task :default => :spec
|
33
|
+
|
34
|
+
require 'rdoc/task'
|
35
|
+
Rake::RDocTask.new do |rdoc|
|
36
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
37
|
+
|
38
|
+
rdoc.rdoc_dir = 'rdoc'
|
39
|
+
rdoc.title = "opera-contacts #{version}"
|
40
|
+
rdoc.rdoc_files.include('README*')
|
41
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
42
|
+
end
|
43
|
+
|
44
|
+
# vim: ft=ruby et sw=2 ts=2 sts=2
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module OperaContacts
|
4
|
+
class Item
|
5
|
+
attr_accessor :id, :name, :created, :description
|
6
|
+
def initialize(id, name, created=Time.new, description='')
|
7
|
+
@id = id
|
8
|
+
@name = name
|
9
|
+
@created = created
|
10
|
+
@description = description
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Collection
|
15
|
+
attr :items
|
16
|
+
def <<(item)
|
17
|
+
@items = [] if @items.nil?
|
18
|
+
@items << item
|
19
|
+
end
|
20
|
+
|
21
|
+
def folders
|
22
|
+
return self.find_all{|i| i.is_a? ContactFolder}
|
23
|
+
end
|
24
|
+
|
25
|
+
def contacts
|
26
|
+
return self.find_all{|i| i.is_a? Contact}
|
27
|
+
end
|
28
|
+
|
29
|
+
include Enumerable
|
30
|
+
def each(&block)
|
31
|
+
@items = [] if @items.nil?
|
32
|
+
@items.each(&block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# A parsed opera contacts file. Treat it as an Enumerable (.each, .map etc.)
|
37
|
+
#
|
38
|
+
# Example of use:
|
39
|
+
#
|
40
|
+
# collection = ContactCollection.new()
|
41
|
+
# folder = ContactFolder.new(123, "folder-name")
|
42
|
+
# collection << folder
|
43
|
+
# collection.map{|i| i.name} # ["folder-name"]
|
44
|
+
# collection.folders.map{|i| i.name} # ["folder-name"]
|
45
|
+
# collection.contacts # []
|
46
|
+
class ContactCollection
|
47
|
+
include Collection
|
48
|
+
end
|
49
|
+
|
50
|
+
# A single contact in the file. Contains details like emails, phone etc.
|
51
|
+
# Type of Item.
|
52
|
+
class Contact < Item
|
53
|
+
attr_accessor :homepage, :phone, :fax, :emails, :postal_address
|
54
|
+
end
|
55
|
+
|
56
|
+
# A folder which can contain Contact and ContactFolder
|
57
|
+
# (use Enumerable functions like .map). Type of Item.
|
58
|
+
#
|
59
|
+
# Example of use:
|
60
|
+
#
|
61
|
+
# folder = ContactFolder.new(321, "folder-name")
|
62
|
+
# contact = Contact.new(123, "contact-name")
|
63
|
+
# folder << contact
|
64
|
+
# folder.map{|i| i.name} # ["contact-name"]
|
65
|
+
# collection.contacts.map{|i| i.name} # ["contact-name"]
|
66
|
+
# collection.folders # []
|
67
|
+
class ContactFolder < Item
|
68
|
+
include Collection
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# vim: ft=ruby et sw=2 ts=2 sts=2
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'opera-contacts/contacts'
|
4
|
+
|
5
|
+
module OperaContacts
|
6
|
+
# Parses the given string containing Hotlist formatted contacts
|
7
|
+
# to a ContactCollection.
|
8
|
+
#
|
9
|
+
# @param hotlist_string A UTF8 string containing the Hotlist info
|
10
|
+
# @return A ContactCollection containing the parse result of hotlist_string
|
11
|
+
def OperaContacts.parse_s(hotlist_string)
|
12
|
+
return ContactsParser.new.parse_s(hotlist_string)
|
13
|
+
end
|
14
|
+
|
15
|
+
class ContactsParser
|
16
|
+
def parse_s(hotlist_string)
|
17
|
+
lines = hotlist_string.lines
|
18
|
+
(meta, remaining_lines) = extract_meta(lines)
|
19
|
+
items = extract_items(remaining_lines)
|
20
|
+
return build_structure(meta, items)
|
21
|
+
end
|
22
|
+
|
23
|
+
def extract_meta(lines)
|
24
|
+
#TODO: Not sure how to turn this into split_at(lines) do |l|...
|
25
|
+
(meta, remaining_lines) = split_at(lines, lambda{|l| l.strip != ""})
|
26
|
+
if meta.count < 1 or not meta.first.start_with?("Opera Hotlist")
|
27
|
+
raise "File broken (missing meta)"
|
28
|
+
end
|
29
|
+
return meta, remaining_lines
|
30
|
+
end
|
31
|
+
|
32
|
+
# Divide an array into two parts based on a block
|
33
|
+
# TODO: Find built in function for this
|
34
|
+
def split_at(arr, predicate)
|
35
|
+
return arr.take_while(&predicate), arr.drop_while(&predicate)
|
36
|
+
end
|
37
|
+
|
38
|
+
def extract_items(lines)
|
39
|
+
rtn = []
|
40
|
+
remaining = lines
|
41
|
+
|
42
|
+
def try_next_if_nil(*opts)
|
43
|
+
opts.each do |opt|
|
44
|
+
result = opt.call()
|
45
|
+
if not result.nil?
|
46
|
+
return result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
return nil
|
51
|
+
end
|
52
|
+
|
53
|
+
while remaining != []
|
54
|
+
(item, remaining) = try_next_if_nil(
|
55
|
+
lambda{parse_empty_line(remaining)},
|
56
|
+
lambda{parse_item(remaining)},
|
57
|
+
lambda{parse_folder_end(remaining)},
|
58
|
+
lambda{fail "Parse error."}
|
59
|
+
)
|
60
|
+
rtn << item if item
|
61
|
+
end
|
62
|
+
|
63
|
+
return rtn
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse_empty_line(lines)
|
67
|
+
if lines.first.strip == ""
|
68
|
+
return nil, lines[1..-1]
|
69
|
+
else
|
70
|
+
return nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse_folder_end(lines)
|
75
|
+
if lines.first.strip == "-"
|
76
|
+
end_folder = {
|
77
|
+
:type=> "end folder",
|
78
|
+
:data=>{}
|
79
|
+
}
|
80
|
+
return end_folder, lines[1..-1]
|
81
|
+
else
|
82
|
+
return nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def parse_item(lines)
|
87
|
+
return nil unless lines.first.start_with? "#"
|
88
|
+
|
89
|
+
type = lines.first
|
90
|
+
item = {
|
91
|
+
:type => lines.first.strip,
|
92
|
+
:data => {},
|
93
|
+
}
|
94
|
+
|
95
|
+
not_finished = true
|
96
|
+
rest = lines[1..-1]
|
97
|
+
while rest != [] and not_finished do
|
98
|
+
line = rest.first
|
99
|
+
if line.start_with? "\t"
|
100
|
+
(key, value) = line[1..-1].split("=", 2)
|
101
|
+
item[:data][key] = add_newlines(value).strip
|
102
|
+
rest = rest[1..-1]
|
103
|
+
else
|
104
|
+
not_finished = false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
return item, rest
|
108
|
+
end
|
109
|
+
|
110
|
+
# Turn's Hotlist's newlines into the conventional character
|
111
|
+
def add_newlines(string)
|
112
|
+
return string.gsub("\x02\x02", "\n")
|
113
|
+
end
|
114
|
+
|
115
|
+
def build_structure(meta, item_hashes)
|
116
|
+
collection = ContactCollection.new
|
117
|
+
build_collection!(collection, item_hashes)
|
118
|
+
return collection
|
119
|
+
end
|
120
|
+
|
121
|
+
# Recursively build a tree structure from item_hashes and add it to parent
|
122
|
+
def build_collection!(parent, item_hashes)
|
123
|
+
remaining = item_hashes
|
124
|
+
not_finished = true
|
125
|
+
while remaining != [] and not_finished do
|
126
|
+
item_hash = remaining.first
|
127
|
+
remaining = remaining[1..-1]
|
128
|
+
|
129
|
+
case item_hash[:type]
|
130
|
+
when "#CONTACT"
|
131
|
+
contact = build_contact(item_hash)
|
132
|
+
parent << contact
|
133
|
+
when "#FOLDER"
|
134
|
+
folder = build_folder(item_hash)
|
135
|
+
parent << folder
|
136
|
+
remaining = build_collection!(folder, remaining)
|
137
|
+
when "end folder"
|
138
|
+
not_finished = false
|
139
|
+
else
|
140
|
+
raise "Unknown type: #{item_hash[:type]}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
return remaining
|
145
|
+
end
|
146
|
+
|
147
|
+
def build_contact(item_hash)
|
148
|
+
data = item_hash[:data]
|
149
|
+
id = Integer(data["ID"])
|
150
|
+
contact = Contact.new(id, data["NAME"],
|
151
|
+
parse_date(data["CREATED"]),
|
152
|
+
data["DESCRIPTION"])
|
153
|
+
contact.homepage = data["URL"] if data["URL"]
|
154
|
+
contact.phone = data["PHONE"] if data["PHONE"]
|
155
|
+
contact.fax = data["FAX"] if data["FAX"]
|
156
|
+
contact.postal_address = data["POSTALADDRESS"] if data["POSTALADDRESS"]
|
157
|
+
#Strip off the newlines left by lines
|
158
|
+
contact.emails = data["MAIL"].lines.map{|l| l.strip} if data["MAIL"]
|
159
|
+
return contact
|
160
|
+
end
|
161
|
+
|
162
|
+
def build_folder(item_hash)
|
163
|
+
data = item_hash[:data]
|
164
|
+
folder = ContactFolder.new(data["ID"], data["NAME"],
|
165
|
+
parse_date(data["CREATED"]),
|
166
|
+
data["DESCRIPTION"])
|
167
|
+
return folder
|
168
|
+
end
|
169
|
+
|
170
|
+
def parse_date(timestamp_string)
|
171
|
+
timestamp = Integer(timestamp_string)
|
172
|
+
return Time.at(timestamp)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# vim: ft=ruby et sw=2 ts=2 sts=2
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: opera-contacts 0.0.0 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "opera-contacts"
|
9
|
+
s.version = "0.0.0"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.authors = ["Stefan Schneider-Kennedy"]
|
13
|
+
s.date = "2014-01-31"
|
14
|
+
s.description = "Parses the opera browser contacts file (ending in .adr) to a ruby data structure. This can then be exported to vCard or whatever."
|
15
|
+
s.email = "code@stefansk.name"
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"LICENSE.txt",
|
18
|
+
"README.rdoc"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"lib/opera-contacts.rb",
|
27
|
+
"lib/opera-contacts/contacts.rb",
|
28
|
+
"lib/opera-contacts/parser.rb",
|
29
|
+
"opera-contacts.gemspec",
|
30
|
+
"spec/parser_spec.rb",
|
31
|
+
"spec/test_files/broken_indentation.adr",
|
32
|
+
"spec/test_files/detailed_contact.adr",
|
33
|
+
"spec/test_files/missing_headers.adr",
|
34
|
+
"spec/test_files/nested_contact.adr"
|
35
|
+
]
|
36
|
+
s.homepage = "http://github.com/splondike/opera-contacts"
|
37
|
+
s.licenses = ["BSD 2-Clause"]
|
38
|
+
s.require_paths = ["lib"]
|
39
|
+
s.rubygems_version = "2.1.11"
|
40
|
+
s.summary = "Parse library for the Opera browser contacts file"
|
41
|
+
|
42
|
+
if s.respond_to? :specification_version then
|
43
|
+
s.specification_version = 4
|
44
|
+
|
45
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
46
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
47
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
48
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0"])
|
49
|
+
s.add_development_dependency(%q<jeweler>, ["~> 2.0.0"])
|
50
|
+
else
|
51
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
52
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
53
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
54
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0.0"])
|
55
|
+
end
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
58
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
59
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
60
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0.0"])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
data/spec/parser_spec.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'rspec/expectations'
|
4
|
+
include RSpec::Matchers
|
5
|
+
|
6
|
+
require 'opera-contacts'
|
7
|
+
|
8
|
+
def load_example(name)
|
9
|
+
return File.open("spec/test_files/#{name}.adr", "r:UTF-8").read
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "Parser" do
|
13
|
+
describe "parse_s" do
|
14
|
+
it "should parse contacts correctly" do
|
15
|
+
detailed_contact = load_example("detailed_contact")
|
16
|
+
|
17
|
+
parsed = OperaContacts.parse_s(detailed_contact)
|
18
|
+
|
19
|
+
expect(parsed.count).to eql(1)
|
20
|
+
contact = parsed.first
|
21
|
+
expect(contact.id).to eql(11)
|
22
|
+
expect(contact.name).to eql("root-contact")
|
23
|
+
expect(contact.description).to eql("Notes\nAnd a new line")
|
24
|
+
expect(contact.emails).to eql(["email@example.com", "additional@example.com", "emails@example.com"])
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should handle nested structures" do
|
28
|
+
nested_contact = load_example("nested_contact")
|
29
|
+
|
30
|
+
parsed = OperaContacts.parse_s(nested_contact)
|
31
|
+
|
32
|
+
expect(parsed.count).to eql(1)
|
33
|
+
parent_folder = parsed.first
|
34
|
+
expect(parent_folder.count).to eql(2)
|
35
|
+
|
36
|
+
parent_child = parent_folder.find{|i| i.name == "child-of-parent-folder"}
|
37
|
+
expect(parent_child).to be_true
|
38
|
+
|
39
|
+
subfolder = parent_folder.find{|i| i.name == "subfolder"}
|
40
|
+
expect(subfolder).to be_true
|
41
|
+
expect(subfolder.count).to eql(1)
|
42
|
+
expect(subfolder.first.name).to eql("child-of-subfolder")
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should fail for missing headers" do
|
46
|
+
missing_headers = load_example("missing_headers")
|
47
|
+
expect {OperaContacts.parse_s(missing_headers)}.to raise_error
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should fail for broken indentation" do
|
51
|
+
broken_indentation = load_example("broken_indentation")
|
52
|
+
expect {OperaContacts.parse_s(broken_indentation)}.to raise_error
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# vim: ft=ruby et sw=2 ts=2 sts=2
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Opera Hotlist version 2.0
|
2
|
+
Options: encoding = utf8, version=3
|
3
|
+
|
4
|
+
#CONTACT
|
5
|
+
ID=11
|
6
|
+
NAME=root-contact
|
7
|
+
URL=http://www.homepage.com/
|
8
|
+
CREATED=1390985060
|
9
|
+
DESCRIPTION=NotesAnd a new line
|
10
|
+
SHORT NAME=chat-nicknames
|
11
|
+
ACTIVE=YES
|
12
|
+
MAIL=email@example.comadditional@example.com emails@example.com
|
13
|
+
PHONE=Phone
|
14
|
+
FAX=Fax
|
15
|
+
POSTALADDRESS=Postal address
|
16
|
+
PICTUREURL=
|
17
|
+
ICON=Contact0
|
18
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Opera Hotlist version 2.0
|
2
|
+
Options: encoding = utf8, version=3
|
3
|
+
|
4
|
+
#CONTACT
|
5
|
+
ID=11
|
6
|
+
NAME=root-contact
|
7
|
+
URL=http://www.homepage.com/
|
8
|
+
CREATED=1390985060
|
9
|
+
DESCRIPTION=NotesAnd a new line
|
10
|
+
SHORT NAME=chat-nicknames
|
11
|
+
ACTIVE=YES
|
12
|
+
MAIL=email@example.comadditional@example.com emails@example.com
|
13
|
+
PHONE=Phone
|
14
|
+
FAX=Fax
|
15
|
+
POSTALADDRESS=Postal address
|
16
|
+
PICTUREURL=
|
17
|
+
ICON=Contact0
|
18
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#CONTACT
|
2
|
+
ID=11
|
3
|
+
NAME=root-contact
|
4
|
+
URL=http://www.homepage.com/
|
5
|
+
CREATED=1390985060
|
6
|
+
DESCRIPTION=NotesAnd a new line
|
7
|
+
SHORT NAME=chat-nicknames
|
8
|
+
ACTIVE=YES
|
9
|
+
MAIL=email@example.comadditional@example.com emails@example.com
|
10
|
+
PHONE=Phone
|
11
|
+
FAX=Fax
|
12
|
+
POSTALADDRESS=Postal address
|
13
|
+
PICTUREURL=
|
14
|
+
ICON=Contact0
|
15
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
Opera Hotlist version 2.0
|
2
|
+
Options: encoding = utf8, version=3
|
3
|
+
|
4
|
+
#FOLDER
|
5
|
+
ID=13
|
6
|
+
NAME=parent-folder
|
7
|
+
CREATED=1390985119
|
8
|
+
DESCRIPTION=Desc
|
9
|
+
EXPANDED=YES
|
10
|
+
UNIQUEID=AE9C6E8088C111E3848EFF2494D63A37
|
11
|
+
|
12
|
+
#FOLDER
|
13
|
+
ID=15
|
14
|
+
NAME=subfolder
|
15
|
+
CREATED=1390985152
|
16
|
+
EXPANDED=YES
|
17
|
+
UNIQUEID=C2B319F088C111E38490DD53BA773824
|
18
|
+
|
19
|
+
#CONTACT
|
20
|
+
ID=16
|
21
|
+
NAME=child-of-subfolder
|
22
|
+
URL=
|
23
|
+
CREATED=1390985172
|
24
|
+
DESCRIPTION=
|
25
|
+
PHONE=
|
26
|
+
FAX=
|
27
|
+
POSTALADDRESS=
|
28
|
+
PICTUREURL=
|
29
|
+
ICON=Contact0
|
30
|
+
|
31
|
+
-
|
32
|
+
|
33
|
+
#CONTACT
|
34
|
+
ID=17
|
35
|
+
NAME=child-of-parent-folder
|
36
|
+
URL=
|
37
|
+
CREATED=1390985172
|
38
|
+
DESCRIPTION=
|
39
|
+
PHONE=
|
40
|
+
FAX=
|
41
|
+
POSTALADDRESS=
|
42
|
+
PICTUREURL=
|
43
|
+
ICON=Contact0
|
44
|
+
|
45
|
+
-
|
46
|
+
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: opera-contacts
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stefan Schneider-Kennedy
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-31 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rdoc
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.12'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.12'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: jeweler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.0.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.0.0
|
69
|
+
description: Parses the opera browser contacts file (ending in .adr) to a ruby data
|
70
|
+
structure. This can then be exported to vCard or whatever.
|
71
|
+
email: code@stefansk.name
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files:
|
75
|
+
- LICENSE.txt
|
76
|
+
- README.rdoc
|
77
|
+
files:
|
78
|
+
- Gemfile
|
79
|
+
- Gemfile.lock
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.rdoc
|
82
|
+
- Rakefile
|
83
|
+
- lib/opera-contacts.rb
|
84
|
+
- lib/opera-contacts/contacts.rb
|
85
|
+
- lib/opera-contacts/parser.rb
|
86
|
+
- opera-contacts.gemspec
|
87
|
+
- spec/parser_spec.rb
|
88
|
+
- spec/test_files/broken_indentation.adr
|
89
|
+
- spec/test_files/detailed_contact.adr
|
90
|
+
- spec/test_files/missing_headers.adr
|
91
|
+
- spec/test_files/nested_contact.adr
|
92
|
+
homepage: http://github.com/splondike/opera-contacts
|
93
|
+
licenses:
|
94
|
+
- BSD 2-Clause
|
95
|
+
metadata: {}
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 2.1.11
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: Parse library for the Opera browser contacts file
|
116
|
+
test_files: []
|