almanack 0.0.1.alpha3 → 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +55 -13
  3. data/almanac.gemspec +2 -1
  4. data/bin/almanack +3 -0
  5. data/example.ru +18 -14
  6. data/lib/almanack.rb +16 -13
  7. data/lib/almanack/calendar.rb +35 -3
  8. data/lib/almanack/cli.rb +131 -0
  9. data/lib/almanack/configuration.rb +20 -4
  10. data/lib/almanack/event.rb +26 -1
  11. data/lib/almanack/event_source/ical_feed.rb +62 -0
  12. data/lib/almanack/event_source/meetup_group.rb +98 -0
  13. data/lib/almanack/event_source/static.rb +23 -0
  14. data/lib/almanack/server.rb +75 -4
  15. data/lib/almanack/themes/legacy/stylesheets/calendar.scss +168 -0
  16. data/lib/almanack/themes/legacy/views/error.erb +13 -0
  17. data/lib/almanack/themes/legacy/views/events.erb +4 -2
  18. data/lib/almanack/themes/legacy/views/layout.erb +9 -6
  19. data/lib/almanack/themes/starter/javascripts/calendar.js +5 -0
  20. data/lib/almanack/themes/starter/stylesheets/calendar.scss +27 -0
  21. data/lib/almanack/themes/starter/views/error.erb +13 -0
  22. data/lib/almanack/themes/starter/views/events.erb +31 -0
  23. data/lib/almanack/themes/starter/views/layout.erb +32 -0
  24. data/lib/almanack/version.rb +4 -1
  25. data/spec/almanack_spec.rb +8 -0
  26. data/spec/calendar_spec.rb +29 -25
  27. data/spec/configuration_spec.rb +19 -3
  28. data/spec/{ical_feed_spec.rb → event_source/ical_feed_spec.rb} +4 -4
  29. data/spec/{meetup_group_spec.rb → event_source/meetup_group_spec.rb} +4 -4
  30. data/spec/event_source/static_spec.rb +23 -0
  31. data/spec/event_spec.rb +24 -4
  32. data/spec/features/calendar_feature_spec.rb +7 -5
  33. data/spec/features/subscription_feature_spec.rb +60 -0
  34. data/spec/spec_helper.rb +2 -1
  35. data/spec/support/server_support.rb +2 -1
  36. data/spec/support/time_comparison_matchers.rb +14 -0
  37. data/templates/gitignore +2 -0
  38. data/templates/new/Gemfile +3 -0
  39. data/templates/new/config.ru.tt +24 -0
  40. metadata +45 -13
  41. data/lib/almanack/ical_feed.rb +0 -60
  42. data/lib/almanack/meetup_group.rb +0 -96
  43. data/lib/almanack/simple_event_collection.rb +0 -11
  44. data/lib/almanack/themes/legacy/views/stylesheets/legacy.css.sass +0 -119
  45. data/spec/simple_event_collection_spec.rb +0 -23
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Consolidated iCal feed", :feature do
4
+ let(:now) { Time.now }
5
+ let(:parsed_feed) { RiCal.parse_string(Almanack.calendar.ical_feed) }
6
+ let(:parsed_cal) { parsed_feed.first }
7
+ let(:parsed_events) { parsed_cal.events }
8
+
9
+ before do
10
+ Timecop.freeze(now)
11
+
12
+ Almanack.reset!
13
+
14
+ Almanack.config.add_events [
15
+ { title: "Basic", start_date: now },
16
+ { title: "Almost a year away", start_date: now + 364 * 24 * 60 * 60 },
17
+ { title: "Over a year away", start_date: now + 366 * 24 * 60 * 60 },
18
+ {
19
+ title: "Complex",
20
+ start_date: now,
21
+ end_date: now + 30 * 24 * 60 * 60,
22
+ description: "Body",
23
+ location: "CA"
24
+ }
25
+ ]
26
+ end
27
+
28
+ after { Timecop.return }
29
+
30
+ it "should contain one calendar entity" do
31
+ expect(parsed_feed.length).to eq(1)
32
+ expect(parsed_feed.first).to be_an_instance_of(RiCal::Component::Calendar)
33
+ end
34
+
35
+ it "contains a year's worth of events" do
36
+ expect(parsed_events.length).to eq(3)
37
+ expect(parsed_events.map(&:summary)).to_not include("Over a year away")
38
+ end
39
+
40
+ it "contains all available event information" do
41
+ complex_event = parsed_events.find { |e| e.summary == 'Complex' }
42
+ expect(complex_event.dtstart).to eq_time(now)
43
+ expect(complex_event.dtend).to eq_time(now + 30 * 24 * 60 * 60)
44
+ expect(complex_event.description).to eq('Body')
45
+ expect(complex_event.location).to eq('CA')
46
+ end
47
+
48
+ it "treats missing end dates as three hours from the start date" do
49
+ three_hours_from_now = now + 3 * 60 * 60
50
+ basic_event = parsed_events.find { |e| e.summary == 'Basic' }
51
+ expect(basic_event.dtend).to eq_time(three_hours_from_now)
52
+ end
53
+
54
+ it "can be accessed from the server" do
55
+ allow(Almanack.calendar).to receive(:ical_feed) { "feed" }
56
+ get "/feed.ics"
57
+ expect(last_response.body).to eq("feed")
58
+ expect(last_response.headers["Content-Type"]).to include("text/calendar")
59
+ end
60
+ end
data/spec/spec_helper.rb CHANGED
@@ -13,10 +13,11 @@ Dir[File.expand_path('../support/*.rb', __FILE__)].each { |file| require file }
13
13
  RSpec.configure do |config|
14
14
  config.include ServerSupport, :feature
15
15
  config.include EventMatchers
16
+ config.include TimeComparisonMatchers
16
17
  end
17
18
 
18
19
  VCR.configure do |config|
19
20
  config.cassette_library_dir = File.expand_path('../fixtures/responses', __FILE__)
20
21
  config.ignore_hosts 'codeclimate.com'
21
22
  config.hook_into :webmock
22
- end
23
+ end
@@ -1,4 +1,5 @@
1
1
  require 'rack/test'
2
+ require 'almanack/server'
2
3
 
3
4
  module ServerSupport
4
5
  include Rack::Test::Methods
@@ -6,4 +7,4 @@ module ServerSupport
6
7
  def app
7
8
  Almanack::Server
8
9
  end
9
- end
10
+ end
@@ -0,0 +1,14 @@
1
+ module TimeComparisonMatchers
2
+ extend RSpec::Matchers::DSL
3
+
4
+ matcher :eq_time do |expected_time|
5
+ match do |actual_time|
6
+ actual_time.to_time.to_i == expected_time.to_time.to_i
7
+ end
8
+
9
+ failure_message do |actual_time|
10
+ "expected #{actual_time.to_time} to be equal in time to #{expected_time.to_time}"
11
+ end
12
+ end
13
+
14
+ end
@@ -0,0 +1,2 @@
1
+ .DS_Store
2
+ Thumbs.db
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'almanack'
@@ -0,0 +1,24 @@
1
+ require 'almanack/server'
2
+
3
+ Almanack.config do |c|
4
+ c.title = "My Calendar"
5
+ c.theme = "<%= theme_name %>" # available: <%= available_themes.join(', ') %>
6
+
7
+ # Your group's URL name is what you'd find at www.meetup.com/Your-Group-URL-Name/
8
+ # You can get a Meetup API key from https://secure.meetup.com/meetup_api/key
9
+ # c.add_meetup_group group_urlname: 'Christchurch-Ruby-Group', key: 'mysecretkey'
10
+
11
+ # For a Google Calendar, find the "iCal" link under your Calendar's settings
12
+ # c.add_ical_feed 'https://example.com/basic.ics'
13
+
14
+ # Static events
15
+ c.add_events [
16
+ {
17
+ title: "Edit my calendar's settings",
18
+ description: "Edit the configuration at #{__FILE__}",
19
+ start_date: Time.now + 30 * 60
20
+ }
21
+ ]
22
+ end
23
+
24
+ run Almanack::Server
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: almanack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.alpha3
4
+ version: 1.0.0.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pete Nicholls
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-03 00:00:00.000000000 Z
11
+ date: 2014-07-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: thor
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: bundler
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -209,7 +223,8 @@ dependencies:
209
223
  description:
210
224
  email:
211
225
  - pete@metanation.com
212
- executables: []
226
+ executables:
227
+ - almanack
213
228
  extensions: []
214
229
  extra_rdoc_files: []
215
230
  files:
@@ -220,31 +235,45 @@ files:
220
235
  - README.md
221
236
  - Rakefile
222
237
  - almanac.gemspec
238
+ - bin/almanack
223
239
  - example.ru
224
240
  - lib/almanack.rb
225
241
  - lib/almanack/calendar.rb
242
+ - lib/almanack/cli.rb
226
243
  - lib/almanack/configuration.rb
227
244
  - lib/almanack/event.rb
228
- - lib/almanack/ical_feed.rb
229
- - lib/almanack/meetup_group.rb
245
+ - lib/almanack/event_source/ical_feed.rb
246
+ - lib/almanack/event_source/meetup_group.rb
247
+ - lib/almanack/event_source/static.rb
230
248
  - lib/almanack/server.rb
231
- - lib/almanack/simple_event_collection.rb
249
+ - lib/almanack/themes/legacy/stylesheets/calendar.scss
250
+ - lib/almanack/themes/legacy/views/error.erb
232
251
  - lib/almanack/themes/legacy/views/events.erb
233
252
  - lib/almanack/themes/legacy/views/layout.erb
234
- - lib/almanack/themes/legacy/views/stylesheets/legacy.css.sass
253
+ - lib/almanack/themes/starter/javascripts/calendar.js
254
+ - lib/almanack/themes/starter/stylesheets/calendar.scss
255
+ - lib/almanack/themes/starter/views/error.erb
256
+ - lib/almanack/themes/starter/views/events.erb
257
+ - lib/almanack/themes/starter/views/layout.erb
235
258
  - lib/almanack/version.rb
259
+ - spec/almanack_spec.rb
236
260
  - spec/calendar_spec.rb
237
261
  - spec/configuration_spec.rb
262
+ - spec/event_source/ical_feed_spec.rb
263
+ - spec/event_source/meetup_group_spec.rb
264
+ - spec/event_source/static_spec.rb
238
265
  - spec/event_spec.rb
239
266
  - spec/features/calendar_feature_spec.rb
267
+ - spec/features/subscription_feature_spec.rb
240
268
  - spec/fixtures/responses/google_calendar.yml
241
269
  - spec/fixtures/responses/meetup.yml
242
- - spec/ical_feed_spec.rb
243
- - spec/meetup_group_spec.rb
244
- - spec/simple_event_collection_spec.rb
245
270
  - spec/spec_helper.rb
246
271
  - spec/support/event_matchers.rb
247
272
  - spec/support/server_support.rb
273
+ - spec/support/time_comparison_matchers.rb
274
+ - templates/gitignore
275
+ - templates/new/Gemfile
276
+ - templates/new/config.ru.tt
248
277
  homepage: https://github.com/Aupajo/sinatra-gcal
249
278
  licenses:
250
279
  - MIT
@@ -270,15 +299,18 @@ signing_key:
270
299
  specification_version: 4
271
300
  summary: Combined events calendar for Google Calendar, iCal, Meetup.com and friends.
272
301
  test_files:
302
+ - spec/almanack_spec.rb
273
303
  - spec/calendar_spec.rb
274
304
  - spec/configuration_spec.rb
305
+ - spec/event_source/ical_feed_spec.rb
306
+ - spec/event_source/meetup_group_spec.rb
307
+ - spec/event_source/static_spec.rb
275
308
  - spec/event_spec.rb
276
309
  - spec/features/calendar_feature_spec.rb
310
+ - spec/features/subscription_feature_spec.rb
277
311
  - spec/fixtures/responses/google_calendar.yml
278
312
  - spec/fixtures/responses/meetup.yml
279
- - spec/ical_feed_spec.rb
280
- - spec/meetup_group_spec.rb
281
- - spec/simple_event_collection_spec.rb
282
313
  - spec/spec_helper.rb
283
314
  - spec/support/event_matchers.rb
284
315
  - spec/support/server_support.rb
316
+ - spec/support/time_comparison_matchers.rb
@@ -1,60 +0,0 @@
1
- require 'net/http'
2
- require 'uri'
3
- require 'ri_cal'
4
-
5
- module Almanack
6
- class IcalFeed
7
- def initialize(url)
8
- @url = url
9
- end
10
-
11
- def events_between(date_range)
12
- occurrences_between(date_range).map do |occurrence|
13
- event_from(occurrence)
14
- end
15
- end
16
-
17
- private
18
-
19
- def each_ical_event(&block)
20
- entities.each do |entity|
21
- entity.events.each(&block) if entity.respond_to?(:events)
22
- end
23
- end
24
-
25
- def occurrences_between(date_range)
26
- to_date = date_range.max
27
- from_date = date_range.min
28
-
29
- occurrences = []
30
-
31
- each_ical_event do |ical_event|
32
- ical_event.occurrences(starting: from_date, before: to_date).each do |occurrence|
33
- occurrences << occurrence
34
- end
35
- end
36
-
37
- occurrences
38
- end
39
-
40
- def event_from(occurrence)
41
- Event.new(
42
- title: occurrence.summary,
43
- start_date: occurrence.dtstart,
44
- end_date: occurrence.dtend,
45
- description: occurrence.description,
46
- location: occurrence.location
47
- )
48
- end
49
-
50
- def entities
51
- RiCal.parse_string(body)
52
- end
53
-
54
- def body
55
- uri = URI(@url)
56
- Net::HTTP.get(uri)
57
- end
58
-
59
- end
60
- end
@@ -1,96 +0,0 @@
1
- require 'net/http'
2
- require 'json'
3
- require 'addressable/uri'
4
-
5
- module Almanack
6
- class MeetupGroup
7
- def initialize(options = {})
8
- @request_options = options
9
- end
10
-
11
- def events_between(date_range)
12
- events.select do |event|
13
- event.start_date >= date_range.min && event.start_date <= date_range.max
14
- end
15
- end
16
-
17
- private
18
-
19
- def events
20
- request = MeetupAPIRequest.new(@request_options)
21
- request.results.map { |result| event_from(result) }
22
- end
23
-
24
- def event_from(result)
25
- # 3 hours, as recommended by Meetup.com if duration isn't present
26
- default_duration_in_ms = 3 * 60 * 60 * 1000
27
-
28
- event_name = [result['group']['name'], result['name']].compact.join(': ')
29
- start_time = Time.at(result['time'] / 1000)
30
- duration_in_secs = (result['duration'] || default_duration_in_ms) / 1000
31
- end_time = start_time + duration_in_secs
32
-
33
- Event.new(
34
- title: event_name,
35
- start_date: start_time.to_datetime,
36
- end_date: end_time.to_datetime,
37
- description: result['description'],
38
- location: location_from_venue(result['venue']),
39
- url: result['event_url']
40
- )
41
- end
42
-
43
- def location_from_venue(venue)
44
- %w{ name address_1 address_2 address_3 city state country }.map do |attr|
45
- venue[attr]
46
- end.compact.join(', ')
47
- end
48
- end
49
-
50
- class MeetupAPIError < StandardError
51
- end
52
-
53
- class MeetupAPIException < Exception
54
- end
55
-
56
- class MeetupAPIRequest
57
- REQUIRED_OPTIONS = [:group_domain, :group_urlname, :group_id]
58
-
59
- attr_accessor :options
60
-
61
- def initialize(options = {})
62
- @options = options
63
- end
64
-
65
- def results
66
- response['results']
67
- end
68
-
69
- def uri
70
- if !options.has_key?(:key)
71
- raise MeetupAPIException, 'Cannot form valid URI, missing :key option'
72
- end
73
-
74
- if (options.keys & REQUIRED_OPTIONS).empty?
75
- raise MeetupAPIException, "Cannot form valid URI, missing one of: #{REQUIRED_OPTIONS}"
76
- end
77
-
78
- endpoint = "https://api.meetup.com/2/events"
79
-
80
- Addressable::URI.parse(endpoint).tap do |uri|
81
- uri.query_values = options
82
- end
83
- end
84
-
85
- def response
86
- json = Net::HTTP.get(uri)
87
- data = JSON.parse(json)
88
-
89
- if data['problem']
90
- raise MeetupAPIError, data['problem']
91
- end
92
-
93
- data
94
- end
95
- end
96
- end
@@ -1,11 +0,0 @@
1
- module Almanack
2
- class SimpleEventCollection
3
- def initialize(events)
4
- @events = events
5
- end
6
-
7
- def events_between(date_range)
8
- @events.map { |attrs| Event.new(attrs) }
9
- end
10
- end
11
- end
@@ -1,119 +0,0 @@
1
- =border-radius($size)
2
- border-radius: $size
3
- -webkit-border-radius: $size
4
- -moz-border-radius: $size
5
-
6
- =box-shadow($x, $y, $size, $colour)
7
- -webkit-box-shadow: $x $y $size $colour
8
- -moz-box-shadow: $x $y $size $colour
9
-
10
- body
11
- background-color: #e3e3e3
12
- color: #595959
13
- font: 12pt / 1.6em "Helvetica Neue", Helvetica, sans-serif
14
- margin: 2em auto
15
- width: 600px
16
-
17
- a
18
- text-decoration: none
19
- &:link, &:visited
20
- color: #2960AA
21
-
22
- em
23
- font-style: italic
24
-
25
- strong
26
- font-weight: bold
27
-
28
- .event
29
- background-color: #efefef
30
- background: -webkit-gradient(linear, left top, left bottom, from(#f9f9f9), to(#efefef))
31
- background: -moz-linear-gradient(top, #f9f9f9, #efefef)
32
- border-top: 1px solid white
33
- +border-radius(4px)
34
- overflow: hidden
35
- padding: 20px
36
- margin-bottom: 1em
37
- +box-shadow(0, 1px, 3px, rgba(0, 0, 0, 0.3))
38
- &.today
39
- background-color: #ebe4de
40
- background: -webkit-gradient(linear, left top, left bottom, from(#f3ebe3), to(#e9d6c1))
41
- .details
42
- float: right
43
- width: 480px
44
- dl
45
- color: #777777
46
- font-size: 0.9em
47
- dt
48
- font-weight: bold
49
- float: left
50
- dd
51
- float: right
52
- margin: 0
53
- width: 420px
54
-
55
- header, footer
56
- // display: block for browsers with poor HTML5 support
57
- display: block
58
-
59
- header
60
- border-bottom: 1px solid #cccccc
61
- overflow: hidden
62
- padding-bottom: 0.5em
63
- margin-bottom: 1em
64
- +box-shadow(0, 1px, 0, #f6f6f6)
65
-
66
- footer
67
- border-top: 1px solid #cccccc
68
- color: #999999
69
- font-size: 0.9em
70
- padding: 1em
71
- .subscribe
72
- float: left
73
- a
74
- margin-right: 1em
75
- .add
76
- float: right
77
- .fork
78
- clear: both
79
- font-size: 0.8em
80
-
81
- a:link, a:visited
82
- color: #8aa0b6
83
-
84
- h1, h2, h3, h4
85
- margin: 0
86
- text-shadow: 0 1px 0 white
87
-
88
- h1
89
- color: #666666
90
- font-size: 1.8em
91
- float: left
92
-
93
- h2
94
- color: #999999
95
- font-size: 1em
96
- float: right
97
- text-transform: uppercase
98
-
99
- h3
100
- color: #333333
101
-
102
- h4
103
- display: inline
104
- font-size: 1em
105
- margin-right: 1em
106
-
107
- .date
108
- background-color: #cc0000
109
- +border-radius(4px)
110
- color: white
111
- float: left
112
- text-align: center
113
- padding: 2px
114
- width: 60px
115
- .day
116
- background-color: white
117
- color: #333333
118
- font-size: 1.6em
119
- padding: 0.2em