fsorg 0.2.2 → 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 +109 -113
  3. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68068de4061b604e9b71e8134e9f17dd1d902c0a6e7c269cbfbb0c13d6dd5d24
4
- data.tar.gz: bd84015820aedcd7a7510bdc75d17655552159ff22713a0d63f396950e36e6aa
3
+ metadata.gz: 7c401683bfc45360ee97ed03f82c6b4175991662e03b553189a7a1766838b578
4
+ data.tar.gz: f95ab0033bb60452eda403e6cd39ed8d7c84e3f66ed27693f98d5eab5663c7a3
5
5
  SHA512:
6
- metadata.gz: 61a442489d374f4274df638384f9955323398930f6b002a550e4f288ddfffb68c1d4e596d4eb9f60c5730f57994bb64269325906ae52dec3d93153a92bb9b9d3
7
- data.tar.gz: 293cbf041418dcd7b05fc63234ec5de5505c2e38c3efff48322d15525cc6240fb761deb380dcdec1bf51b39e92844852bcf0d656d5b2bdf118dbb1b98f80e156
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,26 +116,26 @@ 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
- included_fsorg.preprocess
138
+ included_fsorg.preprocess
136
139
  @data = included_fsorg.data.merge @data
137
140
  output += included_fsorg.document.lines(chomp: true)
138
141
  else
@@ -145,30 +148,29 @@ class Fsorg
145
148
 
146
149
  def store_writes
147
150
  output = []
148
-
149
151
 
150
152
  @document = output.join "\n"
151
153
  end
152
154
 
153
- def turn_puts_into_runs
155
+ def turn_puts_into_runs # rubocop:disable Metrics/MethodLength
154
156
  output = []
155
157
  @document.lines(chomp: true).each_with_index do |line, index|
156
158
  @current_line = index + 1
157
- if /^PUT\s+(?<source>.+?)(\s+AS\s+(?<destination>.+?))?(\s+MODE\s+(?<permissions>.+?))?$/.match(line.strip)
158
- 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]
159
161
  " -m #{$~[:permissions].shellescape}"
160
162
  else
161
163
  ""
162
164
  end)
163
165
  else
164
- output << line
166
+ line
165
167
  end
166
168
  end
167
169
 
168
170
  @document = output.join "\n"
169
171
  end
170
172
 
171
- def process_for
173
+ def process_for # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
172
174
  output = []
173
175
  inside_for_directive = false
174
176
  body = []
@@ -178,7 +180,7 @@ class Fsorg
178
180
  @current_line = index + 1
179
181
  if inside_for_directive
180
182
  current_depth += depth_change line.strip
181
- if current_depth == 0
183
+ if current_depth.zero?
182
184
  inside_for_directive = false
183
185
  output += repeat_for_each(args[:iteratee], args[:iterator], body)
184
186
  else
@@ -196,14 +198,11 @@ class Fsorg
196
198
  end
197
199
  end
198
200
 
199
- def repeat_for_each(iteratee, iterator, directives)
201
+ def repeat_for_each(iteratee, iterator, directives) # rubocop:disable Metrics/AbcSize
200
202
  output = []
201
- unless data[iterator.to_sym]
202
- 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(", ")}."
203
- end
204
- unless data[iterator.to_sym].is_a? Array
205
- raise "[#{@document_path}:#{@current_line}]".colorize :red + "Cannot iterate over '#{iterator}', which is of type #{data[iterator.to_sym].class}."
206
- 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
+
207
206
  data[iterator.to_sym].each do |item|
208
207
  output += directives.map do |directive|
209
208
  directive.gsub "{{#{iteratee}}}", item.to_s
@@ -212,7 +211,7 @@ class Fsorg
212
211
  output
213
212
  end
214
213
 
215
- def process_if
214
+ def process_if # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
216
215
  output = []
217
216
  inside_if = false
218
217
  body_if = []
@@ -221,11 +220,11 @@ class Fsorg
221
220
  current_condition = nil
222
221
  current_depth = 0
223
222
 
224
- @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
225
224
  @current_line = index + 1
226
225
  if inside_if
227
226
  current_depth += depth_change line.strip
228
- if current_depth == 0
227
+ if current_depth.zero?
229
228
  inside_if = false
230
229
  inside_else = false
231
230
  output += body_if if evaluates_to_true? current_condition
@@ -234,7 +233,7 @@ class Fsorg
234
233
  end
235
234
  elsif inside_else
236
235
  current_depth += depth_change line.strip
237
- if current_depth == 0
236
+ if current_depth.zero?
238
237
  inside_else = false
239
238
  inside_if = false
240
239
  output += body_else if evaluates_to_false? current_condition
@@ -247,9 +246,8 @@ class Fsorg
247
246
  current_depth = 1
248
247
  inside_if = true
249
248
  elsif /^(\}\s*)?ELSE\s*\{$/.match(line.strip)
250
- if current_condition.nil?
251
- raise "[#{@document_path}:#{@current_line}] Cannot use ELSE without IF."
252
- end
249
+ raise "[#{@document_path}:#{@current_line}] Cannot use ELSE without IF." if current_condition.nil?
250
+
253
251
  inside_else = true
254
252
  current_depth = 1
255
253
  else
@@ -264,29 +262,25 @@ class Fsorg
264
262
  end
265
263
 
266
264
  def evaluates_to_true?(condition)
267
- unless @data.include? condition.to_sym
268
- raise "[#{@document_path}:#{@current_line}] Variable '#{condition}' not found. Available variables at this point: #{data.keys.join(", ")}."
269
- 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
270
266
 
271
267
  @data[condition.to_sym]
272
268
  end
273
269
 
274
270
  def ask_missing_variables
275
- @document.scan /\{\{(?<variable>[^}]+?)\}\}/ do |variable|
276
- unless @data.include? variable[0].to_sym
277
- @data[variable[0].to_sym] = :ask
278
- end
271
+ @document.scan(/\{\{(?<variable>[^}]+?)\}\}/) do |variable|
272
+ @data[variable[0].to_sym] = :ask unless @data.include? variable[0].to_sym
279
273
  end
280
274
 
281
275
  @data.each do |key, value|
282
276
  if value == :ask
283
277
  print "#{key}? "
284
- @data[key] = YAML.load STDIN.gets.chomp
278
+ @data[key] = YAML.safe_load $stdin.gets.chomp
285
279
  end
286
280
  end
287
281
  end
288
282
 
289
- def process_root
283
+ def process_root # rubocop:disable Metrics/MethodLength
290
284
  @document = @document.lines(chomp: true).map.with_index do |line, index|
291
285
  @current_line = index + 1
292
286
  if /^ROOT\s+(?<root>.+?)$/.match line.strip
@@ -318,18 +312,16 @@ class Fsorg
318
312
  @data[:HERE]
319
313
  end
320
314
 
321
- def walk(dry_run, quiet, verbose)
315
+ def walk(dry_run, quiet, verbose) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
322
316
  current_path = [@root_directory]
323
317
  @data[:HERE] = @root_directory
324
318
  file_to_write = {}
325
319
  inside_write_directive = -> { !file_to_write.keys.empty? }
326
- 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 } }
327
321
 
328
- if verbose
329
- puts "Data is #{@data.except :HERE}".light_black
330
- end
322
+ puts "Data is #{@data.except :HERE}".light_black if verbose
331
323
 
332
- @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
333
325
  @current_line = index + 1
334
326
  @current_depth = current_path.length - 1
335
327
 
@@ -337,29 +329,25 @@ class Fsorg
337
329
  line = line.gsub "{{#{key}}}", value.to_s
338
330
  end
339
331
 
340
- if inside_write_directive.()
332
+ if inside_write_directive.call
341
333
  if line.strip == "}"
342
334
  do_write file_to_write, dry_run, quiet
343
335
  file_to_write = {}
344
336
  else
345
- file_to_write[:content] += line + "\n"
337
+ file_to_write[:content] += "#{line}\n"
346
338
  end
347
- 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
348
340
  file_to_write = $~.named_captures.transform_keys(&:to_sym)
349
341
  file_to_write[:content] = ""
350
342
  elsif /^(?<leaf>.+?)\s+\{/ =~ line.strip
351
343
  current_path << leaf
352
- @data[:HERE] = current_path_as_pathname.()
353
- if verbose
354
- puts "currenly #{current_path.map { |fragment| fragment.to_s }.join " -> "}".light_black
355
- end
344
+ @data[:HERE] = current_path_as_pathname.call
345
+ puts "currenly #{current_path.map(&:to_s).join " -> "}".light_black if verbose
356
346
  do_mkpath current_location, dry_run, quiet
357
347
  elsif line.strip == "}"
358
348
  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
349
+ @data[:HERE] = current_path_as_pathname.call
350
+ puts "currenly #{current_path.map(&:to_s).join " -> "}".light_black if verbose
363
351
  elsif /^RUN\s+(?<command>.+?)$/ =~ line.strip
364
352
  environment = {
365
353
  "FSORG_ROOT" => @root_directory.to_s,
@@ -373,58 +361,54 @@ class Fsorg
373
361
  end
374
362
 
375
363
  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
364
+ puts "#{" " * @current_depth}+ ".cyan.bold + path.relative_path_from(@root_directory).to_s unless quiet
365
+ path.mkpath unless dry_run
382
366
  end
383
367
 
384
- def do_write(future_file, dry_run, quiet)
368
+ def do_write(future_file, dry_run, quiet) # rubocop:disable Metrics/AbcSize
385
369
  indentation = " " * @current_depth
386
370
  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
371
+ do_mkpath dest.parent, dry_run, quiet unless dest.parent.relative_path_from(@root_directory).to_s == "."
390
372
 
391
373
  unless quiet
392
374
  puts "#{indentation}> ".cyan.bold + dest.relative_path_from(@root_directory).to_s + (future_file[:permissions] ? " mode #{future_file[:permissions]}".yellow : "")
393
375
  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
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]
400
383
  end
401
384
 
402
385
  def replace_data(content)
403
- content.gsub /\{\{(?<variable>[^}]+?)\}\}/ do |interpolation|
386
+ content.gsub(/\{\{(?<variable>[^}]+?)\}\}/) do |interpolation|
404
387
  variable = interpolation[2..-3]
405
- @data[variable.to_sym]
388
+ @data[variable.to_sym]
406
389
  end
407
390
  end
408
391
 
409
- 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
410
393
  indentation = " " * @current_depth
411
394
  unless quiet
412
395
  puts "#{indentation}$ ".cyan.bold + command + (verbose ? " at #{inside.relative_path_from(@root_directory)}".light_blue + " with ".light_black + (format_environment_hash environment) : "")
413
396
  end
414
- unless dry_run
415
- stdout, stdout_w = IO.pipe
416
- stderr, stderr_w = IO.pipe
417
397
 
418
- system environment, command, { :chdir => inside.to_s, :out => stdout_w, :err => stderr_w }
419
- stdout_w.close
420
- stderr_w.close
398
+ return if dry_run
421
399
 
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
427
- 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}"
428
412
  end
429
413
  end
430
414
 
@@ -435,21 +419,33 @@ class Fsorg
435
419
  end
436
420
  end
437
421
 
438
- def deindent(text)
439
- using_tabs = text.lines(chomp: true).any? { |line| /^\t/ =~ line }
440
- indenting_with = using_tabs ? "\t" : " "
441
- depth = text.lines.map do |line|
442
- count = 0
443
- until line[count] != indenting_with
444
- 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
445
429
  end
446
- count
447
- end.min
448
- 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
449
436
 
450
- text.lines(chomp: true).map do |line|
451
- line.sub pattern, ""
452
- end.join "\n"
437
+ min_indent = indents.min
438
+ return text if min_indent.zero?
439
+
440
+ lines.map { |line| line =~ /\S/ ? line.gsub(/^ {#{min_indent}}/, "") : line }.join "\n"
441
+ end
442
+
443
+ def ensure_final_newline(text)
444
+ if text.end_with? "\n"
445
+ text
446
+ else
447
+ "#{text}\n"
448
+ end
453
449
  end
454
450
 
455
451
  def capture_output
@@ -458,7 +454,7 @@ def capture_output
458
454
  $stdout = StringIO.new
459
455
  $stderr = StringIO.new
460
456
  yield
461
- return $stdout.string, $stderr.string
457
+ [$stdout.string, $stderr.string]
462
458
  ensure
463
459
  $stdout = old_stdout
464
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.2.2
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-27 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