selene 0.3.1 → 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|