selene 0.3.4 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a94d8b8e9a90bf2658c322521edc6e6a89ccab65
4
- data.tar.gz: d54aa750eab708bd7ea60ac21be9201073be09d0
2
+ SHA256:
3
+ metadata.gz: a7327895d75318126155ef90f53ed6947798227f92066d61d82b38e9a7189d20
4
+ data.tar.gz: abd28599b4fdd3fd5d53f5b1dfe7797a6062421503ac9a1ef3939db97c7e355e
5
5
  SHA512:
6
- metadata.gz: 525d85405335dcf90a579d1bde6d0a7c5472b96d91aa127606c0b81a19aabbc2cb2e33ab707201e884cb0d7a15c107616713f6c756a09d91763d7b2b1349529c
7
- data.tar.gz: 70e1b7e2ce0a7651cb2c3526217107a53b092cc39dd9855d87d0358553615e56a038fe00c2ea40734aac2e3143e7a30ea9ad2c3b424b7bac261a462227a557e2
6
+ metadata.gz: 80738a49e57cb5346990d8c48598aea5234b46129839273d2ec10045c8fab4b246367b4ddff15ff98727f3548bee00957d66863d47ec76ce24235e24d1f432eb
7
+ data.tar.gz: efda1236b74295332b7d01e3f27b1c44cb5ee93ca5b6ce94783d1145bad094537fc4474a9c5941211ef0937e8126870e6a3658ebec2e3e2d07dc1500f6ee70cf
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Cory Kaufman-Schofield
1
+ Copyright (c) 2012-2019 Cory Kaufman-Schofield
2
2
 
3
3
  MIT License
4
4
 
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
19
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
20
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
21
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,37 +1,27 @@
1
+ require 'selene/component_validator'
2
+
1
3
  module Selene
2
4
  # This is the base class for all component builders.
3
5
  #
4
6
  # Properties are specified one per property with optional rules, e.g.:
5
7
  #
6
- # property 'version', :required => true, :multiple => false
8
+ # property 'version', required: true, multiple: false
7
9
  #
8
10
  # If :required is truthy, a component is not valid without that property.
9
11
  # If :multiple is falsy, a component can only have one of that property
10
12
  #
11
13
  # Custom rules can be implemented by overriding can_add?(property) or valid?
12
14
  class ComponentBuilder
13
- class ParseError < StandardError; end
15
+ include ComponentValidator
14
16
 
15
- @property_rules = {}
16
-
17
- class << self
18
- attr_accessor :property_rules
19
- end
17
+ class ParseError < StandardError; end
20
18
 
21
19
  attr_accessor :component, :errors, :name, :parent
22
20
 
23
- def self.property(name, rules = {})
24
- property_rules[name] = rules
25
- end
26
-
27
- def self.inherited(subclass)
28
- subclass.instance_variable_set('@property_rules', @property_rules)
29
- end
30
-
31
21
  def initialize(name)
32
22
  @name = name
33
- @component = Hash.new { |component, property| component[property] = [] }
34
- @errors = Hash.new { |errors, property| errors[property] = [] }
23
+ @component = Hash.new { |h, k| h[k] = [] }
24
+ @errors = Hash.new { |h, k| h[k] = [] }
35
25
  end
36
26
 
37
27
  def add(name, builder)
@@ -87,44 +77,8 @@ module Selene
87
77
  property.value
88
78
  end
89
79
 
90
- def contains_property?(property)
91
- @component.key?(property.to_s)
92
- end
93
-
94
- def can_contain?(component_name)
95
- true
96
- end
97
-
98
- def error(property, message)
99
- @errors[property] << message
100
- end
101
-
102
- def properties
103
- self.class.property_rules.keys
104
- end
105
-
106
- def required?(property)
107
- self.class.property_rules[property][:required]
108
- end
109
-
110
- def multiple?(property)
111
- self.class.property_rules[property][:multiple]
112
- end
113
-
114
- def can_add?(property)
115
- if contains_property?(property.name) && !multiple?(property.name)
116
- error(property.name, "property '%s' must not occur more than once" % property.name)
117
- end
118
- @errors.empty?
119
- end
120
-
121
- def valid?
122
- properties.select { |property| required?(property) }.each do |property|
123
- if !contains_property?(property)
124
- error(property, "missing required property '%s'" % property)
125
- end
126
- end
127
- @errors.empty?
80
+ def contains?(property)
81
+ @component.key?(property)
128
82
  end
129
83
  end
130
84
  end
@@ -0,0 +1,64 @@
1
+ module Selene
2
+ module ComponentValidator
3
+ module ClassMethods
4
+ def properties
5
+ @properties ||= Hash.new({})
6
+ end
7
+
8
+ def properties=(properties)
9
+ @properties = properties
10
+ end
11
+
12
+ def property(name, rules = {})
13
+ properties[name] = rules
14
+ end
15
+ end
16
+
17
+ def self.included(base)
18
+ base.extend(ClassMethods)
19
+ end
20
+
21
+ def self.inherited(subclass)
22
+ subclass.properties = properties
23
+ end
24
+
25
+ def properties
26
+ self.class.properties.keys
27
+ end
28
+
29
+ # Determine whether this property is required. Defauls to false.
30
+ def required?(property_name)
31
+ self.class.properties[property_name].fetch(:required, false)
32
+ end
33
+
34
+ # Determine whether this property can be set more than once. Defaults
35
+ # to true.
36
+ def multiple?(property_name)
37
+ self.class.properties[property_name].fetch(:multiple, true)
38
+ end
39
+
40
+ def can_contain?(property)
41
+ true
42
+ end
43
+
44
+ def can_add?(property)
45
+ if contains?(property.name) && !multiple?(property.name)
46
+ error(property.name, "property '%s' must not occur more than once" % property.name)
47
+ end
48
+ @errors.empty?
49
+ end
50
+
51
+ def error(property_name, message)
52
+ @errors[property_name] << message
53
+ end
54
+
55
+ def valid?
56
+ properties.select { |property_name| required?(property_name) }.each do |property_name|
57
+ if !contains?(property_name)
58
+ error(property_name, "missing required property '%s'" % property_name)
59
+ end
60
+ end
61
+ @errors.empty?
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,28 @@
1
+ module Selene
2
+ class Component
3
+ def initialize(properties = {})
4
+ @properties = properties
5
+ end
6
+
7
+ def to_h
8
+ @properties
9
+ end
10
+
11
+ def to_ical
12
+ end
13
+ end
14
+
15
+ class Event < Component
16
+ def initialize(properties = {})
17
+ end
18
+
19
+ def to_ical
20
+ lines = []
21
+ if @properties['exdate'] && @properties['exdate'].count > 0
22
+ exdate_values = @exdate.map { |time| time.strftime('%Y%m%dT%H%M%S') }
23
+ lines << "EXDATE;TZID=America/Detroit:#{exdate_values.join(',')}"
24
+ end
25
+ lines.join("\n")
26
+ end
27
+ end
28
+ end
@@ -86,16 +86,16 @@ module Selene
86
86
  end
87
87
 
88
88
  def can_add?(property)
89
- if property.name == 'dtend' && contains_property?('duration')
89
+ if property.name == 'dtend' && contains?('duration')
90
90
  error('dtend', "The 'dtend' property cannot be set if the 'duration' property already exists")
91
- elsif property.name == 'duration' && contains_property?('dtend')
91
+ elsif property.name == 'duration' && contains?('dtend')
92
92
  error('duration', "The 'duration' property cannot be set if the 'dtend' property already exists")
93
93
  end
94
94
  super(property)
95
95
  end
96
96
 
97
97
  def valid?
98
- if !contains_property?('dtstart') && !parent.contains_property?('method')
98
+ if !contains?('dtstart') && parent && !parent.contains?('method')
99
99
  error('dtstart', "The 'dtstart' property is required if the calendar does not have a 'method' property")
100
100
  end
101
101
  super
@@ -17,15 +17,19 @@ module Selene
17
17
  PARAM_KEY_VALUE = /(?<key>[^=]+)=(?<value>.*)/
18
18
 
19
19
  # Split a string into content lines
20
- def self.split(string)
21
- delimiter = string.scan(/\r\n?|\n/).first || "\r\n"
22
- string.gsub(/#{delimiter}\s/, '').split(delimiter).map { |line| parse(line) }
20
+ def self.split(string, &block)
21
+ separator = string.match(/\r\n|\r|\n/, &:to_s) || "\r\n"
22
+ string.gsub("#{separator}\s", '').split(separator).map do |line_string|
23
+ parse(line_string).tap do |line|
24
+ block.call(line) if line && block
25
+ end
26
+ end
23
27
  end
24
28
 
25
- # Parse a content line into a line object
26
- def self.parse(content_line)
27
- content_line.match(/#{NAME}#{PARAMS}?:#{VALUE}/) do |match|
28
- return new(match[:name], match[:value], parse_params(match[:params]))
29
+ # convert line string into line object
30
+ def self.parse(line_string)
31
+ line_string.match(/#{NAME}#{PARAMS}?:#{VALUE}/) do |match|
32
+ new(match[:name], match[:value], parse_params(match[:params]))
29
33
  end
30
34
  end
31
35
 
@@ -11,18 +11,22 @@ require 'selene/time_zone_builder'
11
11
 
12
12
  module Selene
13
13
  class Parser
14
- def self.parse(string)
15
- new(string).parse
14
+ def self.parse(data)
15
+ new(data).parse
16
16
  end
17
17
 
18
- def initialize(string)
19
- @string = string
18
+ def initialize(data)
19
+ @data = encode(data)
20
+ end
21
+
22
+ def encode(data)
23
+ data.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
20
24
  end
21
25
 
22
26
  def parse
23
27
  feed = FeedBuilder.new
24
28
  stack = [feed]
25
- Line.split(@string).each do |line|
29
+ Line.split(@data) do |line|
26
30
  if line.begin_component?
27
31
  builder = create_builder(line.component_name)
28
32
  stack[-1].add(line.component_name, builder)
@@ -1,3 +1,3 @@
1
1
  module Selene
2
- VERSION = "0.3.4"
2
+ VERSION = "0.3.5"
3
3
  end
@@ -20,7 +20,7 @@ module Selene
20
20
  assert_equal builder.component['vtimezone'].first, { 'tzid' => 'America/Detroit' }
21
21
  end
22
22
 
23
- # Test properties with :required => true
23
+ # Test properties with required: true
24
24
  %w(prodid version).each do |property|
25
25
  define_method "test_#{property}_required" do
26
26
  assert_required(CalendarBuilder.new, property)
@@ -33,6 +33,5 @@ module Selene
33
33
  assert_single(CalendarBuilder.new, property)
34
34
  end
35
35
  end
36
-
37
36
  end
38
37
  end
@@ -4,7 +4,7 @@ module Selene
4
4
  class ComponentBuilderTest < MiniTest::Test
5
5
  def test_sets_property_rules
6
6
  ComponentBuilder.property :version, :required => true
7
- assert_equal({ :required => true }, ComponentBuilder.property_rules[:version])
7
+ assert_equal({ :required => true }, ComponentBuilder.properties[:version])
8
8
  end
9
9
  end
10
10
  end
@@ -17,8 +17,8 @@ module Selene
17
17
  end
18
18
 
19
19
  def test_parses_params
20
- assert_equal Line.parse('DTSTART;TZID=America/New_York:20130110T183000'),
21
- Line.new('DTSTART', '20130110T183000', 'tzid' => 'America/New_York')
20
+ assert_equal Line.new('DTSTART', '20130110T183000', 'tzid' => 'America/New_York'),
21
+ Line.parse('DTSTART;TZID=America/New_York:20130110T183000')
22
22
  end
23
23
  end
24
24
  end
@@ -7,17 +7,15 @@ module Selene
7
7
  include FixtureHelper
8
8
 
9
9
  def test_parses_blank_string
10
- assert_equal Parser.parse(""), {}
10
+ assert_equal({}, Parser.parse(""))
11
11
  end
12
12
 
13
13
  def test_parses_simple_calendar
14
- assert_equal Parser.parse("BEGIN:VCALENDAR\r\nSUMMARY:Meetups\r\nEND:VCALENDAR"),
15
- { 'vcalendar' => [{ 'summary' => 'Meetups' }] }
14
+ assert_equal({ 'vcalendar' => [{ 'summary' => 'Meetups' }] }, Parser.parse("BEGIN:VCALENDAR\r\nSUMMARY:Meetups\r\nEND:VCALENDAR"))
16
15
  end
17
16
 
18
17
  def test_parses_meetup_calendar
19
- assert_equal Parser.parse(fixture('meetup.ics')),
20
- JSON.parse(fixture('meetup.json'))
18
+ assert_equal JSON.parse(fixture('meetup.json')), Parser.parse(fixture('meetup.ics'))
21
19
  end
22
20
 
23
21
  def test_contain_error
@@ -1,7 +1,8 @@
1
1
  require 'bundler/setup'
2
+ require 'byebug'
2
3
  require 'minitest/autorun'
3
- require 'minitest/reporters'
4
4
  require 'minitest/mock'
5
+ require 'minitest/reporters'
5
6
  require 'selene'
6
7
 
7
8
  Minitest::Reporters.use! Minitest::Reporters::DefaultReporter.new
@@ -34,9 +35,7 @@ module Selene
34
35
  end
35
36
 
36
37
  module BuilderTestHelper
37
-
38
38
  def assert_required builder, property
39
- builder.valid?
40
39
  assert_error builder, property, "missing required property '#{property}'"
41
40
  end
42
41
 
@@ -49,6 +48,7 @@ module Selene
49
48
  end
50
49
 
51
50
  def assert_error builder, property, message
51
+ builder.valid?
52
52
  assert builder.errors[property].any? { |e| e =~ /#{message}/ }, "#{builder.class.name}: #{message}"
53
53
  end
54
54
  end
metadata CHANGED
@@ -1,97 +1,97 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: selene
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cory Kaufman-Schofield
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-08 00:00:00.000000000 Z
11
+ date: 2019-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ice_cube
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: byebug
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest-reporters
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: guard
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - '>='
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - '>='
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: guard-minitest
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - '>='
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - '>='
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  description: Selene is an iCalendar parser for Ruby
@@ -101,8 +101,8 @@ executables: []
101
101
  extensions: []
102
102
  extra_rdoc_files: []
103
103
  files:
104
- - .gitattributes
105
- - .gitignore
104
+ - ".gitattributes"
105
+ - ".gitignore"
106
106
  - Gemfile
107
107
  - Guardfile
108
108
  - LICENSE.txt
@@ -113,7 +113,9 @@ files:
113
113
  - lib/selene/calendar_builder.rb
114
114
  - lib/selene/component_builder.rb
115
115
  - lib/selene/component_errors.rb
116
+ - lib/selene/component_validator.rb
116
117
  - lib/selene/daylight_savings_time_builder.rb
118
+ - lib/selene/event.rb
117
119
  - lib/selene/event_builder.rb
118
120
  - lib/selene/feed_builder.rb
119
121
  - lib/selene/line.rb
@@ -143,17 +145,17 @@ require_paths:
143
145
  - lib
144
146
  required_ruby_version: !ruby/object:Gem::Requirement
145
147
  requirements:
146
- - - '>='
148
+ - - ">="
147
149
  - !ruby/object:Gem::Version
148
150
  version: '0'
149
151
  required_rubygems_version: !ruby/object:Gem::Requirement
150
152
  requirements:
151
- - - '>='
153
+ - - ">="
152
154
  - !ruby/object:Gem::Version
153
155
  version: '0'
154
156
  requirements: []
155
157
  rubyforge_project:
156
- rubygems_version: 2.4.2
158
+ rubygems_version: 2.7.3
157
159
  signing_key:
158
160
  specification_version: 4
159
161
  summary: Selene is an iCalendar parser for Ruby