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.
- checksums.yaml +4 -4
- data/README.md +55 -13
- data/almanac.gemspec +2 -1
- data/bin/almanack +3 -0
- data/example.ru +18 -14
- data/lib/almanack.rb +16 -13
- data/lib/almanack/calendar.rb +35 -3
- data/lib/almanack/cli.rb +131 -0
- data/lib/almanack/configuration.rb +20 -4
- data/lib/almanack/event.rb +26 -1
- data/lib/almanack/event_source/ical_feed.rb +62 -0
- data/lib/almanack/event_source/meetup_group.rb +98 -0
- data/lib/almanack/event_source/static.rb +23 -0
- data/lib/almanack/server.rb +75 -4
- data/lib/almanack/themes/legacy/stylesheets/calendar.scss +168 -0
- data/lib/almanack/themes/legacy/views/error.erb +13 -0
- data/lib/almanack/themes/legacy/views/events.erb +4 -2
- data/lib/almanack/themes/legacy/views/layout.erb +9 -6
- data/lib/almanack/themes/starter/javascripts/calendar.js +5 -0
- data/lib/almanack/themes/starter/stylesheets/calendar.scss +27 -0
- data/lib/almanack/themes/starter/views/error.erb +13 -0
- data/lib/almanack/themes/starter/views/events.erb +31 -0
- data/lib/almanack/themes/starter/views/layout.erb +32 -0
- data/lib/almanack/version.rb +4 -1
- data/spec/almanack_spec.rb +8 -0
- data/spec/calendar_spec.rb +29 -25
- data/spec/configuration_spec.rb +19 -3
- data/spec/{ical_feed_spec.rb → event_source/ical_feed_spec.rb} +4 -4
- data/spec/{meetup_group_spec.rb → event_source/meetup_group_spec.rb} +4 -4
- data/spec/event_source/static_spec.rb +23 -0
- data/spec/event_spec.rb +24 -4
- data/spec/features/calendar_feature_spec.rb +7 -5
- data/spec/features/subscription_feature_spec.rb +60 -0
- data/spec/spec_helper.rb +2 -1
- data/spec/support/server_support.rb +2 -1
- data/spec/support/time_comparison_matchers.rb +14 -0
- data/templates/gitignore +2 -0
- data/templates/new/Gemfile +3 -0
- data/templates/new/config.ru.tt +24 -0
- metadata +45 -13
- data/lib/almanack/ical_feed.rb +0 -60
- data/lib/almanack/meetup_group.rb +0 -96
- data/lib/almanack/simple_event_collection.rb +0 -11
- data/lib/almanack/themes/legacy/views/stylesheets/legacy.css.sass +0 -119
- data/spec/simple_event_collection_spec.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a36a4f4c0b5adffd13cfac0f50c223065fed1285
|
4
|
+
data.tar.gz: 3c5788c29f66ce658e7def8103707a4897660523
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8673f497e3c53d7adf7f6b5a668ddfe38a29e2ec949e235489b9f1302cd93573c1ec7c12bf19993528fbb0c5d93a4991f5d431408f8372b6258caa706e70e2e
|
7
|
+
data.tar.gz: 0a3d77922eef10c8fe8f36591e13a3d6f982d353ed7bdc5b4ea72e073e56190ff249920b687fbbb6dcc125cbd0109d6e359d01ebcfa4832292073dee196ce51a
|
data/README.md
CHANGED
@@ -4,34 +4,76 @@
|
|
4
4
|
[![Code Climate](https://codeclimate.com/github/Aupajo/sinatra-gcal.png)](https://codeclimate.com/github/Aupajo/sinatra-gcal)
|
5
5
|
[![Code Climate](https://codeclimate.com/github/Aupajo/sinatra-gcal/coverage.png)](https://codeclimate.com/github/Aupajo/sinatra-gcal)
|
6
6
|
|
7
|
-
|
7
|
+
A calendar that combines events from different sources (such as Google Calendar, Meetup.com, and iCal feeds), and can be hosted for free on [Heroku](http://heroku.com).
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
* Aggregate multiple calendars together into one stream
|
12
|
+
* Supports iCal feeds (incuding Google Calendars)
|
13
|
+
* Supports Meetup.com groups
|
14
|
+
* Just supply a hash to create any arbitrary event
|
15
|
+
* Supports being freely hosted on Heroku
|
16
|
+
* 100% customisable themes with Sass and CoffeeScript support
|
17
|
+
* Server optional (you can use the underlying calendar library by itself)
|
18
|
+
* Rack-compatible (mount inside any Rails application)
|
8
19
|
|
9
20
|
## Installation
|
10
21
|
|
11
|
-
|
22
|
+
Run the following command:
|
23
|
+
|
24
|
+
gem install almanack --pre
|
25
|
+
|
26
|
+
## Creating a calendar
|
27
|
+
|
28
|
+
Generate a new calendar with:
|
29
|
+
|
30
|
+
almanack new my-calendar
|
31
|
+
|
32
|
+
This will create a directory called `my-calendar` and set up your new project.
|
33
|
+
|
34
|
+
Once set up, run:
|
35
|
+
|
36
|
+
cd my-calendar
|
37
|
+
almanack start
|
38
|
+
|
39
|
+
By default, your calendar will run on http://localhost:9292.
|
12
40
|
|
13
|
-
|
41
|
+
## Configuration
|
14
42
|
|
15
|
-
|
43
|
+
See examples inside `config.ru` for iCal feeds, Meetup.com, or static events.
|
16
44
|
|
17
|
-
|
45
|
+
```ruby
|
46
|
+
Almanack.config do |c|
|
47
|
+
c.title = 'My Calendar'
|
48
|
+
c.theme = 'my-custom-theme'
|
49
|
+
c.add_ical_feed 'http://example.org/events.ics'
|
50
|
+
c.add_ical_feed 'http://example.org/more-events.ics'
|
51
|
+
c.add_meetup_group group_urlname: 'Christchurch-Ruby-Group', key: 'mysecretkey'
|
52
|
+
end
|
53
|
+
```
|
18
54
|
|
19
|
-
|
55
|
+
**Note:** You'll need your [Meetup.com API key](https://secure.meetup.com/meetup_api/key) to use Meetup.
|
20
56
|
|
21
|
-
|
57
|
+
## Custom themes
|
22
58
|
|
23
|
-
|
59
|
+
Inside your project, you can generate a new theme with:
|
24
60
|
|
25
|
-
|
61
|
+
almanack theme my-theme-name
|
26
62
|
|
27
|
-
|
63
|
+
Remember to update your `config.ru` to switch themes:
|
28
64
|
|
29
|
-
|
65
|
+
```ruby
|
66
|
+
Almanack.config do |c|
|
67
|
+
...
|
68
|
+
c.theme = 'my-theme-name'
|
69
|
+
...
|
70
|
+
end
|
71
|
+
```
|
30
72
|
|
31
73
|
## Contributing
|
32
74
|
|
33
|
-
1. Fork it ( http://github.com/Aupajo/
|
75
|
+
1. Fork it ( http://github.com/Aupajo/almanack/fork )
|
34
76
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
35
77
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
36
78
|
4. Push to the branch (`git push origin my-new-feature`)
|
37
|
-
5. Create new Pull Request
|
79
|
+
5. Create new Pull Request
|
data/almanac.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Pete Nicholls"]
|
10
10
|
spec.email = ["pete@metanation.com"]
|
11
11
|
spec.summary = %q{Combined events calendar for Google Calendar, iCal, Meetup.com and friends.}
|
12
|
-
spec.homepage =
|
12
|
+
spec.homepage = Almanack::HOMEPAGE
|
13
13
|
spec.license = "MIT"
|
14
14
|
|
15
15
|
spec.files = `git ls-files -z`.split("\x0")
|
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_dependency "sass"
|
23
23
|
spec.add_dependency "ri_cal"
|
24
24
|
spec.add_dependency "addressable"
|
25
|
+
spec.add_dependency "thor"
|
25
26
|
|
26
27
|
spec.add_development_dependency "bundler", "~> 1.5"
|
27
28
|
spec.add_development_dependency "rake"
|
data/bin/almanack
ADDED
data/example.ru
CHANGED
@@ -1,20 +1,24 @@
|
|
1
1
|
require "bundler/setup"
|
2
|
-
require "
|
3
|
-
require "almanack"
|
2
|
+
require "almanack/server"
|
4
3
|
|
5
|
-
|
4
|
+
now = Time.now
|
6
5
|
|
7
|
-
Almanack.config
|
6
|
+
Almanack.config do |calendar|
|
7
|
+
calendar.title = 'Discworld Holidays'
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
9
|
+
calendar.theme = 'starter'
|
10
|
+
|
11
|
+
calendar.add_events [
|
12
|
+
{
|
13
|
+
title: "Hogswatch",
|
14
|
+
start_date: now + Almanack::Calendar::ONE_DAY,
|
15
|
+
end_date: now + Almanack::Calendar::ONE_DAY * 2,
|
16
|
+
description: 'The sausages have been strung, the wreaths of oakleaves hung and the stockings dangled. The pork pie, the sherry and the all-important turnip await their festive guests. The poker lent against the fireplace may or may not have been bent over the head of some nightmare creature.',
|
17
|
+
location: 'Castle of Bones'
|
18
|
+
},
|
19
|
+
{ title: "Soul Cake Tuesday", start_date: now + 10 * Almanack::Calendar::ONE_DAY },
|
20
|
+
{ title: "Eve of Small Gods", start_date: now + 30 * Almanack::Calendar::ONE_DAY },
|
21
|
+
]
|
22
|
+
end
|
19
23
|
|
20
24
|
run Almanack::Server
|
data/lib/almanack.rb
CHANGED
@@ -1,23 +1,26 @@
|
|
1
1
|
require "pathname"
|
2
2
|
require "almanack/version"
|
3
|
-
require "almanack/server"
|
4
3
|
require "almanack/configuration"
|
5
4
|
require "almanack/calendar"
|
6
|
-
require "almanack/simple_event_collection"
|
7
|
-
require "almanack/meetup_group"
|
8
|
-
require "almanack/ical_feed"
|
9
5
|
require "almanack/event"
|
6
|
+
require "almanack/event_source/static"
|
7
|
+
require "almanack/event_source/meetup_group"
|
8
|
+
require "almanack/event_source/ical_feed"
|
10
9
|
|
11
10
|
module Almanack
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
class << self
|
12
|
+
def config(&block)
|
13
|
+
@config ||= Configuration.new
|
14
|
+
yield @config if block_given?
|
15
|
+
@config
|
16
|
+
end
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
18
|
+
def calendar
|
19
|
+
@calendar ||= Calendar.new(config)
|
20
|
+
end
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
+
def reset!
|
23
|
+
config.reset!
|
24
|
+
end
|
22
25
|
end
|
23
|
-
end
|
26
|
+
end
|
data/lib/almanack/calendar.rb
CHANGED
@@ -2,6 +2,11 @@ require 'forwardable'
|
|
2
2
|
|
3
3
|
module Almanack
|
4
4
|
class Calendar
|
5
|
+
ONE_HOUR = 60 * 60
|
6
|
+
ONE_DAY = 24 * ONE_HOUR
|
7
|
+
ONE_MONTH = 30 * ONE_DAY
|
8
|
+
ONE_YEAR = 365 * ONE_DAY
|
9
|
+
|
5
10
|
extend Forwardable
|
6
11
|
def_delegators :@config, :event_sources, :title
|
7
12
|
|
@@ -10,14 +15,41 @@ module Almanack
|
|
10
15
|
end
|
11
16
|
|
12
17
|
def events
|
13
|
-
|
14
|
-
|
18
|
+
now = Time.now
|
19
|
+
future = now + days_lookahead * ONE_DAY
|
20
|
+
events_between(now..future)
|
21
|
+
end
|
15
22
|
|
23
|
+
def events_between(date_range)
|
16
24
|
event_sources.map do |event_source|
|
17
|
-
event_source.events_between(
|
25
|
+
event_source.events_between(date_range)
|
18
26
|
end.flatten.sort_by(&:start_date)
|
19
27
|
end
|
20
28
|
|
29
|
+
def ical_feed
|
30
|
+
now = Time.now
|
31
|
+
future = now + ONE_YEAR
|
32
|
+
|
33
|
+
# Three hours is the duration for events missing end dates, a
|
34
|
+
# recommendation suggested by Meetup.com.
|
35
|
+
three_hours = 3 * ONE_HOUR
|
36
|
+
|
37
|
+
ical = RiCal.Calendar
|
38
|
+
|
39
|
+
events_between(now..future).each do |event|
|
40
|
+
ical_event = RiCal.Event
|
41
|
+
ical_event.summary = event.title
|
42
|
+
ical_event.dtstart = event.start_date.utc
|
43
|
+
ical_event.dtend = (event.end_date || event.start_date + three_hours).utc
|
44
|
+
ical_event.description = event.description if event.description
|
45
|
+
ical_event.location = event.location if event.location
|
46
|
+
|
47
|
+
ical.add_subcomponent(ical_event)
|
48
|
+
end
|
49
|
+
|
50
|
+
ical.to_s
|
51
|
+
end
|
52
|
+
|
21
53
|
def days_lookahead
|
22
54
|
30
|
23
55
|
end
|
data/lib/almanack/cli.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "pathname"
|
3
|
+
|
4
|
+
module Almanack
|
5
|
+
class CLI < Thor
|
6
|
+
include Thor::Actions
|
7
|
+
|
8
|
+
SKIP_THEMES = %w( starter )
|
9
|
+
|
10
|
+
map "--version" => :version
|
11
|
+
|
12
|
+
def self.available_themes
|
13
|
+
dirs = Pathname(__dir__).join("themes").children.select(&:directory?)
|
14
|
+
|
15
|
+
dirs.map do |path|
|
16
|
+
path.basename.to_s unless SKIP_THEMES.include?(path.basename.to_s)
|
17
|
+
end.compact
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "version", "Show Almanack version"
|
21
|
+
def version
|
22
|
+
say "Almanack version #{VERSION} (#{CODENAME})"
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "start", "Start an Almanack server"
|
26
|
+
option :config, aliases: "-c", desc: "Path to config file"
|
27
|
+
def start
|
28
|
+
exec "bundle exec rackup #{options[:config]}"
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "new PATH", "Create a new Almanack project"
|
32
|
+
option :theme, default: 'legacy', desc: "Which theme to use (available: #{available_themes.join(', ')})"
|
33
|
+
option :git, type: :boolean, default: true, desc: "Whether to initialize an empty git repo"
|
34
|
+
def new(path)
|
35
|
+
path = Pathname(path).cleanpath
|
36
|
+
|
37
|
+
directory "templates/new", path
|
38
|
+
|
39
|
+
if options[:git]
|
40
|
+
template('templates/gitignore', path.join(".gitignore"))
|
41
|
+
end
|
42
|
+
|
43
|
+
inside path do
|
44
|
+
say_status :installing, "bundler dependencies"
|
45
|
+
system "bundle install --quiet"
|
46
|
+
|
47
|
+
if options[:git]
|
48
|
+
say_status :git, "initializing repository"
|
49
|
+
git "init"
|
50
|
+
end
|
51
|
+
|
52
|
+
say
|
53
|
+
say "==> Run your new calendar!"
|
54
|
+
say
|
55
|
+
say " cd #{path}"
|
56
|
+
say " almnack start"
|
57
|
+
say
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
desc "theme NAME", "Create a new theme"
|
62
|
+
def theme(name)
|
63
|
+
directory "lib/almanack/themes/starter", "themes/#{name}"
|
64
|
+
|
65
|
+
config_file = Pathname("config.ru")
|
66
|
+
|
67
|
+
if config_file.exist?
|
68
|
+
say_status :update, "config.ru"
|
69
|
+
set_theme_pattern = /\.theme\s*=\s*['"].*?['"]/
|
70
|
+
replacement = config_file.read.gsub(set_theme_pattern, ".theme = '#{name}'")
|
71
|
+
config_file.open('w') { |file| file.puts replacement }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
desc "deploy [NAME]", "Deploy your site to Heroku (requires Heroku toolbelt)"
|
76
|
+
def deploy(name = nil)
|
77
|
+
remotes = `git remote -v`
|
78
|
+
|
79
|
+
heroku_remote = remotes.lines.find do |remote|
|
80
|
+
remote.split(' ').first == "heroku"
|
81
|
+
end
|
82
|
+
|
83
|
+
if !heroku_remote
|
84
|
+
say "No Heroku remote detected."
|
85
|
+
create_heroku_app(name)
|
86
|
+
end
|
87
|
+
|
88
|
+
current_branch = git("rev-parse --abbrev-ref HEAD")
|
89
|
+
|
90
|
+
say "Deploying #{current_branch}..."
|
91
|
+
run "git push heroku #{current_branch}:master --force"
|
92
|
+
end
|
93
|
+
|
94
|
+
no_tasks do
|
95
|
+
def create_heroku_app(name)
|
96
|
+
heroku_command = `which heroku`.strip
|
97
|
+
|
98
|
+
if heroku_command.empty?
|
99
|
+
say "Heroku Toolbelt not detected. Please install from:"
|
100
|
+
say " https://toolbelt.heroku.com"
|
101
|
+
abort
|
102
|
+
end
|
103
|
+
|
104
|
+
say_status :heroku, "creating app..."
|
105
|
+
run "#{heroku_command} create #{name}"
|
106
|
+
end
|
107
|
+
|
108
|
+
def theme_name
|
109
|
+
options[:theme]
|
110
|
+
end
|
111
|
+
|
112
|
+
def git(command)
|
113
|
+
output = `git #{command}`
|
114
|
+
abort "Git failed: #{output}" if $?.exitstatus != 0
|
115
|
+
output.strip
|
116
|
+
end
|
117
|
+
|
118
|
+
def available_themes
|
119
|
+
self.class.available_themes
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.exit_on_failure?
|
124
|
+
true
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.source_root
|
128
|
+
Pathname(__dir__).parent.parent
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -4,27 +4,43 @@ require 'ri_cal'
|
|
4
4
|
|
5
5
|
module Almanack
|
6
6
|
class Configuration
|
7
|
+
class ThemeNotFound < StandardError; end
|
8
|
+
|
9
|
+
DEFAULT_THEME = "legacy"
|
10
|
+
|
7
11
|
attr_reader :event_sources
|
8
|
-
attr_accessor :title
|
12
|
+
attr_accessor :title, :theme, :theme_paths, :theme_root
|
9
13
|
|
10
14
|
def initialize
|
11
15
|
reset!
|
12
16
|
end
|
13
17
|
|
14
18
|
def reset!
|
19
|
+
@theme = DEFAULT_THEME
|
15
20
|
@event_sources = []
|
21
|
+
|
22
|
+
@theme_paths = [
|
23
|
+
Pathname.pwd.join('themes'),
|
24
|
+
Pathname(__dir__).join('themes')
|
25
|
+
]
|
26
|
+
end
|
27
|
+
|
28
|
+
def theme_root
|
29
|
+
paths = theme_paths.map { |path| path.join(theme) }
|
30
|
+
root = paths.find { |path| path.exist? }
|
31
|
+
root || raise(ThemeNotFound, "Could not find theme #{theme} in #{paths}")
|
16
32
|
end
|
17
33
|
|
18
34
|
def add_ical_feed(url)
|
19
|
-
@event_sources << IcalFeed.new(url)
|
35
|
+
@event_sources << EventSource::IcalFeed.new(url)
|
20
36
|
end
|
21
37
|
|
22
38
|
def add_events(events)
|
23
|
-
@event_sources <<
|
39
|
+
@event_sources << EventSource::Static.new(events)
|
24
40
|
end
|
25
41
|
|
26
42
|
def add_meetup_group(options)
|
27
|
-
@event_sources << MeetupGroup.new(options)
|
43
|
+
@event_sources << EventSource::MeetupGroup.new(options)
|
28
44
|
end
|
29
45
|
end
|
30
46
|
end
|
data/lib/almanack/event.rb
CHANGED
@@ -2,5 +2,30 @@ require 'ostruct'
|
|
2
2
|
|
3
3
|
module Almanack
|
4
4
|
class Event < OpenStruct
|
5
|
+
def formatted_date
|
6
|
+
formatted = "#{formatted_day(start_date)} at #{formatted_time(start_date)}"
|
7
|
+
|
8
|
+
if end_date
|
9
|
+
formatted << " to "
|
10
|
+
formatted << "#{formatted_day(end_date)} at " unless ends_on_same_day?
|
11
|
+
formatted << formatted_time(end_date)
|
12
|
+
end
|
13
|
+
|
14
|
+
formatted
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def ends_on_same_day?
|
20
|
+
[start_date.year, start_date.yday] == [end_date.year, end_date.yday]
|
21
|
+
end
|
22
|
+
|
23
|
+
def formatted_time(time)
|
24
|
+
time.strftime('%-l:%M%P')
|
25
|
+
end
|
26
|
+
|
27
|
+
def formatted_day(time)
|
28
|
+
time.strftime('%B %-d %Y')
|
29
|
+
end
|
5
30
|
end
|
6
|
-
end
|
31
|
+
end
|