ruby-vobject 0.1.0 → 1.0.1

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.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/.hound.yml +3 -0
  3. data/.rubocop.tb.yml +650 -0
  4. data/.rubocop.yml +1077 -0
  5. data/.travis.yml +16 -3
  6. data/Gemfile +1 -1
  7. data/LICENSE.txt +21 -17
  8. data/README.adoc +142 -0
  9. data/Rakefile +1 -1
  10. data/lib/c.rb +173 -0
  11. data/lib/error.rb +19 -0
  12. data/lib/vcalendar.rb +77 -0
  13. data/lib/vcard.rb +67 -0
  14. data/lib/vobject.rb +13 -170
  15. data/lib/vobject/component.rb +87 -36
  16. data/lib/vobject/parameter.rb +116 -0
  17. data/lib/vobject/parametervalue.rb +26 -0
  18. data/lib/vobject/property.rb +134 -55
  19. data/lib/vobject/propertyvalue.rb +46 -0
  20. data/lib/vobject/vcalendar/component.rb +106 -0
  21. data/lib/vobject/vcalendar/grammar.rb +595 -0
  22. data/lib/vobject/vcalendar/paramcheck.rb +259 -0
  23. data/lib/vobject/vcalendar/propertyparent.rb +98 -0
  24. data/lib/vobject/vcalendar/propertyvalue.rb +606 -0
  25. data/lib/vobject/vcalendar/typegrammars.rb +605 -0
  26. data/lib/vobject/vcard/v3_0/component.rb +40 -0
  27. data/lib/vobject/vcard/v3_0/grammar.rb +176 -0
  28. data/lib/vobject/vcard/v3_0/paramcheck.rb +111 -0
  29. data/lib/vobject/vcard/v3_0/parameter.rb +17 -0
  30. data/lib/vobject/vcard/v3_0/property.rb +18 -0
  31. data/lib/vobject/vcard/v3_0/propertyvalue.rb +401 -0
  32. data/lib/vobject/vcard/v3_0/typegrammars.rb +426 -0
  33. data/lib/vobject/vcard/v4_0/component.rb +40 -0
  34. data/lib/vobject/vcard/v4_0/grammar.rb +225 -0
  35. data/lib/vobject/vcard/v4_0/paramcheck.rb +270 -0
  36. data/lib/vobject/vcard/v4_0/parameter.rb +18 -0
  37. data/lib/vobject/vcard/v4_0/property.rb +63 -0
  38. data/lib/vobject/vcard/v4_0/propertyvalue.rb +404 -0
  39. data/lib/vobject/vcard/v4_0/typegrammars.rb +540 -0
  40. data/lib/vobject/vcard/version.rb +3 -0
  41. data/lib/vobject/version.rb +1 -1
  42. data/ruby-vobject.gemspec +19 -11
  43. metadata +93 -17
  44. data/README.md +0 -94
@@ -0,0 +1,67 @@
1
+ require "vobject"
2
+ require "vobject/component"
3
+
4
+ class Vcard < Vobject::Component
5
+ attr_accessor :version
6
+
7
+ class << self
8
+ def blank(version)
9
+ new VERSION: { value: version }
10
+ end
11
+
12
+ def decode(vcard_str, version = nil)
13
+ version_str = version.nil? ? "4.0" : /\nVERSION:([^\n\r]+)/i.match(vcard_str)[1]
14
+ blank(version_str).parse(vcard_str)
15
+ end
16
+
17
+ def parse(vcf, version, strict)
18
+ hash = version == "3.0" ? Vcard::V3_0::Component.parse(vcf, strict) : Vcard::V4_0::Component.parse(vcf, strict)
19
+ # comp_name = hash.keys.first
20
+ # return self.new(comp_name, hash[:vobject][comp_name], hash[:errors] )
21
+ hash
22
+ end
23
+
24
+ private
25
+
26
+ def raise_invalid_parsing
27
+ raise "vCard parse failed"
28
+ end
29
+ end
30
+
31
+ def initialize(version)
32
+ self.version = version
33
+ super VCARD: { VERSION: { value: version } }
34
+ end
35
+
36
+ private
37
+
38
+ def name
39
+ :VCARD
40
+ end
41
+
42
+ def property_base_class
43
+ version == "3.0" ? Vcard::V3_0::Property : Vcard::V4_0::Property
44
+ # version_class.const_get(:Property)
45
+ end
46
+
47
+ def component_base_class
48
+ version == "3.0" ? Vcard::V3_0::Component : Vcard::V4_0::Component
49
+ # version_class.const_get(:Component)
50
+ end
51
+ end
52
+
53
+ def require_dir(dir)
54
+ base = File.expand_path("../", __FILE__)
55
+ Dir.glob(File.join(base, dir, "**", "*.rb")).each do |path|
56
+ require path.gsub(/\.rb\Z/, "")
57
+ end
58
+ end
59
+
60
+ require "vobject/vcard/v4_0/component"
61
+ require "vobject/vcard/v4_0/property"
62
+ require_dir "vobject/vcard/v4_0/component"
63
+ require_dir "vobject/vcard/v4_0/property"
64
+ require "vobject/vcard/v3_0/component"
65
+ require "vobject/vcard/v3_0/property"
66
+ require_dir "vobject/vcard/v3_0/component"
67
+ require_dir "vobject/vcard/v3_0/property"
@@ -1,182 +1,25 @@
1
- require "vobject/version"
2
-
3
1
  module Vobject
4
-
5
- module Rules
6
-
7
- module ABNF
8
- IANAToken = '[a-zA-Z\d\-]+?'
9
- Cr = "\u000d"
10
- Lf = "\u000a"
11
- Crlf = "(#{Cr}|#{Lf})"
12
- Utf8_tail = '[\u0080-\u00bf]'
13
- Utf8_2 = '([\u00c2-\u00df]|' + "#{Utf8_tail})"
14
- Utf8_3 = '([\u00e0\u00a0-\u00bf\u00e1-\u00ec\u00ed\u0080-\u009f\u00ee-\u00ef]|' + "#{Utf8_tail})"
15
- Utf8_4 = '([\u00f0\u0090-\u00bf\u00f1-\u00f3\u00f4\u0080-\u008f]|' + "#{Utf8_tail})"
16
- Wsp = '[ \t]'
17
- VChar = '[\u0021-\u007e]'
18
- NonASCII = "(#{Utf8_2}|#{Utf8_3}|#{Utf8_4})"
19
- QSafeChar = "(#{Wsp}|" + '[!\u0023-\u007e]' + "|#{NonASCII})"
20
- SafeChar = "(#{Wsp}|" + '[!\u0023-\u0039\u003c-\u007e]' + "|#{NonASCII})"
21
- ValueChar = "(#{Wsp}|#{VChar}|#{NonASCII})"
22
- DQuote = '"'
23
- PText = "#{SafeChar}*?"
24
- QuotedString = "#{DQuote}(#{QSafeChar}*?)#{DQuote}"
25
- XName = "[xX]-#{IANAToken}"
26
- Group = IANAToken
27
- Name = "(#{XName}|#{IANAToken})"
28
- ParamName = "(#{XName}|#{IANAToken})"
29
- ParamValue = "(#{PText}|#{QuotedString})"
30
- PValueList = "(?<head>#{ParamValue})(?<tail>(,#{ParamValue})*)"
31
- Pid = '\d+(\.\d+)*'
32
- PidList = "(?<head>#{Pid})(?<tail>(,#{Pid})*)"
33
- Param = "(?<pname>#{ParamName})=(?<pvalue>#{PValueList})"
34
- Params = "(;(?<phead>#{Param}))(?<ptail>(;#{Param})*)"
35
- Value = "#{ValueChar}*?"
36
- LineGroup = "((?<group>#{Group})" + '\.' + ")?"
37
- Contentline = "#{LineGroup}(?<key>#{Name})(?<params>(#{Params})?):(?<value>#{Value})#{Crlf}"
38
- BeginLine = "BEGIN:#{IANAToken}#{Crlf}"
39
- VersionLine = "VERSION:#{Value}#{Crlf}"
40
- EndLine = "END:#{IANAToken}#{Crlf}"
41
- Vobject = "#{BeginLine}#{VersionLine}(#{Contentline})+#{EndLine}"
42
- end
43
-
44
- end
45
-
46
2
  class << self
47
-
48
- def parse(vobject)
49
- vobject = unfold(vobject)
50
- lines = []
51
- rule = "(?<line>#{Rules::ABNF::Contentline})(?<remainder>(#{Rules::ABNF::Contentline})*)"
52
-
53
- parse_for_rule(Rules::ABNF::Vobject, vobject) do |parsed|
54
-
55
- remainder = vobject
56
-
57
- while !remainder.empty?
58
- parse_for_rule(rule, remainder) do |remainder_parsed|
59
- lines << remainder_parsed[:line]
60
- remainder = remainder_parsed[:remainder]
61
- end
62
- end
63
- end
64
-
65
- parse_lines lines
66
- end
67
-
68
- private
69
-
3
+ MAX_LINE_WIDTH = 75
70
4
  def unfold(str)
71
- str.gsub(/#{Rules::ABNF::Crlf}#{Rules::ABNF::Wsp}/, '')
72
- end
73
-
74
- def parse_lines lines
75
- lines.each_with_index.reduce([]) do |hash_stack, (line, i)|
76
- prop = parse_line(line)
77
-
78
- if prop.has_key?(:BEGIN)
79
- comp = prop[:BEGIN][:value].to_sym
80
- hash = { comp => [] }
81
- next hash_stack << hash
82
- end
83
-
84
- if prop.has_key?(:END)
85
- hash = hash_stack.pop
86
- comp = hash.keys.first
87
-
88
- raise_invalid_parsing if comp != prop[:END][:value].to_sym
89
-
90
- prev_hash = hash_stack.last
91
-
92
- raise_invalid_parsing if !prev_hash && i != lines.length - 1
93
-
94
- return hash unless prev_hash
95
-
96
- prev_hash[prev_hash.keys.first] << hash
97
-
98
- next hash_stack
99
- end
100
-
101
- prev_hash = hash_stack.last
102
-
103
- prev_hash[prev_hash.keys.first] << prop
104
- hash_stack
105
- end
5
+ str.gsub(/(\r|\n|\r\n)[ \t]/, "")
106
6
  end
107
7
 
108
- def parse_line(line)
109
- parse_for_rule(Rules::ABNF::Contentline, unfold(line)) do |parsed|
110
- key = parsed[:key].to_sym
111
-
112
- group = parsed[:group]
113
- params = parse_params(parsed[:params])
114
- value = parsed[:value]
115
-
116
- hash = { key => {} }
117
- hash[key][:group] = group if group
118
- hash[key][:params] = params if !params.empty?
119
- hash[key][:value] = value
120
-
121
- hash
122
- end
123
- end
124
-
125
- def parse_params(params_str)
126
- params = {}
127
-
128
- while !params_str.empty?
129
- parse_for_rule(Rules::ABNF::Params, params_str) do |parsed|
130
-
131
- parse_for_rule(Rules::ABNF::Param, parsed[:phead]) do |param_parsed|
132
- pname = param_parsed[:pname].to_sym
133
- pvalue = param_parsed[:pvalue].sub(
134
- Regexp.new("^#{Rules::ABNF::QuotedString}$"),
135
- '\1'
136
- )
137
-
138
- pvalue.gsub!(/\\n/, "\n")
8
+ # This implements the line folding as specified in
9
+ # http://tools.ietf.org/html/rfc6350#section-3.2
10
+ # NOTE: the "line" here is not including the trailing \n
11
+ def fold_line(line)
12
+ folded_line = line[0, MAX_LINE_WIDTH]
13
+ remainder_line = line[MAX_LINE_WIDTH, line.length - MAX_LINE_WIDTH] || ""
139
14
 
140
- params[pname] = if params[pname]
141
- "#{params[pname]},#{pvalue}"
142
- else
143
- pvalue
144
- end
145
- end
15
+ max_width = MAX_LINE_WIDTH - 1
146
16
 
147
- params_str = parsed[:ptail]
148
- end
17
+ (0..((remainder_line.length - 1) / max_width)).each do |i|
18
+ folded_line << "\n "
19
+ folded_line << remainder_line[i * max_width, max_width]
149
20
  end
150
21
 
151
- params
22
+ folded_line
152
23
  end
153
-
154
- #Method: parse_for_rule
155
- #Parameter: String containing the regular expression, String to be parsed
156
- #and optional block to indicate whether to yield the resulting hash
157
- #Return: a hash with keys indicating their regex names
158
- def parse_for_rule(rule, str, &block)
159
- matched = /\A#{rule}\Z/.match(str)
160
-
161
- raise_invalid_parsing unless matched
162
-
163
- parsed = matched.names.reduce({}) do |parsed_hash, name|
164
- #can we reduce memory consumption by only creating Keys whose value is not nil?
165
- parsed_hash[name.to_sym] = matched[name.to_sym] if matched[name.to_sym]
166
- #parsed_hash[name.to_sym] = matched[name.to_sym]
167
- parsed_hash
168
- end
169
-
170
- return yield(parsed) if block
171
-
172
- parsed
173
- end
174
-
175
- def raise_invalid_parsing
176
- raise "VObject parse failed"
177
- end
178
-
179
24
  end
180
-
181
25
  end
182
-
@@ -1,64 +1,112 @@
1
- require 'vobject'
2
- require 'vobject/property'
1
+ require "vobject"
2
+ require "vobject/property"
3
+ require "vobject/vcalendar/grammar"
4
+ require "json"
3
5
 
4
6
  class Vobject::Component
7
+ attr_accessor :comp_name, :children, :multiple_components, :errors, :norm
5
8
 
6
- attr_accessor :comp_name, :children
7
-
8
- class << self
9
-
10
- def parse(vcf)
11
- hash = Vobject.parse(vcf)
12
- comp_name = hash.keys.first
13
-
14
- self.new comp_name, hash[comp_name]
15
- end
16
-
17
- private
18
-
19
- def raise_invalid_parsing
20
- raise "Vobject component parse failed"
21
- end
9
+ def <=>(another)
10
+ me = self.to_norm
11
+ o = another.to_norm
12
+ me <=> o
13
+ end
22
14
 
15
+ def blank(version)
16
+ ingest VOBJECT: { VERSION: { value: version } }
23
17
  end
24
18
 
25
- def initialize key, cs
19
+ def initialize(key, cs, err)
26
20
  self.comp_name = key
27
-
28
21
  raise_invalid_initialization if key != name
22
+ self.children = []
23
+ if cs.nil?
24
+ else
25
+ cs.each_key do |k|
26
+ val = cs[k]
27
+ # iteration of array || hash values is making the value a key!
28
+ next if k.class == Array
29
+ next if k.class == Hash
30
+ cc = child_class(k, val)
31
+ if val.is_a?(Hash) && val.has_key?(:component)
32
+ val[:component].each do |x|
33
+ children << cc.new(k, x, [])
34
+ end
35
+ else
36
+ children << cc.new(k, val)
37
+ end
38
+ end
39
+ end
40
+ self.errors = err.select { |e| !e.nil? }
41
+ self.norm = nil
42
+ end
29
43
 
30
- self.children = cs.map do |c|
31
- key = c.keys.first
32
- val = c[key]
44
+ def get_errors
45
+ errors
46
+ end
33
47
 
34
- cc = child_class(key, val)
35
- cc.new key, val
36
- end
48
+ def child_class(key, val)
49
+ base_class = if val.is_a?(Hash) && val.has_key?(:component)
50
+ component_base_class
51
+ elsif !(val.is_a?(Hash) && !val.has_key?(:value))
52
+ property_base_class
53
+ else
54
+ component_base_class
55
+ end
56
+ return base_class if [:CLASS, :OBJECT, :METHOD].include? key
57
+ camelized_key = key.to_s.downcase.split("_").map(&:capitalize).join("")
58
+ base_class.const_get(camelized_key) rescue base_class
37
59
  end
38
60
 
39
61
  def to_s
40
62
  s = "BEGIN:#{name}\n"
41
-
42
63
  children.each do |c|
43
64
  s << c.to_s
44
65
  end
45
-
46
66
  s << "END:#{name}\n"
47
-
48
67
  s
49
68
  end
50
69
 
51
- private
70
+ def to_norm
71
+ if norm.nil?
72
+ s = "BEGIN:#{name.upcase}\n"
73
+ properties = children.select { |c| c.is_a? Vobject::Property }
74
+ components = children.select { |c| not c.is_a? Vobject::Property }
75
+ # create to_norm in advance
76
+ properties.each { |p| p.to_norm }
77
+ properties.sort.each do |p|
78
+ s << p.to_norm
79
+ end
80
+ components.sort.each { |p| s << p.to_norm }
81
+ s << "END:#{name.upcase}\n"
82
+ norm = s
83
+ end
84
+ norm
85
+ end
86
+
87
+ def to_hash
88
+ a = {}
89
+ children.each do |c|
90
+ if c.is_a?(Vobject::Component)
91
+ a = a.merge(c.to_hash) { |_, old, new| [old, new].flatten }
92
+ elsif c.is_a?(Vobject::Property)
93
+ a = a.merge(c.to_hash) { |_, old, new| [old, new].flatten }
94
+ else
95
+ a[c.name] = c.to_hash
96
+ end
97
+ end
98
+ { comp_name => a }
99
+ end
100
+
101
+ def to_json
102
+ to_hash.to_json
103
+ end
52
104
 
53
105
  def name
54
106
  comp_name
55
107
  end
56
108
 
57
- def child_class key, val
58
- base_class = val.is_a?(Array) ? component_base_class : property_base_class
59
- camelized_key = key.to_s.downcase.split("_").map(&:capitalize).join("")
60
- base_class.const_get(camelized_key) rescue base_class
61
- end
109
+ private
62
110
 
63
111
  def property_base_class
64
112
  Vobject::Property
@@ -68,8 +116,11 @@ class Vobject::Component
68
116
  Vobject::Component
69
117
  end
70
118
 
119
+ def parameter_base_class
120
+ Vobject::Parameter
121
+ end
122
+
71
123
  def raise_invalid_initialization
72
124
  raise "vObject component initialization failed"
73
125
  end
74
-
75
126
  end
@@ -0,0 +1,116 @@
1
+ module Vobject
2
+ class Parameter
3
+ attr_accessor :param_name, :value, :multiple, :norm
4
+
5
+ def <=>(another)
6
+ self.to_norm <=> another.to_norm
7
+ end
8
+
9
+ def initialize(key, options)
10
+ self.param_name = key
11
+ if options.class == Array
12
+ self.multiple = []
13
+ options.each do |v|
14
+ multiple << parameter_base_class.new(key, v)
15
+ self.param_name = key
16
+ end
17
+ else
18
+ self.value = options
19
+ end
20
+ norm = nil
21
+ raise_invalid_initialization(key, name) if key != name
22
+ end
23
+
24
+ def to_s
25
+ # we made param names have underscore instead of dash as symbols
26
+ line = param_name.to_s.tr("_", "-")
27
+ line << "="
28
+ if multiple
29
+ arr = []
30
+ multiple.each { |v| arr << to_s_line(v.value.to_s) }
31
+ line << arr.join(",")
32
+ else
33
+ line << to_s_line(value.to_s)
34
+ end
35
+ line
36
+ end
37
+
38
+ def to_s_line(val)
39
+ # RFC 6868
40
+ val = val.to_s.gsub(/\^/, "^^").gsub(/\n/, "^n").gsub(/"/, "^'")
41
+ if val =~ /[:;,]/
42
+ val = '"' + val + '"'
43
+ end
44
+ val
45
+ end
46
+
47
+ def to_norm
48
+ if norm.nil?
49
+ line = param_name.to_s.tr("_", "-").upcase
50
+ line << "="
51
+ if multiple
52
+ arr = []
53
+ multiple.sort.each { |v| arr << to_norm_line(v.value) }
54
+ line << arr.join(",")
55
+ else
56
+ line << to_norm_line(value)
57
+ end
58
+ norm = line
59
+ end
60
+ norm
61
+ end
62
+
63
+ def to_norm_line(val)
64
+ # RFC 6868
65
+ val = val.to_s.gsub(/\^/, "^^").gsub(/\n/, "^n").gsub(/"/, "^'")
66
+ #if val =~ /[:;,]/
67
+ val = '"' + val + '"'
68
+ #end
69
+ val
70
+ end
71
+
72
+ def to_hash
73
+ if multiple
74
+ val = []
75
+ multiple.each do |c|
76
+ val << c.value
77
+ end
78
+ { param_name => val }
79
+ else
80
+ { param_name => value }
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def name
87
+ param_name
88
+ end
89
+
90
+ def parse_value(value)
91
+ parse_method = :"parse_#{value_type}_value"
92
+ parse_method = respond_to?(parse_method, true) ? parse_method : :parse_text_value
93
+ send(parse_method, value)
94
+ end
95
+
96
+ def parse_text_value(value)
97
+ value
98
+ end
99
+
100
+ def value_type
101
+ (params || {})[:VALUE] || default_value_type
102
+ end
103
+
104
+ def default_value_type
105
+ "text"
106
+ end
107
+
108
+ def parameter_base_class
109
+ Vobject::Parameter
110
+ end
111
+
112
+ def raise_invalid_initialization(key, name)
113
+ raise "vObject property initialization failed (#{key}, #{name})"
114
+ end
115
+ end
116
+ end