selene 0.3.1 → 0.3.4
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.
- checksums.yaml +7 -0
- data/Gemfile +1 -1
- data/Guardfile +4 -0
- data/Rakefile +0 -1
- data/lib/selene.rb +0 -2
- data/lib/selene/alarm_builder.rb +3 -0
- data/lib/selene/calendar_builder.rb +8 -2
- data/lib/selene/component_builder.rb +107 -23
- data/lib/selene/component_errors.rb +7 -0
- data/lib/selene/daylight_savings_time_builder.rb +3 -1
- data/lib/selene/event_builder.rb +80 -23
- data/lib/selene/feed_builder.rb +7 -5
- data/lib/selene/line.rb +15 -10
- data/lib/selene/parser.rb +26 -20
- data/lib/selene/standard_time_builder.rb +3 -0
- data/lib/selene/time_zone_builder.rb +3 -0
- data/lib/selene/version.rb +1 -1
- data/selene.gemspec +6 -2
- data/test/helper.rb +6 -0
- data/test/selene/alarm_builder_test.rb +5 -8
- data/test/selene/calendar_builder_test.rb +10 -44
- data/test/selene/component_builder_test.rb +10 -0
- data/test/selene/daylight_savings_time_builder_test.rb +3 -4
- data/test/selene/event_builder_test.rb +47 -95
- data/test/selene/line_test.rb +7 -7
- data/test/selene/parser_test.rb +12 -5
- data/test/selene/time_zone_builder_test.rb +1 -1
- data/test/test_helper.rb +13 -23
- metadata +63 -31
- data/.rspec +0 -2
- data/lib/selene/component_rules.rb +0 -38
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a94d8b8e9a90bf2658c322521edc6e6a89ccab65
|
4
|
+
data.tar.gz: d54aa750eab708bd7ea60ac21be9201073be09d0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 525d85405335dcf90a579d1bde6d0a7c5472b96d91aa127606c0b81a19aabbc2cb2e33ab707201e884cb0d7a15c107616713f6c756a09d91763d7b2b1349529c
|
7
|
+
data.tar.gz: 70e1b7e2ce0a7651cb2c3526217107a53b092cc39dd9855d87d0358553615e56a038fe00c2ea40734aac2e3143e7a30ea9ad2c3b424b7bac261a462227a557e2
|
data/Gemfile
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
source
|
1
|
+
source 'https://rubygems.org'
|
2
2
|
gemspec
|
data/Guardfile
ADDED
data/Rakefile
CHANGED
data/lib/selene.rb
CHANGED
data/lib/selene/alarm_builder.rb
CHANGED
@@ -1,9 +1,15 @@
|
|
1
1
|
module Selene
|
2
2
|
class CalendarBuilder < ComponentBuilder
|
3
|
+
property 'prodid', required: true, :multiple => false
|
4
|
+
property 'version', :required => true, :multiple => false
|
3
5
|
|
4
|
-
|
6
|
+
property 'calscale', :multiple => false
|
7
|
+
property 'method', :multiple => false
|
5
8
|
|
6
|
-
|
9
|
+
# Custom properties: x-prop, iana-prop
|
7
10
|
|
11
|
+
def initialize
|
12
|
+
super('vcalendar')
|
13
|
+
end
|
8
14
|
end
|
9
15
|
end
|
@@ -1,46 +1,130 @@
|
|
1
1
|
module Selene
|
2
|
+
# This is the base class for all component builders.
|
3
|
+
#
|
4
|
+
# Properties are specified one per property with optional rules, e.g.:
|
5
|
+
#
|
6
|
+
# property 'version', :required => true, :multiple => false
|
7
|
+
#
|
8
|
+
# If :required is truthy, a component is not valid without that property.
|
9
|
+
# If :multiple is falsy, a component can only have one of that property
|
10
|
+
#
|
11
|
+
# Custom rules can be implemented by overriding can_add?(property) or valid?
|
2
12
|
class ComponentBuilder
|
3
|
-
|
13
|
+
class ParseError < StandardError; end
|
4
14
|
|
5
|
-
|
15
|
+
@property_rules = {}
|
6
16
|
|
7
|
-
|
8
|
-
|
9
|
-
|
17
|
+
class << self
|
18
|
+
attr_accessor :property_rules
|
19
|
+
end
|
10
20
|
|
11
|
-
|
12
|
-
|
13
|
-
|
21
|
+
attr_accessor :component, :errors, :name, :parent
|
22
|
+
|
23
|
+
def self.property(name, rules = {})
|
24
|
+
property_rules[name] = rules
|
14
25
|
end
|
15
26
|
|
16
|
-
def
|
27
|
+
def self.inherited(subclass)
|
28
|
+
subclass.instance_variable_set('@property_rules', @property_rules)
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(name)
|
32
|
+
@name = name
|
17
33
|
@component = Hash.new { |component, property| component[property] = [] }
|
18
|
-
@errors = []
|
19
|
-
@property_rules = property_rules(self)
|
20
|
-
@component_rules = component_rules(self)
|
34
|
+
@errors = Hash.new { |errors, property| errors[property] = [] }
|
21
35
|
end
|
22
36
|
|
23
|
-
def
|
24
|
-
|
25
|
-
@component[name(line)] = value(line)
|
37
|
+
def add(name, builder)
|
38
|
+
@component[name] << builder.component
|
26
39
|
end
|
27
40
|
|
28
|
-
def
|
29
|
-
|
41
|
+
def parse(*properties)
|
42
|
+
properties.each do |property|
|
43
|
+
case property
|
44
|
+
when Line
|
45
|
+
@component[name(property)] = value(property) if can_add?(property)
|
46
|
+
when String
|
47
|
+
Line.split(property).each { |line| parse(line) }
|
48
|
+
else
|
49
|
+
raise ParseError, "Cannot parse argument of type #{property.class}"
|
50
|
+
end
|
51
|
+
end
|
30
52
|
end
|
31
53
|
|
32
|
-
def
|
33
|
-
|
54
|
+
def to_ical(component)
|
55
|
+
lines = []
|
56
|
+
component.each_pair do |key, value|
|
57
|
+
keys = []
|
58
|
+
values = []
|
59
|
+
keys << key.upcase
|
60
|
+
case value
|
61
|
+
when Array
|
62
|
+
if value[1].is_a?(Hash)
|
63
|
+
value[1].each_pair do |pkey, pvalue|
|
64
|
+
keys << [pkey.upcase, pvalue].join('=')
|
65
|
+
end
|
66
|
+
values << value[0]
|
67
|
+
else
|
68
|
+
values += value
|
69
|
+
end
|
70
|
+
when Hash
|
71
|
+
value.each_pair do |vkey, vvalue|
|
72
|
+
values << [vkey.upcase, vvalue].join('=')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
lines << [keys.join(';'), values.join(';')].join(':')
|
77
|
+
end
|
78
|
+
|
79
|
+
lines.join("\n")
|
34
80
|
end
|
35
81
|
|
36
|
-
def
|
37
|
-
|
82
|
+
def name(property)
|
83
|
+
property.name
|
38
84
|
end
|
39
85
|
|
40
|
-
def
|
41
|
-
|
86
|
+
def value(property)
|
87
|
+
property.value
|
88
|
+
end
|
89
|
+
|
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
|
42
118
|
@errors.empty?
|
43
119
|
end
|
44
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?
|
128
|
+
end
|
45
129
|
end
|
46
130
|
end
|
data/lib/selene/event_builder.rb
CHANGED
@@ -1,29 +1,80 @@
|
|
1
1
|
module Selene
|
2
2
|
class EventBuilder < ComponentBuilder
|
3
3
|
|
4
|
-
#
|
5
|
-
|
4
|
+
# Required properties
|
5
|
+
property 'dtstamp', required: true, multiple: false
|
6
|
+
property 'uid', required: true, multiple: false
|
6
7
|
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
# Optional properties
|
9
|
+
property 'class', multiple: false
|
10
|
+
property 'created', multiple: false
|
11
|
+
property 'description', multiple: false
|
12
|
+
property 'dtend', multiple: false
|
13
|
+
property 'dtstart', multiple: false
|
14
|
+
property 'duration', multiple: false
|
15
|
+
property 'geo', multiple: false
|
16
|
+
property 'last-mod', multiple: false
|
17
|
+
property 'location', multiple: false
|
18
|
+
property 'organizer', multiple: false
|
19
|
+
property 'priority', multiple: false
|
20
|
+
property 'recurid', multiple: false
|
21
|
+
property 'rrule' # The rrule property should not occur more than once (but can if necessary)
|
22
|
+
property 'seq', multiple: false
|
23
|
+
property 'status', multiple: false
|
24
|
+
property 'summary', multiple: false
|
25
|
+
property 'transp', multiple: false
|
26
|
+
property 'url', multiple: false
|
27
|
+
property 'attach'
|
28
|
+
property 'attendee'
|
29
|
+
property 'categories'
|
30
|
+
property 'comment'
|
31
|
+
property 'contact'
|
32
|
+
property 'exdate'
|
33
|
+
property 'rdate'
|
34
|
+
property 'related'
|
35
|
+
property 'related-to'
|
36
|
+
property 'resources'
|
37
|
+
property 'rstatus'
|
11
38
|
|
12
|
-
#
|
13
|
-
EXCLUSIVE_PROPERTIES = [
|
14
|
-
%w(dtend duration)
|
15
|
-
]
|
39
|
+
# Custom properties: x-prop, iana-prop
|
16
40
|
|
17
|
-
#
|
18
|
-
#
|
19
|
-
# multi-day durations must be 'dur-day' or 'dur-week'
|
41
|
+
# TODO: if dtstart is a date, dtend must be as well
|
42
|
+
# TODO: multi-day durations must be 'dur-day' or 'dur-week'
|
20
43
|
|
21
|
-
def
|
22
|
-
|
44
|
+
def self.update(component, properties)
|
45
|
+
new('vevent', component).update_properties(properties)
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
super('vevent')
|
50
|
+
end
|
51
|
+
|
52
|
+
def update_properties(properties)
|
53
|
+
properties.inject(self.component) do |component, (name, value)|
|
54
|
+
component = update_property(component, name, value)
|
55
|
+
component
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def update_property(component, name, value)
|
60
|
+
case name
|
61
|
+
when 'dtstart'
|
62
|
+
component.merge('dtstart' => [value.strftime('%Y%m%dT%H%M%S'), component['dtstart'][1]])
|
63
|
+
else
|
64
|
+
component
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def value(property)
|
69
|
+
case property.name
|
23
70
|
when 'dtstamp', 'dtstart', 'dtend'
|
24
|
-
|
71
|
+
property.value_with_params
|
72
|
+
when 'exdate'
|
73
|
+
property.values_with_params
|
25
74
|
when 'geo'
|
26
|
-
|
75
|
+
property.values
|
76
|
+
when 'rrule'
|
77
|
+
property.rrule
|
27
78
|
else
|
28
79
|
super
|
29
80
|
end
|
@@ -34,14 +85,20 @@ module Selene
|
|
34
85
|
super(builder)
|
35
86
|
end
|
36
87
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
88
|
+
def can_add?(property)
|
89
|
+
if property.name == 'dtend' && contains_property?('duration')
|
90
|
+
error('dtend', "The 'dtend' property cannot be set if the 'duration' property already exists")
|
91
|
+
elsif property.name == 'duration' && contains_property?('dtend')
|
92
|
+
error('duration', "The 'duration' property cannot be set if the 'dtend' property already exists")
|
43
93
|
end
|
94
|
+
super(property)
|
44
95
|
end
|
45
96
|
|
97
|
+
def valid?
|
98
|
+
if !contains_property?('dtstart') && !parent.contains_property?('method')
|
99
|
+
error('dtstart', "The 'dtstart' property is required if the calendar does not have a 'method' property")
|
100
|
+
end
|
101
|
+
super
|
102
|
+
end
|
46
103
|
end
|
47
104
|
end
|
data/lib/selene/feed_builder.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
module Selene
|
2
|
-
class FeedBuilder <
|
3
|
-
|
2
|
+
class FeedBuilder < ComponentBuilder
|
4
3
|
def initialize
|
5
|
-
|
4
|
+
super('feed')
|
6
5
|
end
|
7
6
|
|
8
|
-
def
|
9
|
-
|
7
|
+
def can_contain?(builder)
|
8
|
+
!%w(vevent vtimezone valarm standard daylight).include?(builder.name)
|
10
9
|
end
|
11
10
|
|
11
|
+
def error(message)
|
12
|
+
@component['errors'] << message
|
13
|
+
end
|
12
14
|
end
|
13
15
|
end
|
data/lib/selene/line.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Selene
|
2
|
-
class Line < Struct.new(:name, :
|
2
|
+
class Line < Struct.new(:name, :value, :params)
|
3
3
|
|
4
4
|
# Match everything until we hit ';' (parameter separator) or ':' (value separator)
|
5
5
|
NAME = /(?<name>[^:\;]+)/
|
@@ -13,7 +13,7 @@ module Selene
|
|
13
13
|
# Match everything that is not ';' (parameter separator)
|
14
14
|
PARAM = /[^\;]+/
|
15
15
|
|
16
|
-
# Match
|
16
|
+
# Match parameter key and value
|
17
17
|
PARAM_KEY_VALUE = /(?<key>[^=]+)=(?<value>.*)/
|
18
18
|
|
19
19
|
# Split a string into content lines
|
@@ -25,7 +25,7 @@ module Selene
|
|
25
25
|
# Parse a content line into a line object
|
26
26
|
def self.parse(content_line)
|
27
27
|
content_line.match(/#{NAME}#{PARAMS}?:#{VALUE}/) do |match|
|
28
|
-
return new(match[:name],
|
28
|
+
return new(match[:name], match[:value], parse_params(match[:params]))
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
@@ -41,35 +41,40 @@ module Selene
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
def initialize(name,
|
45
|
-
|
46
|
-
self.params = params || {}
|
47
|
-
self.value = value
|
44
|
+
def initialize(name, value, params = {})
|
45
|
+
super(name.downcase, value, params)
|
48
46
|
end
|
49
47
|
|
50
48
|
def begin_component?
|
51
49
|
name == 'begin'
|
52
50
|
end
|
53
51
|
|
52
|
+
def component_name
|
53
|
+
value.downcase
|
54
|
+
end
|
55
|
+
|
54
56
|
def end_component?
|
55
57
|
name == 'end'
|
56
58
|
end
|
57
59
|
|
58
60
|
def params?
|
59
|
-
|
61
|
+
!params.empty?
|
60
62
|
end
|
61
63
|
|
62
64
|
def value_with_params
|
63
65
|
params? ? [value, params] : value
|
64
66
|
end
|
65
67
|
|
68
|
+
def values_with_params
|
69
|
+
params? ? [values, params] : values
|
70
|
+
end
|
71
|
+
|
66
72
|
def rrule
|
67
73
|
Hash[values.map { |values| k, v = values.split('=', 2); [k.downcase, v] }]
|
68
74
|
end
|
69
75
|
|
70
76
|
def values
|
71
|
-
value.split(
|
77
|
+
value.split(/[;,]/)
|
72
78
|
end
|
73
|
-
|
74
79
|
end
|
75
80
|
end
|
data/lib/selene/parser.rb
CHANGED
@@ -1,36 +1,31 @@
|
|
1
1
|
require 'selene/line'
|
2
|
-
require 'selene/component_rules'
|
3
|
-
require 'selene/component_builder'
|
4
2
|
|
3
|
+
require 'selene/component_builder'
|
5
4
|
require 'selene/alarm_builder'
|
6
5
|
require 'selene/calendar_builder'
|
7
6
|
require 'selene/daylight_savings_time_builder'
|
8
7
|
require 'selene/event_builder'
|
8
|
+
require 'selene/feed_builder'
|
9
9
|
require 'selene/standard_time_builder'
|
10
10
|
require 'selene/time_zone_builder'
|
11
11
|
|
12
12
|
module Selene
|
13
|
-
|
13
|
+
class Parser
|
14
|
+
def self.parse(string)
|
15
|
+
new(string).parse
|
16
|
+
end
|
14
17
|
|
15
|
-
def
|
16
|
-
|
17
|
-
when 'vcalendar' then CalendarBuilder
|
18
|
-
when 'vtimezone' then TimeZoneBuilder
|
19
|
-
when 'daylight' then DaylightSavingsTimeBuilder
|
20
|
-
when 'standard' then StandardTimeBuilder
|
21
|
-
when 'vevent' then EventBuilder
|
22
|
-
when 'valarm' then AlarmBuilder
|
23
|
-
else ComponentBuilder
|
24
|
-
end
|
18
|
+
def initialize(string)
|
19
|
+
@string = string
|
25
20
|
end
|
26
21
|
|
27
|
-
def
|
28
|
-
|
29
|
-
stack
|
30
|
-
Line.split(string).each do |line|
|
22
|
+
def parse
|
23
|
+
feed = FeedBuilder.new
|
24
|
+
stack = [feed]
|
25
|
+
Line.split(@string).each do |line|
|
31
26
|
if line.begin_component?
|
32
|
-
builder =
|
33
|
-
stack[-1].add(line.
|
27
|
+
builder = create_builder(line.component_name)
|
28
|
+
stack[-1].add(line.component_name, builder)
|
34
29
|
stack << builder
|
35
30
|
elsif line.end_component?
|
36
31
|
stack.pop
|
@@ -38,8 +33,19 @@ module Selene
|
|
38
33
|
stack[-1].parse(line)
|
39
34
|
end
|
40
35
|
end
|
41
|
-
|
36
|
+
feed.component
|
42
37
|
end
|
43
38
|
|
39
|
+
def create_builder(name)
|
40
|
+
case name
|
41
|
+
when 'daylight' then DaylightSavingsTimeBuilder.new
|
42
|
+
when 'standard' then StandardTimeBuilder.new
|
43
|
+
when 'valarm' then AlarmBuilder.new
|
44
|
+
when 'vcalendar' then CalendarBuilder.new
|
45
|
+
when 'vevent' then EventBuilder.new
|
46
|
+
when 'vtimezone' then TimeZoneBuilder.new
|
47
|
+
else ComponentBuilder.new(name)
|
48
|
+
end
|
49
|
+
end
|
44
50
|
end
|
45
51
|
end
|
data/lib/selene/version.rb
CHANGED
data/selene.gemspec
CHANGED
@@ -17,7 +17,11 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
18
|
gem.require_paths = ["lib"]
|
19
19
|
|
20
|
-
gem.
|
21
|
-
|
20
|
+
gem.add_dependency 'ice_cube'
|
21
|
+
|
22
|
+
gem.add_development_dependency 'byebug'
|
23
|
+
gem.add_development_dependency 'minitest-reporters'
|
22
24
|
gem.add_development_dependency 'rake'
|
25
|
+
gem.add_development_dependency 'guard'
|
26
|
+
gem.add_development_dependency 'guard-minitest'
|
23
27
|
end
|
data/test/helper.rb
ADDED
@@ -1,20 +1,17 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
module Selene
|
4
|
-
class AlarmBuilderTest < MiniTest::
|
4
|
+
class AlarmBuilderTest < MiniTest::Test
|
5
5
|
include BuilderTestHelper
|
6
6
|
|
7
|
-
def
|
7
|
+
def test_parses_action
|
8
|
+
builder.parse(Line.new('ACTION', 'AUDIO'))
|
9
|
+
assert_equal builder.component['action'], 'AUDIO'
|
8
10
|
end
|
9
11
|
|
12
|
+
private
|
10
13
|
def builder
|
11
14
|
@builder ||= AlarmBuilder.new
|
12
15
|
end
|
13
|
-
|
14
|
-
def test_parses_action
|
15
|
-
parse_line 'ACTION', {}, 'AUDIO'
|
16
|
-
assert_equal builder.component['action'], 'AUDIO'
|
17
|
-
end
|
18
|
-
|
19
16
|
end
|
20
17
|
end
|
@@ -1,70 +1,36 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
module Selene
|
4
|
-
class CalendarBuilderTest < MiniTest::
|
4
|
+
class CalendarBuilderTest < MiniTest::Test
|
5
5
|
include BuilderTestHelper
|
6
6
|
|
7
|
-
def builder
|
8
|
-
@builder ||= CalendarBuilder.new
|
9
|
-
end
|
10
|
-
|
11
|
-
def event_builder
|
12
|
-
@event_builder ||= EventBuilder.new.extend(Stubbable)
|
13
|
-
end
|
14
|
-
|
15
|
-
def time_zone_builder
|
16
|
-
@time_zone_builder ||= TimeZoneBuilder.new.extend(Stubbable)
|
17
|
-
end
|
18
|
-
|
19
|
-
def test_parse_prodid
|
20
|
-
parse_line('PRODID', {}, '-//Meetup//RemoteApi//EN')
|
21
|
-
assert_equal builder.component['prodid'], '-//Meetup//RemoteApi//EN'
|
22
|
-
end
|
23
|
-
|
24
|
-
def test_parse_version
|
25
|
-
parse_line('VERSION', {}, '2.0')
|
26
|
-
assert_equal @builder.component['version'], '2.0'
|
27
|
-
end
|
28
|
-
|
29
|
-
def test_parse_calscale
|
30
|
-
parse_line('CALSCALE', {}, 'Gregorian')
|
31
|
-
assert_equal builder.component['calscale'], 'Gregorian'
|
32
|
-
end
|
33
|
-
|
34
|
-
def test_parse_method
|
35
|
-
parse_line('METHOD', {}, 'Publish')
|
36
|
-
assert_equal builder.component['method'], 'Publish'
|
37
|
-
end
|
38
|
-
|
39
|
-
def test_parse_x_prop
|
40
|
-
parse_line('X-ORIGINAL-URL', {}, 'http://www.google.com')
|
41
|
-
assert_equal builder.component['x-original-url'], 'http://www.google.com'
|
42
|
-
end
|
43
|
-
|
44
7
|
def test_append_event_builder
|
8
|
+
builder = CalendarBuilder.new
|
9
|
+
event_builder = EventBuilder.new.extend(Stubbable)
|
45
10
|
event_builder.stub :component, { 'summary' => "Bluth's Best Party" }
|
46
11
|
builder.add('vevent', event_builder)
|
47
12
|
assert_equal builder.component['vevent'].first['summary'], "Bluth's Best Party"
|
48
13
|
end
|
49
14
|
|
50
15
|
def test_append_time_zone_builder
|
16
|
+
builder = CalendarBuilder.new
|
17
|
+
time_zone_builder = TimeZoneBuilder.new.extend(Stubbable)
|
51
18
|
time_zone_builder.stub :component, { 'tzid' => 'America/Detroit' }
|
52
19
|
builder.add('vtimezone', time_zone_builder)
|
53
20
|
assert_equal builder.component['vtimezone'].first, { 'tzid' => 'America/Detroit' }
|
54
21
|
end
|
55
22
|
|
56
|
-
#
|
57
|
-
|
23
|
+
# Test properties with :required => true
|
58
24
|
%w(prodid version).each do |property|
|
59
25
|
define_method "test_#{property}_required" do
|
60
|
-
assert_required property
|
26
|
+
assert_required(CalendarBuilder.new, property)
|
61
27
|
end
|
62
28
|
end
|
63
29
|
|
30
|
+
# Test properties with :multiple => false
|
64
31
|
%w(prodid version calscale method).each do |property|
|
65
|
-
define_method "test_#{property}
|
66
|
-
assert_single property
|
67
|
-
assert_multiple_values_do_not_overwrite property
|
32
|
+
define_method "test_#{property}_cannot_be_defined_more_than_once" do
|
33
|
+
assert_single(CalendarBuilder.new, property)
|
68
34
|
end
|
69
35
|
end
|
70
36
|
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Selene
|
4
|
+
class ComponentBuilderTest < MiniTest::Test
|
5
|
+
def test_sets_property_rules
|
6
|
+
ComponentBuilder.property :version, :required => true
|
7
|
+
assert_equal({ :required => true }, ComponentBuilder.property_rules[:version])
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
module Selene
|
4
|
-
class DaylightSavingsTimeBuilderTest < MiniTest::
|
4
|
+
class DaylightSavingsTimeBuilderTest < MiniTest::Test
|
5
5
|
include BuilderTestHelper
|
6
6
|
|
7
7
|
def builder
|
@@ -9,9 +9,8 @@ module Selene
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def test_parses_rrule
|
12
|
-
|
13
|
-
assert_equal
|
12
|
+
builder.parse(Line.new('RRULE', 'FREQ=YEARLY;BYMONTH=3;BYDAY=2SU'))
|
13
|
+
assert_equal({ 'freq' => 'YEARLY', 'bymonth' => '3', 'byday' => '2SU' }, builder.component['rrule'])
|
14
14
|
end
|
15
|
-
|
16
15
|
end
|
17
16
|
end
|
@@ -1,121 +1,73 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
module Selene
|
4
|
-
class EventBuilderTest < MiniTest::
|
4
|
+
class EventBuilderTest < MiniTest::Test
|
5
5
|
include BuilderTestHelper
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
def test_parses_dtstart
|
16
|
-
parse_line('DTSTART', { 'tzid' => 'America/New_York' }, '20130110T183000')
|
17
|
-
assert_equal builder.component['dtstart'],
|
18
|
-
['20130110T183000', { 'tzid' => 'America/New_York' }]
|
19
|
-
end
|
20
|
-
|
21
|
-
def test_parses_dtstamp
|
22
|
-
parse_line('DTSTAMP', {}, '20121231T093631Z')
|
23
|
-
assert_equal builder.component['dtstamp'], '20121231T093631Z'
|
24
|
-
end
|
25
|
-
|
26
|
-
def test_parses_dtstart_without_tzid
|
27
|
-
parse_line('DTSTART', {}, '20130110T183000')
|
28
|
-
assert_equal builder.component['dtstart'], '20130110T183000'
|
29
|
-
end
|
30
|
-
|
31
|
-
def test_parses_dtend
|
32
|
-
parse_line('DTEND', { 'tzid' => 'America/New_York' }, '20130110T183000')
|
33
|
-
assert_equal builder.component['dtend'], ['20130110T183000', { 'tzid' => 'America/New_York' }]
|
34
|
-
end
|
35
|
-
|
36
|
-
def test_parses_dtstart_without_tzid
|
37
|
-
parse_line('DTEND', {}, '20130110T183000')
|
38
|
-
assert_equal builder.component['dtend'], '20130110T183000'
|
39
|
-
end
|
40
|
-
|
41
|
-
def test_parses_status
|
42
|
-
parse_line('STATUS', {}, 'CONFIRMED')
|
43
|
-
assert_equal builder.component['status'], 'CONFIRMED'
|
44
|
-
end
|
45
|
-
|
46
|
-
def test_parses_summary
|
47
|
-
parse_line('SUMMARY', {}, 'DetroitRuby: 2012 Plan + Lightning talks')
|
48
|
-
assert_equal builder.component['summary'], 'DetroitRuby: 2012 Plan + Lightning talks'
|
49
|
-
end
|
50
|
-
|
51
|
-
def test_parses_description
|
52
|
-
parse_line('DESCRIPTION', {}, 'DetroitRuby\nThursday\, January 10 at 6:30 PM\n\n')
|
53
|
-
assert_equal builder.component['description'], 'DetroitRuby\nThursday\, January 10 at 6:30 PM\n\n'
|
54
|
-
end
|
55
|
-
|
56
|
-
def test_parses_class
|
57
|
-
parse_line('CLASS', {}, 'PUBLIC')
|
58
|
-
assert_equal builder.component['class'], 'PUBLIC'
|
59
|
-
end
|
60
|
-
|
61
|
-
def test_parses_created
|
62
|
-
parse_line('CREATED', {}, '20120106T161509Z')
|
63
|
-
assert_equal builder.component['created'], '20120106T161509Z'
|
7
|
+
# Test properties that can contain property parameters
|
8
|
+
%w(dtstamp dtstart dtend).each do |property|
|
9
|
+
define_method "test_parses_#{property}_with_property_parameters" do
|
10
|
+
builder = EventBuilder.new
|
11
|
+
builder.parse(Line.new(property.upcase, '20130110T183000', 'tzid' => 'America/New_York'))
|
12
|
+
assert_equal ['20130110T183000', { 'tzid' => 'America/New_York' }],
|
13
|
+
builder.component[property]
|
14
|
+
end
|
64
15
|
end
|
65
16
|
|
66
|
-
def
|
67
|
-
|
17
|
+
def test_parses_geo_with_multiple_values
|
18
|
+
builder = EventBuilder.new
|
19
|
+
builder.parse(Line.new('GEO', '42.33;-83.05'))
|
68
20
|
assert_equal builder.component['geo'], ['42.33', '-83.05']
|
69
21
|
end
|
70
22
|
|
71
|
-
|
72
|
-
parse_line('LOCATION', {}, 'Compuware Building (One Campus Martius\, Detroit\, MI 48226)')
|
73
|
-
assert_equal builder.component['location'], 'Compuware Building (One Campus Martius\, Detroit\, MI 48226)'
|
74
|
-
end
|
75
|
-
|
76
|
-
def test_parses_url
|
77
|
-
parse_line('URL', {}, 'http://www.meetup.com/DetroitRuby/events/93346412/')
|
78
|
-
assert_equal builder.component['url'], 'http://www.meetup.com/DetroitRuby/events/93346412/'
|
79
|
-
end
|
80
|
-
|
81
|
-
def test_parses_last_modified
|
82
|
-
parse_line('LAST-MODIFIED', {}, '20120106T161509Z')
|
83
|
-
assert_equal builder.component['last-modified'], '20120106T161509Z'
|
84
|
-
end
|
85
|
-
|
86
|
-
def test_parses_uid
|
87
|
-
parse_line('UID', {}, 'event_qgkxkcyrcbnb@meetup.com')
|
88
|
-
assert_equal builder.component['uid'], 'event_qgkxkcyrcbnb@meetup.com'
|
89
|
-
end
|
90
|
-
|
91
|
-
def test_sets_parent_calendar
|
92
|
-
assert builder_with_parent.parent.is_a?(CalendarBuilder), "Must be able to set the parent calendar builder"
|
93
|
-
end
|
94
|
-
|
95
|
-
# Validation
|
96
|
-
|
23
|
+
# Test properties with :required => true
|
97
24
|
%w(dtstamp uid).each do |property|
|
98
25
|
define_method "test_#{property}_required" do
|
99
|
-
|
26
|
+
builder = EventBuilder.new
|
27
|
+
builder.parent = CalendarBuilder.new
|
28
|
+
assert_required(builder, property)
|
100
29
|
end
|
101
30
|
end
|
102
31
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
32
|
+
# Test properties with :multiple => false
|
33
|
+
%w(dtstamp uid dtstart class created description geo last-mod location
|
34
|
+
organizer priority seq status summary transp url recurid).each do |property|
|
35
|
+
define_method "test_#{property}_cannot_be_defined_more_than_once" do
|
36
|
+
assert_single(EventBuilder.new, property)
|
107
37
|
end
|
108
38
|
end
|
109
39
|
|
110
|
-
def
|
40
|
+
def test_invalid_parent
|
111
41
|
assert_raises Exception do
|
112
|
-
|
42
|
+
EventBuilder.new.parent = TimeZoneBuilder.new
|
113
43
|
end
|
114
44
|
end
|
115
45
|
|
116
46
|
def test_dtstart_required_if_no_calendar_method
|
117
|
-
|
118
|
-
|
47
|
+
builder = EventBuilder.new
|
48
|
+
builder.parent = CalendarBuilder.new
|
49
|
+
builder.valid?
|
50
|
+
assert_error builder, 'dtstart', "The 'dtstart' property is required if the calendar does not have a 'method' property"
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_dtend_invalid_if_duration
|
54
|
+
builder = EventBuilder.new
|
55
|
+
builder.parse(Line.new('DURATION', {}, 'PT15M'))
|
56
|
+
builder.parse(Line.new('DTEND', {}, '19970903T190000Z'))
|
57
|
+
assert_error builder, 'dtend', "The 'dtend' property cannot be set if the 'duration' property already exists"
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_duration_invalid_if_dtend
|
61
|
+
builder = EventBuilder.new
|
62
|
+
builder.parse(Line.new('DTEND', {}, '19970903T190000Z'))
|
63
|
+
builder.parse(Line.new('DURATION', {}, 'PT15M'))
|
64
|
+
assert_error builder, 'duration', "The 'duration' property cannot be set if the 'dtend' property already exists"
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_exdate
|
68
|
+
builder = EventBuilder.new
|
69
|
+
builder.parse("EXDATE;VALUE=DATE-TIME;TZID=America/Detroit:19960402T010000,19960403T010000,19960404T010000")
|
70
|
+
assert_equal builder.component['exdate'], [['19960402T010000','19960403T010000','19960404T010000'], { 'value' => 'DATE-TIME', 'tzid' => 'America/Detroit' }]
|
119
71
|
end
|
120
72
|
end
|
121
73
|
end
|
data/test/selene/line_test.rb
CHANGED
@@ -1,24 +1,24 @@
|
|
1
1
|
require 'test_helper'
|
2
|
+
require 'selene/line'
|
2
3
|
|
3
4
|
module Selene
|
4
|
-
class LineTest < MiniTest::
|
5
|
-
|
5
|
+
class LineTest < MiniTest::Test
|
6
6
|
def test_lines_are_unfolded_before_splitting
|
7
|
-
assert_equal Line.split("
|
7
|
+
assert_equal Line.new('WHAT', "Say it ain't so"), Line.split("WHAT:Say it\r\n ain't so").first
|
8
8
|
end
|
9
9
|
|
10
10
|
def test_parses_content_line
|
11
|
-
assert_equal Line.
|
11
|
+
assert_equal Line.new('VERSION', '2.0'), Line.parse('VERSION:2.0')
|
12
12
|
end
|
13
13
|
|
14
14
|
def test_parses_url
|
15
|
-
assert_equal Line.
|
16
|
-
Line.
|
15
|
+
assert_equal Line.new('TZURL', 'https://townstage.com'),
|
16
|
+
Line.parse('TZURL:https://townstage.com')
|
17
17
|
end
|
18
18
|
|
19
19
|
def test_parses_params
|
20
20
|
assert_equal Line.parse('DTSTART;TZID=America/New_York:20130110T183000'),
|
21
|
-
Line.new('DTSTART',
|
21
|
+
Line.new('DTSTART', '20130110T183000', 'tzid' => 'America/New_York')
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
data/test/selene/parser_test.rb
CHANGED
@@ -1,22 +1,29 @@
|
|
1
|
-
|
1
|
+
require_relative '../test_helper'
|
2
2
|
require 'json'
|
3
|
+
require 'selene/parser'
|
3
4
|
|
4
5
|
module Selene
|
5
|
-
class ParserTest < MiniTest::
|
6
|
+
class ParserTest < MiniTest::Test
|
6
7
|
include FixtureHelper
|
7
8
|
|
8
9
|
def test_parses_blank_string
|
9
|
-
assert_equal
|
10
|
+
assert_equal Parser.parse(""), {}
|
10
11
|
end
|
11
12
|
|
12
13
|
def test_parses_simple_calendar
|
13
|
-
assert_equal
|
14
|
+
assert_equal Parser.parse("BEGIN:VCALENDAR\r\nSUMMARY:Meetups\r\nEND:VCALENDAR"),
|
14
15
|
{ 'vcalendar' => [{ 'summary' => 'Meetups' }] }
|
15
16
|
end
|
16
17
|
|
17
18
|
def test_parses_meetup_calendar
|
18
|
-
assert_equal
|
19
|
+
assert_equal Parser.parse(fixture('meetup.ics')),
|
19
20
|
JSON.parse(fixture('meetup.json'))
|
20
21
|
end
|
22
|
+
|
23
|
+
def test_contain_error
|
24
|
+
skip
|
25
|
+
ical = Parser.parse("BEGIN:VEVENT\r\nEND:VEVENT")
|
26
|
+
assert ical['errors'].any? { |e| e.include? "can't contain" }, "Feed can't contain a vevent"
|
27
|
+
end
|
21
28
|
end
|
22
29
|
end
|
data/test/test_helper.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'bundler/setup'
|
2
|
-
require 'debugger'
|
3
2
|
require 'minitest/autorun'
|
4
|
-
require 'minitest/
|
3
|
+
require 'minitest/reporters'
|
5
4
|
require 'minitest/mock'
|
6
5
|
require 'selene'
|
7
6
|
|
7
|
+
Minitest::Reporters.use! Minitest::Reporters::DefaultReporter.new
|
8
|
+
|
8
9
|
module Selene
|
9
10
|
module Stubbable
|
10
11
|
|
@@ -34,32 +35,21 @@ module Selene
|
|
34
35
|
|
35
36
|
module BuilderTestHelper
|
36
37
|
|
37
|
-
|
38
|
-
# and passes them to the builder to be parsed.
|
39
|
-
def parse_line(name, params, value)
|
40
|
-
builder.parse(Line.new(name, params, value))
|
41
|
-
end
|
42
|
-
|
43
|
-
def assert_required property
|
38
|
+
def assert_required builder, property
|
44
39
|
builder.valid?
|
45
|
-
|
46
|
-
assert builder.errors.any? { |e| e[:message] =~ /#{message}/ }, message
|
40
|
+
assert_error builder, property, "missing required property '#{property}'"
|
47
41
|
end
|
48
42
|
|
49
|
-
def assert_single property
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
assert_equal
|
43
|
+
def assert_single builder, property
|
44
|
+
builder.parse(Line.new(property, 'First Value'))
|
45
|
+
original_value = builder.component[property]
|
46
|
+
builder.parse(Line.new(property, 'Second Value'))
|
47
|
+
assert_error builder, property, "property '#{property}' must not occur more than once"
|
48
|
+
assert_equal original_value, builder.component[property]
|
55
49
|
end
|
56
50
|
|
57
|
-
def
|
58
|
-
|
59
|
-
value = builder.component[property].dup
|
60
|
-
parse_line(property, {}, 'Another Value')
|
61
|
-
assert_equal builder.component[property], value
|
51
|
+
def assert_error builder, property, message
|
52
|
+
assert builder.errors[property].any? { |e| e =~ /#{message}/ }, "#{builder.class.name}: #{message}"
|
62
53
|
end
|
63
|
-
|
64
54
|
end
|
65
55
|
end
|
metadata
CHANGED
@@ -1,62 +1,97 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: selene
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
5
|
-
prerelease:
|
4
|
+
version: 0.3.4
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Cory Kaufman-Schofield
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2015-03-08 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
14
|
+
name: ice_cube
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
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: byebug
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
20
32
|
- !ruby/object:Gem::Version
|
21
33
|
version: '0'
|
22
34
|
type: :development
|
23
35
|
prerelease: false
|
24
36
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
37
|
requirements:
|
27
|
-
- -
|
38
|
+
- - '>='
|
28
39
|
- !ruby/object:Gem::Version
|
29
40
|
version: '0'
|
30
41
|
- !ruby/object:Gem::Dependency
|
31
|
-
name: minitest-
|
42
|
+
name: minitest-reporters
|
32
43
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
44
|
requirements:
|
35
|
-
- -
|
45
|
+
- - '>='
|
36
46
|
- !ruby/object:Gem::Version
|
37
47
|
version: '0'
|
38
48
|
type: :development
|
39
49
|
prerelease: false
|
40
50
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
51
|
requirements:
|
43
|
-
- -
|
52
|
+
- - '>='
|
44
53
|
- !ruby/object:Gem::Version
|
45
54
|
version: '0'
|
46
55
|
- !ruby/object:Gem::Dependency
|
47
56
|
name: rake
|
48
57
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
58
|
requirements:
|
51
|
-
- -
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: guard-minitest
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
52
88
|
- !ruby/object:Gem::Version
|
53
89
|
version: '0'
|
54
90
|
type: :development
|
55
91
|
prerelease: false
|
56
92
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
93
|
requirements:
|
59
|
-
- -
|
94
|
+
- - '>='
|
60
95
|
- !ruby/object:Gem::Version
|
61
96
|
version: '0'
|
62
97
|
description: Selene is an iCalendar parser for Ruby
|
@@ -68,8 +103,8 @@ extra_rdoc_files: []
|
|
68
103
|
files:
|
69
104
|
- .gitattributes
|
70
105
|
- .gitignore
|
71
|
-
- .rspec
|
72
106
|
- Gemfile
|
107
|
+
- Guardfile
|
73
108
|
- LICENSE.txt
|
74
109
|
- README.md
|
75
110
|
- Rakefile
|
@@ -77,7 +112,7 @@ files:
|
|
77
112
|
- lib/selene/alarm_builder.rb
|
78
113
|
- lib/selene/calendar_builder.rb
|
79
114
|
- lib/selene/component_builder.rb
|
80
|
-
- lib/selene/
|
115
|
+
- lib/selene/component_errors.rb
|
81
116
|
- lib/selene/daylight_savings_time_builder.rb
|
82
117
|
- lib/selene/event_builder.rb
|
83
118
|
- lib/selene/feed_builder.rb
|
@@ -89,8 +124,10 @@ files:
|
|
89
124
|
- selene.gemspec
|
90
125
|
- test/fixtures/meetup.ics
|
91
126
|
- test/fixtures/meetup.json
|
127
|
+
- test/helper.rb
|
92
128
|
- test/selene/alarm_builder_test.rb
|
93
129
|
- test/selene/calendar_builder_test.rb
|
130
|
+
- test/selene/component_builder_test.rb
|
94
131
|
- test/selene/daylight_savings_time_builder_test.rb
|
95
132
|
- test/selene/event_builder_test.rb
|
96
133
|
- test/selene/line_test.rb
|
@@ -99,39 +136,34 @@ files:
|
|
99
136
|
- test/test_helper.rb
|
100
137
|
homepage: https://github.com/allspiritseve/selene
|
101
138
|
licenses: []
|
139
|
+
metadata: {}
|
102
140
|
post_install_message:
|
103
141
|
rdoc_options: []
|
104
142
|
require_paths:
|
105
143
|
- lib
|
106
144
|
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
-
none: false
|
108
145
|
requirements:
|
109
|
-
- -
|
146
|
+
- - '>='
|
110
147
|
- !ruby/object:Gem::Version
|
111
148
|
version: '0'
|
112
|
-
segments:
|
113
|
-
- 0
|
114
|
-
hash: 500821961400754664
|
115
149
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
-
none: false
|
117
150
|
requirements:
|
118
|
-
- -
|
151
|
+
- - '>='
|
119
152
|
- !ruby/object:Gem::Version
|
120
153
|
version: '0'
|
121
|
-
segments:
|
122
|
-
- 0
|
123
|
-
hash: 500821961400754664
|
124
154
|
requirements: []
|
125
155
|
rubyforge_project:
|
126
|
-
rubygems_version:
|
156
|
+
rubygems_version: 2.4.2
|
127
157
|
signing_key:
|
128
|
-
specification_version:
|
158
|
+
specification_version: 4
|
129
159
|
summary: Selene is an iCalendar parser for Ruby
|
130
160
|
test_files:
|
131
161
|
- test/fixtures/meetup.ics
|
132
162
|
- test/fixtures/meetup.json
|
163
|
+
- test/helper.rb
|
133
164
|
- test/selene/alarm_builder_test.rb
|
134
165
|
- test/selene/calendar_builder_test.rb
|
166
|
+
- test/selene/component_builder_test.rb
|
135
167
|
- test/selene/daylight_savings_time_builder_test.rb
|
136
168
|
- test/selene/event_builder_test.rb
|
137
169
|
- test/selene/line_test.rb
|
data/.rspec
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
module Selene
|
2
|
-
module ComponentRules
|
3
|
-
|
4
|
-
def property_rules(component)
|
5
|
-
{}.tap do |rules|
|
6
|
-
rules["property %s must not occur more than once"] = lambda do |property, line, message|
|
7
|
-
invalid = @component.key?(property) && component.class::DISTINCT_PROPERTIES.include?(property)
|
8
|
-
!invalid.tap { @errors << { :message => message % property } if invalid }
|
9
|
-
end
|
10
|
-
|
11
|
-
rules["properties '%s' and '%s' cannot occur in the same component"] = lambda do |property, line, message|
|
12
|
-
passed = true
|
13
|
-
component.class::EXCLUSIVE_PROPERTIES.each do |properties|
|
14
|
-
other_property = properties.find { |p| @component.key?(p) && p != property }
|
15
|
-
next unless other_property
|
16
|
-
passed = false
|
17
|
-
@errors << { :message => message % [property, other_property] }
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def component_rules(component)
|
24
|
-
{}.tap do |rules|
|
25
|
-
rules["missing required property '%s'"] = lambda do |message|
|
26
|
-
passed = true
|
27
|
-
component.class::REQUIRED_PROPERTIES.each do |required|
|
28
|
-
next if @component.key?(required)
|
29
|
-
passed = false
|
30
|
-
@errors << { :message => message % required }
|
31
|
-
end
|
32
|
-
passed
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
end
|
38
|
-
end
|