treecard 0.0.2
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/README.md +1 -0
- data/lib/treecard/attribute.rb +20 -0
- data/lib/treecard/nodes/content_line_node.rb +16 -0
- data/lib/treecard/nodes/param_node.rb +11 -0
- data/lib/treecard/param.rb +9 -0
- data/lib/treecard.rb +109 -0
- data/test/test_helper.rb +12 -0
- data/test/unit/treecard_test.rb +40 -0
- metadata +105 -0
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
At some point this will be documented. But not today.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# A line in the VCard file.
|
2
|
+
#
|
3
|
+
# * +name+ is the name of the attribute, like 'fn', 'adr', etc.
|
4
|
+
# * +param+ is a list of +TreeCard::Param+ objects, representing each
|
5
|
+
# parameter associated with this attribute
|
6
|
+
# * +value+ is the raw text of the attribute's value. It is up to
|
7
|
+
# the caller to parse, if necessary.
|
8
|
+
class TreeCard::Attribute
|
9
|
+
|
10
|
+
attr_reader :name, :params, :value
|
11
|
+
|
12
|
+
def initialize(name, params, value)
|
13
|
+
@name = name
|
14
|
+
@params = {}
|
15
|
+
params.each do |param|
|
16
|
+
@params[param.name.downcase] = param
|
17
|
+
end
|
18
|
+
@value = value
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class TreeCard::ContentLineNode < Treetop::Runtime::SyntaxNode
|
2
|
+
|
3
|
+
# Returns the TreeCard::Attribute representing this node.
|
4
|
+
def attribute
|
5
|
+
TreeCard::Attribute.new(name.text_value, param_objects, value.text_value)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def param_objects
|
11
|
+
param_list.elements.map do |semicolon_and_param|
|
12
|
+
semicolon_and_param.param.param_object
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class TreeCard::ParamNode < Treetop::Runtime::SyntaxNode
|
2
|
+
|
3
|
+
# Returns the TreeCard::Param object representing this node.
|
4
|
+
def param_object
|
5
|
+
param_values = []
|
6
|
+
if self.respond_to?(:param_value)
|
7
|
+
param_values = [param_value.text_value] + extra_params.elements.map { |extra_param| extra_param.param_value.text_value }
|
8
|
+
end
|
9
|
+
TreeCard::Param.new(param_name.text_value, param_values)
|
10
|
+
end
|
11
|
+
end
|
data/lib/treecard.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'treetop'
|
2
|
+
require 'grammar/vcard' # implements the vcard spec, found at http://tools.ietf.org/html/rfc2425
|
3
|
+
|
4
|
+
class TreeCard; end
|
5
|
+
|
6
|
+
require 'treecard/nodes/content_line_node'
|
7
|
+
require 'treecard/nodes/param_node'
|
8
|
+
require 'treecard/attribute'
|
9
|
+
require 'treecard/param'
|
10
|
+
|
11
|
+
class TreeCard
|
12
|
+
|
13
|
+
attr_reader :raw_data
|
14
|
+
attr_reader :attributes
|
15
|
+
|
16
|
+
def initialize(data)
|
17
|
+
@attributes = Hash.new {|h, k| h[k] = []}
|
18
|
+
@raw_data = self.class.unfold_lines(data)
|
19
|
+
@parser = VCardGrammarParser.new
|
20
|
+
ast = @parser.parse(@raw_data)
|
21
|
+
if ast
|
22
|
+
parse(ast)
|
23
|
+
else
|
24
|
+
raise TreeCard::ParseError, @parser.failure_reason
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def name
|
29
|
+
attr = attributes['fn']
|
30
|
+
attr &&= attr.first
|
31
|
+
attr.value if attr
|
32
|
+
end
|
33
|
+
|
34
|
+
def emails
|
35
|
+
attr = attributes['email']
|
36
|
+
attr.map {|email| email.value} if attr
|
37
|
+
end
|
38
|
+
|
39
|
+
def phone
|
40
|
+
attr = attribute_with_param('tel', 'voice')
|
41
|
+
attr.value if attr
|
42
|
+
end
|
43
|
+
|
44
|
+
def fax
|
45
|
+
attr = attribute_with_param('tel', 'fax')
|
46
|
+
attr.value if attr
|
47
|
+
end
|
48
|
+
|
49
|
+
def photo
|
50
|
+
attr = attributes['photo']
|
51
|
+
attr &&= attr.first
|
52
|
+
attr.value if attr
|
53
|
+
end
|
54
|
+
|
55
|
+
def address
|
56
|
+
attr = attributes['adr']
|
57
|
+
attr &&= attr.first
|
58
|
+
attr.value.split(';').join("\n").strip if attr
|
59
|
+
end
|
60
|
+
|
61
|
+
def company
|
62
|
+
attr = attributes['org']
|
63
|
+
attr &&= attr.first
|
64
|
+
attr.value if attr
|
65
|
+
end
|
66
|
+
|
67
|
+
# VCards automatically wrap lines longer than 75 characters. Wrapped
|
68
|
+
# lines are signified by leading whitespace.
|
69
|
+
def self.unfold_lines(data)
|
70
|
+
unfolded_data = ""
|
71
|
+
current_line = ""
|
72
|
+
data.lines.each do |line|
|
73
|
+
if line =~ /^\s\S+/
|
74
|
+
current_line << line[1..-1].chomp
|
75
|
+
else
|
76
|
+
if current_line != ""
|
77
|
+
if current_line =~ /=0D=0A=$/ #weird quoted printable thing
|
78
|
+
unfolded_data << current_line.chomp
|
79
|
+
else
|
80
|
+
unfolded_data << current_line + "\n"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
current_line = line.chomp
|
84
|
+
end
|
85
|
+
end
|
86
|
+
unfolded_data << current_line + "\n"
|
87
|
+
unfolded_data
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns the first attribute with +attr_name+ where +param_name+ is
|
91
|
+
# set.
|
92
|
+
def attribute_with_param(attr_name, param_name)
|
93
|
+
attributes = self.attributes[attr_name.downcase]
|
94
|
+
found_attributes = attributes.select { |attribute| attribute.params[param_name.downcase] }
|
95
|
+
found_attributes.first
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def parse(root)
|
101
|
+
root.elements.each do |contentline|
|
102
|
+
parse_contentline(contentline)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def parse_contentline(node)
|
107
|
+
attributes[node.name.text_value.downcase] << node.attribute
|
108
|
+
end
|
109
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'shoulda'
|
2
|
+
require 'treecard'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
class Test::Unit::TestCase
|
6
|
+
|
7
|
+
@@my_path = Pathname.new(File.expand_path(File.dirname(__FILE__)))
|
8
|
+
|
9
|
+
def vcard_data(vcard_file_name)
|
10
|
+
File.read(@@my_path + 'data' + vcard_file_name)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TreeCardTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "A new TreeCard populated with data" do
|
6
|
+
setup do
|
7
|
+
@vcard = TreeCard.new(vcard_data('Mark_Ericsson.vcf'))
|
8
|
+
end
|
9
|
+
|
10
|
+
should "unfold its lines" do
|
11
|
+
assert_equal "REV:20090729T163338Z", @vcard.raw_data.split("\n")[15]
|
12
|
+
end
|
13
|
+
|
14
|
+
should "parse its data" do
|
15
|
+
assert_equal "Mark S. Ericsson", @vcard.name
|
16
|
+
assert_equal ["mericsson@example.com"], @vcard.emails
|
17
|
+
assert_equal "(925) 930-6000", @vcard.phone
|
18
|
+
assert_equal "(925) 934-5377", @vcard.fax
|
19
|
+
assert_equal "Youngman, Ericsson & Low, LLP", @vcard.company
|
20
|
+
assert_equal "1981 N. Broadway, Suite 300\nWalnut Creek\nCA\n94596-3841\nUnited States of America", @vcard.address
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "TreeCard#unfold_lines" do
|
25
|
+
should "unfold lines that begin with a single whitespace character" do
|
26
|
+
data = "This is the first line\nThis is a long descrip\n tion that exists o\n\tn a long line"
|
27
|
+
assert_equal "This is the first line\nThis is a long description that exists on a long line\n", TreeCard.unfold_lines(data)
|
28
|
+
end
|
29
|
+
|
30
|
+
should "not unfold if there is no real data on a line" do
|
31
|
+
data = "This is another line\n \n"
|
32
|
+
assert_equal "This is another line\n \n", TreeCard.unfold_lines(data)
|
33
|
+
end
|
34
|
+
|
35
|
+
should "not unfold more than one whitespace character" do
|
36
|
+
data = "This is a line\n test text"
|
37
|
+
assert_equal "This is a line\n test text\n", TreeCard.unfold_lines(data)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: treecard
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Justin Weiss
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-21 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: treetop
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 23
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 4
|
33
|
+
- 8
|
34
|
+
version: 1.4.8
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: shoulda
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 33
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 10
|
49
|
+
- 3
|
50
|
+
version: 2.10.3
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id002
|
53
|
+
description:
|
54
|
+
email: jweiss@avvo.com
|
55
|
+
executables: []
|
56
|
+
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
extra_rdoc_files:
|
60
|
+
- README.md
|
61
|
+
files:
|
62
|
+
- lib/treecard/attribute.rb
|
63
|
+
- lib/treecard/nodes/content_line_node.rb
|
64
|
+
- lib/treecard/nodes/param_node.rb
|
65
|
+
- lib/treecard/param.rb
|
66
|
+
- lib/treecard.rb
|
67
|
+
- test/test_helper.rb
|
68
|
+
- test/unit/treecard_test.rb
|
69
|
+
- README.md
|
70
|
+
has_rdoc: true
|
71
|
+
homepage:
|
72
|
+
licenses: []
|
73
|
+
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 3
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 3
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
version: "0"
|
97
|
+
requirements: []
|
98
|
+
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 1.3.7
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: A simple vcard parser using Treetop
|
104
|
+
test_files: []
|
105
|
+
|