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