fsorg 0.3.0 → 0.4.0

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 (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/fsorg.rb +102 -113
  3. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e1ce86ca67be5d602db8974b0741160b2b3c5d9c2036af4c335c565de0729529
4
- data.tar.gz: f305cfa72e0b889ceca6dda5e6046d82aabf37ed2049657c97f7c5e2654281da
3
+ metadata.gz: 7c401683bfc45360ee97ed03f82c6b4175991662e03b553189a7a1766838b578
4
+ data.tar.gz: f95ab0033bb60452eda403e6cd39ed8d7c84e3f66ed27693f98d5eab5663c7a3
5
5
  SHA512:
6
- metadata.gz: '080d45289d3ddcab4f72b467cd40f5958c0dc94983f84738498201f62eec41ff98438e7019e8c3389efc030176f21a9fe757a96a7f95598927063602ab285535'
7
- data.tar.gz: 8cf2527ec7f16ac0bf2cd63d5d2ad4c241a783f5135d911320cbce438728cac46214c4529133581739bd4e67269ba6ff1e2b74f5dafdbd833750af7e08f7bf9f
6
+ metadata.gz: 83d7e4eae3fd10ab1acca9d12b35880075a33dc4d19cc3e5ecc7c792860dfbd16fcebe401ffd93fc395199b4f6d7a5116bc8061e59ff9f61eb9f25202599c5b4
7
+ data.tar.gz: a8795af243ce4767ebeb885b3cc2dc9765a809da18ce2edb62293a58cd1eee5559b472cc3963fedb04179b01b08200caed39dc74a6b6fdb6c40fd541dea5597c
data/lib/fsorg.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "colorize"
2
4
  require "set"
3
5
  require "shellwords"
@@ -7,17 +9,17 @@ PERMISSIONS_PATTERN = /[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+/
7
9
 
8
10
  def d(thing)
9
11
  p thing
10
- return thing
12
+ thing
11
13
  end
12
14
 
13
- class Fsorg
15
+ class Fsorg # rubocop:disable Metrics/ClassLength,Style/Documentation
14
16
  attr_accessor :data, :document, :document_path, :root_directory
15
17
 
16
18
  def initialize(root_directory, data, document, absolute_path)
17
19
  @root_directory = root_directory
18
20
  @shell = "/usr/bin/env bash"
19
21
  @document_path = absolute_path
20
- @data = { :HERE => @root_directory.to_s }.merge data
22
+ @data = { HERE: @root_directory.to_s }.merge data
21
23
  @document = document
22
24
  @current_line = 0
23
25
  @current_depth = -1
@@ -29,10 +31,10 @@ class Fsorg
29
31
 
30
32
  def depth_change(line)
31
33
  change = 0
32
- if /\}$/ =~ line
34
+ if /\}\s*$/ =~ line
33
35
  change -= 1
34
36
  end
35
- if /^\{/ =~ line
37
+ if /^\s*\{/ =~ line
36
38
  change += 1
37
39
  end
38
40
  change
@@ -43,7 +45,7 @@ class Fsorg
43
45
  strip_comments
44
46
  desugar
45
47
  process_includes
46
- # TODO process_see
48
+ # TODO: process_see
47
49
  process_for
48
50
  process_if
49
51
  turn_puts_into_runs
@@ -52,13 +54,14 @@ class Fsorg
52
54
  process_shell
53
55
  end
54
56
 
55
- def process_front_matter
57
+ def process_front_matter # rubocop:disable Metrics/MethodLength
56
58
  unless /^-+$/ =~ @document.lines(chomp: true).first
57
59
  return
58
60
  end
59
61
 
60
62
  # Remove front matter from document
61
- front_matter_raw, rest = "", ""
63
+ front_matter_raw = ""
64
+ rest = ""
62
65
  inside_front_matter = false
63
66
  @document.lines(chomp: true).each_with_index do |line, index|
64
67
  @current_linecur = index + 1
@@ -68,19 +71,19 @@ class Fsorg
68
71
  end
69
72
 
70
73
  if inside_front_matter
71
- front_matter_raw += line + "\n"
74
+ front_matter_raw += "#{line}\n"
72
75
  else
73
- rest += line + "\n"
76
+ rest += "#{line}\n"
74
77
  end
75
78
  end
76
79
 
77
- front_matter = YAML.load(front_matter_raw, symbolize_names: true) or {}
80
+ front_matter = YAML.safe_load(front_matter_raw, symbolize_names: true) or {}
78
81
 
79
82
  @data = front_matter.merge @data
80
83
  @document = rest
81
84
  end
82
85
 
83
- def desugar
86
+ def desugar # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
84
87
  output = []
85
88
  inside_write_directive_shorthand = false
86
89
  @document.lines(chomp: true).each_with_index do |line, index|
@@ -101,8 +104,8 @@ class Fsorg
101
104
  output << $~[1]
102
105
  output << $~[2]
103
106
  output << "}"
104
- elsif /^(?<filename>.+?)\s*(\s+\((?<permissions>#{PERMISSIONS_PATTERN.to_s})\))?\s*\[$/.match line.strip
105
- output << "WRITE #{$~[:filename]} " + ($~[:permissions] ? "MODE #{$~[:permissions]}" : "") + " {"
107
+ elsif /^(?<filename>.+?)\s*(\s+\((?<permissions>#{PERMISSIONS_PATTERN})\))?\s*\[$/.match line.strip
108
+ output << "WRITE #{$~[:filename]} #{$~[:permissions] ? "MODE #{$~[:permissions]}" : ""} {"
106
109
  inside_write_directive_shorthand = true
107
110
  else
108
111
  output << line
@@ -113,23 +116,23 @@ class Fsorg
113
116
 
114
117
  def strip_comments
115
118
  output = []
116
- @document.lines(chomp: true).each_with_index do |line, index|
117
- if /^(?<content>.*)#\s.*$/ =~ line
118
- output << content
119
+ @document.lines(chomp: true).each_with_index do |line, _index|
120
+ output << if /^(?<content>.*)#\s.*$/ =~ line
121
+ content
119
122
  else
120
- output << line
123
+ line
121
124
  end
122
125
  end
123
126
  @document = output.join "\n"
124
127
  end
125
128
 
126
- def process_includes
129
+ def process_includes # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
127
130
  output = []
128
131
 
129
132
  @document.lines(chomp: true).each_with_index do |line, index|
130
133
  @current_line = index + 1
131
134
  if line.start_with? "INCLUDE "
132
- filepath = @document_path.parent.join(line.sub /^INCLUDE /, "")
135
+ filepath = @document_path.parent.join(line.sub(/^INCLUDE /, ""))
133
136
  included_raw = File.new(filepath).read.strip
134
137
  included_fsorg = Fsorg.new(@root_directory, @data, included_raw, filepath)
135
138
  included_fsorg.preprocess
@@ -149,25 +152,25 @@ class Fsorg
149
152
  @document = output.join "\n"
150
153
  end
151
154
 
152
- def turn_puts_into_runs
155
+ def turn_puts_into_runs # rubocop:disable Metrics/MethodLength
153
156
  output = []
154
157
  @document.lines(chomp: true).each_with_index do |line, index|
155
158
  @current_line = index + 1
156
- if /^PUT\s+(?<source>.+?)(\s+AS\s+(?<destination>.+?))?(\s+MODE\s+(?<permissions>.+?))?$/.match(line.strip)
157
- output << "RUN install -D \"$DOCUMENT_DIR/#{$~[:source]}\" #{($~[:destination] || $~[:source]).shellescape}" + (if $~[:permissions]
159
+ output << if /^PUT\s+(?<source>.+?)(\s+AS\s+(?<destination>.+?))?(\s+MODE\s+(?<permissions>.+?))?$/.match(line.strip) # rubocop:disable Lint/MixedRegexpCaptureTypes
160
+ "RUN install -D \"$FSORG_ROOT/#{$~[:source]}\" #{($~[:destination] || $~[:source]).shellescape}" + (if $~[:permissions]
158
161
  " -m #{$~[:permissions].shellescape}"
159
162
  else
160
163
  ""
161
164
  end)
162
165
  else
163
- output << line
166
+ line
164
167
  end
165
168
  end
166
169
 
167
170
  @document = output.join "\n"
168
171
  end
169
172
 
170
- def process_for
173
+ def process_for # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
171
174
  output = []
172
175
  inside_for_directive = false
173
176
  body = []
@@ -177,7 +180,7 @@ class Fsorg
177
180
  @current_line = index + 1
178
181
  if inside_for_directive
179
182
  current_depth += depth_change line.strip
180
- if current_depth == 0
183
+ if current_depth.zero?
181
184
  inside_for_directive = false
182
185
  output += repeat_for_each(args[:iteratee], args[:iterator], body)
183
186
  else
@@ -195,14 +198,11 @@ class Fsorg
195
198
  end
196
199
  end
197
200
 
198
- def repeat_for_each(iteratee, iterator, directives)
201
+ def repeat_for_each(iteratee, iterator, directives) # rubocop:disable Metrics/AbcSize
199
202
  output = []
200
- unless data[iterator.to_sym]
201
- raise "[#{@document_path}:#{@current_line}]".colorize :red + "Variable '#{iterator}' not found (iterators cannot be asked for interactively). Available variables at this point: #{data.keys.join(", ")}."
202
- end
203
- unless data[iterator.to_sym].is_a? Array
204
- raise "[#{@document_path}:#{@current_line}]".colorize :red + "Cannot iterate over '#{iterator}', which is of type #{data[iterator.to_sym].class}."
205
- end
203
+ raise "[#{@document_path}:#{@current_line}]".colorize :red + "Variable '#{iterator}' not found (iterators cannot be asked for interactively). Available variables at this point: #{data.keys.join(", ")}." unless data[iterator.to_sym]
204
+ raise "[#{@document_path}:#{@current_line}]".colorize :red + "Cannot iterate over '#{iterator}', which is of type #{data[iterator.to_sym].class}." unless data[iterator.to_sym].is_a? Array
205
+
206
206
  data[iterator.to_sym].each do |item|
207
207
  output += directives.map do |directive|
208
208
  directive.gsub "{{#{iteratee}}}", item.to_s
@@ -211,7 +211,7 @@ class Fsorg
211
211
  output
212
212
  end
213
213
 
214
- def process_if
214
+ def process_if # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
215
215
  output = []
216
216
  inside_if = false
217
217
  body_if = []
@@ -220,11 +220,11 @@ class Fsorg
220
220
  current_condition = nil
221
221
  current_depth = 0
222
222
 
223
- @document.lines(chomp: true).each_with_index do |line, index|
223
+ @document.lines(chomp: true).each_with_index do |line, index| # rubocop:disable Metrics/BlockLength
224
224
  @current_line = index + 1
225
225
  if inside_if
226
226
  current_depth += depth_change line.strip
227
- if current_depth == 0
227
+ if current_depth.zero?
228
228
  inside_if = false
229
229
  inside_else = false
230
230
  output += body_if if evaluates_to_true? current_condition
@@ -233,7 +233,7 @@ class Fsorg
233
233
  end
234
234
  elsif inside_else
235
235
  current_depth += depth_change line.strip
236
- if current_depth == 0
236
+ if current_depth.zero?
237
237
  inside_else = false
238
238
  inside_if = false
239
239
  output += body_else if evaluates_to_false? current_condition
@@ -246,9 +246,8 @@ class Fsorg
246
246
  current_depth = 1
247
247
  inside_if = true
248
248
  elsif /^(\}\s*)?ELSE\s*\{$/.match(line.strip)
249
- if current_condition.nil?
250
- raise "[#{@document_path}:#{@current_line}] Cannot use ELSE without IF."
251
- end
249
+ raise "[#{@document_path}:#{@current_line}] Cannot use ELSE without IF." if current_condition.nil?
250
+
252
251
  inside_else = true
253
252
  current_depth = 1
254
253
  else
@@ -263,29 +262,25 @@ class Fsorg
263
262
  end
264
263
 
265
264
  def evaluates_to_true?(condition)
266
- unless @data.include? condition.to_sym
267
- raise "[#{@document_path}:#{@current_line}] Variable '#{condition}' not found. Available variables at this point: #{data.keys.join(", ")}."
268
- end
265
+ raise "[#{@document_path}:#{@current_line}] Variable '#{condition}' not found. Available variables at this point: #{data.keys.join(", ")}." unless @data.include? condition.to_sym
269
266
 
270
267
  @data[condition.to_sym]
271
268
  end
272
269
 
273
270
  def ask_missing_variables
274
- @document.scan /\{\{(?<variable>[^}]+?)\}\}/ do |variable|
275
- unless @data.include? variable[0].to_sym
276
- @data[variable[0].to_sym] = :ask
277
- end
271
+ @document.scan(/\{\{(?<variable>[^}]+?)\}\}/) do |variable|
272
+ @data[variable[0].to_sym] = :ask unless @data.include? variable[0].to_sym
278
273
  end
279
274
 
280
275
  @data.each do |key, value|
281
276
  if value == :ask
282
277
  print "#{key}? "
283
- @data[key] = YAML.load STDIN.gets.chomp
278
+ @data[key] = YAML.safe_load $stdin.gets.chomp
284
279
  end
285
280
  end
286
281
  end
287
282
 
288
- def process_root
283
+ def process_root # rubocop:disable Metrics/MethodLength
289
284
  @document = @document.lines(chomp: true).map.with_index do |line, index|
290
285
  @current_line = index + 1
291
286
  if /^ROOT\s+(?<root>.+?)$/.match line.strip
@@ -317,18 +312,16 @@ class Fsorg
317
312
  @data[:HERE]
318
313
  end
319
314
 
320
- def walk(dry_run, quiet, verbose)
315
+ def walk(dry_run, quiet, verbose) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
321
316
  current_path = [@root_directory]
322
317
  @data[:HERE] = @root_directory
323
318
  file_to_write = {}
324
319
  inside_write_directive = -> { !file_to_write.keys.empty? }
325
- current_path_as_pathname = -> { current_path.reduce(Pathname.new "") { |path, fragment| path.join fragment } }
320
+ current_path_as_pathname = -> { current_path.reduce(Pathname.new("")) { |path, fragment| path.join fragment } }
326
321
 
327
- if verbose
328
- puts "Data is #{@data.except :HERE}".light_black
329
- end
322
+ puts "Data is #{@data.except :HERE}".light_black if verbose
330
323
 
331
- @document.lines(chomp: true).each_with_index do |line, index|
324
+ @document.lines(chomp: true).each_with_index do |line, index| # rubocop:disable Metrics/BlockLength
332
325
  @current_line = index + 1
333
326
  @current_depth = current_path.length - 1
334
327
 
@@ -336,29 +329,25 @@ class Fsorg
336
329
  line = line.gsub "{{#{key}}}", value.to_s
337
330
  end
338
331
 
339
- if inside_write_directive.()
332
+ if inside_write_directive.call
340
333
  if line.strip == "}"
341
334
  do_write file_to_write, dry_run, quiet
342
335
  file_to_write = {}
343
336
  else
344
- file_to_write[:content] += line + "\n"
337
+ file_to_write[:content] += "#{line}\n"
345
338
  end
346
- elsif /^WRITE(\s+INTO)?\s+(?<destination>.+?)(?:\s+MODE\s+(?<permissions>.+?))?\s*\{$/.match line.strip
339
+ elsif /^WRITE(\s+INTO)?\s+(?<destination>.+?)(?:\s+MODE\s+(?<permissions>.+?))?\s*\{$/.match line.strip # rubocop:disable Lint/MixedRegexpCaptureTypes
347
340
  file_to_write = $~.named_captures.transform_keys(&:to_sym)
348
341
  file_to_write[:content] = ""
349
342
  elsif /^(?<leaf>.+?)\s+\{/ =~ line.strip
350
343
  current_path << leaf
351
- @data[:HERE] = current_path_as_pathname.()
352
- if verbose
353
- puts "currenly #{current_path.map { |fragment| fragment.to_s }.join " -> "}".light_black
354
- end
344
+ @data[:HERE] = current_path_as_pathname.call
345
+ puts "currenly #{current_path.map(&:to_s).join " -> "}".light_black if verbose
355
346
  do_mkpath current_location, dry_run, quiet
356
347
  elsif line.strip == "}"
357
348
  current_path.pop
358
- @data[:HERE] = current_path_as_pathname.()
359
- if verbose
360
- puts "currenly #{current_path.map { |fragment| fragment.to_s }.join " -> "}".light_black
361
- end
349
+ @data[:HERE] = current_path_as_pathname.call
350
+ puts "currenly #{current_path.map(&:to_s).join " -> "}".light_black if verbose
362
351
  elsif /^RUN\s+(?<command>.+?)$/ =~ line.strip
363
352
  environment = {
364
353
  "FSORG_ROOT" => @root_directory.to_s,
@@ -372,58 +361,54 @@ class Fsorg
372
361
  end
373
362
 
374
363
  def do_mkpath(path, dry_run, quiet)
375
- unless quiet
376
- puts "#{" " * @current_depth}+ ".cyan.bold + path.relative_path_from(@root_directory).to_s
377
- end
378
- unless dry_run
379
- path.mkpath
380
- end
364
+ puts "#{" " * @current_depth}+ ".cyan.bold + path.relative_path_from(@root_directory).to_s unless quiet
365
+ path.mkpath unless dry_run
381
366
  end
382
367
 
383
- def do_write(future_file, dry_run, quiet)
368
+ def do_write(future_file, dry_run, quiet) # rubocop:disable Metrics/AbcSize
384
369
  indentation = " " * @current_depth
385
370
  dest = from_relative_to_root(current_location) / future_file[:destination]
386
- unless dest.parent.relative_path_from(@root_directory).to_s == "."
387
- do_mkpath dest.parent, dry_run, quiet
388
- end
371
+ do_mkpath dest.parent, dry_run, quiet unless dest.parent.relative_path_from(@root_directory).to_s == "."
389
372
 
390
373
  unless quiet
391
374
  puts "#{indentation}> ".cyan.bold + dest.relative_path_from(@root_directory).to_s + (future_file[:permissions] ? " mode #{future_file[:permissions]}".yellow : "")
392
375
  end
393
- unless dry_run
394
- dest.write ensure_final_newline deindent replace_data future_file[:content]
395
- # Not using dest.chmod as the syntax for permissions is more than just integers,
396
- # and matches in fact the exact syntax of chmod's argument, per the manpage, chmod(1) (line "Each MODE is of the form…")
397
- `chmod #{future_file[:permissions]} #{dest}` if future_file[:permissions]
398
- end
376
+
377
+ return if dry_run
378
+
379
+ dest.write ensure_final_newline(dedent(replace_data(future_file[:content])))
380
+ # Not using dest.chmod as the syntax for permissions is more than just integers,
381
+ # and matches in fact the exact syntax of chmod's argument, per the manpage, chmod(1) (line "Each MODE is of the form…")
382
+ `chmod #{future_file[:permissions]} #{dest}` if future_file[:permissions]
399
383
  end
400
384
 
401
385
  def replace_data(content)
402
- content.gsub /\{\{(?<variable>[^}]+?)\}\}/ do |interpolation|
386
+ content.gsub(/\{\{(?<variable>[^}]+?)\}\}/) do |interpolation|
403
387
  variable = interpolation[2..-3]
404
388
  @data[variable.to_sym]
405
389
  end
406
390
  end
407
391
 
408
- def do_run(command, inside, environment, dry_run, quiet, verbose)
392
+ def do_run(command, inside, environment, dry_run, quiet, verbose) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/ParameterLists
409
393
  indentation = " " * @current_depth
410
394
  unless quiet
411
395
  puts "#{indentation}$ ".cyan.bold + command + (verbose ? " at #{inside.relative_path_from(@root_directory)}".light_blue + " with ".light_black + (format_environment_hash environment) : "")
412
396
  end
413
- unless dry_run
414
- stdout, stdout_w = IO.pipe
415
- stderr, stderr_w = IO.pipe
416
397
 
417
- system environment, command, { :chdir => inside.to_s, :out => stdout_w, :err => stderr_w }
418
- stdout_w.close
419
- stderr_w.close
398
+ return if dry_run
420
399
 
421
- stdout.read.each_line(chomp: true) do |line|
422
- puts " " + indentation + line
423
- end
424
- stderr.read.each_line(chomp: true) do |line|
425
- puts " " + indentation + line.red
426
- end
400
+ stdout, stdout_w = IO.pipe
401
+ stderr, stderr_w = IO.pipe
402
+
403
+ system environment, command, { chdir: inside.to_s, out: stdout_w, err: stderr_w }
404
+ stdout_w.close
405
+ stderr_w.close
406
+
407
+ stdout.read.each_line(chomp: true) do |line|
408
+ puts " #{indentation}#{line}"
409
+ end
410
+ stderr.read.each_line(chomp: true) do |line|
411
+ puts " #{indentation}#{line.red}"
427
412
  end
428
413
  end
429
414
 
@@ -434,28 +419,32 @@ class Fsorg
434
419
  end
435
420
  end
436
421
 
437
- def deindent(text)
438
- using_tabs = text.lines(chomp: true).any? { |line| /^\t/ =~ line }
439
- indenting_with = using_tabs ? "\t" : " "
440
- depth = text.lines.map do |line|
441
- count = 0
442
- until line[count] != indenting_with
443
- count += 1
422
+ def dedent(text) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
423
+ lines = text.split "\n"
424
+ return text if lines.empty?
425
+
426
+ indents = lines.map do |line|
427
+ if line =~ /\S/
428
+ line.start_with?(" ") ? line.match(/^ +/).offset(0)[1] : 0
444
429
  end
445
- count
446
- end.min
447
- pattern = /^#{using_tabs ? '\t' : " "}{#{depth}}/
430
+ end
431
+ indents.compact!
432
+ if indents.empty?
433
+ # No lines had any non-whitespace characters.
434
+ return ([""] * lines.size).join "\n"
435
+ end
436
+
437
+ min_indent = indents.min
438
+ return text if min_indent.zero?
448
439
 
449
- text.lines(chomp: true).map do |line|
450
- line.sub pattern, ""
451
- end.join "\n"
440
+ lines.map { |line| line =~ /\S/ ? line.gsub(/^ {#{min_indent}}/, "") : line }.join "\n"
452
441
  end
453
442
 
454
443
  def ensure_final_newline(text)
455
- unless text.end_with? "\n"
456
- text + "\n"
457
- else
444
+ if text.end_with? "\n"
458
445
  text
446
+ else
447
+ "#{text}\n"
459
448
  end
460
449
  end
461
450
 
@@ -465,7 +454,7 @@ def capture_output
465
454
  $stdout = StringIO.new
466
455
  $stderr = StringIO.new
467
456
  yield
468
- return $stdout.string, $stderr.string
457
+ [$stdout.string, $stderr.string]
469
458
  ensure
470
459
  $stdout = old_stdout
471
460
  $stderr = old_stderr
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fsorg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ewen Le Bihan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-28 00:00:00.000000000 Z
11
+ date: 2024-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: docopt
@@ -94,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
96
  requirements: []
97
- rubygems_version: 3.3.8
97
+ rubygems_version: 3.3.25
98
98
  signing_key:
99
99
  specification_version: 4
100
100
  summary: Create directories from a file that describes them