confinement 0.0.2 → 0.0.3
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/Gemfile.lock +10 -6
- data/README.md +2 -0
- data/confinement.gemspec +4 -1
- data/exe/confinement +246 -20
- data/lib/confinement.rb +238 -109
- data/lib/confinement/filewatcher/LICENSE +20 -0
- data/lib/confinement/filewatcher/filewatcher.rb +135 -0
- data/lib/confinement/filewatcher/filewatcher/cycles.rb +44 -0
- data/lib/confinement/filewatcher/filewatcher/version.rb +7 -0
- data/lib/confinement/version.rb +1 -1
- metadata +49 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 317875d42d9d6449fe4f5ba24a5411defb9211b88a22270cac6457afa55308bb
|
4
|
+
data.tar.gz: 331794f2b0b08897f03c40b5147d08cc474d76ee941f8e87d264042256f84d77
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53a6285bc8cacda51cb4a4edd45562ce41ba7b483f643032276b5720dae0b774137324c3ca410dde65b9a007369a334d8b71cd0f427788edc67a0db244f4cc91
|
7
|
+
data.tar.gz: b6713bcc1eff930f4e4007d85579090409e2f56196abc533170b85ea5858be96876a6e8c029cb0ad4045fdbe501962c9439582231cbf46363803587db96687eb
|
data/Gemfile.lock
CHANGED
@@ -1,24 +1,28 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
confinement (0.0.
|
4
|
+
confinement (0.0.3)
|
5
5
|
erubi
|
6
|
+
puma (~> 4.3)
|
7
|
+
rack (~> 2.2)
|
8
|
+
zeitwerk (~> 2.3)
|
6
9
|
|
7
10
|
GEM
|
8
11
|
remote: https://rubygems.org/
|
9
12
|
specs:
|
10
|
-
byebug (11.1.1)
|
11
13
|
coderay (1.1.2)
|
12
14
|
erubi (1.9.0)
|
13
15
|
method_source (1.0.0)
|
14
16
|
minitest (5.14.0)
|
17
|
+
nio4r (2.5.2)
|
15
18
|
pry (0.13.0)
|
16
19
|
coderay (~> 1.1)
|
17
20
|
method_source (~> 1.0)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
puma (4.3.3)
|
22
|
+
nio4r (~> 2.0)
|
23
|
+
rack (2.2.2)
|
21
24
|
rake (13.0.1)
|
25
|
+
zeitwerk (2.3.0)
|
22
26
|
|
23
27
|
PLATFORMS
|
24
28
|
ruby
|
@@ -27,7 +31,7 @@ DEPENDENCIES
|
|
27
31
|
bundler (~> 2.0)
|
28
32
|
confinement!
|
29
33
|
minitest (~> 5.0)
|
30
|
-
pry
|
34
|
+
pry
|
31
35
|
rake (~> 13.0)
|
32
36
|
|
33
37
|
BUNDLED WITH
|
data/README.md
CHANGED
data/confinement.gemspec
CHANGED
@@ -26,7 +26,10 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_development_dependency "bundler", "~> 2.0"
|
27
27
|
spec.add_development_dependency "rake", "~> 13.0"
|
28
28
|
spec.add_development_dependency "minitest", "~> 5.0"
|
29
|
-
spec.add_development_dependency "pry
|
29
|
+
spec.add_development_dependency "pry"
|
30
30
|
|
31
31
|
spec.add_runtime_dependency("erubi")
|
32
|
+
spec.add_runtime_dependency("zeitwerk", "~> 2.3")
|
33
|
+
spec.add_runtime_dependency("rack", "~> 2.2")
|
34
|
+
spec.add_runtime_dependency("puma", "~> 4.3")
|
32
35
|
end
|
data/exe/confinement
CHANGED
@@ -9,6 +9,7 @@ end
|
|
9
9
|
require "optparse"
|
10
10
|
require "pathname"
|
11
11
|
|
12
|
+
require "confinement"
|
12
13
|
require "confinement/version"
|
13
14
|
|
14
15
|
module Confinement
|
@@ -16,6 +17,8 @@ module Confinement
|
|
16
17
|
def self.subcommands
|
17
18
|
@subcommands ||= {
|
18
19
|
"init" => Init.new,
|
20
|
+
"server" => Server.new,
|
21
|
+
"build" => Build.new,
|
19
22
|
}
|
20
23
|
end
|
21
24
|
|
@@ -34,6 +37,12 @@ module Confinement
|
|
34
37
|
|
35
38
|
opts.on("-h", "--help", "Prints this help message") do
|
36
39
|
end
|
40
|
+
|
41
|
+
opts.separator("")
|
42
|
+
opts.separator("Subcommands")
|
43
|
+
self.class.subcommands.each do |name, _|
|
44
|
+
opts.separator(" #{name}")
|
45
|
+
end
|
37
46
|
end
|
38
47
|
end
|
39
48
|
|
@@ -60,6 +69,202 @@ module Confinement
|
|
60
69
|
|
61
70
|
private
|
62
71
|
|
72
|
+
class Build
|
73
|
+
def optparser
|
74
|
+
@options ||= {
|
75
|
+
"--setup" => "config/setup.rb",
|
76
|
+
"--rules" => "rules.rb",
|
77
|
+
}
|
78
|
+
@optparser ||= OptionParser.new do |opts|
|
79
|
+
opts.banner = "Usage: #{CLI.script_name} build [options]"
|
80
|
+
opts.on("--setup=PATH", "Path to setup file (default: #{@options["--setup"]})") do |path|
|
81
|
+
@options["--setup"] = path
|
82
|
+
end
|
83
|
+
opts.on("--rules=PATH", "Path to rules file (default: #{@options["--rules"]})") do |path|
|
84
|
+
@options["--rules"] = path
|
85
|
+
end
|
86
|
+
opts.on("--help", "Print this help message") do
|
87
|
+
puts self
|
88
|
+
exit
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def call(argv)
|
94
|
+
setup_path = @options.fetch("--setup")
|
95
|
+
rules_path = @options.fetch("--rules")
|
96
|
+
|
97
|
+
load Pathname.new(setup_path).expand_path
|
98
|
+
@compiler = Compiler.new(Confinement.config)
|
99
|
+
|
100
|
+
load Pathname.new(rules_path).expand_path
|
101
|
+
@compiler.compile_everything(Confinement.site)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class Server
|
106
|
+
using Easier
|
107
|
+
|
108
|
+
def optparser
|
109
|
+
@options ||= {
|
110
|
+
"--setup" => "config/setup.rb",
|
111
|
+
"--rules" => "rules.rb",
|
112
|
+
"--port" => "7000"
|
113
|
+
}
|
114
|
+
@optparser ||= OptionParser.new do |opts|
|
115
|
+
opts.banner = "Usage: #{CLI.script_name} server [options]"
|
116
|
+
opts.on("--setup=PATH", "Path to setup file (default: #{@options["--setup"]})") do |path|
|
117
|
+
@options["--setup"] = path
|
118
|
+
end
|
119
|
+
opts.on("--rules=PATH", "Path to rules file (default: #{@options["--rules"]})") do |path|
|
120
|
+
@options["--rules"] = path
|
121
|
+
end
|
122
|
+
opts.on("--port=PORT", "Port to listen to (default: #{@options["--port"]})") do |port|
|
123
|
+
@options["--port"] = port
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def call(argv)
|
129
|
+
if argv.size > 1
|
130
|
+
puts optparser
|
131
|
+
exit
|
132
|
+
end
|
133
|
+
|
134
|
+
setup_path = @options.fetch("--setup")
|
135
|
+
rules_path = @options.fetch("--rules")
|
136
|
+
|
137
|
+
setup_path = Pathname.new(setup_path).expand_path
|
138
|
+
rules_path = Pathname.new(rules_path).expand_path
|
139
|
+
|
140
|
+
if !setup_path.exist?
|
141
|
+
raise "Cannot find setup file at: #{setup_path}"
|
142
|
+
end
|
143
|
+
|
144
|
+
require "rack"
|
145
|
+
require "puma"
|
146
|
+
require "puma/configuration"
|
147
|
+
require "confinement/filewatcher/filewatcher"
|
148
|
+
|
149
|
+
load setup_path
|
150
|
+
|
151
|
+
dirty = Dirty.new
|
152
|
+
|
153
|
+
puma_config = Puma::Configuration.new do |user_config, file_config, default_config|
|
154
|
+
user_config.port(@options.fetch("--port").to_i)
|
155
|
+
user_config.app(Rack::CommonLogger.new(App.new(Confinement.config, rules_path, dirty)))
|
156
|
+
end
|
157
|
+
|
158
|
+
assets_filewatcher = Filewatcher.new(Confinement.config.watcher.assets)
|
159
|
+
contents_filewatcher = Filewatcher.new(Confinement.config.watcher.contents)
|
160
|
+
puma_launcher = Puma::Launcher.new(puma_config, events: Puma::Events.stdio)
|
161
|
+
|
162
|
+
assets_thread = Thread.new do
|
163
|
+
assets_filewatcher.watch { dirty.dirty_assets! }
|
164
|
+
end
|
165
|
+
|
166
|
+
contents_thread = Thread.new do
|
167
|
+
contents_filewatcher.watch { dirty.dirty_contents! }
|
168
|
+
end
|
169
|
+
|
170
|
+
puma_launcher.run
|
171
|
+
end
|
172
|
+
|
173
|
+
class Dirty
|
174
|
+
def initialize
|
175
|
+
@dirty_assets = true
|
176
|
+
@dirty_contents = true
|
177
|
+
@mutex = Mutex.new
|
178
|
+
end
|
179
|
+
|
180
|
+
def dirty_assets!
|
181
|
+
@mutex.synchronize { @dirty_assets = true }
|
182
|
+
end
|
183
|
+
|
184
|
+
def dirty_contents!
|
185
|
+
@mutex.synchronize { @dirty_contents = true }
|
186
|
+
end
|
187
|
+
|
188
|
+
def clean!
|
189
|
+
@mutex.synchronize do
|
190
|
+
if @dirty_assets || @dirty_contents
|
191
|
+
yield(@dirty_assets, @dirty_contents)
|
192
|
+
end
|
193
|
+
|
194
|
+
@dirty_assets = false
|
195
|
+
@dirty_contents = false
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
class App
|
201
|
+
def initialize(config, rules_path, dirty)
|
202
|
+
@config = config
|
203
|
+
@logger = config.logger
|
204
|
+
@rules_path = rules_path
|
205
|
+
@dirty = dirty
|
206
|
+
@output_root_path = config.compiler.output_root_path
|
207
|
+
@output_directory_index = config.compiler.output_directory_index
|
208
|
+
@compiler = Compiler.new(config)
|
209
|
+
@reload = false
|
210
|
+
end
|
211
|
+
|
212
|
+
def call(env)
|
213
|
+
if !["GET", "HEAD"].include?(env["REQUEST_METHOD"])
|
214
|
+
return [405, { "Content-Type" => "text/plain" }, ["Unsupported method: ", env["REQUEST_METHOD"]]]
|
215
|
+
end
|
216
|
+
|
217
|
+
@dirty.clean! do |is_dirty_assets, is_dirty_contents|
|
218
|
+
@logger.debug { "dirty: assets=#{is_dirty_assets}, contents=#{is_dirty_contents}" }
|
219
|
+
|
220
|
+
if @reload
|
221
|
+
@logger.debug { "reloading with Zeitwerk" }
|
222
|
+
Confinement.config.loader.reload
|
223
|
+
else
|
224
|
+
@reload = true
|
225
|
+
end
|
226
|
+
|
227
|
+
if is_dirty_assets
|
228
|
+
@logger.debug { "loading: #{@rules_path}" }
|
229
|
+
load @rules_path
|
230
|
+
@compiler.compile_everything(Confinement.site)
|
231
|
+
elsif is_dirty_contents
|
232
|
+
partial_compilation = Confinement.site.partial_compilation
|
233
|
+
@logger.debug { "loading: #{@rules_path}" }
|
234
|
+
load @rules_path
|
235
|
+
precompilation_partial_compilation = Confinement.site.partial_compilation
|
236
|
+
|
237
|
+
if @compiler.partial_compilation_dirty?(before: partial_compilation, after: precompilation_partial_compilation)
|
238
|
+
@logger.info { "detected asset change" }
|
239
|
+
@compiler.compile_everything(Confinement.site)
|
240
|
+
else
|
241
|
+
Confinement.site.partial_compilation = partial_compilation
|
242
|
+
@compiler.compile_contents(Confinement.site)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
@logger.debug { "dirty: finished!" }
|
247
|
+
end
|
248
|
+
|
249
|
+
file_path = @output_root_path.concat(env["SCRIPT_NAME"] + env["PATH_INFO"])
|
250
|
+
|
251
|
+
if file_path.directory?
|
252
|
+
file_path = file_path.concat(@output_directory_index)
|
253
|
+
end
|
254
|
+
|
255
|
+
if !file_path.exist?
|
256
|
+
return [404, {}, ["Page not found!\n", file_path.to_s]]
|
257
|
+
end
|
258
|
+
|
259
|
+
if env["REQUEST_METHOD"] == "HEAD"
|
260
|
+
return [204, {}, []]
|
261
|
+
end
|
262
|
+
|
263
|
+
[200, {}, [file_path.read]]
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
63
268
|
class Init
|
64
269
|
def optparser
|
65
270
|
@options ||= {}
|
@@ -99,10 +304,14 @@ module Confinement
|
|
99
304
|
[Pathname.new(File.join(root, path.strip)), body.strip + "\n"]
|
100
305
|
end
|
101
306
|
|
307
|
+
mkdir(root, root)
|
308
|
+
mkdir(root.join("lib"), root)
|
309
|
+
mkdir(root.join("tmp"), root)
|
310
|
+
|
102
311
|
templates
|
103
312
|
.map { |path, _| path.dirname }
|
104
313
|
.uniq
|
105
|
-
.each { |path| mkdir(path, root) }
|
314
|
+
.each { |path| mkdir(path, root) if path != root }
|
106
315
|
|
107
316
|
templates.each do |path, body|
|
108
317
|
write(path, body, root)
|
@@ -175,25 +384,47 @@ end
|
|
175
384
|
Confinement::CLI.new.call(ARGV.dup)
|
176
385
|
|
177
386
|
__END__
|
178
|
-
==> boot.rb
|
387
|
+
==> config/boot.rb
|
179
388
|
require "confinement"
|
180
389
|
|
181
|
-
|
390
|
+
==> config/setup.rb
|
391
|
+
require_relative "boot"
|
182
392
|
|
183
|
-
Confinement.
|
184
|
-
root: Confinement.root,
|
185
|
-
assets: "assets",
|
186
|
-
contents: "contents",
|
187
|
-
layouts: "layouts",
|
188
|
-
config: {
|
189
|
-
index: "index.html",
|
190
|
-
}
|
191
|
-
)
|
393
|
+
Confinement.config = Confinement::Config.new(root: File.dirname(__dir__))
|
192
394
|
|
193
|
-
|
194
|
-
|
395
|
+
Confinement.config.compiler do |compiler|
|
396
|
+
compiler.output_root = compiler.default_output_root
|
397
|
+
compiler.output_assets = "assets"
|
398
|
+
compiler.output_directory_index = "index.html"
|
399
|
+
end
|
400
|
+
|
401
|
+
Confinement.config.source do |source|
|
402
|
+
source.assets = "assets"
|
403
|
+
source.contents = "contents"
|
404
|
+
source.layouts = "layouts"
|
405
|
+
end
|
195
406
|
|
196
|
-
Confinement.
|
407
|
+
Confinement.config.loader do |loader|
|
408
|
+
loader.push_dir("lib")
|
409
|
+
loader.enable_reloading
|
410
|
+
end
|
411
|
+
|
412
|
+
Confinement.config.watcher do |paths|
|
413
|
+
paths.assets.push("assets/")
|
414
|
+
paths.assets.push("package.json")
|
415
|
+
paths.assets.push("yarn.lock")
|
416
|
+
paths.contents.push("contents/")
|
417
|
+
paths.contents.push("lib/")
|
418
|
+
paths.contents.push("rules.rb")
|
419
|
+
end
|
420
|
+
|
421
|
+
==> rules.rb
|
422
|
+
Confinement.site = Confinement::Site.new(Confinement.config) do |site|
|
423
|
+
site.view_context_helpers = []
|
424
|
+
site.guesses = Confinement::Renderer.guesses
|
425
|
+
end
|
426
|
+
|
427
|
+
Confinement.site.rules do |assets:, layouts:, contents:, routes:|
|
197
428
|
assets.init("application.js", entrypoint: true)
|
198
429
|
assets.init("application.css", entrypoint: false)
|
199
430
|
|
@@ -204,11 +435,6 @@ Confinement.site.build do |assets:, layouts:, contents:, routes:|
|
|
204
435
|
end
|
205
436
|
end
|
206
437
|
|
207
|
-
==> write.rb
|
208
|
-
require_relative "build"
|
209
|
-
|
210
|
-
Confinement::Publish.new(Confinement.site).write
|
211
|
-
|
212
438
|
==> package.json
|
213
439
|
{
|
214
440
|
"name": "website",
|
data/lib/confinement.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
if Gem.loaded_specs.has_key?("pry
|
4
|
-
require "pry-byebug"
|
5
|
-
elsif Gem.loaded_specs.has_key?("pry-byebug")
|
3
|
+
if Gem.loaded_specs.has_key?("pry")
|
6
4
|
require "pry"
|
7
5
|
end
|
8
6
|
|
9
7
|
# standard library
|
10
8
|
require "digest"
|
9
|
+
require "logger"
|
11
10
|
require "open3"
|
12
11
|
require "pathname"
|
13
12
|
require "yaml"
|
@@ -15,6 +14,7 @@ require "yaml"
|
|
15
14
|
# gems
|
16
15
|
require "erubi"
|
17
16
|
require "erubi/capture_end"
|
17
|
+
require "zeitwerk"
|
18
18
|
|
19
19
|
# internal
|
20
20
|
require_relative "confinement/version"
|
@@ -76,68 +76,183 @@ module Confinement
|
|
76
76
|
|
77
77
|
using Easier
|
78
78
|
|
79
|
+
module BuilderGetterInitialization
|
80
|
+
def builder_getter(method_name, klass, ivar, new: [])
|
81
|
+
init_parameters = [*new, "&block"].join(", ")
|
82
|
+
|
83
|
+
class_eval(<<~RUBY, __FILE__, __LINE__)
|
84
|
+
def #{method_name}(&block)
|
85
|
+
if #{ivar}
|
86
|
+
if block_given?
|
87
|
+
raise "#{method_name} is already set up"
|
88
|
+
end
|
89
|
+
|
90
|
+
return #{ivar}
|
91
|
+
end
|
92
|
+
|
93
|
+
if !block_given?
|
94
|
+
raise "Can't initialize #{method_name} without block"
|
95
|
+
end
|
96
|
+
|
97
|
+
#{ivar} = #{klass}.new(#{init_parameters})
|
98
|
+
#{ivar}
|
99
|
+
end
|
100
|
+
RUBY
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
79
104
|
class << self
|
80
|
-
|
105
|
+
extend BuilderGetterInitialization
|
106
|
+
|
107
|
+
attr_accessor :config
|
81
108
|
attr_accessor :site
|
109
|
+
attr_writer :env
|
82
110
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
111
|
+
def env
|
112
|
+
@env ||= ENV.fetch("CONFINEMENT_ENV", "development")
|
113
|
+
end
|
114
|
+
end
|
87
115
|
|
88
|
-
|
89
|
-
|
116
|
+
class Config
|
117
|
+
extend BuilderGetterInitialization
|
118
|
+
|
119
|
+
builder_getter("loader", "ZeitwerkProxy", "@loader")
|
120
|
+
builder_getter("watcher", "WatcherPaths", "@watcher", new: ["root: @root"])
|
121
|
+
builder_getter("compiler", "Config::Compiler", "@compiler", new: ["root: @root"])
|
122
|
+
builder_getter("source", "Config::Source", "@source", new: ["root: @root"])
|
123
|
+
|
124
|
+
def initialize(root:)
|
125
|
+
@root = Pathname.new(root).expand_path.cleanpath
|
126
|
+
|
127
|
+
if !@root.exist?
|
128
|
+
raise Error::PathDoesNotExist, "Root path does not exist: #{@root}"
|
90
129
|
end
|
130
|
+
end
|
131
|
+
|
132
|
+
attr_reader :root
|
133
|
+
attr_writer :logger
|
134
|
+
|
135
|
+
def logger
|
136
|
+
@logger ||= default_logger
|
137
|
+
end
|
91
138
|
|
92
|
-
|
139
|
+
def default_logger
|
140
|
+
Logger.new($stdout).tap do |l|
|
141
|
+
l.level = Logger::INFO
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class ZeitwerkProxy
|
146
|
+
def initialize
|
147
|
+
@loader = Zeitwerk::Loader.new
|
148
|
+
yield(self)
|
149
|
+
@loader.setup
|
150
|
+
end
|
151
|
+
|
152
|
+
def push_dir(dir)
|
153
|
+
@loader.push_dir(dir)
|
154
|
+
end
|
155
|
+
|
156
|
+
def enable_reloading
|
157
|
+
@loader.enable_reloading
|
158
|
+
end
|
159
|
+
|
160
|
+
def reload
|
161
|
+
@loader.reload
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class WatcherPaths
|
166
|
+
def initialize(root:)
|
167
|
+
@root = root
|
168
|
+
@assets = []
|
169
|
+
@contents = []
|
170
|
+
|
171
|
+
yield(self)
|
172
|
+
|
173
|
+
@assets = @assets.map { |path| @root.concat(path) }
|
174
|
+
@contents = @contents.map { |path| @root.concat(path) }
|
175
|
+
end
|
176
|
+
|
177
|
+
attr_reader :assets
|
178
|
+
attr_reader :contents
|
179
|
+
end
|
180
|
+
|
181
|
+
class Compiler
|
182
|
+
def initialize(root:)
|
183
|
+
@root = root
|
184
|
+
yield(self)
|
185
|
+
|
186
|
+
self.output_root ||= default_output_root
|
187
|
+
end
|
188
|
+
|
189
|
+
attr_accessor :output_root
|
190
|
+
attr_accessor :output_assets
|
191
|
+
attr_accessor :output_directory_index
|
192
|
+
|
193
|
+
def output_root_path
|
194
|
+
@root.concat(output_root).cleanpath.expand_path
|
195
|
+
end
|
196
|
+
|
197
|
+
def output_assets_path
|
198
|
+
@root.concat(output_root, output_assets).cleanpath.expand_path
|
199
|
+
end
|
200
|
+
|
201
|
+
def default_output_root
|
202
|
+
"tmp/build-#{Confinement.env}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
class Source
|
207
|
+
def initialize(root:)
|
208
|
+
@root = root
|
209
|
+
yield(self)
|
210
|
+
end
|
211
|
+
|
212
|
+
attr_accessor :assets
|
213
|
+
attr_accessor :contents
|
214
|
+
attr_accessor :layouts
|
215
|
+
|
216
|
+
def assets_path
|
217
|
+
@root.concat(assets).cleanpath.expand_path
|
218
|
+
end
|
219
|
+
|
220
|
+
def contents_path
|
221
|
+
@root.concat(contents).cleanpath.expand_path
|
222
|
+
end
|
223
|
+
|
224
|
+
def layouts_path
|
225
|
+
@root.concat(layouts).cleanpath.expand_path
|
226
|
+
end
|
93
227
|
end
|
94
228
|
end
|
95
229
|
|
96
230
|
class Site
|
97
|
-
def initialize(
|
98
|
-
root
|
99
|
-
|
100
|
-
|
101
|
-
layouts:,
|
102
|
-
view_context_helpers: [],
|
103
|
-
guesses: Renderer.guesses,
|
104
|
-
config: {}
|
105
|
-
)
|
106
|
-
@root = root
|
107
|
-
@assets = assets
|
108
|
-
@contents = contents
|
109
|
-
@layouts = layouts
|
110
|
-
|
111
|
-
@view_context_helpers = view_context_helpers
|
112
|
-
@guessing_registry = guesses
|
113
|
-
|
114
|
-
@config = {
|
115
|
-
index: config.fetch(:index, "index.html")
|
116
|
-
}
|
231
|
+
def initialize(config)
|
232
|
+
@root = config.root
|
233
|
+
|
234
|
+
yield(self)
|
117
235
|
|
118
|
-
@
|
119
|
-
@
|
236
|
+
@view_context_helpers ||= []
|
237
|
+
@guesses ||= Rendering.guesses
|
120
238
|
|
121
239
|
@route_identifiers = RouteIdentifiers.new
|
122
|
-
@asset_blobs = Blobs.new(scoped_root: assets_path, file_abstraction_class: Asset)
|
123
|
-
@content_blobs = Blobs.new(scoped_root: contents_path, file_abstraction_class: Content)
|
124
|
-
@layout_blobs = Blobs.new(scoped_root: layouts_path, file_abstraction_class: Layout)
|
240
|
+
@asset_blobs = Blobs.new(scoped_root: config.source.assets_path, file_abstraction_class: Asset)
|
241
|
+
@content_blobs = Blobs.new(scoped_root: config.source.contents_path, file_abstraction_class: Content)
|
242
|
+
@layout_blobs = Blobs.new(scoped_root: config.source.layouts_path, file_abstraction_class: Layout)
|
125
243
|
end
|
126
244
|
|
127
245
|
attr_reader :root
|
128
|
-
attr_reader :output_root
|
129
|
-
attr_reader :assets_root
|
130
|
-
attr_reader :config
|
131
246
|
|
132
247
|
attr_reader :route_identifiers
|
133
248
|
attr_reader :asset_blobs
|
134
249
|
attr_reader :content_blobs
|
135
250
|
attr_reader :layout_blobs
|
136
251
|
|
137
|
-
|
138
|
-
|
252
|
+
attr_accessor :view_context_helpers
|
253
|
+
attr_accessor :guesses
|
139
254
|
|
140
|
-
def
|
255
|
+
def rules
|
141
256
|
yield(
|
142
257
|
assets: @asset_blobs,
|
143
258
|
layouts: @layout_blobs,
|
@@ -145,7 +260,7 @@ module Confinement
|
|
145
260
|
routes: @route_identifiers
|
146
261
|
)
|
147
262
|
|
148
|
-
guesser = Rendering::Guesser.new(
|
263
|
+
guesser = Rendering::Guesser.new(guesses)
|
149
264
|
guess_renderers(guesser, @layout_blobs)
|
150
265
|
guess_renderers(guesser, @content_blobs)
|
151
266
|
|
@@ -157,6 +272,18 @@ module Confinement
|
|
157
272
|
nil
|
158
273
|
end
|
159
274
|
|
275
|
+
def partial_compilation
|
276
|
+
{ asset_blobs: @asset_blobs }
|
277
|
+
end
|
278
|
+
|
279
|
+
def partial_compilation=(previous_partial_compilation)
|
280
|
+
return if previous_partial_compilation.nil?
|
281
|
+
|
282
|
+
@asset_blobs = previous_partial_compilation.fetch(:asset_blobs)
|
283
|
+
|
284
|
+
nil
|
285
|
+
end
|
286
|
+
|
160
287
|
private
|
161
288
|
|
162
289
|
def guess_renderers(guesser, blobs)
|
@@ -170,18 +297,6 @@ module Confinement
|
|
170
297
|
end
|
171
298
|
end
|
172
299
|
end
|
173
|
-
|
174
|
-
def contents_path
|
175
|
-
@contents_path ||= @root.concat(@contents).cleanpath
|
176
|
-
end
|
177
|
-
|
178
|
-
def layouts_path
|
179
|
-
@layouts_path ||= @root.concat(@layouts).cleanpath
|
180
|
-
end
|
181
|
-
|
182
|
-
def assets_path
|
183
|
-
@assets_path ||= @root.concat(@assets).cleanpath
|
184
|
-
end
|
185
300
|
end
|
186
301
|
|
187
302
|
# RouteIdentifiers is called such because it doesn't hold the actual
|
@@ -515,49 +630,50 @@ module Confinement
|
|
515
630
|
end
|
516
631
|
|
517
632
|
class Compiler
|
518
|
-
def initialize(
|
519
|
-
@
|
520
|
-
@
|
633
|
+
def initialize(config)
|
634
|
+
@config = config
|
635
|
+
@logger = config.logger
|
521
636
|
end
|
522
637
|
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
# to worry about deadlocks or anything
|
528
|
-
@lock.synchronize do
|
529
|
-
# Assets first since it's almost always a dependency of contents
|
530
|
-
compile_assets(site.asset_blobs.send(:lookup))
|
531
|
-
compile_contents(site.route_identifiers.send(:lookup).values)
|
532
|
-
end
|
638
|
+
def compile_everything(site)
|
639
|
+
# Assets first since it's almost always a dependency of contents
|
640
|
+
compile_assets(site)
|
641
|
+
compile_contents(site)
|
533
642
|
end
|
534
643
|
|
535
|
-
private
|
536
|
-
|
537
644
|
PARCEL_FILES_OUTPUT_REGEX = /^✨[^\n]+\n\n(.*)Done in(?:.*)\z/m
|
538
645
|
PARCEL_FILE_OUTPUT_REGEX = /^(?<page>.*?)\s+(?<size>[0-9\.]+\s*[A-Z]?B)\s+(?<time>[0-9\.]+[a-z]?s)$/
|
539
646
|
|
540
|
-
def compile_assets(
|
647
|
+
def compile_assets(site)
|
648
|
+
@logger.info { "compiling assets" }
|
649
|
+
create_destination_directory
|
650
|
+
asset_files = site.asset_blobs.send(:lookup)
|
541
651
|
asset_paths = asset_files.values
|
542
652
|
|
543
|
-
|
653
|
+
command = [
|
544
654
|
"yarn",
|
545
655
|
"run",
|
546
656
|
"parcel",
|
547
657
|
"build",
|
548
658
|
"--no-cache",
|
549
|
-
"--dist-dir",
|
550
|
-
"--public-url",
|
659
|
+
"--dist-dir", @config.compiler.output_assets_path.to_s,
|
660
|
+
"--public-url", @config.compiler.output_assets_path.basename.to_s,
|
551
661
|
*asset_paths.select(&:entrypoint?).map(&:input_path).map(&:to_s)
|
552
|
-
|
662
|
+
]
|
663
|
+
|
664
|
+
@logger.debug { "running: #{command.join(" ")}" }
|
665
|
+
|
666
|
+
out, status = Open3.capture2(*command)
|
553
667
|
|
554
668
|
if !status.success?
|
669
|
+
@logger.fatal { "asset compilation failed" }
|
555
670
|
raise "Asset compilation failed"
|
556
671
|
end
|
557
672
|
|
558
673
|
matches = PARCEL_FILES_OUTPUT_REGEX.match(out)[1]
|
559
674
|
|
560
675
|
if !matches
|
676
|
+
@logger.fatal { "asset compilation ouptut parsing failed" }
|
561
677
|
raise "Asset compilation output parsing failed"
|
562
678
|
end
|
563
679
|
|
@@ -566,30 +682,68 @@ module Confinement
|
|
566
682
|
processed_file_paths.map do |file|
|
567
683
|
output_file, *input_files = file.strip.split(/\n(?:└|├)── /)
|
568
684
|
|
569
|
-
output_path =
|
685
|
+
output_path = @config.root.concat(output_file[PARCEL_FILE_OUTPUT_REGEX, 1])
|
570
686
|
|
571
687
|
input_files.each do |input_file|
|
572
|
-
input_path =
|
688
|
+
input_path = @config.root.concat(input_file[PARCEL_FILE_OUTPUT_REGEX, 1])
|
573
689
|
|
574
690
|
if !asset_files.key?(input_path)
|
575
691
|
next
|
576
692
|
end
|
577
693
|
|
578
|
-
url_path = output_path.relative_path_from(
|
694
|
+
url_path = output_path.relative_path_from(@config.compiler.output_root_path)
|
695
|
+
@logger.debug { "processesd asset: #{input_path}, #{url_path}, #{output_path}" }
|
579
696
|
asset_files[input_path].url_path = url_path.to_s
|
580
697
|
asset_files[input_path].output_path = output_path
|
581
698
|
asset_files[input_path].body = output_path.read
|
582
699
|
end
|
583
700
|
end
|
701
|
+
|
702
|
+
@logger.info { "finished compiling assets" }
|
584
703
|
end
|
585
704
|
|
586
|
-
def compile_contents(
|
705
|
+
def compile_contents(site)
|
706
|
+
@logger.info { "compiling contents" }
|
707
|
+
create_destination_directory
|
708
|
+
contents = site.route_identifiers.send(:lookup).values
|
587
709
|
contents.each do |content|
|
588
|
-
compile_content(content)
|
710
|
+
compile_content(site, content)
|
589
711
|
end
|
712
|
+
@logger.info { "finished compiling contents" }
|
590
713
|
end
|
591
714
|
|
592
|
-
def
|
715
|
+
def partial_compilation_dirty?(before:, after:)
|
716
|
+
return true if !before.key?(:asset_blobs)
|
717
|
+
return true if !after.key?(:asset_blobs)
|
718
|
+
|
719
|
+
before_assets = before[:asset_blobs].send(:lookup)
|
720
|
+
after_assets = after[:asset_blobs].send(:lookup)
|
721
|
+
|
722
|
+
return true if before_assets.keys.sort != after_assets.keys.sort
|
723
|
+
return true if before_assets.any? { |k, v| v.input_path != after_assets[k].input_path }
|
724
|
+
return true if before_assets.any? { |k, v| v.entrypoint? != after_assets[k].entrypoint? }
|
725
|
+
|
726
|
+
false
|
727
|
+
end
|
728
|
+
|
729
|
+
private
|
730
|
+
|
731
|
+
def create_destination_directory
|
732
|
+
destination = @config.compiler.output_root_path
|
733
|
+
|
734
|
+
if destination.exist?
|
735
|
+
return
|
736
|
+
end
|
737
|
+
|
738
|
+
if !destination.dirname.exist?
|
739
|
+
raise Error::PathDoesNotExist, "Destination's parent path does not exist: #{destination.dirname}"
|
740
|
+
end
|
741
|
+
|
742
|
+
destination.mkpath
|
743
|
+
end
|
744
|
+
|
745
|
+
def compile_content(site, content)
|
746
|
+
@logger.debug { "compiling content: #{content.input_path}, #{content.renderers}" }
|
593
747
|
view_context = Rendering::ViewContext.new(
|
594
748
|
routes: site.route_identifiers,
|
595
749
|
layouts: site.layout_blobs,
|
@@ -607,9 +761,9 @@ module Confinement
|
|
607
761
|
|
608
762
|
content.output_path =
|
609
763
|
if content.url_path[-1] == "/"
|
610
|
-
|
764
|
+
@config.compiler.output_root_path.concat(content.url_path, @config.compiler.output_directory_index)
|
611
765
|
else
|
612
|
-
|
766
|
+
@config.compiler.output_root_path.concat(content.url_path)
|
613
767
|
end
|
614
768
|
|
615
769
|
if content.output_path.exist?
|
@@ -618,7 +772,7 @@ module Confinement
|
|
618
772
|
end
|
619
773
|
end
|
620
774
|
|
621
|
-
if
|
775
|
+
if !@config.compiler.output_root_path.include?(content.output_path)
|
622
776
|
return
|
623
777
|
end
|
624
778
|
|
@@ -631,29 +785,4 @@ module Confinement
|
|
631
785
|
nil
|
632
786
|
end
|
633
787
|
end
|
634
|
-
|
635
|
-
class Publish
|
636
|
-
def initialize(site)
|
637
|
-
@site = site
|
638
|
-
@compiler = Compiler.new(@site)
|
639
|
-
end
|
640
|
-
|
641
|
-
def write
|
642
|
-
find_or_raise_or_mkdir(@site.output_root)
|
643
|
-
|
644
|
-
@compiler.compile_everything
|
645
|
-
end
|
646
|
-
|
647
|
-
private
|
648
|
-
|
649
|
-
def find_or_raise_or_mkdir(destination)
|
650
|
-
if !destination.exist?
|
651
|
-
if !destination.dirname.exist?
|
652
|
-
raise Error::PathDoesNotExist, "Destination's parent path does not exist: #{destination.dirname}"
|
653
|
-
end
|
654
|
-
|
655
|
-
destination.mkpath
|
656
|
-
end
|
657
|
-
end
|
658
|
-
end
|
659
788
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 - 2018 Thomas Flemming
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'filewatcher/cycles'
|
4
|
+
|
5
|
+
# Simple file watcher. Detect changes in files and directories.
|
6
|
+
#
|
7
|
+
# Issues: Currently doesn't monitor changes in directorynames
|
8
|
+
class Filewatcher
|
9
|
+
include Filewatcher::Cycles
|
10
|
+
|
11
|
+
attr_accessor :interval
|
12
|
+
attr_reader :keep_watching
|
13
|
+
|
14
|
+
def update_spinner(label)
|
15
|
+
return unless @show_spinner
|
16
|
+
@spinner ||= %w[\\ | / -]
|
17
|
+
print "#{' ' * 30}\r#{label} #{@spinner.rotate!.first}\r"
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(unexpanded_filenames, options = {})
|
21
|
+
@unexpanded_filenames = unexpanded_filenames
|
22
|
+
@unexpanded_excluded_filenames = options[:exclude]
|
23
|
+
@keep_watching = false
|
24
|
+
@pausing = false
|
25
|
+
@immediate = options[:immediate]
|
26
|
+
@show_spinner = options[:spinner]
|
27
|
+
@interval = options.fetch(:interval, 0.5)
|
28
|
+
end
|
29
|
+
|
30
|
+
def watch(&on_update)
|
31
|
+
@on_update = on_update
|
32
|
+
@keep_watching = true
|
33
|
+
yield('', '') if @immediate
|
34
|
+
|
35
|
+
main_cycle
|
36
|
+
|
37
|
+
@end_snapshot = mtime_snapshot
|
38
|
+
finalize(&on_update)
|
39
|
+
ensure
|
40
|
+
stop
|
41
|
+
end
|
42
|
+
|
43
|
+
def pause
|
44
|
+
@pausing = true
|
45
|
+
update_spinner('Initiating pause')
|
46
|
+
# Ensure we wait long enough to enter pause loop in #watch
|
47
|
+
sleep @interval
|
48
|
+
end
|
49
|
+
|
50
|
+
def resume
|
51
|
+
if !@keep_watching || !@pausing
|
52
|
+
raise "Can't resume unless #watch and #pause were first called"
|
53
|
+
end
|
54
|
+
@last_snapshot = mtime_snapshot # resume with fresh snapshot
|
55
|
+
@pausing = false
|
56
|
+
update_spinner('Resuming')
|
57
|
+
sleep @interval # Wait long enough to exit pause loop in #watch
|
58
|
+
end
|
59
|
+
|
60
|
+
# Ends the watch, allowing any remaining changes to be finalized.
|
61
|
+
# Used mainly in multi-threaded situations.
|
62
|
+
def stop
|
63
|
+
@keep_watching = false
|
64
|
+
update_spinner('Stopping')
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# Calls the update block repeatedly until all changes in the
|
69
|
+
# current snapshot are dealt with
|
70
|
+
def finalize(&on_update)
|
71
|
+
on_update = @on_update unless block_given?
|
72
|
+
while filesystem_updated?(@end_snapshot || mtime_snapshot)
|
73
|
+
update_spinner('Finalizing')
|
74
|
+
trigger_changes(on_update)
|
75
|
+
end
|
76
|
+
@end_snapshot = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def last_found_filenames
|
80
|
+
last_snapshot.keys
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def last_snapshot
|
86
|
+
@last_snapshot ||= mtime_snapshot
|
87
|
+
end
|
88
|
+
|
89
|
+
# Takes a snapshot of the current status of watched files.
|
90
|
+
# (Allows avoidance of potential race condition during #finalize)
|
91
|
+
def mtime_snapshot
|
92
|
+
snapshot = {}
|
93
|
+
filenames = expand_directories(@unexpanded_filenames)
|
94
|
+
|
95
|
+
# Remove files in the exclude filenames list
|
96
|
+
filenames -= expand_directories(@unexpanded_excluded_filenames)
|
97
|
+
|
98
|
+
filenames.each do |filename|
|
99
|
+
mtime = File.exist?(filename) ? File.mtime(filename) : Time.new(0)
|
100
|
+
snapshot[filename] = mtime
|
101
|
+
end
|
102
|
+
snapshot
|
103
|
+
end
|
104
|
+
|
105
|
+
def filesystem_updated?(snapshot = mtime_snapshot)
|
106
|
+
@changes = {}
|
107
|
+
|
108
|
+
(snapshot.to_a - last_snapshot.to_a).each do |file, _mtime|
|
109
|
+
@changes[file] = last_snapshot[file] ? :updated : :created
|
110
|
+
end
|
111
|
+
|
112
|
+
(last_snapshot.keys - snapshot.keys).each do |file|
|
113
|
+
@changes[file] = :deleted
|
114
|
+
end
|
115
|
+
|
116
|
+
@last_snapshot = snapshot
|
117
|
+
@changes.any?
|
118
|
+
end
|
119
|
+
|
120
|
+
def expand_directories(patterns)
|
121
|
+
patterns = Array(patterns) unless patterns.is_a? Array
|
122
|
+
expanded_patterns = patterns.map do |pattern|
|
123
|
+
pattern = File.expand_path(pattern)
|
124
|
+
Dir[
|
125
|
+
File.directory?(pattern) ? File.join(pattern, '**', '*') : pattern
|
126
|
+
]
|
127
|
+
end
|
128
|
+
expanded_patterns.flatten!
|
129
|
+
expanded_patterns.uniq!
|
130
|
+
expanded_patterns
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Require at end of file to not overwrite `Filewatcher` class
|
135
|
+
require_relative 'filewatcher/version'
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Filewatcher
|
4
|
+
# Module for all cycles in `Filewatcher#watch`
|
5
|
+
module Cycles
|
6
|
+
private
|
7
|
+
|
8
|
+
def main_cycle
|
9
|
+
while @keep_watching
|
10
|
+
@end_snapshot = mtime_snapshot if @pausing
|
11
|
+
|
12
|
+
pausing_cycle
|
13
|
+
|
14
|
+
watching_cycle
|
15
|
+
|
16
|
+
# test and clear @changes to prevent yielding the last
|
17
|
+
# changes twice if @keep_watching has just been set to false
|
18
|
+
trigger_changes
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def pausing_cycle
|
23
|
+
while @keep_watching && @pausing
|
24
|
+
update_spinner('Pausing')
|
25
|
+
sleep @interval
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def watching_cycle
|
30
|
+
while @keep_watching && !filesystem_updated? && !@pausing
|
31
|
+
update_spinner('Watching')
|
32
|
+
sleep @interval
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def trigger_changes(on_update = @on_update)
|
37
|
+
thread = Thread.new do
|
38
|
+
on_update.call(@changes.dup)
|
39
|
+
@changes.clear
|
40
|
+
end
|
41
|
+
thread.join
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/confinement/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: confinement
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Ahn
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-04-
|
11
|
+
date: 2020-04-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '5.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name: pry
|
56
|
+
name: pry
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
@@ -80,6 +80,48 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: zeitwerk
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.3'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.3'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rack
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.2'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.2'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: puma
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '4.3'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '4.3'
|
83
125
|
description:
|
84
126
|
email:
|
85
127
|
- engineering@zachahn.com
|
@@ -102,6 +144,10 @@ files:
|
|
102
144
|
- confinement.gemspec
|
103
145
|
- exe/confinement
|
104
146
|
- lib/confinement.rb
|
147
|
+
- lib/confinement/filewatcher/LICENSE
|
148
|
+
- lib/confinement/filewatcher/filewatcher.rb
|
149
|
+
- lib/confinement/filewatcher/filewatcher/cycles.rb
|
150
|
+
- lib/confinement/filewatcher/filewatcher/version.rb
|
105
151
|
- lib/confinement/version.rb
|
106
152
|
homepage: https://github.com/zachahn/confinement
|
107
153
|
licenses:
|