selene 0.2.0 → 0.3.0

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.
@@ -9,8 +9,8 @@ module Selene
9
9
  @component
10
10
  end
11
11
 
12
- def parse(name, params, value)
13
- @component[name.downcase] = value
12
+ def parse(line)
13
+ @component[line.name.downcase] = line.value
14
14
  end
15
15
 
16
16
  end
@@ -2,13 +2,22 @@ module Selene
2
2
  class CalendarBuilder
3
3
 
4
4
  def initialize
5
+ @errors = []
5
6
  @component = Hash.new { |component, property| component[property] = [] }
6
7
  end
7
8
 
8
- def parse(name, params, value)
9
+ def parse(line)
10
+ set_property line.name, line.value
11
+ end
12
+
13
+ def set_property(name, value)
9
14
  @component[name.downcase] = value
10
15
  end
11
16
 
17
+ def name(line)
18
+ line.name.downcase
19
+ end
20
+
12
21
  def append(builder)
13
22
  case builder
14
23
  when EventBuilder
@@ -9,12 +9,12 @@ module Selene
9
9
  @component
10
10
  end
11
11
 
12
- def parse(name, params, value)
13
- @component[name.downcase] = case name
14
- when 'RRULE'
15
- Hash[value.split(';').map { |vs| k, v = vs.split('=', 2); [k.downcase, v] }]
12
+ def parse(line)
13
+ @component[line.name.downcase] = case line.name
14
+ when 'RRULE'
15
+ Hash[line.value.split(';').map { |vs| k, v = vs.split('=', 2); [k.downcase, v] }]
16
16
  else
17
- value
17
+ line.value
18
18
  end
19
19
  end
20
20
  end
@@ -9,14 +9,14 @@ module Selene
9
9
  @component
10
10
  end
11
11
 
12
- def parse(name, params, value)
13
- component[name.downcase] = case name
12
+ def parse(line)
13
+ component[line.name.downcase] = case line.name
14
14
  when 'DTSTAMP', 'DTSTART', 'DTEND'
15
- Parser.parse_date_and_time(name, params, value)
15
+ line.value_with_params
16
16
  when 'GEO'
17
- value.split(';')
17
+ line.value.split(';')
18
18
  else
19
- value
19
+ line.value
20
20
  end
21
21
  end
22
22
 
@@ -0,0 +1,72 @@
1
+ module Selene
2
+ class Line < Struct.new(:name, :params, :value)
3
+
4
+ # Match everything until we hit ';' (parameter separator) or ':' (value separator)
5
+ NAME = /(?<name>[^:\;]+)/
6
+
7
+ # Match everything until we hit ':' (value separator)
8
+ PARAMS = /(?:\;(?<params>[^:]*))/
9
+
10
+ # Match everything that's left
11
+ VALUE = /(?<value>.*)/
12
+
13
+ # Match everything that is not ';' (parameter separator)
14
+ PARAM = /[^\;]+/
15
+
16
+ # Match everything before '='
17
+ PARAM_KEY_VALUE = /(?<key>[^=]+)=(?<value>.*)/
18
+
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) }
23
+ end
24
+
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], parse_params(match[:params]), match[:value])
29
+ end
30
+ end
31
+
32
+
33
+ def self.parse_params(params_string)
34
+ {}.tap do |params|
35
+ return params unless params_string
36
+ params_string.scan(PARAM).map do |param|
37
+ param.match(PARAM_KEY_VALUE) do |match|
38
+ params[match[:key].downcase] = match[:value]
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def initialize(name, params, value)
45
+ self.name = name.upcase
46
+ self.params = params || {}
47
+ self.value = value
48
+ end
49
+
50
+ def begin_component?
51
+ name == 'BEGIN'
52
+ end
53
+
54
+ def end_component?
55
+ name == 'END'
56
+ end
57
+
58
+ def params?
59
+ params && !params.empty?
60
+ end
61
+
62
+ def value_with_params
63
+ params? ? [value, params] : value
64
+ end
65
+
66
+ def ==(line)
67
+ super(line) unless line.is_a?(Hash)
68
+ members.all? { |key| line[key] == self.send(key) }
69
+ end
70
+
71
+ end
72
+ end
@@ -2,6 +2,7 @@ require 'selene/alarm_builder'
2
2
  require 'selene/calendar_builder'
3
3
  require 'selene/daylight_savings_time_builder'
4
4
  require 'selene/event_builder'
5
+ require 'selene/line'
5
6
  require 'selene/standard_time_builder'
6
7
  require 'selene/time_zone_builder'
7
8
 
@@ -23,11 +24,10 @@ module Selene
23
24
  def self.parse(string)
24
25
  { 'calendars' => [] }.tap do |feed|
25
26
  stack = []
26
- split_content_lines(string).each_with_index do |content_line, i|
27
- line = parse_content_line(content_line)
28
- if line[:name].upcase == 'BEGIN'
29
- stack << builder(line[:value]).new
30
- elsif line[:name].upcase == 'END'
27
+ Line.split(string).each do |line|
28
+ if line.begin_component?
29
+ stack << builder(line.value).new
30
+ elsif line.end_component?
31
31
  builder = stack.pop
32
32
  if !stack.empty?
33
33
  stack[-1].append(builder)
@@ -35,35 +35,11 @@ module Selene
35
35
  feed['calendars'] << builder.component
36
36
  end
37
37
  else
38
- stack[-1].parse(line[:name], line[:params], line[:value])
38
+ stack[-1].parse(line)
39
39
  end
40
40
  end
41
41
  end
42
42
  end
43
43
 
44
- def self.parse_content_line(string)
45
- string =~ /([^:\;]+)(?:\;([^:]*))?:(.*)/i
46
- { :name => $1, :params => parse_params($2), :value => $3 }
47
- end
48
-
49
- def self.parse_date_and_time(name, params, value)
50
- params && params != {} ? [value, params] : value
51
- end
52
-
53
- def self.parse_params(raw_params)
54
- return unless raw_params
55
- {}.tap do |params|
56
- raw_params.scan(/[^\;]+/i).map do |param|
57
- param =~ /([^=]+)=(.*)/
58
- params[$1.downcase] = $2
59
- end
60
- end
61
- end
62
-
63
- def self.split_content_lines(string)
64
- line_break = string.scan(/\r\n?|\n/).first || "\r\n"
65
- string.gsub(/#{line_break}\s/, '').split(line_break)
66
- end
67
-
68
44
  end
69
45
  end
@@ -9,8 +9,8 @@ module Selene
9
9
  @component
10
10
  end
11
11
 
12
- def parse(name, params, value)
13
- @component[name.downcase] = value
12
+ def parse(line)
13
+ @component[line.name.downcase] = line.value
14
14
  end
15
15
  end
16
16
  end
@@ -9,8 +9,8 @@ module Selene
9
9
  @component
10
10
  end
11
11
 
12
- def parse(name, params, value)
13
- @component[name.downcase] = value
12
+ def parse(line)
13
+ @component[line.name.downcase] = line.value
14
14
  end
15
15
 
16
16
  def append(builder)
@@ -1,3 +1,3 @@
1
1
  module Selene
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -0,0 +1,16 @@
1
+ require 'test_helper'
2
+
3
+ module Selene
4
+ class AlarmBuilderTest < TestCase
5
+
6
+ def builder
7
+ @builder ||= AlarmBuilder.new
8
+ end
9
+
10
+ def test_parses_action
11
+ parse_line 'ACTION', {}, 'AUDIO'
12
+ assert_equal builder.component['action'], 'AUDIO'
13
+ end
14
+
15
+ end
16
+ end
@@ -16,27 +16,27 @@ module Selene
16
16
  end
17
17
 
18
18
  def test_parse_prodid
19
- builder.parse('PRODID', '', '-//Meetup//RemoteApi//EN')
19
+ parse_line('PRODID', '', '-//Meetup//RemoteApi//EN')
20
20
  assert_equal builder.component['prodid'], '-//Meetup//RemoteApi//EN'
21
21
  end
22
22
 
23
23
  def test_parse_version
24
- builder.parse('VERSION', '', '2.0')
24
+ parse_line('VERSION', '', '2.0')
25
25
  assert_equal @builder.component['version'], '2.0'
26
26
  end
27
27
 
28
28
  def test_parse_calscale
29
- builder.parse('CALSCALE', '', 'Gregorian')
29
+ parse_line('CALSCALE', '', 'Gregorian')
30
30
  assert_equal builder.component['calscale'], 'Gregorian'
31
31
  end
32
32
 
33
33
  def test_parse_method
34
- builder.parse('METHOD', '', 'Publish')
34
+ parse_line('METHOD', '', 'Publish')
35
35
  assert_equal builder.component['method'], 'Publish'
36
36
  end
37
37
 
38
38
  def test_parse_x_prop
39
- builder.parse('X-ORIGINAL-URL', '', 'http://www.google.com')
39
+ parse_line('X-ORIGINAL-URL', '', 'http://www.google.com')
40
40
  assert_equal builder.component['x-original-url'], 'http://www.google.com'
41
41
  end
42
42
 
@@ -8,7 +8,7 @@ module Selene
8
8
  end
9
9
 
10
10
  def test_parses_rrule
11
- builder.parse('RRULE', '', 'FREQ=YEARLY;BYMONTH=3;BYDAY=2SU')
11
+ parse_line('RRULE', '', 'FREQ=YEARLY;BYMONTH=3;BYDAY=2SU')
12
12
  assert_equal builder.component['rrule'], { 'freq' => 'YEARLY', 'bymonth' => '3', 'byday' => '2SU' }
13
13
  end
14
14
 
@@ -9,78 +9,78 @@ module Selene
9
9
 
10
10
  def test_parses_dtstart
11
11
  expected = ['20130110T183000', { 'tzid' => 'America/New_York' }]
12
- builder.parse('DTSTART', { 'tzid' => 'America/New_York' }, '20130110T183000')
12
+ parse_line('DTSTART', { 'tzid' => 'America/New_York' }, '20130110T183000')
13
13
  assert_equal builder.component['dtstart'], expected
14
14
  end
15
15
 
16
16
  def test_parses_dtstamp
17
- builder.parse('DTSTAMP', nil, '20121231T093631Z')
17
+ parse_line('DTSTAMP', nil, '20121231T093631Z')
18
18
  assert_equal builder.component['dtstamp'], '20121231T093631Z'
19
19
  end
20
20
 
21
21
  def test_parses_dtstart_without_tzid
22
- builder.parse('DTSTART', nil, '20130110T183000')
22
+ parse_line('DTSTART', {}, '20130110T183000')
23
23
  assert_equal builder.component['dtstart'], '20130110T183000'
24
24
  end
25
25
 
26
26
  def test_parses_dtend
27
27
  expected = ['20130110T183000', { 'tzid' => 'America/New_York' }]
28
- builder.parse('DTEND', { 'tzid' => 'America/New_York' }, '20130110T183000')
28
+ parse_line('DTEND', { 'tzid' => 'America/New_York' }, '20130110T183000')
29
29
  assert_equal builder.component['dtend'], expected
30
30
  end
31
31
 
32
32
  def test_parses_dtstart_without_tzid
33
- builder.parse('DTEND', nil, '20130110T183000')
33
+ parse_line('DTEND', nil, '20130110T183000')
34
34
  assert_equal builder.component['dtend'], '20130110T183000'
35
35
  end
36
36
 
37
37
  def test_parses_status
38
- builder.parse('STATUS', nil, 'CONFIRMED')
38
+ parse_line('STATUS', nil, 'CONFIRMED')
39
39
  assert_equal builder.component['status'], 'CONFIRMED'
40
40
  end
41
41
 
42
42
  def test_parses_summary
43
- builder.parse('SUMMARY', nil, 'DetroitRuby: 2012 Plan + Lightning talks')
43
+ parse_line('SUMMARY', nil, 'DetroitRuby: 2012 Plan + Lightning talks')
44
44
  assert_equal builder.component['summary'], 'DetroitRuby: 2012 Plan + Lightning talks'
45
45
  end
46
46
 
47
47
  def test_parses_description
48
- builder.parse('DESCRIPTION', nil, 'DetroitRuby\nThursday\, January 10 at 6:30 PM\n\n')
48
+ parse_line('DESCRIPTION', nil, 'DetroitRuby\nThursday\, January 10 at 6:30 PM\n\n')
49
49
  assert_equal builder.component['description'], 'DetroitRuby\nThursday\, January 10 at 6:30 PM\n\n'
50
50
  end
51
51
 
52
52
  def test_parses_class
53
- builder.parse('CLASS', nil, 'PUBLIC')
53
+ parse_line('CLASS', nil, 'PUBLIC')
54
54
  assert_equal builder.component['class'], 'PUBLIC'
55
55
  end
56
56
 
57
57
  def test_parses_created
58
- builder.parse('CREATED', nil, '20120106T161509Z')
58
+ parse_line('CREATED', nil, '20120106T161509Z')
59
59
  assert_equal builder.component['created'], '20120106T161509Z'
60
60
  end
61
61
 
62
62
  def test_parses_geo
63
- builder.parse('GEO', nil, '42.33;-83.05')
63
+ parse_line('GEO', nil, '42.33;-83.05')
64
64
  assert_equal builder.component['geo'], ['42.33', '-83.05']
65
65
  end
66
66
 
67
67
  def test_parses_location
68
- builder.parse('LOCATION', nil, 'Compuware Building (One Campus Martius\, Detroit\, MI 48226)')
68
+ parse_line('LOCATION', nil, 'Compuware Building (One Campus Martius\, Detroit\, MI 48226)')
69
69
  assert_equal builder.component['location'], 'Compuware Building (One Campus Martius\, Detroit\, MI 48226)'
70
70
  end
71
71
 
72
72
  def test_parses_url
73
- builder.parse('URL', nil, 'http://www.meetup.com/DetroitRuby/events/93346412/')
73
+ parse_line('URL', nil, 'http://www.meetup.com/DetroitRuby/events/93346412/')
74
74
  assert_equal builder.component['url'], 'http://www.meetup.com/DetroitRuby/events/93346412/'
75
75
  end
76
76
 
77
77
  def test_parses_last_modified
78
- builder.parse('LAST-MODIFIED', nil, '20120106T161509Z')
78
+ parse_line('LAST-MODIFIED', nil, '20120106T161509Z')
79
79
  assert_equal builder.component['last-modified'], '20120106T161509Z'
80
80
  end
81
81
 
82
82
  def test_parses_uid
83
- builder.parse('UID', nil, 'event_qgkxkcyrcbnb@meetup.com')
83
+ parse_line('UID', nil, 'event_qgkxkcyrcbnb@meetup.com')
84
84
  assert_equal builder.component['uid'], 'event_qgkxkcyrcbnb@meetup.com'
85
85
  end
86
86
 
@@ -0,0 +1,24 @@
1
+ require 'test_helper'
2
+
3
+ module Selene
4
+ class LineTest < TestCase
5
+
6
+ def test_lines_are_unfolded_before_splitting
7
+ assert_equal Line.split("TEST:This is a\r\n test").first.value, "This is a test"
8
+ end
9
+
10
+ def test_parses_content_line
11
+ assert_equal Line.parse('VERSION:2.0'), { :name => 'VERSION', :params => {}, :value => '2.0' }
12
+ end
13
+
14
+ def test_parses_url
15
+ expected = { :name => 'TZURL', :params => {}, :value => 'http://www.meetup.com/DetroitRuby/events/ical/DetroitRuby/' }
16
+ assert_equal Line.parse('TZURL:http://www.meetup.com/DetroitRuby/events/ical/DetroitRuby/'), expected
17
+ end
18
+
19
+ def test_parses_params
20
+ expected = { :name => 'DTSTART', :params => { 'tzid' => 'America/New_York' }, :value => '20130110T183000' }
21
+ assert_equal Line.parse('DTSTART;TZID=America/New_York:20130110T183000'), expected
22
+ end
23
+ end
24
+ end
@@ -4,30 +4,10 @@ require 'json'
4
4
  module Selene
5
5
  class ParserTest < TestCase
6
6
 
7
- def test_lines_are_unfolded_before_splitting
8
- assert_equal Selene::Parser.split_content_lines("This is a\r\n test").first, "This is a test"
9
- end
10
-
11
7
  def test_parses_blank_string
12
8
  assert_equal Selene::Parser.parse(""), { 'calendars' => [] }
13
9
  end
14
10
 
15
- def test_parses_content_line
16
- assert_equal Selene::Parser.parse_content_line('VERSION:2.0'), { :name => 'VERSION', :params => nil, :value => '2.0' }
17
- end
18
-
19
- def test_parses_content_line_with_url
20
- expected = { :name => 'TZURL', :params => nil, :value => 'http://www.meetup.com/DetroitRuby/events/ical/DetroitRuby/' }
21
- assert_equal Selene::Parser.parse_content_line('TZURL:http://www.meetup.com/DetroitRuby/events/ical/DetroitRuby/'), expected
22
- end
23
-
24
- def test_parses_content_line_with_param
25
- expected = { :name => 'DTSTART', :params => { 'tzid' => 'America/New_York' }, :value => '20130110T183000' }
26
- assert_equal Selene::Parser.parse_content_line('DTSTART;TZID=America/New_York:20130110T183000'), expected
27
- end
28
-
29
- # Sanity tests just to make sure the thing works
30
-
31
11
  def test_parses_simple_calendar
32
12
  assert_equal Selene::Parser.parse("BEGIN:VCALENDAR\r\nSUMMARY:Meetups\r\nEND:VCALENDAR"), { 'calendars' => [{ 'summary' => 'Meetups' }] }
33
13
  end
@@ -12,6 +12,13 @@ module Selene
12
12
  File.read(File.join(File.dirname(__FILE__), 'fixtures', filename))
13
13
  end
14
14
 
15
+ # This is a helper method that takes a name, params and a value, turns them into a proper line hash,
16
+ # and passes them to the builder to be parsed.
17
+ def parse_line(name, params, value)
18
+ raise "Must define builder method in test case" unless builder
19
+ builder.parse(Line.new(name, params, value))
20
+ end
21
+
15
22
  end
16
23
  end
17
24
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: selene
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-03 00:00:00.000000000 Z
12
+ date: 2013-01-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: debugger
@@ -79,6 +79,7 @@ files:
79
79
  - lib/selene/calendar_builder.rb
80
80
  - lib/selene/daylight_savings_time_builder.rb
81
81
  - lib/selene/event_builder.rb
82
+ - lib/selene/line.rb
82
83
  - lib/selene/parser.rb
83
84
  - lib/selene/standard_time_builder.rb
84
85
  - lib/selene/time_zone_builder.rb
@@ -86,9 +87,11 @@ files:
86
87
  - selene.gemspec
87
88
  - test/fixtures/meetup.ics
88
89
  - test/fixtures/meetup.json
90
+ - test/selene/alarm_builder_test.rb
89
91
  - test/selene/calendar_builder_test.rb
90
92
  - test/selene/daylight_savings_time_builder_test.rb
91
93
  - test/selene/event_builder_test.rb
94
+ - test/selene/line_test.rb
92
95
  - test/selene/parser_test.rb
93
96
  - test/selene/time_zone_builder_test.rb
94
97
  - test/test_helper.rb
@@ -106,7 +109,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
106
109
  version: '0'
107
110
  segments:
108
111
  - 0
109
- hash: 3007603395114085871
112
+ hash: -4237878709699740480
110
113
  required_rubygems_version: !ruby/object:Gem::Requirement
111
114
  none: false
112
115
  requirements:
@@ -115,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
118
  version: '0'
116
119
  segments:
117
120
  - 0
118
- hash: 3007603395114085871
121
+ hash: -4237878709699740480
119
122
  requirements: []
120
123
  rubyforge_project:
121
124
  rubygems_version: 1.8.24
@@ -125,9 +128,11 @@ summary: Selene is an iCalendar parser for Ruby
125
128
  test_files:
126
129
  - test/fixtures/meetup.ics
127
130
  - test/fixtures/meetup.json
131
+ - test/selene/alarm_builder_test.rb
128
132
  - test/selene/calendar_builder_test.rb
129
133
  - test/selene/daylight_savings_time_builder_test.rb
130
134
  - test/selene/event_builder_test.rb
135
+ - test/selene/line_test.rb
131
136
  - test/selene/parser_test.rb
132
137
  - test/selene/time_zone_builder_test.rb
133
138
  - test/test_helper.rb