hyperstack-config 0.1

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 (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.travis.yml +29 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +303 -0
  6. data/README.md +73 -0
  7. data/Rakefile +11 -0
  8. data/bin/hyperstack-hotloader +22 -0
  9. data/hyperstack-config.gemspec +45 -0
  10. data/lib/hyperstack-config.rb +45 -0
  11. data/lib/hyperstack-hotloader-config.js.erb +7 -0
  12. data/lib/hyperstack-loader-application.rb.erb +1 -0
  13. data/lib/hyperstack-loader-system-code.rb.erb +1 -0
  14. data/lib/hyperstack-loader.js +6 -0
  15. data/lib/hyperstack-prerender-loader-application.rb.erb +1 -0
  16. data/lib/hyperstack-prerender-loader-system-code.rb.erb +1 -0
  17. data/lib/hyperstack-prerender-loader.js +4 -0
  18. data/lib/hyperstack/active_support_string_inquirer.rb +32 -0
  19. data/lib/hyperstack/autoloader.rb +140 -0
  20. data/lib/hyperstack/autoloader_starter.rb +15 -0
  21. data/lib/hyperstack/boot.rb +41 -0
  22. data/lib/hyperstack/client_readers.rb +19 -0
  23. data/lib/hyperstack/client_stubs.rb +12 -0
  24. data/lib/hyperstack/config/version.rb +5 -0
  25. data/lib/hyperstack/config_settings.rb +49 -0
  26. data/lib/hyperstack/context.rb +36 -0
  27. data/lib/hyperstack/deprecation_warning.rb +10 -0
  28. data/lib/hyperstack/env.rb +10 -0
  29. data/lib/hyperstack/environment/development/hyperstack_env.rb +5 -0
  30. data/lib/hyperstack/environment/production/hyperstack_env.rb +5 -0
  31. data/lib/hyperstack/environment/staging/hyperstack_env.rb +5 -0
  32. data/lib/hyperstack/environment/test/hyperstack_env.rb +5 -0
  33. data/lib/hyperstack/hotloader.rb +129 -0
  34. data/lib/hyperstack/hotloader/add_error_boundry.rb +31 -0
  35. data/lib/hyperstack/hotloader/css_reloader.rb +41 -0
  36. data/lib/hyperstack/hotloader/server.rb +296 -0
  37. data/lib/hyperstack/hotloader/short_cut.js +9 -0
  38. data/lib/hyperstack/hotloader/socket.rb +136 -0
  39. data/lib/hyperstack/hotloader/stack-trace.js +2977 -0
  40. data/lib/hyperstack/hotloader/stub.rb +10 -0
  41. data/lib/hyperstack/imports.rb +104 -0
  42. data/lib/hyperstack/js_imports.rb +18 -0
  43. data/lib/hyperstack/on_client.rb +5 -0
  44. data/lib/hyperstack/on_error.rb +5 -0
  45. data/lib/hyperstack/rail_tie.rb +87 -0
  46. data/lib/hyperstack/string.rb +6 -0
  47. metadata +350 -0
@@ -0,0 +1,36 @@
1
+ module Hyperstack
2
+ # Allows interactive systems to reset context to the state at boot. Any
3
+ # modules or classes that set context instance variables to hold things like
4
+ # call backs should use Hyperstack::Context.set_var(self, :@var_name) { .... }
5
+ # the provided block will be rerun and the instance variable re-initialized
6
+ # when the reset! method is called
7
+ module Context
8
+ # Replace @foo ||= ... with
9
+ # Context.set_var(self, :@foo) { ... }
10
+ # If reset! has been called then the instance variable will be record, and
11
+ # will be reset on the next call to reset!
12
+ # If you want to record the current value of the instance variable then set
13
+ # force to true.
14
+ def self.set_var(ctx, var, force: nil)
15
+ inst_value_b4 = ctx.instance_variable_get(var)
16
+ if @context && !@context[ctx].key?(var) && (force || !inst_value_b4)
17
+ @context[ctx][var] = (inst_value_b4 && inst_value_b4.dup)
18
+ end
19
+ inst_value_b4 || ctx.instance_variable_set(var, yield)
20
+ end
21
+
22
+ def self.reset!(reboot = true)
23
+ # if @context is already initialized then reset all the instance
24
+ # vars using their corresponding blocks. Otherwise initialize
25
+ # @context.
26
+ if @context
27
+ @context.each do |ctx, vars|
28
+ vars.each { |var, init| ctx.instance_variable_set(var, init.dup) }
29
+ end
30
+ Hyperstack::Application::Boot.run if reboot
31
+ else
32
+ @context = Hash.new { |h, k| h[k] = {} }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,10 @@
1
+ module Hyperstack
2
+ def self.deprecation_warning(name, message)
3
+ return if env.production?
4
+ @deprecation_messages ||= []
5
+ message = "Warning: Deprecated feature used in #{name}. #{message}"
6
+ return if @deprecation_messages.include? message
7
+ @deprecation_messages << message
8
+ `console.warn.apply(console, [message])`
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Hyperstack
2
+ def self.env
3
+ @environment ||= begin
4
+ env = Rails.env if defined? Rails
5
+ env ||= ENV['RACK_ENV']
6
+ env = 'development' unless %w[development production test staging].include? env
7
+ ActiveSupport::StringInquirer.new(env)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ module Hyperstack
2
+ def self.env
3
+ @environment ||= ActiveSupport::StringInquirer.new('development')
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Hyperstack
2
+ def self.env
3
+ @environment ||= ActiveSupport::StringInquirer.new('production')
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Hyperstack
2
+ def self.env
3
+ @environment ||= ActiveSupport::StringInquirer.new('staging')
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Hyperstack
2
+ def self.env
3
+ @environment ||= ActiveSupport::StringInquirer.new('test')
4
+ end
5
+ end
@@ -0,0 +1,129 @@
1
+ require 'hyperstack/hotloader/add_error_boundry'
2
+ require 'hyperstack/hotloader/stack-trace.js'
3
+ require 'hyperstack/hotloader/css_reloader'
4
+ require 'opal-parser' # gives me 'eval', for hot-loading code
5
+
6
+ require 'json'
7
+ require 'hyperstack/hotloader/short_cut.js'
8
+
9
+ # Opal client to support hot reloading
10
+ $eval_proc = proc do |file_name, s|
11
+ $_hyperstack_reloader_file_name = file_name
12
+ eval s
13
+ end
14
+
15
+ module Hyperstack
16
+
17
+ class Hotloader
18
+ def self.callbackmaps
19
+ @@callbackmaps ||= Hash.new { |h, k| h[k] = Hash.new { |h1, k1| h1[k1] = Hash.new { |h2, k2| h2[k2] = Array.new }}}
20
+ end
21
+
22
+ def self.record(klass, instance_var, depth, *items)
23
+ if $_hyperstack_reloader_file_name
24
+ callbackmaps[$_hyperstack_reloader_file_name][klass][instance_var].concat items
25
+ else
26
+ callback = lambda do |stack_frames|
27
+ file_name = `#{stack_frames[depth]}.fileName`
28
+ match = /^(.+\/assets\/)(.+\/)\2/.match(file_name)
29
+ if match
30
+ file_name = file_name.gsub(match[1]+match[2], '')
31
+ callbackmaps[file_name][klass][instance_var].concat items
32
+ end
33
+ end
34
+ error = lambda do |err|
35
+ `console.error(#{"hyperstack hot loader could not find source file for callback: #{err}"})`
36
+ end
37
+ `StackTrace.get().then(#{callback}).catch(#{error})`
38
+ end
39
+ end
40
+
41
+ def self.remove(file_name)
42
+ callbackmaps[file_name].each do |klass, instance_vars|
43
+ instance_vars.each do |instance_var, items|
44
+ klass.instance_variable_get(instance_var).reject! { |item| items.include? item }
45
+ end
46
+ end
47
+ end
48
+
49
+ def connect_to_websocket(port)
50
+ host = `window.location.host`.sub(/:\d+/, '')
51
+ host = '127.0.0.1' if host == ''
52
+ protocol = `window.location.protocol` == 'https:' ? 'wss:' : 'ws:'
53
+ ws_url = "#{host}:#{port}"
54
+ puts "Hot-Reloader connecting to #{ws_url}"
55
+ ws = `new WebSocket(#{protocol} + '//' + #{ws_url})`
56
+ `#{ws}.onmessage = #{lambda { |e| reload(e) }}`
57
+ `setInterval(function() { #{ws}.send('') }, #{@ping * 1000})` if @ping
58
+ end
59
+
60
+ def notify_error(reload_request)
61
+ msg = "Hotloader #{reload_request[:filename]} RELOAD ERROR:\n\n#{$!}"
62
+ puts msg
63
+ alert msg if use_alert?
64
+ end
65
+
66
+ @@USE_ALERT = true
67
+ def self.alerts_on!
68
+ @@USE_ALERT = true
69
+ end
70
+
71
+ def self.alerts_off!
72
+ @@USE_ALERT = false
73
+ end
74
+
75
+ def use_alert?
76
+ @@USE_ALERT
77
+ end
78
+
79
+ def reload(e)
80
+ reload_request = JSON.parse(`e.data`)
81
+ if reload_request[:type] == "ruby"
82
+ puts "Reloading #{reload_request[:filename]} (asset_path: #{reload_request[:asset_path]})"
83
+ begin
84
+ #Hyperstack::Context.reset! false
85
+ file_name = reload_request[:asset_path] #.gsub(/.+hyperstack\//, '')
86
+ Hotloader.remove(file_name)
87
+ $eval_proc.call file_name, reload_request[:source_code]
88
+ rescue
89
+ notify_error(reload_request)
90
+ end
91
+ if @reload_post_callback
92
+ @reload_post_callback.call
93
+ else
94
+ puts "no reloading callback to call"
95
+ end
96
+ end
97
+ if reload_request[:type] == "css"
98
+ @css_reloader.reload(reload_request, `document`)
99
+ end
100
+ end
101
+
102
+ # @param port [Integer] opal hot reloader port to connect to
103
+ # @param reload_post_callback [Proc] optional callback to be called after re evaluating a file for example in react.rb files we want to do a React::Component.force_update!
104
+ def initialize(port=25222, ping=nil, &reload_post_callback)
105
+ @port = port
106
+ @reload_post_callback = reload_post_callback
107
+ @css_reloader = CssReloader.new
108
+ @ping = ping
109
+ end
110
+ # Opens a websocket connection that evaluates new files and runs the optional @reload_post_callback
111
+ def listen
112
+ connect_to_websocket(@port)
113
+ end
114
+
115
+ def self.listen(port=25222, ping=nil)
116
+ ::Hyperstack::Internal::Component::TopLevelRailsComponent.include AddErrorBoundry
117
+ @server = Hotloader.new(port, ping) do
118
+ # TODO: check this out when Operations are integrated
119
+ # if defined?(Hyperloop::Internal::Operation::ClientDrivers) &&
120
+ # Hyperloop::ClientDrivers.respond_to?(:initialize_client_drivers_on_boot)
121
+ # Hyperloop::ClientDrivers.initialize_client_drivers_on_boot
122
+ # end
123
+ Hyperstack::Component.force_update!
124
+ end
125
+ @server.listen
126
+ end
127
+
128
+ end
129
+ end
@@ -0,0 +1,31 @@
1
+ module Hyperstack
2
+ class Hotloader
3
+ module AddErrorBoundry
4
+ def self.included(base)
5
+ base.after_error do |*err|
6
+ @err = err
7
+ Hyperstack::Component.force_update!
8
+ end
9
+ base.define_method :render do
10
+ @err ? parse_display_and_clear_error : top_level_render
11
+ end
12
+ end
13
+
14
+ def parse_display_and_clear_error
15
+ e = @err[0]
16
+ component_stack = @err[1]['componentStack'].split("\n ")
17
+ @err = nil
18
+ display_error(e, component_stack)
19
+ end
20
+
21
+ def display_error(e, component_stack)
22
+ DIV do
23
+ DIV { "Uncaught error: #{e}" }
24
+ component_stack.each do |line|
25
+ DIV { line }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,41 @@
1
+ require 'native'
2
+ module Hyperstack
3
+ class Hotloader
4
+ class CssReloader
5
+
6
+ def reload(reload_request, document)
7
+ url = reload_request[:url]
8
+ puts "Reloading CSS: #{url}"
9
+ to_append = "t_hot_reload=#{Time.now.to_i}"
10
+ links = Native(`document.getElementsByTagName("link")`)
11
+ (0..links.length-1).each { |i|
12
+ link = links[i]
13
+ if link.rel == 'stylesheet' && is_matching_stylesheet?(link.href, url)
14
+ if link.href !~ /\?/
15
+ link.href += "?#{to_append}"
16
+ else
17
+ if link.href !~ /t_hot_reload/
18
+ link.href += "&#{to_append}"
19
+ else
20
+ link.href = link.href.sub(/t_hot_reload=\d+/, to_append)
21
+ end
22
+ end
23
+ end
24
+ }
25
+ end
26
+
27
+ def is_matching_stylesheet?(href, url)
28
+ # straight match, like in Rack::Sass::Plugin
29
+ if href.index(url)
30
+ true
31
+ else
32
+ # Rails asset pipeline match
33
+ url_base = File.basename(url).sub(/\.s?css+/, '').sub(/\.s?css+/, '')
34
+ href_base = File.basename(href).sub(/\.self-?.*.css.+/, '')
35
+ url_base == href_base
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,296 @@
1
+ require 'websocket'
2
+ require 'socket'
3
+ require 'fiber'
4
+ require 'listen'
5
+ require 'optparse'
6
+ require 'json'
7
+
8
+ module Hyperstack
9
+ class Hotloader
10
+ # Most of this lifted from https://github.com/saward/Rubame
11
+ class Server
12
+
13
+ attr_reader :directories
14
+ def initialize(options)
15
+ Socket.do_not_reverse_lookup
16
+ @hostname = '0.0.0.0'
17
+ @port = options[:port]
18
+ setup_directories(options)
19
+
20
+ @reading = []
21
+ @writing = []
22
+
23
+ @clients = {} # Socket as key, and Client as value
24
+
25
+ @socket = TCPServer.new(@hostname, @port)
26
+ @reading.push @socket
27
+ end
28
+
29
+ # adds known directories automatically if they exist
30
+ # - rails js app/assets/javascripts app/assets/stylesheets
31
+ # - reactrb rails defaults app/views/components
32
+ # - you tell me and I'll add them
33
+ def setup_directories(options)
34
+ @directories = options[:directories] || []
35
+ [
36
+ 'app/assets/javascripts',
37
+ 'app/assets/stylesheets',
38
+ 'app/views/components'
39
+ ].each { |known_dir|
40
+ if !@directories.include?(known_dir) && File.exists?(known_dir)
41
+ @directories << known_dir
42
+ end
43
+ }
44
+ end
45
+
46
+ def accept
47
+ socket = @socket.accept_nonblock
48
+ @reading.push socket
49
+ handshake = WebSocket::Handshake::Server.new
50
+ client = Client.new(socket, handshake, self)
51
+
52
+ while line = socket.gets
53
+ client.handshake << line
54
+ break if client.handshake.finished?
55
+ end
56
+ if client.handshake.valid?
57
+ @clients[socket] = client
58
+ client.write handshake.to_s
59
+ client.opened = true
60
+ return client
61
+ else
62
+ close(client)
63
+ end
64
+ return nil
65
+ end
66
+
67
+ def send_updated_file(modified_file)
68
+ if modified_file =~ /\.rb(\.erb)?$/
69
+ if File.exist?(modified_file)
70
+ file_contents = File.read(modified_file).force_encoding(Encoding::UTF_8)
71
+ end
72
+ relative_path = Pathname.new(modified_file).relative_path_from(Pathname.new(Dir.pwd)).to_s
73
+ asset_path = nil
74
+ @directories.each do |directory|
75
+ next unless relative_path =~ /^#{directory}/
76
+ asset_path = relative_path.gsub("#{directory}/", '')
77
+ end
78
+ update = {
79
+ type: 'ruby',
80
+ filename: modified_file,
81
+ asset_path: asset_path,
82
+ source_code: file_contents || ''
83
+ }.to_json
84
+ end
85
+ if modified_file =~ /\.s?[ac]ss$/ && File.exist?(modified_file)
86
+ # Don't know how to remove css definitions at the moment
87
+ # TODO: Switch from hard-wired path assumptions to using SASS/sprockets config
88
+ relative_path = Pathname.new(modified_file).relative_path_from(Pathname.new(Dir.pwd))
89
+ url = relative_path.to_s
90
+ .sub('public/','')
91
+ .sub('/sass/','/')
92
+ .sub(/\.s[ac]ss/, '.css')
93
+ update = {
94
+ type: 'css',
95
+ filename: modified_file,
96
+ url: url
97
+ }.to_json
98
+ end
99
+ if update
100
+ @clients.each { |socket, client| client.send(update) }
101
+ end
102
+ end
103
+
104
+ PROGRAM = 'opal-hot-reloader'
105
+ def loop
106
+ listener = Listen.to(*@directories, only: %r{\.(rb(\.erb)?|s?[ac]ss)$}) do |modified, added, removed|
107
+ (removed + modified + added).each { |file| send_updated_file(file) }
108
+ puts "removed absolute path: #{removed}"
109
+ puts "modified absolute path: #{modified}"
110
+ puts "added absolute path: #{added}"
111
+ end
112
+ listener.start
113
+
114
+ puts "#{PROGRAM}: starting..."
115
+ while (!$quit)
116
+ run do |client|
117
+ client.onopen do
118
+ puts "#{PROGRAM}: client open"
119
+ end
120
+ client.onmessage do |mess|
121
+ puts "PROGRAM: message received: #{mess}" unless mess == ''
122
+ end
123
+ client.onclose do
124
+ puts "#{PROGRAM}: client closed"
125
+ end
126
+ end
127
+ sleep 0.2
128
+ end
129
+ end
130
+
131
+ def read(client)
132
+
133
+ pairs = client.socket.recvfrom(2000)
134
+ messages = []
135
+
136
+ if pairs[0].length == 0
137
+ close(client)
138
+ else
139
+ client.frame << pairs[0]
140
+
141
+ while f = client.frame.next
142
+ if (f.type == :close)
143
+ close(client)
144
+ return messages
145
+ else
146
+ messages.push f
147
+ end
148
+ end
149
+
150
+ end
151
+
152
+ return messages
153
+
154
+ end
155
+
156
+ def close(client)
157
+ @reading.delete client.socket
158
+ @clients.delete client.socket
159
+ begin
160
+ client.socket.close
161
+ rescue
162
+ end
163
+ client.closed = true
164
+ end
165
+
166
+ def run(time = 0, &blk)
167
+ readable, writable = IO.select(@reading, @writing, nil, 0)
168
+
169
+ if readable
170
+ readable.each do |socket|
171
+ client = @clients[socket]
172
+ if socket == @socket
173
+ client = accept
174
+ else
175
+ msg = read(client)
176
+ client.messaged = msg
177
+ end
178
+
179
+ blk.call(client) if client and blk
180
+ end
181
+ end
182
+
183
+ # Check for lazy send items
184
+ timer_start = Time.now
185
+ time_passed = 0
186
+ begin
187
+ @clients.each do |s, c|
188
+ c.send_some_lazy(5)
189
+ end
190
+ time_passed = Time.now - timer_start
191
+ end while time_passed < time
192
+ end
193
+
194
+ def stop
195
+ @socket.close
196
+ end
197
+ end
198
+
199
+ class Client
200
+ attr_accessor :socket, :handshake, :frame, :opened, :messaged, :closed
201
+
202
+ def initialize(socket, handshake, server)
203
+ @socket = socket
204
+ @handshake = handshake
205
+ @frame = WebSocket::Frame::Incoming::Server.new(:version => @handshake.version)
206
+ @opened = false
207
+ @messaged = []
208
+ @lazy_queue = []
209
+ @lazy_current_queue = nil
210
+ @closed = false
211
+ @server = server
212
+ end
213
+
214
+ def write(data)
215
+ @socket.write data
216
+ end
217
+
218
+ def send(data)
219
+ frame = WebSocket::Frame::Outgoing::Server.new(:version => @handshake.version, :data => data, :type => :text)
220
+ begin
221
+ @socket.write frame
222
+ @socket.flush
223
+ rescue
224
+ @server.close(self) unless @closed
225
+ end
226
+ end
227
+
228
+ def lazy_send(data)
229
+ @lazy_queue.push data
230
+ end
231
+
232
+ def get_lazy_fiber
233
+ # Create the fiber if needed
234
+ if @lazy_fiber == nil or !@lazy_fiber.alive?
235
+ @lazy_fiber = Fiber.new do
236
+ @lazy_current_queue.each do |data|
237
+ send(data)
238
+ Fiber.yield unless @lazy_current_queue[-1] == data
239
+ end
240
+ end
241
+ end
242
+
243
+ return @lazy_fiber
244
+ end
245
+
246
+ def send_some_lazy(count)
247
+ # To save on cpu cycles, we don't want to be chopping and changing arrays, which could get quite large. Instead,
248
+ # we iterate over an array which we are sure won't change out from underneath us.
249
+ unless @lazy_current_queue
250
+ @lazy_current_queue = @lazy_queue
251
+ @lazy_queue = []
252
+ end
253
+
254
+ completed = 0
255
+ begin
256
+ get_lazy_fiber.resume
257
+ completed += 1
258
+ end while (@lazy_queue.count > 0 or @lazy_current_queue.count > 0) and completed < count
259
+
260
+ end
261
+
262
+ def onopen(&blk)
263
+ if @opened
264
+ begin
265
+ blk.call
266
+ ensure
267
+ @opened = false
268
+ end
269
+ end
270
+ end
271
+
272
+ def onmessage(&blk)
273
+ if @messaged.size > 0
274
+ begin
275
+ @messaged.each do |x|
276
+ blk.call(x.to_s)
277
+ end
278
+ ensure
279
+ @messaged = []
280
+ end
281
+ end
282
+ end
283
+
284
+ def onclose(&blk)
285
+ if @closed
286
+ begin
287
+ blk.call
288
+ ensure
289
+ end
290
+ end
291
+ end
292
+ end
293
+
294
+ line = 0
295
+ end
296
+ end