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.
- 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
|
[](https://codeclimate.com/github/Aupajo/sinatra-gcal)
|
5
5
|
[](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
|