selene 0.0.1 → 0.2.0

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