selene 0.2.0 → 0.3.0

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