skypager 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/ARCHITECTURE.md +34 -0
- data/CONTRIBUTING.md +7 -0
- data/README.md +94 -36
- data/Rakefile +1 -1
- data/lib/skypager.rb +6 -0
- data/lib/skypager/builder.rb +158 -0
- data/lib/skypager/builder/server.rb +63 -0
- data/lib/skypager/builder/webhook_handler.rb +73 -0
- data/lib/skypager/cli/commands/build.rb +48 -0
- data/lib/skypager/cli/commands/config.rb +1 -1
- data/lib/skypager/cli/commands/setup.rb +13 -0
- data/lib/skypager/cli/commands/site.rb +22 -0
- data/lib/skypager/cli/commands/sync.rb +1 -1
- data/lib/skypager/configuration.rb +32 -7
- data/lib/skypager/core_ext.rb +6 -0
- data/lib/skypager/data/google_spreadsheet.rb +3 -3
- data/lib/skypager/data/source.rb +24 -1
- data/lib/skypager/extension.rb +84 -21
- data/lib/skypager/proxy.rb +0 -1
- data/lib/skypager/router.rb +15 -0
- data/lib/skypager/site.rb +104 -21
- data/lib/skypager/sync/dropbox/delta.rb +1 -1
- data/lib/skypager/sync/folder.rb +7 -1
- data/lib/skypager/sync/github.rb +55 -0
- data/lib/skypager/version.rb +1 -1
- data/skypager.gemspec +21 -13
- data/spec/dummy/site-one/.gitignore +18 -0
- data/spec/dummy/site-one/Gemfile +14 -0
- data/spec/dummy/site-one/config.rb +13 -0
- data/spec/dummy/site-one/source/images/background.png +0 -0
- data/spec/dummy/site-one/source/images/middleman.png +0 -0
- data/spec/dummy/site-one/source/index.html.erb +10 -0
- data/spec/dummy/site-one/source/javascripts/all.js +1 -0
- data/spec/dummy/site-one/source/layouts/layout.erb +19 -0
- data/spec/dummy/site-one/source/stylesheets/all.css +55 -0
- data/spec/dummy/site-one/source/stylesheets/normalize.css +375 -0
- data/spec/lib/skypager/builder/server_spec.rb +5 -0
- data/spec/lib/skypager/configuration_spec.rb +7 -0
- data/spec/lib/skypager/site_spec.rb +40 -1
- data/spec/lib/skypager_spec.rb +9 -0
- data/spec/skypager-test-config.rb.example +22 -0
- data/spec/spec_helper.rb +38 -3
- data/spec/support/fixtures/cwd_config.json +3 -0
- data/spec/support/fixtures/home_config.json +1 -0
- metadata +112 -33
- data/lib/skypager/build_server.rb +0 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
module Skypager::Builder::WebhookHandler
|
2
|
+
def self.handle(service_identifier=:custom, request)
|
3
|
+
unless respond_to?("handle_#{service_identifier}")
|
4
|
+
return [404, {}, [""]]
|
5
|
+
end
|
6
|
+
|
7
|
+
puts "== Handling #{ service_identifier }: #{ request.path } #{ request.params }"
|
8
|
+
send("handle_#{service_identifier}", request)
|
9
|
+
end
|
10
|
+
|
11
|
+
# The Dropbox API requires us to support
|
12
|
+
# two different types of Webhook requests.
|
13
|
+
#
|
14
|
+
# One will include the UID of the users who have changes.
|
15
|
+
# We will need to find all of the skypager sites which are
|
16
|
+
# registered against this UID in order to find the credentials
|
17
|
+
# needed to make a delta request to the API.
|
18
|
+
#
|
19
|
+
# We will then use the paths we find in the delta, to find
|
20
|
+
# the specific site that we need to mark for building
|
21
|
+
def self.handle_dropbox(request)
|
22
|
+
puts "== Dropbox handler initiated"
|
23
|
+
body = request.body.read
|
24
|
+
|
25
|
+
puts "== Request body: #{ body }"
|
26
|
+
puts "== Params: #{ request.params.inspect }"
|
27
|
+
|
28
|
+
begin
|
29
|
+
if request.params['challenge']
|
30
|
+
puts "== challenge request"
|
31
|
+
response = "#{request.params['challenge']}"
|
32
|
+
elsif body.length > 0
|
33
|
+
data = JSON.parse(body) rescue nil
|
34
|
+
data = data.to_mash if data.respond_to?(:to_mash)
|
35
|
+
|
36
|
+
Array(data.delta.users).each do |uid|
|
37
|
+
puts "== Attempting to find a site for #{ uid }"
|
38
|
+
sites = Skypager::Site.find_sites_for_dropbox_uid(uid)
|
39
|
+
|
40
|
+
if sites.length > 0
|
41
|
+
sites.each do |site|
|
42
|
+
puts "== Marking site for build: #{ site.name }"
|
43
|
+
site.requires_build!
|
44
|
+
end
|
45
|
+
else
|
46
|
+
puts "== Could not find site for uid: #{ uid }"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
response = data.delta.users.to_json
|
51
|
+
end
|
52
|
+
rescue => e
|
53
|
+
response = "== Error: #{ e.message }"
|
54
|
+
puts response
|
55
|
+
end
|
56
|
+
|
57
|
+
[200, {}, [response]]
|
58
|
+
end
|
59
|
+
|
60
|
+
# TODO
|
61
|
+
# Investigate the makeup of google drive webhook request
|
62
|
+
def self.handle_google(request)
|
63
|
+
end
|
64
|
+
|
65
|
+
# TODO
|
66
|
+
# Presumably we will get commit notifications from Github,
|
67
|
+
# and will find the site that matches the notification
|
68
|
+
def self.handle_github(request)
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.handle_custom(request)
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
if defined?(Slim::Engine)
|
2
|
+
Slim::Engine.disable_option_validator!
|
3
|
+
end
|
4
|
+
|
5
|
+
command 'build' do |c|
|
6
|
+
c.syntax = "skypager build"
|
7
|
+
c.description = "performs a build on the site"
|
8
|
+
|
9
|
+
c.option "--force", nil, 'Whether or not we should force a build'
|
10
|
+
|
11
|
+
c.action do |args, options|
|
12
|
+
require 'rack/test'
|
13
|
+
require 'skypager/builder'
|
14
|
+
|
15
|
+
app = Skypager.app
|
16
|
+
builder = Skypager::Builder.new(app, clean: true, verbose: true)
|
17
|
+
|
18
|
+
if app.site.requires_build? || options.force.present?
|
19
|
+
puts "== Building site: #{ app.site.name }"
|
20
|
+
builder.build(options.force.present?)
|
21
|
+
else
|
22
|
+
puts "== Site is not marked to be built. Skipping unless the --force flag is passed"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
command 'run build server' do |c|
|
28
|
+
c.syntax = 'skypager run build server [OPTIONS]'
|
29
|
+
c.description = 'run the skypager webhook handler'
|
30
|
+
|
31
|
+
c.option '--port', Fixnum, 'What port should this run on?'
|
32
|
+
c.option '--host', String, 'What host should this run on?'
|
33
|
+
|
34
|
+
c.action do |args, options|
|
35
|
+
require 'rack'
|
36
|
+
require 'skypager/builder/server'
|
37
|
+
|
38
|
+
opts = {
|
39
|
+
Host: options.host || '127.0.0.1',
|
40
|
+
Port: options.port || 3000
|
41
|
+
}
|
42
|
+
|
43
|
+
Rack::Handler::WEBrick.run(Skypager::Builder::Server.new, opts) do |server|
|
44
|
+
[:INT, :TERM].each { |sig| trap(sig) { server.stop } }
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -26,7 +26,7 @@ end
|
|
26
26
|
|
27
27
|
command 'config get' do |c|
|
28
28
|
c.syntax = 'skypager config:get [options]'
|
29
|
-
c.description = '
|
29
|
+
c.description = 'view a skypager configuration setting'
|
30
30
|
|
31
31
|
c.action do |args, _options|
|
32
32
|
Skypager::Configuration.initialize!
|
@@ -122,3 +122,16 @@ command 'setup google' do |c|
|
|
122
122
|
client_secret: options.client_secret)
|
123
123
|
end
|
124
124
|
end
|
125
|
+
|
126
|
+
command 'setup github' do |c|
|
127
|
+
c.syntax = 'skypager setup github [options]'
|
128
|
+
c.description = 'Setup github syncing'
|
129
|
+
|
130
|
+
c.option '--github-access-token STRING', String, 'What is the Github access token?'
|
131
|
+
|
132
|
+
c.action do |args, options|
|
133
|
+
Skypager::Sync::Github.setup(github_access_token: options.github_access_token)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
command 'promote site' do |c|
|
2
|
+
c.syntax = 'skypager promote site'
|
3
|
+
c.description = 'Promotes this current folder to be the main folder for this site in the build server'
|
4
|
+
|
5
|
+
c.action do
|
6
|
+
app = Skypager.app
|
7
|
+
site = app.site
|
8
|
+
site.set(:root, app.root_path)
|
9
|
+
app.save_site_config
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
command 'list build queue' do |c|
|
14
|
+
c.syntax = "skypager list build queue"
|
15
|
+
c.description = "lists the sites in the build queue"
|
16
|
+
|
17
|
+
c.action do
|
18
|
+
Skypager::Site.requiring_build.each do |site|
|
19
|
+
puts "== site #{ site.name } at #{ site.root } requires a build"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -26,6 +26,9 @@ module Skypager
|
|
26
26
|
google_refresh_token: '',
|
27
27
|
google_access_token: '',
|
28
28
|
|
29
|
+
github_access_token: '',
|
30
|
+
github_repository: '',
|
31
|
+
|
29
32
|
domain: "skypager.io",
|
30
33
|
|
31
34
|
sites_directory: { }
|
@@ -47,8 +50,14 @@ module Skypager
|
|
47
50
|
super
|
48
51
|
end
|
49
52
|
|
50
|
-
def initialize!
|
53
|
+
def initialize!(fresh=false)
|
54
|
+
return if home_config_path.exist? && !fresh
|
55
|
+
|
51
56
|
FileUtils.mkdir_p home_config_path.dirname
|
57
|
+
|
58
|
+
home_config_path.open("w+") do |fh|
|
59
|
+
fh.write(DefaultSettings.to_json)
|
60
|
+
end
|
52
61
|
end
|
53
62
|
|
54
63
|
def dnsimple_setup?
|
@@ -98,13 +107,21 @@ module Skypager
|
|
98
107
|
save! if persist == true
|
99
108
|
end
|
100
109
|
|
110
|
+
def defaults
|
111
|
+
DefaultSettings.dup
|
112
|
+
end
|
113
|
+
|
101
114
|
def current
|
102
115
|
@current ||= begin
|
103
|
-
defaults
|
104
|
-
Hashie::Mash.new(defaults.merge(home_config.merge(cwd_config).merge(applied_config)))
|
116
|
+
defaults.merge(home_config.merge(cwd_config.merge(applied_config))).to_mash
|
105
117
|
end
|
106
118
|
end
|
107
119
|
|
120
|
+
def apply_config(hash={})
|
121
|
+
applied_config.merge!(hash)
|
122
|
+
current.merge(applied_config)
|
123
|
+
end
|
124
|
+
|
108
125
|
def apply_config_from_path(path)
|
109
126
|
path = Pathname(path)
|
110
127
|
parsed = JSON.parse(path.read) rescue {}
|
@@ -115,6 +132,8 @@ module Skypager
|
|
115
132
|
def save!
|
116
133
|
save_home_config
|
117
134
|
save_cwd_config
|
135
|
+
@current = nil
|
136
|
+
true
|
118
137
|
end
|
119
138
|
|
120
139
|
def save_cwd_config
|
@@ -146,6 +165,8 @@ module Skypager
|
|
146
165
|
end
|
147
166
|
|
148
167
|
def home_config
|
168
|
+
initialize! unless home_config_path.exist?
|
169
|
+
|
149
170
|
@home_config ||= begin
|
150
171
|
(home_config_path.exist? rescue false) ? JSON.parse(home_config_path.read) : {}
|
151
172
|
rescue
|
@@ -153,16 +174,20 @@ module Skypager
|
|
153
174
|
end
|
154
175
|
end
|
155
176
|
|
156
|
-
def
|
157
|
-
Pathname(
|
177
|
+
def home_config_path= value
|
178
|
+
@home_config_path = Pathname(value)
|
158
179
|
end
|
159
180
|
|
160
181
|
def home_config_path
|
161
|
-
|
182
|
+
@home_config_path || Pathname(ENV['HOME']).join(".skypager","config.json")
|
183
|
+
end
|
184
|
+
|
185
|
+
def cwd_config_path= value
|
186
|
+
@cwd_config_path = Pathname(value)
|
162
187
|
end
|
163
188
|
|
164
189
|
def cwd_config_path
|
165
|
-
Pathname(Dir.pwd).join('skypager.json')
|
190
|
+
@cwd_config_path || Pathname(Dir.pwd).join('skypager.json')
|
166
191
|
end
|
167
192
|
end
|
168
193
|
end
|
data/lib/skypager/core_ext.rb
CHANGED
@@ -150,7 +150,7 @@ module Skypager
|
|
150
150
|
end
|
151
151
|
|
152
152
|
def last_updated_at
|
153
|
-
if value = spreadsheet.
|
153
|
+
if value = spreadsheet.document_feed_entry_internal.css('updated').try(:text) rescue nil
|
154
154
|
DateTime.parse(value).to_i
|
155
155
|
end
|
156
156
|
end
|
@@ -166,7 +166,7 @@ module Skypager
|
|
166
166
|
protected
|
167
167
|
|
168
168
|
def process_worksheets
|
169
|
-
worksheets.inject(
|
169
|
+
worksheets.inject({}.to_mash) do |memo, parts|
|
170
170
|
k, ws = parts
|
171
171
|
header_row = Array(ws.rows[0])
|
172
172
|
column_names = header_row.map {|cell| "#{ cell }".parameterize.underscore }
|
@@ -205,7 +205,7 @@ module Skypager
|
|
205
205
|
end
|
206
206
|
|
207
207
|
def worksheets
|
208
|
-
@worksheets ||= _worksheets.inject(
|
208
|
+
@worksheets ||= _worksheets.inject({}.to_mash) do |memo,ws|
|
209
209
|
key = ws.title.strip.downcase.underscore.gsub(/\s+/,'_')
|
210
210
|
memo[key] = ws
|
211
211
|
memo
|
data/lib/skypager/data/source.rb
CHANGED
@@ -57,6 +57,24 @@ module Skypager
|
|
57
57
|
|
58
58
|
row
|
59
59
|
end
|
60
|
+
|
61
|
+
processors.each do |processor|
|
62
|
+
original = self.processed.dup
|
63
|
+
modified = []
|
64
|
+
|
65
|
+
original.each_with_index do |record, index|
|
66
|
+
previous = original[index - 1]
|
67
|
+
modified.push(processor.call(record, index, previous: previous, set: original))
|
68
|
+
end
|
69
|
+
|
70
|
+
self.processed = modified
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def processors &block
|
75
|
+
@processors ||= []
|
76
|
+
@processors << block if block_given?
|
77
|
+
@processors
|
60
78
|
end
|
61
79
|
|
62
80
|
# makes sure that the required options for this data source
|
@@ -94,6 +112,10 @@ module Skypager
|
|
94
112
|
!need_to_refresh? && (age > max_age)
|
95
113
|
end
|
96
114
|
|
115
|
+
def fresh_on_server?
|
116
|
+
need_to_refresh?
|
117
|
+
end
|
118
|
+
|
97
119
|
def max_age
|
98
120
|
max = ENV['MAX_DATA_SOURCE_AGE']
|
99
121
|
(max && max.to_i) || 120
|
@@ -139,7 +161,7 @@ module Skypager
|
|
139
161
|
return @refreshed_at if @refreshed_at.to_i > 0
|
140
162
|
|
141
163
|
if path_to_file.exist?
|
142
|
-
@refreshed_at =
|
164
|
+
@refreshed_at = File.mtime(path.join(file)).to_i
|
143
165
|
end
|
144
166
|
end
|
145
167
|
|
@@ -157,6 +179,7 @@ module Skypager
|
|
157
179
|
|
158
180
|
def file
|
159
181
|
@file ||= name.parameterize if name.respond_to?(:parameterize)
|
182
|
+
@file.gsub!("-","_")
|
160
183
|
@file = "#{@file}.json" unless @file.match(/\.json/i)
|
161
184
|
@file
|
162
185
|
end
|
data/lib/skypager/extension.rb
CHANGED
@@ -7,6 +7,7 @@ module Skypager
|
|
7
7
|
option :synced_folders, {}, 'The settings for dropbox source syncing'
|
8
8
|
option :data_sources, [], 'The data source mappings for this site'
|
9
9
|
option :deploy_options, {}, 'Deploy options: domain, bucket_name, use_cdn, aliases, auto_deploy'
|
10
|
+
option :skypager, {}, 'Options to be passed to Skypager.config'
|
10
11
|
|
11
12
|
def initialize(app, options_hash={}, &block)
|
12
13
|
# if only skypager/extension is required this will be necessary
|
@@ -16,27 +17,20 @@ module Skypager
|
|
16
17
|
|
17
18
|
app.include(ClassMethods)
|
18
19
|
|
19
|
-
options_hash.reverse_merge!(:data_sources => {},
|
20
|
-
:synced_folders => {},
|
21
|
-
:deploy_options => {},
|
20
|
+
options_hash.reverse_merge!(:data_sources => {}.to_mash,
|
21
|
+
:synced_folders => {}.to_mash,
|
22
|
+
:deploy_options => {}.to_mash,
|
22
23
|
:site_name => File.basename(app.root))
|
23
24
|
|
24
25
|
options_hash.each do |key, value|
|
25
26
|
app.set(key, value)
|
26
27
|
end
|
27
28
|
|
28
|
-
unless $skypager_command
|
29
|
+
unless $skypager_command || ENV['DISABLE_SKYPAGER_SYNC']
|
29
30
|
app.ready do
|
30
|
-
|
31
|
-
|
32
|
-
end
|
33
|
-
|
31
|
+
sync_folders()
|
32
|
+
load_stores()
|
34
33
|
# Load the data source and save it to disk
|
35
|
-
data_sources.each do |name, source|
|
36
|
-
source.refresh
|
37
|
-
source.save_to_disk unless source.persisted?
|
38
|
-
data.callbacks(name, -> { source.data })
|
39
|
-
end
|
40
34
|
end
|
41
35
|
|
42
36
|
# In development, we will try to refresh remote data stores (e.g. google spreadsheets)
|
@@ -46,19 +40,55 @@ module Skypager
|
|
46
40
|
data_source.refresh_if_stale?
|
47
41
|
end if development?
|
48
42
|
end
|
43
|
+
end
|
49
44
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
end
|
45
|
+
app.after_build do
|
46
|
+
app.site.requires_build!(false)
|
47
|
+
app.deploy! if app.auto_deploy?
|
48
|
+
end
|
55
49
|
|
50
|
+
app.after_configuration do
|
51
|
+
save_site_config()
|
56
52
|
end
|
53
|
+
|
57
54
|
end
|
58
55
|
|
59
56
|
helpers do
|
57
|
+
def deploy!
|
58
|
+
site.deploy()
|
59
|
+
end
|
60
|
+
|
61
|
+
def auto_deploy?
|
62
|
+
!!deploy_options[:auto_deploy]
|
63
|
+
end
|
64
|
+
|
65
|
+
def save_site_config
|
66
|
+
syncable_config = site.config.syncables ||= {}.to_mash
|
67
|
+
|
68
|
+
syncables.each do |folder|
|
69
|
+
type = folder.type
|
70
|
+
cfg = syncable_config[type] ||= {}.to_mash
|
71
|
+
cfg.paths ||= []
|
72
|
+
cfg.paths.push(syncable.remote_path.to_s)
|
73
|
+
cfg.paths.uniq!
|
74
|
+
end
|
75
|
+
|
76
|
+
if Skypager.config.dropbox_setup?
|
77
|
+
syncable_config.dropbox ||= {}
|
78
|
+
syncable_config.dropbox.uid = Skypager.dropbox.account.uid
|
79
|
+
end
|
80
|
+
|
81
|
+
site.set(:syncables, syncable_config)
|
82
|
+
end
|
83
|
+
|
60
84
|
def site
|
61
|
-
@site
|
85
|
+
return @site if @site
|
86
|
+
|
87
|
+
@site = Skypager::Site.new(site_name, deploy_options: deploy_options).tap do |site|
|
88
|
+
site.load_config
|
89
|
+
site.set(:deploy_options, deploy_options)
|
90
|
+
site.set(:root, root) unless site.config.key?(:root)
|
91
|
+
end
|
62
92
|
end
|
63
93
|
end
|
64
94
|
|
@@ -108,6 +138,39 @@ module Skypager
|
|
108
138
|
self.synced_folders[folder] = Skypager::Sync::Folder.new(f.merge(app: self, root:Pathname(root)))
|
109
139
|
|
110
140
|
save_dropbox_settings
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
def app
|
145
|
+
self
|
146
|
+
end
|
147
|
+
|
148
|
+
def stores
|
149
|
+
data_sources.values
|
150
|
+
end
|
151
|
+
|
152
|
+
def load_stores persist=false
|
153
|
+
data_sources.each do |name, source|
|
154
|
+
source.refresh
|
155
|
+
source.save_to_disk if persist || !source.persisted?
|
156
|
+
data.callbacks(name, -> { source.data })
|
157
|
+
end
|
158
|
+
|
159
|
+
after_stores_loaded.each do |callback|
|
160
|
+
callback = app.method(callback) if callback.is_a?(Symbol)
|
161
|
+
app.instance_eval(&callback) if callback.respond_to?(:call)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# middleman does have an event system, we can piggyback on that instead
|
166
|
+
def after_stores_loaded &block
|
167
|
+
@after_stores_callbacks ||= []
|
168
|
+
@after_stores_callbacks << block if block_given?
|
169
|
+
@after_stores_callbacks
|
170
|
+
end
|
171
|
+
|
172
|
+
def sync_folders
|
173
|
+
syncables.each(&:sync)
|
111
174
|
end
|
112
175
|
|
113
176
|
def syncables
|
@@ -129,9 +192,9 @@ module Skypager
|
|
129
192
|
end
|
130
193
|
|
131
194
|
@dropbox_data ||= begin
|
132
|
-
|
195
|
+
JSON.parse(dropbox_data_file.read).to_mash
|
133
196
|
rescue
|
134
|
-
|
197
|
+
{}.to_mash
|
135
198
|
end
|
136
199
|
end
|
137
200
|
|