almanack 0.0.1.alpha3 → 1.0.0.pre

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.
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