skypager 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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