fsorg 0.3.0 → 0.4.0

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