selene 0.0.1 → 0.2.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.
@@ -0,0 +1 @@
1
+ *.ics text eol=crlf
data/.gitignore CHANGED
@@ -9,6 +9,7 @@ _yardoc
9
9
  coverage
10
10
  doc/
11
11
  lib/bundler/man
12
+ notes.md
12
13
  pkg
13
14
  rdoc
14
15
  spec/reports
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3@selene --create
data/Gemfile CHANGED
@@ -1,5 +1,2 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in selene.gemspec
1
+ source :rubygems
4
2
  gemspec
5
- gem 'rspec'
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Selene
2
2
 
3
- Selene is a simple iCalendar parser. It takes a string and outputs a ruby hash.
3
+ Selene is an iCalendar parser for Ruby. It takes a string in iCalendar format (RFC 5545) and outputs a hash.
4
+
5
+ ![Selene](http://corykaufman.com/images/selene.png)
4
6
 
5
7
  ## Installation
6
8
 
@@ -20,6 +22,7 @@ Or install it yourself as:
20
22
 
21
23
  ```ruby
22
24
  ical = Selene.parse(File.read('calendar.ics'))
25
+ ```
23
26
 
24
27
  ## Contributing
25
28
 
data/Rakefile CHANGED
@@ -1 +1,24 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
+ require 'debugger'
3
+ require 'json'
4
+ require 'pp'
5
+ require 'rake/testtask'
6
+ require 'selene'
7
+
8
+ Rake::TestTask.new do |t|
9
+ t.libs << 'test'
10
+ t.test_files = FileList['test/**/*_test.rb']
11
+ end
12
+
13
+ task :meetup do
14
+ require 'selene'
15
+ require 'json'
16
+ require 'pp'
17
+ require 'debugger'
18
+
19
+ File.open('test/fixtures/meetup.json', 'wb') do |file|
20
+ feed = Selene.parse(File.read('test/fixtures/meetup.ics'))
21
+ pp feed
22
+ file.write(JSON.pretty_generate(feed))
23
+ end
24
+ end
@@ -1,15 +1,10 @@
1
- require "selene/version"
1
+ require 'selene/parser'
2
+ require 'selene/version'
2
3
 
3
- class Selene
4
-
5
- FEED = {
6
- :calendars => []
7
- }
4
+ module Selene
8
5
 
9
6
  def self.parse(string)
10
- { :calendars => [] }.tap do |feed|
11
-
12
- end
7
+ Parser.parse(string)
13
8
  end
14
9
 
15
10
  end
@@ -0,0 +1,17 @@
1
+ module Selene
2
+ class AlarmBuilder
3
+
4
+ def initialize
5
+ @component = {}
6
+ end
7
+
8
+ def component
9
+ @component
10
+ end
11
+
12
+ def parse(name, params, value)
13
+ @component[name.downcase] = value
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ module Selene
2
+ class CalendarBuilder
3
+
4
+ def initialize
5
+ @component = Hash.new { |component, property| component[property] = [] }
6
+ end
7
+
8
+ def parse(name, params, value)
9
+ @component[name.downcase] = value
10
+ end
11
+
12
+ def append(builder)
13
+ case builder
14
+ when EventBuilder
15
+ @component['events'] << builder.component
16
+ when TimeZoneBuilder
17
+ @component['time_zones'] << builder.component
18
+ end
19
+ end
20
+
21
+ def component
22
+ @component
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ module Selene
2
+ class DaylightSavingsTimeBuilder
3
+
4
+ def initialize
5
+ @component = {}
6
+ end
7
+
8
+ def component
9
+ @component
10
+ end
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] }]
16
+ else
17
+ value
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ module Selene
2
+ class EventBuilder
3
+
4
+ def initialize
5
+ @component = Hash.new { |component, property| component[property] = [] }
6
+ end
7
+
8
+ def component
9
+ @component
10
+ end
11
+
12
+ def parse(name, params, value)
13
+ component[name.downcase] = case name
14
+ when 'DTSTAMP', 'DTSTART', 'DTEND'
15
+ Parser.parse_date_and_time(name, params, value)
16
+ when 'GEO'
17
+ value.split(';')
18
+ else
19
+ value
20
+ end
21
+ end
22
+
23
+ def append(builder)
24
+ case builder
25
+ when AlarmBuilder
26
+ @component['alarms'] << builder.component
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,69 @@
1
+ require 'selene/alarm_builder'
2
+ require 'selene/calendar_builder'
3
+ require 'selene/daylight_savings_time_builder'
4
+ require 'selene/event_builder'
5
+ require 'selene/standard_time_builder'
6
+ require 'selene/time_zone_builder'
7
+
8
+ module Selene
9
+ module Parser
10
+
11
+ def self.builder(component)
12
+ case component
13
+ when 'VCALENDAR' then CalendarBuilder
14
+ when 'VTIMEZONE' then TimeZoneBuilder
15
+ when 'DAYLIGHT' then DaylightSavingsTimeBuilder
16
+ when 'STANDARD' then StandardTimeBuilder
17
+ when 'VEVENT' then EventBuilder
18
+ when 'VALARM' then AlarmBuilder
19
+ else raise "Unknown component #{component}"
20
+ end
21
+ end
22
+
23
+ def self.parse(string)
24
+ { 'calendars' => [] }.tap do |feed|
25
+ 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'
31
+ builder = stack.pop
32
+ if !stack.empty?
33
+ stack[-1].append(builder)
34
+ else
35
+ feed['calendars'] << builder.component
36
+ end
37
+ else
38
+ stack[-1].parse(line[:name], line[:params], line[:value])
39
+ end
40
+ end
41
+ end
42
+ end
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
+ end
69
+ end
@@ -0,0 +1,16 @@
1
+ module Selene
2
+ class StandardTimeBuilder
3
+
4
+ def initialize
5
+ @component = {}
6
+ end
7
+
8
+ def component
9
+ @component
10
+ end
11
+
12
+ def parse(name, params, value)
13
+ @component[name.downcase] = value
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ module Selene
2
+ class TimeZoneBuilder
3
+
4
+ def initialize
5
+ @component = Hash.new { |component, property| component[property] = [] }
6
+ end
7
+
8
+ def component
9
+ @component
10
+ end
11
+
12
+ def parse(name, params, value)
13
+ @component[name.downcase] = value
14
+ end
15
+
16
+ def append(builder)
17
+ case builder
18
+ when DaylightSavingsTimeBuilder
19
+ @component['daylight'] << builder.component
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -1,3 +1,3 @@
1
1
  module Selene
2
- VERSION = "0.0.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -8,12 +8,16 @@ Gem::Specification.new do |gem|
8
8
  gem.version = Selene::VERSION
9
9
  gem.authors = ["Cory Kaufman-Schofield"]
10
10
  gem.email = ["cory@corykaufman.com"]
11
- gem.description = %q{Selene is a simple iCalendar parser}
12
- gem.summary = %q{Selene is a simple iCalendar parser}
13
- gem.homepage = ""
11
+ gem.description = %q{Selene is an iCalendar parser for Ruby}
12
+ gem.summary = %q{Selene is an iCalendar parser for Ruby}
13
+ gem.homepage = "https://github.com/allspiritseve/selene"
14
14
 
15
15
  gem.files = `git ls-files`.split($/)
16
16
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency 'debugger'
21
+ gem.add_development_dependency 'minitest-colorize'
22
+ gem.add_development_dependency 'rake'
19
23
  end
@@ -0,0 +1,131 @@
1
+ BEGIN:VCALENDAR
2
+ VERSION:2.0
3
+ PRODID:-//Meetup//RemoteApi//EN
4
+ CALSCALE:GREGORIAN
5
+ METHOD:PUBLISH
6
+ X-ORIGINAL-URL:http://www.meetup.com/DetroitRuby/events/ical/DetroitRuby/
7
+
8
+ X-WR-CALNAME:Events - DetroitRuby
9
+ BEGIN:VTIMEZONE
10
+ TZID:America/New_York
11
+ TZURL:http://tzurl.org/zoneinfo-outlook/America/New_York
12
+ X-LIC-LOCATION:America/New_York
13
+ BEGIN:DAYLIGHT
14
+ TZOFFSETFROM:-0500
15
+ TZOFFSETTO:-0400
16
+ TZNAME:EDT
17
+ DTSTART:19700308T020000
18
+ RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
19
+ END:DAYLIGHT
20
+ BEGIN:STANDARD
21
+ TZOFFSETFROM:-0400
22
+ TZOFFSETTO:-0500
23
+ TZNAME:EST
24
+ DTSTART:19701101T020000
25
+ RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
26
+ END:STANDARD
27
+ END:VTIMEZONE
28
+ BEGIN:VEVENT
29
+ DTSTAMP:20121231T093631Z
30
+ DTSTART;TZID=America/New_York:20130110T183000
31
+ DTEND;TZID=America/New_York:20130110T213000
32
+ STATUS:CONFIRMED
33
+ SUMMARY:DetroitRuby: 2012 Plan + Lightning talks
34
+ DESCRIPTION:DetroitRuby\nThursday\, January 10 at 6:30 PM\n\nIts wild\, i
35
+ ts crazy\, its DetroitRuby 2012! Hey everyone Happy New year! A lot of c
36
+ hanges for this year for DetroitRuby! We are hoping to do away with ...\
37
+ n\nDetails: http://www.meetup.com/DetroitRuby/events/93346412/
38
+ CLASS:PUBLIC
39
+ CREATED:20120106T161509Z
40
+ GEO:42.33;-83.05
41
+ LOCATION:Compuware Building (One Campus Martius\, Detroit\, MI 48226)
42
+ URL:http://www.meetup.com/DetroitRuby/events/93346412/
43
+ LAST-MODIFIED:20120106T161509Z
44
+ UID:event_qgkxkcyrcbnb@meetup.com
45
+ END:VEVENT
46
+ BEGIN:VEVENT
47
+ DTSTAMP:20121231T093631Z
48
+ DTSTART;TZID=America/New_York:20130214T183000
49
+ DTEND;TZID=America/New_York:20130214T213000
50
+ STATUS:CONFIRMED
51
+ SUMMARY:DetroitRuby: 2012 Plan + Lightning talks
52
+ DESCRIPTION:DetroitRuby\nThursday\, February 14 at 6:30 PM\n\nIts wild\,
53
+ its crazy\, its DetroitRuby 2012! Hey everyone Happy New year! A lot of
54
+ changes for this year for DetroitRuby! We are hoping to do away with ...
55
+ \n\nDetails: http://www.meetup.com/DetroitRuby/events/qgkxkcyrdbsb/
56
+ CLASS:PUBLIC
57
+ CREATED:20120106T161509Z
58
+ GEO:42.33;-83.05
59
+ URL:http://www.meetup.com/DetroitRuby/events/qgkxkcyrdbsb/
60
+ LAST-MODIFIED:20120106T161509Z
61
+ UID:event_qgkxkcyrdbsb@meetup.com
62
+ END:VEVENT
63
+ BEGIN:VEVENT
64
+ DTSTAMP:20121231T093631Z
65
+ DTSTART;TZID=America/New_York:20130314T183000
66
+ DTEND;TZID=America/New_York:20130314T213000
67
+ STATUS:CONFIRMED
68
+ SUMMARY:DetroitRuby: 2012 Plan + Lightning talks
69
+ DESCRIPTION:DetroitRuby\nThursday\, March 14 at 6:30 PM\n\nIts wild\, its
70
+ crazy\, its DetroitRuby 2012! Hey everyone Happy New year! A lot of cha
71
+ nges for this year for DetroitRuby! We are hoping to do away with ...\n\
72
+ nDetails: http://www.meetup.com/DetroitRuby/events/qgkxkcyrfbsb/
73
+ CLASS:PUBLIC
74
+ CREATED:20120106T161509Z
75
+ GEO:42.33;-83.05
76
+ URL:http://www.meetup.com/DetroitRuby/events/qgkxkcyrfbsb/
77
+ LAST-MODIFIED:20120106T161509Z
78
+ UID:event_qgkxkcyrfbsb@meetup.com
79
+ END:VEVENT
80
+ BEGIN:VEVENT
81
+ DTSTAMP:20121231T093631Z
82
+ DTSTART;TZID=America/New_York:20130411T183000
83
+ DTEND;TZID=America/New_York:20130411T213000
84
+ STATUS:CONFIRMED
85
+ SUMMARY:DetroitRuby: 2012 Plan + Lightning talks
86
+ DESCRIPTION:DetroitRuby\nThursday\, April 11 at 6:30 PM\n\nIts wild\, its
87
+ crazy\, its DetroitRuby 2012! Hey everyone Happy New year! A lot of cha
88
+ nges for this year for DetroitRuby! We are hoping to do away with ...\n\
89
+ nDetails: http://www.meetup.com/DetroitRuby/events/qgkxkcyrgbpb/
90
+ CLASS:PUBLIC
91
+ CREATED:20120106T161509Z
92
+ GEO:42.33;-83.05
93
+ URL:http://www.meetup.com/DetroitRuby/events/qgkxkcyrgbpb/
94
+ LAST-MODIFIED:20120106T161509Z
95
+ UID:event_qgkxkcyrgbpb@meetup.com
96
+ END:VEVENT
97
+ BEGIN:VEVENT
98
+ DTSTAMP:20121231T093631Z
99
+ DTSTART;TZID=America/New_York:20130509T183000
100
+ DTEND;TZID=America/New_York:20130509T213000
101
+ STATUS:CONFIRMED
102
+ SUMMARY:DetroitRuby: 2012 Plan + Lightning talks
103
+ DESCRIPTION:DetroitRuby\nThursday\, May 9 at 6:30 PM\n\nIts wild\, its cr
104
+ azy\, its DetroitRuby 2012! Hey everyone Happy New year! A lot of change
105
+ s for this year for DetroitRuby! We are hoping to do away with ...\n\nDe
106
+ tails: http://www.meetup.com/DetroitRuby/events/qgkxkcyrhbmb/
107
+ CLASS:PUBLIC
108
+ CREATED:20120106T161509Z
109
+ GEO:42.33;-83.05
110
+ URL:http://www.meetup.com/DetroitRuby/events/qgkxkcyrhbmb/
111
+ LAST-MODIFIED:20120106T161509Z
112
+ UID:event_qgkxkcyrhbmb@meetup.com
113
+ END:VEVENT
114
+ BEGIN:VEVENT
115
+ DTSTAMP:20121231T093631Z
116
+ DTSTART;TZID=America/New_York:20130613T183000
117
+ DTEND;TZID=America/New_York:20130613T213000
118
+ STATUS:CONFIRMED
119
+ SUMMARY:DetroitRuby: 2012 Plan + Lightning talks
120
+ DESCRIPTION:DetroitRuby\nThursday\, June 13 at 6:30 PM\n\nIts wild\, its
121
+ crazy\, its DetroitRuby 2012! Hey everyone Happy New year! A lot of chan
122
+ ges for this year for DetroitRuby! We are hoping to do away with ...\n\n
123
+ Details: http://www.meetup.com/DetroitRuby/events/qgkxkcyrjbrb/
124
+ CLASS:PUBLIC
125
+ CREATED:20120106T161509Z
126
+ GEO:42.33;-83.05
127
+ URL:http://www.meetup.com/DetroitRuby/events/qgkxkcyrjbrb/
128
+ LAST-MODIFIED:20120106T161509Z
129
+ UID:event_qgkxkcyrjbrb@meetup.com
130
+ END:VEVENT
131
+ END:VCALENDAR
@@ -0,0 +1,197 @@
1
+ {
2
+ "calendars": [
3
+ {
4
+ "version": "2.0",
5
+ "prodid": "-//Meetup//RemoteApi//EN",
6
+ "calscale": "GREGORIAN",
7
+ "method": "PUBLISH",
8
+ "x-original-url": "http://www.meetup.com/DetroitRuby/events/ical/DetroitRuby/",
9
+ "x-wr-calname": "Events - DetroitRuby",
10
+ "time_zones": [
11
+ {
12
+ "tzid": "America/New_York",
13
+ "tzurl": "http://tzurl.org/zoneinfo-outlook/America/New_York",
14
+ "x-lic-location": "America/New_York",
15
+ "daylight": [
16
+ {
17
+ "tzoffsetfrom": "-0500",
18
+ "tzoffsetto": "-0400",
19
+ "tzname": "EDT",
20
+ "dtstart": "19700308T020000",
21
+ "rrule": {
22
+ "freq": "YEARLY",
23
+ "bymonth": "3",
24
+ "byday": "2SU"
25
+ }
26
+ }
27
+ ]
28
+ }
29
+ ],
30
+ "events": [
31
+ {
32
+ "dtstamp": "20121231T093631Z",
33
+ "dtstart": [
34
+ "20130110T183000",
35
+ {
36
+ "tzid": "America/New_York"
37
+ }
38
+ ],
39
+ "dtend": [
40
+ "20130110T213000",
41
+ {
42
+ "tzid": "America/New_York"
43
+ }
44
+ ],
45
+ "status": "CONFIRMED",
46
+ "summary": "DetroitRuby: 2012 Plan + Lightning talks",
47
+ "description": "DetroitRuby\\nThursday\\, January 10 at 6:30 PM\\n\\nIts wild\\, its crazy\\, its DetroitRuby 2012! Hey everyone Happy New year! A lot of changes for this year for DetroitRuby! We are hoping to do away with ...\\n\\nDetails: http://www.meetup.com/DetroitRuby/events/93346412/",
48
+ "class": "PUBLIC",
49
+ "created": "20120106T161509Z",
50
+ "geo": [
51
+ "42.33",
52
+ "-83.05"
53
+ ],
54
+ "location": "Compuware Building (One Campus Martius\\, Detroit\\, MI 48226)",
55
+ "url": "http://www.meetup.com/DetroitRuby/events/93346412/",
56
+ "last-modified": "20120106T161509Z",
57
+ "uid": "event_qgkxkcyrcbnb@meetup.com"
58
+ },
59
+ {
60
+ "dtstamp": "20121231T093631Z",
61
+ "dtstart": [
62
+ "20130214T183000",
63
+ {
64
+ "tzid": "America/New_York"
65
+ }
66
+ ],
67
+ "dtend": [
68
+ "20130214T213000",
69
+ {
70
+ "tzid": "America/New_York"
71
+ }
72
+ ],
73
+ "status": "CONFIRMED",
74
+ "summary": "DetroitRuby: 2012 Plan + Lightning talks",
75
+ "description": "DetroitRuby\\nThursday\\, February 14 at 6:30 PM\\n\\nIts wild\\, its crazy\\, its DetroitRuby 2012! Hey everyone Happy New year! A lot of changes for this year for DetroitRuby! We are hoping to do away with ...\\n\\nDetails: http://www.meetup.com/DetroitRuby/events/qgkxkcyrdbsb/",
76
+ "class": "PUBLIC",
77
+ "created": "20120106T161509Z",
78
+ "geo": [
79
+ "42.33",
80
+ "-83.05"
81
+ ],
82
+ "url": "http://www.meetup.com/DetroitRuby/events/qgkxkcyrdbsb/",
83
+ "last-modified": "20120106T161509Z",
84
+ "uid": "event_qgkxkcyrdbsb@meetup.com"
85
+ },
86
+ {
87
+ "dtstamp": "20121231T093631Z",
88
+ "dtstart": [
89
+ "20130314T183000",
90
+ {
91
+ "tzid": "America/New_York"
92
+ }
93
+ ],
94
+ "dtend": [
95
+ "20130314T213000",
96
+ {
97
+ "tzid": "America/New_York"
98
+ }
99
+ ],
100
+ "status": "CONFIRMED",
101
+ "summary": "DetroitRuby: 2012 Plan + Lightning talks",
102
+ "description": "DetroitRuby\\nThursday\\, March 14 at 6:30 PM\\n\\nIts wild\\, its crazy\\, its DetroitRuby 2012! Hey everyone Happy New year! A lot of changes for this year for DetroitRuby! We are hoping to do away with ...\\n\\nDetails: http://www.meetup.com/DetroitRuby/events/qgkxkcyrfbsb/",
103
+ "class": "PUBLIC",
104
+ "created": "20120106T161509Z",
105
+ "geo": [
106
+ "42.33",
107
+ "-83.05"
108
+ ],
109
+ "url": "http://www.meetup.com/DetroitRuby/events/qgkxkcyrfbsb/",
110
+ "last-modified": "20120106T161509Z",
111
+ "uid": "event_qgkxkcyrfbsb@meetup.com"
112
+ },
113
+ {
114
+ "dtstamp": "20121231T093631Z",
115
+ "dtstart": [
116
+ "20130411T183000",
117
+ {
118
+ "tzid": "America/New_York"
119
+ }
120
+ ],
121
+ "dtend": [
122
+ "20130411T213000",
123
+ {
124
+ "tzid": "America/New_York"
125
+ }
126
+ ],
127
+ "status": "CONFIRMED",
128
+ "summary": "DetroitRuby: 2012 Plan + Lightning talks",
129
+ "description": "DetroitRuby\\nThursday\\, April 11 at 6:30 PM\\n\\nIts wild\\, its crazy\\, its DetroitRuby 2012! Hey everyone Happy New year! A lot of changes for this year for DetroitRuby! We are hoping to do away with ...\\n\\nDetails: http://www.meetup.com/DetroitRuby/events/qgkxkcyrgbpb/",
130
+ "class": "PUBLIC",
131
+ "created": "20120106T161509Z",
132
+ "geo": [
133
+ "42.33",
134
+ "-83.05"
135
+ ],
136
+ "url": "http://www.meetup.com/DetroitRuby/events/qgkxkcyrgbpb/",
137
+ "last-modified": "20120106T161509Z",
138
+ "uid": "event_qgkxkcyrgbpb@meetup.com"
139
+ },
140
+ {
141
+ "dtstamp": "20121231T093631Z",
142
+ "dtstart": [
143
+ "20130509T183000",
144
+ {
145
+ "tzid": "America/New_York"
146
+ }
147
+ ],
148
+ "dtend": [
149
+ "20130509T213000",
150
+ {
151
+ "tzid": "America/New_York"
152
+ }
153
+ ],
154
+ "status": "CONFIRMED",
155
+ "summary": "DetroitRuby: 2012 Plan + Lightning talks",
156
+ "description": "DetroitRuby\\nThursday\\, May 9 at 6:30 PM\\n\\nIts wild\\, its crazy\\, its DetroitRuby 2012! Hey everyone Happy New year! A lot of changes for this year for DetroitRuby! We are hoping to do away with ...\\n\\nDetails: http://www.meetup.com/DetroitRuby/events/qgkxkcyrhbmb/",
157
+ "class": "PUBLIC",
158
+ "created": "20120106T161509Z",
159
+ "geo": [
160
+ "42.33",
161
+ "-83.05"
162
+ ],
163
+ "url": "http://www.meetup.com/DetroitRuby/events/qgkxkcyrhbmb/",
164
+ "last-modified": "20120106T161509Z",
165
+ "uid": "event_qgkxkcyrhbmb@meetup.com"
166
+ },
167
+ {
168
+ "dtstamp": "20121231T093631Z",
169
+ "dtstart": [
170
+ "20130613T183000",
171
+ {
172
+ "tzid": "America/New_York"
173
+ }
174
+ ],
175
+ "dtend": [
176
+ "20130613T213000",
177
+ {
178
+ "tzid": "America/New_York"
179
+ }
180
+ ],
181
+ "status": "CONFIRMED",
182
+ "summary": "DetroitRuby: 2012 Plan + Lightning talks",
183
+ "description": "DetroitRuby\\nThursday\\, June 13 at 6:30 PM\\n\\nIts wild\\, its crazy\\, its DetroitRuby 2012! Hey everyone Happy New year! A lot of changes for this year for DetroitRuby! We are hoping to do away with ...\\n\\nDetails: http://www.meetup.com/DetroitRuby/events/qgkxkcyrjbrb/",
184
+ "class": "PUBLIC",
185
+ "created": "20120106T161509Z",
186
+ "geo": [
187
+ "42.33",
188
+ "-83.05"
189
+ ],
190
+ "url": "http://www.meetup.com/DetroitRuby/events/qgkxkcyrjbrb/",
191
+ "last-modified": "20120106T161509Z",
192
+ "uid": "event_qgkxkcyrjbrb@meetup.com"
193
+ }
194
+ ]
195
+ }
196
+ ]
197
+ }
@@ -0,0 +1,56 @@
1
+ require 'test_helper'
2
+
3
+ module Selene
4
+ class CalendarBuilderTest < TestCase
5
+
6
+ def builder
7
+ @builder ||= CalendarBuilder.new
8
+ end
9
+
10
+ def event_builder
11
+ @event_builder ||= EventBuilder.new.extend(Stubbable)
12
+ end
13
+
14
+ def time_zone_builder
15
+ @time_zone_builder ||= TimeZoneBuilder.new.extend(Stubbable)
16
+ end
17
+
18
+ def test_parse_prodid
19
+ builder.parse('PRODID', '', '-//Meetup//RemoteApi//EN')
20
+ assert_equal builder.component['prodid'], '-//Meetup//RemoteApi//EN'
21
+ end
22
+
23
+ def test_parse_version
24
+ builder.parse('VERSION', '', '2.0')
25
+ assert_equal @builder.component['version'], '2.0'
26
+ end
27
+
28
+ def test_parse_calscale
29
+ builder.parse('CALSCALE', '', 'Gregorian')
30
+ assert_equal builder.component['calscale'], 'Gregorian'
31
+ end
32
+
33
+ def test_parse_method
34
+ builder.parse('METHOD', '', 'Publish')
35
+ assert_equal builder.component['method'], 'Publish'
36
+ end
37
+
38
+ def test_parse_x_prop
39
+ builder.parse('X-ORIGINAL-URL', '', 'http://www.google.com')
40
+ assert_equal builder.component['x-original-url'], 'http://www.google.com'
41
+ end
42
+
43
+ def test_append_event_builder
44
+ event_builder.stub :component, { 'summary' => "Bluth's Best Party" }
45
+ builder.append(event_builder)
46
+ assert_equal builder.component['events'].first['summary'], "Bluth's Best Party"
47
+ end
48
+
49
+ def test_append_time_zone_builder
50
+ time_zone_builder.stub :component, { 'tzid' => 'America/Detroit' }
51
+ builder.append(time_zone_builder)
52
+ assert_equal builder.component['time_zones'].first, { 'tzid' => 'America/Detroit' }
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,16 @@
1
+ require 'test_helper'
2
+
3
+ module Selene
4
+ class DaylightSavingsTimeBuilderTest < TestCase
5
+
6
+ def builder
7
+ @builder ||= DaylightSavingsTimeBuilder.new
8
+ end
9
+
10
+ def test_parses_rrule
11
+ builder.parse('RRULE', '', 'FREQ=YEARLY;BYMONTH=3;BYDAY=2SU')
12
+ assert_equal builder.component['rrule'], { 'freq' => 'YEARLY', 'bymonth' => '3', 'byday' => '2SU' }
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,88 @@
1
+ require 'test_helper'
2
+
3
+ module Selene
4
+ class EventBuilderTest < TestCase
5
+
6
+ def builder
7
+ @builder ||= EventBuilder.new
8
+ end
9
+
10
+ def test_parses_dtstart
11
+ expected = ['20130110T183000', { 'tzid' => 'America/New_York' }]
12
+ builder.parse('DTSTART', { 'tzid' => 'America/New_York' }, '20130110T183000')
13
+ assert_equal builder.component['dtstart'], expected
14
+ end
15
+
16
+ def test_parses_dtstamp
17
+ builder.parse('DTSTAMP', nil, '20121231T093631Z')
18
+ assert_equal builder.component['dtstamp'], '20121231T093631Z'
19
+ end
20
+
21
+ def test_parses_dtstart_without_tzid
22
+ builder.parse('DTSTART', nil, '20130110T183000')
23
+ assert_equal builder.component['dtstart'], '20130110T183000'
24
+ end
25
+
26
+ def test_parses_dtend
27
+ expected = ['20130110T183000', { 'tzid' => 'America/New_York' }]
28
+ builder.parse('DTEND', { 'tzid' => 'America/New_York' }, '20130110T183000')
29
+ assert_equal builder.component['dtend'], expected
30
+ end
31
+
32
+ def test_parses_dtstart_without_tzid
33
+ builder.parse('DTEND', nil, '20130110T183000')
34
+ assert_equal builder.component['dtend'], '20130110T183000'
35
+ end
36
+
37
+ def test_parses_status
38
+ builder.parse('STATUS', nil, 'CONFIRMED')
39
+ assert_equal builder.component['status'], 'CONFIRMED'
40
+ end
41
+
42
+ def test_parses_summary
43
+ builder.parse('SUMMARY', nil, 'DetroitRuby: 2012 Plan + Lightning talks')
44
+ assert_equal builder.component['summary'], 'DetroitRuby: 2012 Plan + Lightning talks'
45
+ end
46
+
47
+ def test_parses_description
48
+ builder.parse('DESCRIPTION', nil, 'DetroitRuby\nThursday\, January 10 at 6:30 PM\n\n')
49
+ assert_equal builder.component['description'], 'DetroitRuby\nThursday\, January 10 at 6:30 PM\n\n'
50
+ end
51
+
52
+ def test_parses_class
53
+ builder.parse('CLASS', nil, 'PUBLIC')
54
+ assert_equal builder.component['class'], 'PUBLIC'
55
+ end
56
+
57
+ def test_parses_created
58
+ builder.parse('CREATED', nil, '20120106T161509Z')
59
+ assert_equal builder.component['created'], '20120106T161509Z'
60
+ end
61
+
62
+ def test_parses_geo
63
+ builder.parse('GEO', nil, '42.33;-83.05')
64
+ assert_equal builder.component['geo'], ['42.33', '-83.05']
65
+ end
66
+
67
+ def test_parses_location
68
+ builder.parse('LOCATION', nil, 'Compuware Building (One Campus Martius\, Detroit\, MI 48226)')
69
+ assert_equal builder.component['location'], 'Compuware Building (One Campus Martius\, Detroit\, MI 48226)'
70
+ end
71
+
72
+ def test_parses_url
73
+ builder.parse('URL', nil, 'http://www.meetup.com/DetroitRuby/events/93346412/')
74
+ assert_equal builder.component['url'], 'http://www.meetup.com/DetroitRuby/events/93346412/'
75
+ end
76
+
77
+ def test_parses_last_modified
78
+ builder.parse('LAST-MODIFIED', nil, '20120106T161509Z')
79
+ assert_equal builder.component['last-modified'], '20120106T161509Z'
80
+ end
81
+
82
+ def test_parses_uid
83
+ builder.parse('UID', nil, 'event_qgkxkcyrcbnb@meetup.com')
84
+ assert_equal builder.component['uid'], 'event_qgkxkcyrcbnb@meetup.com'
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,40 @@
1
+ require 'test_helper'
2
+ require 'json'
3
+
4
+ module Selene
5
+ class ParserTest < TestCase
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
+ def test_parses_blank_string
12
+ assert_equal Selene::Parser.parse(""), { 'calendars' => [] }
13
+ end
14
+
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
+ def test_parses_simple_calendar
32
+ assert_equal Selene::Parser.parse("BEGIN:VCALENDAR\r\nSUMMARY:Meetups\r\nEND:VCALENDAR"), { 'calendars' => [{ 'summary' => 'Meetups' }] }
33
+ end
34
+
35
+ def test_parses_meetup_calendar
36
+ expected = JSON.parse(fixture('meetup.json'))
37
+ assert_equal Selene::Parser.parse(fixture('meetup.ics')), expected
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,14 @@
1
+ require 'test_helper'
2
+
3
+ module Selene
4
+ class TimeZoneBuilderTest < TestCase
5
+
6
+ def builder
7
+ @builder ||= TimeZoneBuilder.new
8
+ end
9
+
10
+ def test_parses_tzurl
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,34 @@
1
+ require 'bundler/setup'
2
+ require 'debugger'
3
+ require 'minitest/autorun'
4
+ require 'minitest/colorize'
5
+ require 'minitest/mock'
6
+ require 'selene'
7
+
8
+ module Selene
9
+ class TestCase < MiniTest::Unit::TestCase
10
+
11
+ def fixture(filename)
12
+ File.read(File.join(File.dirname(__FILE__), 'fixtures', filename))
13
+ end
14
+
15
+ end
16
+ end
17
+
18
+ module Stubbable
19
+
20
+ # stub :method => 'value'
21
+ # stub :method, 'value'
22
+ # stub :method { 'value' }
23
+
24
+ def stub(hash_or_method, value_or_block = nil)
25
+ if hash_or_method.is_a?(Hash)
26
+ hash_or_method.each { |method, value| stub method, Proc.new { value } }
27
+ elsif !value_or_block.is_a?(Proc)
28
+ stub hash_or_method, Proc.new { value_or_block }
29
+ else
30
+ define_singleton_method hash_or_method, value_or_block
31
+ end
32
+ end
33
+
34
+ end
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.0.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,28 +9,90 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-22 00:00:00.000000000 Z
13
- dependencies: []
14
- description: Selene is a simple iCalendar parser
12
+ date: 2013-01-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: debugger
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: minitest-colorize
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Selene is an iCalendar parser for Ruby
15
63
  email:
16
64
  - cory@corykaufman.com
17
65
  executables: []
18
66
  extensions: []
19
67
  extra_rdoc_files: []
20
68
  files:
69
+ - .gitattributes
21
70
  - .gitignore
22
71
  - .rspec
72
+ - .rvmrc
23
73
  - Gemfile
24
74
  - LICENSE.txt
25
75
  - README.md
26
76
  - Rakefile
27
77
  - lib/selene.rb
78
+ - lib/selene/alarm_builder.rb
79
+ - lib/selene/calendar_builder.rb
80
+ - lib/selene/daylight_savings_time_builder.rb
81
+ - lib/selene/event_builder.rb
82
+ - lib/selene/parser.rb
83
+ - lib/selene/standard_time_builder.rb
84
+ - lib/selene/time_zone_builder.rb
28
85
  - lib/selene/version.rb
29
86
  - selene.gemspec
30
- - spec/fixtures/mobile-monday.ics
31
- - spec/selene.rb
32
- - spec/spec_helper.rb
33
- homepage: ''
87
+ - test/fixtures/meetup.ics
88
+ - test/fixtures/meetup.json
89
+ - test/selene/calendar_builder_test.rb
90
+ - test/selene/daylight_savings_time_builder_test.rb
91
+ - test/selene/event_builder_test.rb
92
+ - test/selene/parser_test.rb
93
+ - test/selene/time_zone_builder_test.rb
94
+ - test/test_helper.rb
95
+ homepage: https://github.com/allspiritseve/selene
34
96
  licenses: []
35
97
  post_install_message:
36
98
  rdoc_options: []
@@ -42,19 +104,30 @@ required_ruby_version: !ruby/object:Gem::Requirement
42
104
  - - ! '>='
43
105
  - !ruby/object:Gem::Version
44
106
  version: '0'
107
+ segments:
108
+ - 0
109
+ hash: 3007603395114085871
45
110
  required_rubygems_version: !ruby/object:Gem::Requirement
46
111
  none: false
47
112
  requirements:
48
113
  - - ! '>='
49
114
  - !ruby/object:Gem::Version
50
115
  version: '0'
116
+ segments:
117
+ - 0
118
+ hash: 3007603395114085871
51
119
  requirements: []
52
120
  rubyforge_project:
53
121
  rubygems_version: 1.8.24
54
122
  signing_key:
55
123
  specification_version: 3
56
- summary: Selene is a simple iCalendar parser
124
+ summary: Selene is an iCalendar parser for Ruby
57
125
  test_files:
58
- - spec/fixtures/mobile-monday.ics
59
- - spec/selene.rb
60
- - spec/spec_helper.rb
126
+ - test/fixtures/meetup.ics
127
+ - test/fixtures/meetup.json
128
+ - test/selene/calendar_builder_test.rb
129
+ - test/selene/daylight_savings_time_builder_test.rb
130
+ - test/selene/event_builder_test.rb
131
+ - test/selene/parser_test.rb
132
+ - test/selene/time_zone_builder_test.rb
133
+ - test/test_helper.rb
@@ -1,76 +0,0 @@
1
- BEGIN:VCALENDAR
2
- VERSION:2.0
3
- PRODID:-//Meetup//RemoteApi//EN
4
- CALSCALE:GREGORIAN
5
- METHOD:PUBLISH
6
- X-ORIGINAL-URL:http://www.meetup.com/Mobile-Monday-Detroit/events/ical/Mo
7
- bile+Monday+Detroit/
8
- X-WR-CALNAME:Events - Mobile Monday Detroit
9
- BEGIN:VTIMEZONE
10
- TZID:America/New_York
11
- TZURL:http://tzurl.org/zoneinfo-outlook/America/New_York
12
- X-LIC-LOCATION:America/New_York
13
- BEGIN:DAYLIGHT
14
- TZOFFSETFROM:-0500
15
- TZOFFSETTO:-0400
16
- TZNAME:EDT
17
- DTSTART:19700308T020000
18
- RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
19
- END:DAYLIGHT
20
- BEGIN:STANDARD
21
- TZOFFSETFROM:-0400
22
- TZOFFSETTO:-0500
23
- TZNAME:EST
24
- DTSTART:19701101T020000
25
- RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
26
- END:STANDARD
27
- END:VTIMEZONE
28
- BEGIN:VEVENT
29
- DTSTAMP:20121222T185423Z
30
- DTSTART;TZID=America/New_York:20130114T173000
31
- DTEND;TZID=America/New_York:20130114T203000
32
- STATUS:CONFIRMED
33
- SUMMARY:MoMoDetroit - taking a break in January
34
- DESCRIPTION:Mobile Monday Detroit\nMonday\, January 14 at 5:30 PM\n\nMobi
35
- le Monday Detroit will not be meeting in January 2013. We'll be taking a
36
- month off during which time our organizing committee will be working on
37
- p...\n\nDetails: http://www.meetup.com/Mobile-Monday-Detroit/events/939
38
- 23552/
39
- CLASS:PUBLIC
40
- CREATED:20121203T215142Z
41
- GEO:42.33;-83.05
42
- URL:http://www.meetup.com/Mobile-Monday-Detroit/events/93923552/
43
- LAST-MODIFIED:20121203T215142Z
44
- UID:event_93923552@meetup.com
45
- END:VEVENT
46
- BEGIN:VEVENT
47
- DTSTAMP:20121222T185423Z
48
- DTSTART;TZID=America/New_York:20130211T173000
49
- DTEND;TZID=America/New_York:20130211T200000
50
- STATUS:CONFIRMED
51
- SUMMARY:To be announced
52
- DESCRIPTION:Mobile Monday Detroit\nMonday\, February 11 at 5:30 PM\n\nDet
53
- ails: http://www.meetup.com/Mobile-Monday-Detroit/events/93923892/
54
- CLASS:PUBLIC
55
- CREATED:20121203T215431Z
56
- GEO:42.33;-83.05
57
- URL:http://www.meetup.com/Mobile-Monday-Detroit/events/93923892/
58
- LAST-MODIFIED:20121203T215431Z
59
- UID:event_93923892@meetup.com
60
- END:VEVENT
61
- BEGIN:VEVENT
62
- DTSTAMP:20121222T185423Z
63
- DTSTART;TZID=America/New_York:20130311T173000
64
- DTEND;TZID=America/New_York:20130311T200000
65
- STATUS:CONFIRMED
66
- SUMMARY:To be announced
67
- DESCRIPTION:Mobile Monday Detroit\nMonday\, March 11 at 5:30 PM\n\nDetail
68
- s: http://www.meetup.com/Mobile-Monday-Detroit/events/93924042/
69
- CLASS:PUBLIC
70
- CREATED:20121203T215538Z
71
- GEO:42.33;-83.05
72
- URL:http://www.meetup.com/Mobile-Monday-Detroit/events/93924042/
73
- LAST-MODIFIED:20121203T215538Z
74
- UID:event_93924042@meetup.com
75
- END:VEVENT
76
- END:VCALENDAR
@@ -1,14 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
2
-
3
- describe Selene do
4
-
5
- describe "when parsing" do
6
- let(:actual) { File.read('fixtures/mobile-monday.ics') }
7
- let(:expected) { File.read('fixtures/mobile-monday.json') }
8
-
9
- it "parses calendar" do
10
- Selene.parse(actual).should == JSON.parse(expected)
11
- end
12
- end
13
-
14
- end
@@ -1,6 +0,0 @@
1
- RSpec.configure do |config|
2
- config.treat_symbols_as_metadata_keys_with_true_values = true
3
- config.run_all_when_everything_filtered = true
4
- config.filter_run :focus
5
- config.order = 'random'
6
- end