fsorg 0.0.0 → 0.2.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.
- checksums.yaml +4 -4
- data/bin/fsorg +29 -0
- data/lib/fsorg.rb +147 -73
- metadata +59 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ddcf3941d7eb4d9fc41fff221b3b178121564c52a8bfe29aabc2b30a8e0e70d7
|
4
|
+
data.tar.gz: a7b50828bdccb1a2afc8ed7911fec2c5ef6f4ebe846d86be774438c7480c8e10
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de9ff6a647a0640cac2cc1d7ea297834b70cc9eab702f2be37565acf340cfdaaa9d35353edd6a86979131c18686f348840d7716c597d7516613f1be5e937553f
|
7
|
+
data.tar.gz: 238a5c6b2d33689d66675ea1204b0babd74da5513471c8addd1f90263e8073680c464211d16b69bde824d11e49d7e6aa48a35cf6eb3af4f7b78653a8e4754450
|
data/bin/fsorg
CHANGED
@@ -1,4 +1,33 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
require "docopt"
|
3
|
+
require "yaml"
|
2
4
|
require "fsorg"
|
3
5
|
|
6
|
+
args = Docopt.docopt <<-DOC
|
7
|
+
Usage:
|
8
|
+
fsorg [options] <filepath>
|
9
|
+
fsorg [options] <data> <filepath>
|
4
10
|
|
11
|
+
Options:
|
12
|
+
-h --help Show this screen.
|
13
|
+
-r --root=ROOT_DIRECTORY Set the root directory.
|
14
|
+
-n --dry-run Show what would be done, but don't do it.
|
15
|
+
-q --quiet Don't show actions performed.
|
16
|
+
-v --verbose Show details about actions performed.
|
17
|
+
|
18
|
+
Actions performed:
|
19
|
+
Paths are shown relative to the root directory.
|
20
|
+
+ Create a directory
|
21
|
+
$ Run a command
|
22
|
+
> Write a file
|
23
|
+
DOC
|
24
|
+
|
25
|
+
filepath = Pathname.new args["<filepath>"]
|
26
|
+
document = File.new(filepath).read.strip
|
27
|
+
data_raw = args["<data>"] || "{}"
|
28
|
+
data = YAML.load data_raw, symbolize_names: true
|
29
|
+
root_directory = Pathname.new(args["--root"] || "")
|
30
|
+
|
31
|
+
fsorg = Fsorg.new root_directory, data, document, Pathname.new(Dir.pwd).join(filepath)
|
32
|
+
fsorg.preprocess
|
33
|
+
fsorg.walk args["--dry-run"], args["--quiet"], args["--verbose"]
|
data/lib/fsorg.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
require "docopt"
|
2
1
|
require "colorize"
|
3
2
|
require "set"
|
4
3
|
require "shellwords"
|
5
4
|
require "yaml"
|
6
5
|
|
7
|
-
PERMISSIONS_PATTERN = /[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+/
|
6
|
+
PERMISSIONS_PATTERN = /[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+/
|
8
7
|
|
9
8
|
def d(thing)
|
10
9
|
p thing
|
@@ -21,10 +20,11 @@ class Fsorg
|
|
21
20
|
@data = { :HERE => @root_directory.to_s }.merge data
|
22
21
|
@document = document
|
23
22
|
@current_line = 0
|
23
|
+
@current_depth = -1
|
24
24
|
end
|
25
25
|
|
26
|
-
def
|
27
|
-
|
26
|
+
def from_relative_to_root(path)
|
27
|
+
@root_directory / path
|
28
28
|
end
|
29
29
|
|
30
30
|
def depth_change(line)
|
@@ -38,35 +38,14 @@ class Fsorg
|
|
38
38
|
change
|
39
39
|
end
|
40
40
|
|
41
|
-
def self.from_command_line
|
42
|
-
args = Docopt.docopt <<-DOC
|
43
|
-
Usage:
|
44
|
-
fsorg [options] <filepath>
|
45
|
-
fsorg [options] <data> <filepath>
|
46
|
-
|
47
|
-
Options:
|
48
|
-
-h --help Show this screen.
|
49
|
-
-v --version Show version.
|
50
|
-
-r --root=ROOT_DIRECTORY Set the root directory.
|
51
|
-
DOC
|
52
|
-
|
53
|
-
filepath = Pathname.new args["<filepath>"]
|
54
|
-
document = File.new(filepath).read.strip
|
55
|
-
data_raw = args["<data>"] || "{}"
|
56
|
-
data = YAML.load data_raw, symbolize_names: true
|
57
|
-
root_directory = Pathname.new(args["--root"] || "")
|
58
|
-
|
59
|
-
return Fsorg.new root_directory, data, document, Pathname.new(Dir.pwd).join(filepath)
|
60
|
-
end
|
61
|
-
|
62
41
|
def preprocess
|
63
42
|
process_front_matter
|
43
|
+
strip_comments
|
64
44
|
desugar
|
65
45
|
process_includes
|
66
46
|
# TODO process_see
|
67
47
|
process_for
|
68
48
|
process_if
|
69
|
-
turn_writes_into_runs
|
70
49
|
turn_puts_into_runs
|
71
50
|
ask_missing_variables
|
72
51
|
process_root
|
@@ -103,15 +82,40 @@ class Fsorg
|
|
103
82
|
|
104
83
|
def desugar
|
105
84
|
output = []
|
85
|
+
inside_write_directive_shorthand = false
|
106
86
|
@document.lines(chomp: true).each_with_index do |line, index|
|
107
87
|
@current_line = index + 1
|
108
|
-
if
|
88
|
+
if inside_write_directive_shorthand
|
89
|
+
if line.strip == "]"
|
90
|
+
inside_write_directive_shorthand = false
|
91
|
+
output << "}"
|
92
|
+
else
|
93
|
+
output << line
|
94
|
+
end
|
95
|
+
elsif /^\}(\s*ELSE\s*\{)$/.match line.strip
|
109
96
|
output << "}"
|
110
97
|
output << $~[1]
|
98
|
+
elsif /^FILE\s+(?<filename>.+?)$/ =~ line.strip
|
99
|
+
output << "RUN touch #{filename.shellescape}"
|
111
100
|
elsif /^(\s*[^{]+?\{)([^{]+?)\}$/.match line.strip
|
112
101
|
output << $~[1]
|
113
102
|
output << $~[2]
|
114
103
|
output << "}"
|
104
|
+
elsif /^(?<filename>.+?)\s*(\s+\((?<permissions>#{PERMISSIONS_PATTERN.to_s})\))?\s*\[$/.match line.strip
|
105
|
+
output << "WRITE #{$~[:filename]} " + ($~[:permissions] ? "MODE #{$~[:permissions]}" : "") + " {"
|
106
|
+
inside_write_directive_shorthand = true
|
107
|
+
else
|
108
|
+
output << line
|
109
|
+
end
|
110
|
+
end
|
111
|
+
@document = output.join "\n"
|
112
|
+
end
|
113
|
+
|
114
|
+
def strip_comments
|
115
|
+
output = []
|
116
|
+
@document.lines(chomp: true).each_with_index do |line, index|
|
117
|
+
if /^(?<content>.*)#\s.*$/ =~ line
|
118
|
+
output << content
|
115
119
|
else
|
116
120
|
output << line
|
117
121
|
end
|
@@ -128,7 +132,7 @@ class Fsorg
|
|
128
132
|
filepath = @document_path.parent.join(line.sub /^INCLUDE /, "")
|
129
133
|
included_raw = File.new(filepath).read.strip
|
130
134
|
included_fsorg = Fsorg.new(@root_directory, @data, included_raw, filepath)
|
131
|
-
included_fsorg.preprocess
|
135
|
+
included_fsorg.preprocess
|
132
136
|
@data = included_fsorg.data.merge @data
|
133
137
|
output += included_fsorg.document.lines(chomp: true)
|
134
138
|
else
|
@@ -139,39 +143,9 @@ class Fsorg
|
|
139
143
|
@document = output.join "\n"
|
140
144
|
end
|
141
145
|
|
142
|
-
def
|
146
|
+
def store_writes
|
143
147
|
output = []
|
144
|
-
|
145
|
-
compact = nil
|
146
|
-
current_content = []
|
147
|
-
destination = nil
|
148
|
-
permissions = nil
|
149
|
-
|
150
|
-
@document.lines(chomp: true).each_with_index do |line, index|
|
151
|
-
@current_line = index + 1
|
152
|
-
if inside_write_directive
|
153
|
-
if line.strip == (compact ? "]" : "}")
|
154
|
-
inside_write_directive = false
|
155
|
-
content = deindent(current_content.join("\n")).gsub "\n", '\n'
|
156
|
-
content = content.shellescape.gsub('\{\{', "{{").gsub('\}\}', "}}")
|
157
|
-
output << "RUN" + (destination.include?("/") ? "mkdir -p #{Pathname.new(destination.strip).parent.to_s.shellescape}" : "") + "echo -ne #{content} > #{destination.strip.shellescape}" + (permissions ? " && chmod #{permissions} #{destination.strip.shellescape}" : "")
|
158
|
-
else
|
159
|
-
current_content << line
|
160
|
-
end
|
161
|
-
elsif /^WRITE(\s+INTO)?\s+(?<destination>.+?)(?:\s+MODE\s+(?<permissions>.+?))?\s*\{$/ =~ line.strip
|
162
|
-
inside_write_directive = true
|
163
|
-
compact = false
|
164
|
-
elsif /^(?<destination>.+)(\s*\((?<permissions>#{PERMISSIONS_PATTERN})\))?\s*\[$/ =~ line.strip
|
165
|
-
# Shouldn't be needed, as =~ should assign to destination, but heh, it doesn't work for some reason ¯\_(ツ)_/¯
|
166
|
-
if destination.nil?
|
167
|
-
destination = $~[:destination]
|
168
|
-
end
|
169
|
-
inside_write_directive = true
|
170
|
-
compact = true
|
171
|
-
else
|
172
|
-
output << line
|
173
|
-
end
|
174
|
-
end
|
148
|
+
|
175
149
|
|
176
150
|
@document = output.join "\n"
|
177
151
|
end
|
@@ -181,8 +155,8 @@ class Fsorg
|
|
181
155
|
@document.lines(chomp: true).each_with_index do |line, index|
|
182
156
|
@current_line = index + 1
|
183
157
|
if /^PUT\s+(?<source>.+?)(\s+AS\s+(?<destination>.+?))?(\s+MODE\s+(?<permissions>.+?))?$/.match(line.strip)
|
184
|
-
output << "RUN install -D \"$
|
185
|
-
" -
|
158
|
+
output << "RUN install -D \"$DOCUMENT_DIR/#{$~[:source]}\" #{($~[:destination] || $~[:source]).shellescape}" + (if $~[:permissions]
|
159
|
+
" -m #{$~[:permissions].shellescape}"
|
186
160
|
else
|
187
161
|
""
|
188
162
|
end)
|
@@ -344,29 +318,121 @@ class Fsorg
|
|
344
318
|
@data[:HERE]
|
345
319
|
end
|
346
320
|
|
347
|
-
def walk
|
321
|
+
def walk(dry_run, quiet, verbose)
|
348
322
|
current_path = [@root_directory]
|
349
|
-
current_path_as_pathname = -> { current_path.reduce(Pathname.new "") { |path, fragment| path.join fragment } }
|
350
323
|
@data[:HERE] = @root_directory
|
324
|
+
file_to_write = {}
|
325
|
+
inside_write_directive = -> { !file_to_write.keys.empty? }
|
326
|
+
current_path_as_pathname = -> { current_path.reduce(Pathname.new "") { |path, fragment| path.join fragment } }
|
327
|
+
|
328
|
+
if verbose
|
329
|
+
puts "Data is #{@data.except :HERE}".light_black
|
330
|
+
end
|
351
331
|
|
352
332
|
@document.lines(chomp: true).each_with_index do |line, index|
|
353
333
|
@current_line = index + 1
|
354
|
-
|
334
|
+
@current_depth = current_path.length - 1
|
335
|
+
|
355
336
|
@data.each do |key, value|
|
356
337
|
line = line.gsub "{{#{key}}}", value.to_s
|
357
338
|
end
|
358
339
|
|
359
|
-
if
|
360
|
-
|
361
|
-
|
362
|
-
|
340
|
+
if inside_write_directive.()
|
341
|
+
if line.strip == "}"
|
342
|
+
do_write file_to_write, dry_run, quiet
|
343
|
+
file_to_write = {}
|
344
|
+
else
|
345
|
+
file_to_write[:content] += line + "\n"
|
346
|
+
end
|
347
|
+
elsif /^WRITE(\s+INTO)?\s+(?<destination>.+?)(?:\s+MODE\s+(?<permissions>.+?))?\s*\{$/.match line.strip
|
348
|
+
file_to_write = $~.named_captures.transform_keys(&:to_sym)
|
349
|
+
file_to_write[:content] = ""
|
363
350
|
elsif /^(?<leaf>.+?)\s+\{/ =~ line.strip
|
364
351
|
current_path << leaf
|
365
352
|
@data[:HERE] = current_path_as_pathname.()
|
366
|
-
|
353
|
+
if verbose
|
354
|
+
puts "currenly #{current_path.map { |fragment| fragment.to_s }.join " -> "}".light_black
|
355
|
+
end
|
356
|
+
do_mkpath current_location, dry_run, quiet
|
357
|
+
elsif line.strip == "}"
|
358
|
+
current_path.pop
|
359
|
+
@data[:HERE] = current_path_as_pathname.()
|
360
|
+
if verbose
|
361
|
+
puts "currenly #{current_path.map { |fragment| fragment.to_s }.join " -> "}".light_black
|
362
|
+
end
|
363
|
+
elsif /^RUN\s+(?<command>.+?)$/ =~ line.strip
|
364
|
+
environment = {
|
365
|
+
"FSORG_ROOT" => @root_directory.to_s,
|
366
|
+
"HERE" => @data[:HERE].relative_path_from(@root_directory).to_s,
|
367
|
+
"CWD" => Dir.pwd,
|
368
|
+
"DOCUMENT_DIR" => @document_path.parent.to_s,
|
369
|
+
}
|
370
|
+
do_run command, current_location, environment, dry_run, quiet, verbose
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def do_mkpath(path, dry_run, quiet)
|
376
|
+
unless quiet
|
377
|
+
puts "#{" " * @current_depth}+ ".cyan.bold + path.relative_path_from(@root_directory).to_s
|
378
|
+
end
|
379
|
+
unless dry_run
|
380
|
+
path.mkpath
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
def do_write(future_file, dry_run, quiet)
|
385
|
+
indentation = " " * @current_depth
|
386
|
+
dest = from_relative_to_root(current_location) / future_file[:destination]
|
387
|
+
unless dest.parent.relative_path_from(@root_directory).to_s == "."
|
388
|
+
do_mkpath dest.parent, dry_run, quiet
|
389
|
+
end
|
390
|
+
|
391
|
+
unless quiet
|
392
|
+
puts "#{indentation}> ".cyan.bold + dest.relative_path_from(@root_directory).to_s + (future_file[:permissions] ? " mode #{future_file[:permissions]}".yellow : "")
|
393
|
+
end
|
394
|
+
unless dry_run
|
395
|
+
dest.write deindent replace_data future_file[:content]
|
396
|
+
# Not using dest.chmod as the syntax for permissions is more than just integers,
|
397
|
+
# and matches in fact the exact syntax of chmod's argument, per the manpage, chmod(1) (line "Each MODE is of the form…")
|
398
|
+
`chmod #{future_file[:permissions]} #{dest}` if future_file[:permissions]
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
def replace_data(content)
|
403
|
+
content.gsub /\{\{(?<variable>[^}]+?)\}\}/ do |interpolation|
|
404
|
+
variable = interpolation[2..-3]
|
405
|
+
@data[variable.to_sym]
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
def do_run(command, inside, environment, dry_run, quiet, verbose)
|
410
|
+
indentation = " " * @current_depth
|
411
|
+
unless quiet
|
412
|
+
puts "#{indentation}$ ".cyan.bold + command + (verbose ? " at #{inside.relative_path_from(@root_directory)}".light_blue + " with ".light_black + (format_environment_hash environment) : "")
|
413
|
+
end
|
414
|
+
unless dry_run
|
415
|
+
stdout, stdout_w = IO.pipe
|
416
|
+
stderr, stderr_w = IO.pipe
|
417
|
+
|
418
|
+
system environment, command, { :chdir => inside.to_s, :out => stdout_w, :err => stderr_w }
|
419
|
+
stdout_w.close
|
420
|
+
stderr_w.close
|
421
|
+
|
422
|
+
stdout.read.each_line(chomp: true) do |line|
|
423
|
+
puts " " + indentation + line
|
424
|
+
end
|
425
|
+
stderr.read.each_line(chomp: true) do |line|
|
426
|
+
puts " " + indentation + line.red
|
367
427
|
end
|
368
428
|
end
|
369
429
|
end
|
430
|
+
|
431
|
+
def format_environment_hash(environment)
|
432
|
+
"{ ".light_black + (environment.map do |key, value|
|
433
|
+
"$#{key}".red + "=".light_black + "\"#{value}\"".green
|
434
|
+
end.join ", ".light_black) + " }".light_black
|
435
|
+
end
|
370
436
|
end
|
371
437
|
|
372
438
|
def deindent(text)
|
@@ -386,6 +452,14 @@ def deindent(text)
|
|
386
452
|
end.join "\n"
|
387
453
|
end
|
388
454
|
|
389
|
-
|
390
|
-
|
391
|
-
|
455
|
+
def capture_output
|
456
|
+
old_stdout = $stdout
|
457
|
+
old_stderr = $stderr
|
458
|
+
$stdout = StringIO.new
|
459
|
+
$stderr = StringIO.new
|
460
|
+
yield
|
461
|
+
return $stdout.string, $stderr.string
|
462
|
+
ensure
|
463
|
+
$stdout = old_stdout
|
464
|
+
$stderr = old_stderr
|
465
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fsorg
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ewen Le Bihan
|
@@ -9,7 +9,63 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 2022-04-27 00:00:00.000000000 Z
|
12
|
-
dependencies:
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: docopt
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: shellwords
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.1.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mustache
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: colorize
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.8.1
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.8.1
|
13
69
|
description:
|
14
70
|
email: hey@ewen.works
|
15
71
|
executables:
|
@@ -31,7 +87,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
31
87
|
requirements:
|
32
88
|
- - ">="
|
33
89
|
- !ruby/object:Gem::Version
|
34
|
-
version:
|
90
|
+
version: 3.0.0
|
35
91
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
92
|
requirements:
|
37
93
|
- - ">="
|