opera-contacts 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -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.
@@ -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
+
@@ -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,5 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'opera-contacts/parser'
4
+
5
+ # 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
+
@@ -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: []