vobject 0.1.0 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/macos.yml +38 -0
  3. data/.github/workflows/ubuntu.yml +56 -0
  4. data/.github/workflows/windows.yml +40 -0
  5. data/.hound.yml +3 -0
  6. data/.rubocop.tb.yml +650 -0
  7. data/.rubocop.yml +1077 -0
  8. data/Gemfile +1 -1
  9. data/LICENSE.txt +21 -17
  10. data/README.adoc +151 -0
  11. data/Rakefile +1 -1
  12. data/lib/c.rb +173 -0
  13. data/lib/error.rb +19 -0
  14. data/lib/vcalendar.rb +77 -0
  15. data/lib/vcard.rb +67 -0
  16. data/lib/vobject.rb +13 -170
  17. data/lib/vobject/component.rb +87 -36
  18. data/lib/vobject/parameter.rb +116 -0
  19. data/lib/vobject/parametervalue.rb +26 -0
  20. data/lib/vobject/property.rb +134 -55
  21. data/lib/vobject/propertyvalue.rb +46 -0
  22. data/lib/vobject/vcalendar/component.rb +106 -0
  23. data/lib/vobject/vcalendar/grammar.rb +595 -0
  24. data/lib/vobject/vcalendar/paramcheck.rb +259 -0
  25. data/lib/vobject/vcalendar/propertyparent.rb +98 -0
  26. data/lib/vobject/vcalendar/propertyvalue.rb +606 -0
  27. data/lib/vobject/vcalendar/typegrammars.rb +605 -0
  28. data/lib/vobject/vcard/v3_0/component.rb +40 -0
  29. data/lib/vobject/vcard/v3_0/grammar.rb +175 -0
  30. data/lib/vobject/vcard/v3_0/paramcheck.rb +110 -0
  31. data/lib/vobject/vcard/v3_0/parameter.rb +17 -0
  32. data/lib/vobject/vcard/v3_0/property.rb +18 -0
  33. data/lib/vobject/vcard/v3_0/propertyvalue.rb +401 -0
  34. data/lib/vobject/vcard/v3_0/typegrammars.rb +425 -0
  35. data/lib/vobject/vcard/v4_0/component.rb +40 -0
  36. data/lib/vobject/vcard/v4_0/grammar.rb +224 -0
  37. data/lib/vobject/vcard/v4_0/paramcheck.rb +269 -0
  38. data/lib/vobject/vcard/v4_0/parameter.rb +18 -0
  39. data/lib/vobject/vcard/v4_0/property.rb +63 -0
  40. data/lib/vobject/vcard/v4_0/propertyvalue.rb +404 -0
  41. data/lib/vobject/vcard/v4_0/typegrammars.rb +539 -0
  42. data/lib/vobject/version.rb +1 -1
  43. data/vobject.gemspec +19 -16
  44. metadata +81 -26
  45. data/.travis.yml +0 -5
  46. 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