skypager 0.0.4 → 0.0.5

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/ARCHITECTURE.md +34 -0
  4. data/CONTRIBUTING.md +7 -0
  5. data/README.md +94 -36
  6. data/Rakefile +1 -1
  7. data/lib/skypager.rb +6 -0
  8. data/lib/skypager/builder.rb +158 -0
  9. data/lib/skypager/builder/server.rb +63 -0
  10. data/lib/skypager/builder/webhook_handler.rb +73 -0
  11. data/lib/skypager/cli/commands/build.rb +48 -0
  12. data/lib/skypager/cli/commands/config.rb +1 -1
  13. data/lib/skypager/cli/commands/setup.rb +13 -0
  14. data/lib/skypager/cli/commands/site.rb +22 -0
  15. data/lib/skypager/cli/commands/sync.rb +1 -1
  16. data/lib/skypager/configuration.rb +32 -7
  17. data/lib/skypager/core_ext.rb +6 -0
  18. data/lib/skypager/data/google_spreadsheet.rb +3 -3
  19. data/lib/skypager/data/source.rb +24 -1
  20. data/lib/skypager/extension.rb +84 -21
  21. data/lib/skypager/proxy.rb +0 -1
  22. data/lib/skypager/router.rb +15 -0
  23. data/lib/skypager/site.rb +104 -21
  24. data/lib/skypager/sync/dropbox/delta.rb +1 -1
  25. data/lib/skypager/sync/folder.rb +7 -1
  26. data/lib/skypager/sync/github.rb +55 -0
  27. data/lib/skypager/version.rb +1 -1
  28. data/skypager.gemspec +21 -13
  29. data/spec/dummy/site-one/.gitignore +18 -0
  30. data/spec/dummy/site-one/Gemfile +14 -0
  31. data/spec/dummy/site-one/config.rb +13 -0
  32. data/spec/dummy/site-one/source/images/background.png +0 -0
  33. data/spec/dummy/site-one/source/images/middleman.png +0 -0
  34. data/spec/dummy/site-one/source/index.html.erb +10 -0
  35. data/spec/dummy/site-one/source/javascripts/all.js +1 -0
  36. data/spec/dummy/site-one/source/layouts/layout.erb +19 -0
  37. data/spec/dummy/site-one/source/stylesheets/all.css +55 -0
  38. data/spec/dummy/site-one/source/stylesheets/normalize.css +375 -0
  39. data/spec/lib/skypager/builder/server_spec.rb +5 -0
  40. data/spec/lib/skypager/configuration_spec.rb +7 -0
  41. data/spec/lib/skypager/site_spec.rb +40 -1
  42. data/spec/lib/skypager_spec.rb +9 -0
  43. data/spec/skypager-test-config.rb.example +22 -0
  44. data/spec/spec_helper.rb +38 -3
  45. data/spec/support/fixtures/cwd_config.json +3 -0
  46. data/spec/support/fixtures/home_config.json +1 -0
  47. metadata +112 -33
  48. 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 = 'manipulate skypager configuration settings'
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
@@ -12,7 +12,7 @@ command 'sync' do |c|
12
12
  app.data_sources.each do |name, source|
13
13
  source.refresh
14
14
  source.save_to_disk unless source.persisted?
15
- data.callbacks(name, -> { source.data })
15
+ app.data.callbacks(name, -> { source.data })
16
16
  end
17
17
  end
18
18
  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 = DefaultSettings.dup
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 home_folder
157
- Pathname(ENV['HOME']).join('.skypager')
177
+ def home_config_path= value
178
+ @home_config_path = Pathname(value)
158
179
  end
159
180
 
160
181
  def home_config_path
161
- home_folder.join('config.json')
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
@@ -19,3 +19,9 @@ class String
19
19
  end
20
20
  end
21
21
  end
22
+
23
+ class Hash
24
+ def to_mash
25
+ Hashie::Mash.new(self)
26
+ end
27
+ end
@@ -150,7 +150,7 @@ module Skypager
150
150
  end
151
151
 
152
152
  def last_updated_at
153
- if value = spreadsheet.document_feed_entry.css('updated').try(:text) rescue nil
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(Hashie::Mash.new) do |memo, parts|
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(Hashie::Mash.new) do |memo,ws|
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
@@ -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 = FileUtils.mtime(path.join(file)).to_i
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
@@ -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
- synced_folders.each do |name, folder|
31
- folder.sync()
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
- app.after_build do
51
- if deploy_options[:auto_deploy]
52
- app.site.deploy()
53
- end
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 ||= Skypager::Site.new(site_name, deploy_options: deploy_options)
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
- Hashie::Mash.new(JSON.parse(dropbox_data_file.read))
195
+ JSON.parse(dropbox_data_file.read).to_mash
133
196
  rescue
134
- Hashie::Mash.new({})
197
+ {}.to_mash
135
198
  end
136
199
  end
137
200