markdown_exec 1.3.1 → 1.3.3.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/.rubocop.yml +21 -4
- data/CHANGELOG.md +45 -1
- data/Gemfile +8 -5
- data/Gemfile.lock +125 -29
- data/README.md +5 -80
- data/Rakefile +108 -24
- data/bin/tab_completion.sh +2 -2
- data/lib/cached_nested_file_reader.rb +115 -0
- data/lib/colorize.rb +4 -0
- data/lib/env.rb +8 -3
- data/lib/env_opts.rb +242 -0
- data/lib/environment_opt_parse.rb +28 -19
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +593 -281
- data/lib/menu.yml +70 -4
- data/lib/object_present.rb +44 -1
- data/lib/rspec_helpers.rb +10 -0
- data/lib/tap.rb +81 -17
- metadata +5 -3
- data/lib/globfiles.rb +0 -40
data/Rakefile
CHANGED
@@ -30,10 +30,10 @@ require_relative 'lib/tap'
|
|
30
30
|
include CLI
|
31
31
|
|
32
32
|
include Tap
|
33
|
-
tap_config envvar: MarkdownExec::TAP_DEBUG
|
34
33
|
|
35
34
|
RuboCop::RakeTask.new do |task|
|
36
35
|
task.requires << 'rubocop-minitest'
|
36
|
+
task.requires << 'rubocop-rspec'
|
37
37
|
end
|
38
38
|
|
39
39
|
desc 'named task because minitest not included in rubocop tests'
|
@@ -42,7 +42,7 @@ task :rubocopminitest do
|
|
42
42
|
end
|
43
43
|
|
44
44
|
task default: %i[test reek rubocop rubocopminitest]
|
45
|
-
# task default: %i[
|
45
|
+
# task default: %i[rspec test reek rubocop rubocopminitest]
|
46
46
|
|
47
47
|
# task :default => :build
|
48
48
|
|
@@ -73,6 +73,12 @@ task :clean do
|
|
73
73
|
system 'rm *.gem'
|
74
74
|
end
|
75
75
|
|
76
|
+
desc 'minitest'
|
77
|
+
task :minitest do
|
78
|
+
puts `bundle exec ruby ./lib/object_present.rb`
|
79
|
+
puts `bundle exec ruby ./lib/cached_nested_file_reader.rb`
|
80
|
+
end
|
81
|
+
|
76
82
|
desc 'reek'
|
77
83
|
task :reek do
|
78
84
|
`reek --config .reek .`
|
@@ -237,15 +243,58 @@ task :update_menu_yml do
|
|
237
243
|
|
238
244
|
## secondary options
|
239
245
|
#
|
246
|
+
{
|
247
|
+
arg_name: 'BOOL',
|
248
|
+
default: false,
|
249
|
+
description: 'Display only blocks of type "bash"',
|
250
|
+
env_var: 'MDE_BASH_ONLY',
|
251
|
+
opt_name: 'bash_only',
|
252
|
+
procname: 'val_as_bool'
|
253
|
+
},
|
240
254
|
{
|
241
255
|
arg_name: "INT.#{DISPLAY_LEVEL_BASE}-#{DISPLAY_LEVEL_MAX}",
|
242
256
|
default: DISPLAY_LEVEL_DEFAULT,
|
243
|
-
description:
|
257
|
+
description: 'Output display level ' \
|
258
|
+
"(#{DISPLAY_LEVEL_BASE} to #{DISPLAY_LEVEL_MAX} " \
|
259
|
+
'[data, +context, +info])',
|
244
260
|
env_var: 'MDE_DISPLAY_LEVEL',
|
245
261
|
long_name: 'display-level',
|
246
262
|
opt_name: 'display_level',
|
247
263
|
procname: 'val_as_int'
|
248
264
|
},
|
265
|
+
{
|
266
|
+
arg_name: 'REGEX',
|
267
|
+
default: nil,
|
268
|
+
description: 'Exclude blocks with name matching',
|
269
|
+
env_var: 'MDE_EXCLUDE_BY_NAME_REGEX',
|
270
|
+
opt_name: 'exclude_by_name_regex',
|
271
|
+
procname: 'val_as_str'
|
272
|
+
},
|
273
|
+
{
|
274
|
+
arg_name: 'REGEX',
|
275
|
+
default: nil,
|
276
|
+
description: 'Exclude blocks with shell matching',
|
277
|
+
env_var: 'MDE_EXCLUDE_BY_SHELL_REGEX',
|
278
|
+
opt_name: 'exclude_by_shell_regex',
|
279
|
+
procname: 'val_as_str'
|
280
|
+
},
|
281
|
+
{
|
282
|
+
arg_name: 'BOOL',
|
283
|
+
default: true,
|
284
|
+
description: 'Hide all blocks of type "expect"',
|
285
|
+
env_var: 'MDE_EXCLUDE_EXPECT_BLOCKS',
|
286
|
+
opt_name: 'exclude_expect_blocks',
|
287
|
+
procname: 'val_as_bool'
|
288
|
+
},
|
289
|
+
{
|
290
|
+
arg_name: 'BOOL',
|
291
|
+
default: true,
|
292
|
+
description: 'Exclude blocks with name matching expression " \
|
293
|
+
"`block_name_hidden_match`',
|
294
|
+
env_var: 'MDE_HIDE_BLOCKS_BY_NAME',
|
295
|
+
opt_name: 'hide_blocks_by_name',
|
296
|
+
procname: 'val_as_bool'
|
297
|
+
},
|
249
298
|
{
|
250
299
|
arg_name: 'INT.1-',
|
251
300
|
default: 32,
|
@@ -260,7 +309,6 @@ task :update_menu_yml do
|
|
260
309
|
default: MarkdownExec::BIN_NAME,
|
261
310
|
description: 'Name prefix for stdout files',
|
262
311
|
env_var: 'MDE_LOGGED_STDOUT_FILENAME_PREFIX',
|
263
|
-
# long_name: 'logged-stdout-filename-prefix',
|
264
312
|
opt_name: 'logged_stdout_filename_prefix',
|
265
313
|
procname: 'val_as_str'
|
266
314
|
},
|
@@ -269,7 +317,6 @@ task :update_menu_yml do
|
|
269
317
|
default: false,
|
270
318
|
description: 'Display document name in block selection menu',
|
271
319
|
env_var: 'MDE_MENU_BLOCKS_WITH_DOCNAME',
|
272
|
-
# long_name: 'menu-blocks-with-docname',
|
273
320
|
opt_name: 'menu_blocks_with_docname',
|
274
321
|
procname: 'val_as_bool'
|
275
322
|
},
|
@@ -278,10 +325,25 @@ task :update_menu_yml do
|
|
278
325
|
default: false,
|
279
326
|
description: 'Display headings (levels 1,2,3) in block selection menu',
|
280
327
|
env_var: 'MDE_MENU_BLOCKS_WITH_HEADINGS',
|
281
|
-
# long_name: 'menu-blocks-with-headings',
|
282
328
|
opt_name: 'menu_blocks_with_headings',
|
283
329
|
procname: 'val_as_bool'
|
284
330
|
},
|
331
|
+
{
|
332
|
+
arg_name: 'BOOL',
|
333
|
+
default: true,
|
334
|
+
description: 'Display Exit option at top of menu',
|
335
|
+
env_var: 'MDE_MENU_EXIT_AT_TOP',
|
336
|
+
opt_name: 'menu_exit_at_top',
|
337
|
+
procname: 'val_as_bool'
|
338
|
+
},
|
339
|
+
{
|
340
|
+
arg_name: 'BOOL',
|
341
|
+
default: true,
|
342
|
+
description: 'Display Exit option in menu',
|
343
|
+
env_var: 'MDE_MENU_WITH_EXIT',
|
344
|
+
opt_name: 'menu_with_exit',
|
345
|
+
procname: 'val_as_bool'
|
346
|
+
},
|
285
347
|
{
|
286
348
|
arg_name: 'BOOL',
|
287
349
|
default: false,
|
@@ -291,6 +353,14 @@ task :update_menu_yml do
|
|
291
353
|
opt_name: 'output_execution_summary',
|
292
354
|
procname: 'val_as_bool'
|
293
355
|
},
|
356
|
+
{
|
357
|
+
arg_name: 'BOOL',
|
358
|
+
default: false,
|
359
|
+
description: 'Output saved script filename at end of execution',
|
360
|
+
env_var: 'MDE_OUTPUT_SAVED_SCRIPT_FILENAME',
|
361
|
+
opt_name: 'output_saved_script_filename',
|
362
|
+
procname: 'val_as_bool'
|
363
|
+
},
|
294
364
|
{
|
295
365
|
arg_name: 'BOOL',
|
296
366
|
default: false,
|
@@ -332,7 +402,6 @@ task :update_menu_yml do
|
|
332
402
|
default: 0o755,
|
333
403
|
description: 'chmod for saved scripts',
|
334
404
|
env_var: 'MDE_SAVED_SCRIPT_CHMOD',
|
335
|
-
# long_name: 'saved-script-chmod',
|
336
405
|
opt_name: 'saved_script_chmod',
|
337
406
|
procname: 'val_as_int'
|
338
407
|
},
|
@@ -341,7 +410,6 @@ task :update_menu_yml do
|
|
341
410
|
default: MarkdownExec::BIN_NAME,
|
342
411
|
description: 'Name prefix for saved scripts',
|
343
412
|
env_var: 'MDE_SAVED_SCRIPT_FILENAME_PREFIX',
|
344
|
-
# long_name: 'saved-script-filename-prefix',
|
345
413
|
opt_name: 'saved_script_filename_prefix',
|
346
414
|
procname: 'val_as_str'
|
347
415
|
},
|
@@ -359,7 +427,6 @@ task :update_menu_yml do
|
|
359
427
|
default: 'mde_*.sh',
|
360
428
|
description: 'Glob matching saved scripts',
|
361
429
|
env_var: 'MDE_SAVED_SCRIPT_GLOB',
|
362
|
-
# long_name: 'saved-script-glob',
|
363
430
|
opt_name: 'saved_script_glob',
|
364
431
|
procname: 'val_as_str'
|
365
432
|
},
|
@@ -377,11 +444,25 @@ task :update_menu_yml do
|
|
377
444
|
default: 'mde_*.out.txt',
|
378
445
|
description: 'Glob matching saved outputs',
|
379
446
|
env_var: 'MDE_SAVED_STDOUT_GLOB',
|
380
|
-
# long_name: 'saved-stdout-glob',
|
381
447
|
opt_name: 'saved_stdout_glob',
|
382
448
|
procname: 'val_as_str'
|
383
449
|
},
|
384
|
-
|
450
|
+
{
|
451
|
+
arg_name: 'REGEX',
|
452
|
+
default: nil,
|
453
|
+
description: 'Select blocks with name matching',
|
454
|
+
env_var: 'MDE_SELECT_BY_NAME_REGEX',
|
455
|
+
opt_name: 'select_by_name_regex',
|
456
|
+
procname: 'val_as_str'
|
457
|
+
},
|
458
|
+
{
|
459
|
+
arg_name: 'REGEX',
|
460
|
+
default: nil,
|
461
|
+
description: 'Select blocks with shell matching',
|
462
|
+
env_var: 'MDE_SELECT_BY_SHELL_REGEX',
|
463
|
+
opt_name: 'select_by_shell_regex',
|
464
|
+
procname: 'val_as_str'
|
465
|
+
},
|
385
466
|
{
|
386
467
|
default: '^[\(\[].*[\)\]]$',
|
387
468
|
description: 'Pattern for blocks to hide from user-selection',
|
@@ -408,13 +489,13 @@ task :update_menu_yml do
|
|
408
489
|
procname: 'val_as_str'
|
409
490
|
},
|
410
491
|
{
|
411
|
-
default: '<(?<full>(?<type>\$)?(?<name>[A-Za-
|
492
|
+
default: '<(?<full>(?<type>\$)?(?<name>[A-Za-z_\-\.\w]+))',
|
412
493
|
env_var: 'MDE_BLOCK_STDIN_SCAN',
|
413
494
|
opt_name: 'block_stdin_scan',
|
414
495
|
procname: 'val_as_str'
|
415
496
|
},
|
416
497
|
{
|
417
|
-
default: '>(?<full>(?<type>\$)?(?<name>[A-Za-
|
498
|
+
default: '>(?<full>(?<type>\$)?(?<name>[A-Za-z_\-\.\w]+))',
|
418
499
|
env_var: 'MDE_BLOCK_STDOUT_SCAN',
|
419
500
|
opt_name: 'block_stdout_scan',
|
420
501
|
procname: 'val_as_str'
|
@@ -432,7 +513,7 @@ task :update_menu_yml do
|
|
432
513
|
procname: 'val_as_str'
|
433
514
|
},
|
434
515
|
{
|
435
|
-
default: '^`{3,}(?<shell>[^`\s]*) *(?<
|
516
|
+
default: '^`{3,}(?<shell>[^`\s]*) *:?(?<name>[^\s]*) *(?<rest>.*) *$',
|
436
517
|
env_var: 'MDE_FENCED_START_EX_MATCH',
|
437
518
|
opt_name: 'fenced_start_ex_match',
|
438
519
|
procname: 'val_as_str'
|
@@ -455,6 +536,12 @@ task :update_menu_yml do
|
|
455
536
|
opt_name: 'heading3_match',
|
456
537
|
procname: 'val_as_str'
|
457
538
|
},
|
539
|
+
{
|
540
|
+
default: '^ *@import (.+)$',
|
541
|
+
env_var: 'MDE_IMPORT_PATTERN',
|
542
|
+
opt_name: 'import_pattern',
|
543
|
+
procname: 'val_as_str'
|
544
|
+
},
|
458
545
|
{
|
459
546
|
default: '*.[Mm][Dd]',
|
460
547
|
env_var: 'MDE_MD_FILENAME_GLOB',
|
@@ -605,18 +692,12 @@ task :update_menu_yml do
|
|
605
692
|
env_var: 'MDE_OUTPUT_DIVIDER_COLOR',
|
606
693
|
opt_name: 'output_divider_color',
|
607
694
|
procname: 'val_as_str'
|
608
|
-
# },
|
609
|
-
# {
|
610
|
-
# default: '',
|
611
|
-
# description: '',
|
612
|
-
# env_var: 'MDE_PROMPT_',
|
613
|
-
# opt_name: 'prompt_',
|
614
|
-
# procname: 'val_as_str'
|
615
695
|
}
|
616
696
|
]
|
617
697
|
|
618
698
|
File.write(MENU_YML,
|
619
|
-
"# #{MarkdownExec::APP_NAME} - #{MarkdownExec::APP_DESC}
|
699
|
+
"# #{MarkdownExec::APP_NAME} - #{MarkdownExec::APP_DESC} " \
|
700
|
+
"(#{MarkdownExec::VERSION})\n" +
|
620
701
|
menu_options.to_yaml)
|
621
702
|
puts `stat #{MENU_YML}`
|
622
703
|
end
|
@@ -630,10 +711,13 @@ def update_tab_completion(target)
|
|
630
711
|
|
631
712
|
svhs = YAML.load File.open(MENU_YML)
|
632
713
|
svhs.each do |svh|
|
633
|
-
svh[:compreply] = CLI
|
714
|
+
svh[:compreply] = CLI.value_for_cli(svh[:default]) if svh[:compreply].nil?
|
634
715
|
end.tap_inspect name: :svhs, type: :yaml
|
635
716
|
|
636
|
-
File.write target,
|
717
|
+
File.write target,
|
718
|
+
ERB.new(File.read(filespec = File.join(BF,
|
719
|
+
'tab_completion.sh.erb')))
|
720
|
+
.result(binding)
|
637
721
|
puts `stat #{filespec}`
|
638
722
|
end
|
639
723
|
|
data/bin/tab_completion.sh
CHANGED
@@ -13,7 +13,7 @@ __filedirs_all()
|
|
13
13
|
}
|
14
14
|
|
15
15
|
_mde_echo_version() {
|
16
|
-
echo "1.3.1"
|
16
|
+
echo "1.3.3.1"
|
17
17
|
}
|
18
18
|
|
19
19
|
_mde() {
|
@@ -138,4 +138,4 @@ _mde() {
|
|
138
138
|
|
139
139
|
complete -o filenames -o nospace -F _mde mde
|
140
140
|
# _mde_echo_version
|
141
|
-
# echo "Updated:
|
141
|
+
# echo "Updated: 2023-10-04 01:23:26 UTC"
|
@@ -0,0 +1,115 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# encoding=utf-8
|
5
|
+
|
6
|
+
# version 2023-10-03
|
7
|
+
|
8
|
+
require 'bundler/setup'
|
9
|
+
Bundler.require(:default)
|
10
|
+
|
11
|
+
##
|
12
|
+
# The CachedNestedFileReader class provides functionality to read file lines with the ability
|
13
|
+
# to process '#import filename' directives. When such a directive is encountered in a file,
|
14
|
+
# the corresponding 'filename' is read and its contents are inserted at that location.
|
15
|
+
# This class caches read files to avoid re-reading the same file multiple times.
|
16
|
+
# It allows clients to read lines with or without providing a block.
|
17
|
+
#
|
18
|
+
class CachedNestedFileReader
|
19
|
+
def initialize(import_pattern: /^ *#import (.+)$/)
|
20
|
+
@file_cache = {}
|
21
|
+
@import_pattern = import_pattern
|
22
|
+
end
|
23
|
+
|
24
|
+
def readlines(filename, &block)
|
25
|
+
if @file_cache.key?(filename)
|
26
|
+
@file_cache[filename].each(&block) if block_given?
|
27
|
+
return @file_cache[filename]
|
28
|
+
end
|
29
|
+
|
30
|
+
directory_path = File.dirname(filename)
|
31
|
+
lines = File.readlines(filename, chomp: true)
|
32
|
+
processed_lines = []
|
33
|
+
|
34
|
+
lines.each do |line|
|
35
|
+
if (match = line.match(@import_pattern))
|
36
|
+
included_file_path = if match[1].strip.match %r{^/}
|
37
|
+
match[1].strip
|
38
|
+
else
|
39
|
+
File.join(directory_path, match[1].strip)
|
40
|
+
end
|
41
|
+
processed_lines += readlines(included_file_path, &block)
|
42
|
+
else
|
43
|
+
processed_lines.push(line)
|
44
|
+
yield line if block_given?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
@file_cache[filename] = processed_lines
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def fetch_lines(filename)
|
54
|
+
@fetch_lines_cache[filename] ||= File.readlines(filename, chomp: true)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if $PROGRAM_NAME == __FILE__
|
59
|
+
require 'minitest/autorun'
|
60
|
+
require 'tempfile'
|
61
|
+
|
62
|
+
##
|
63
|
+
# The CachedNestedFileReaderTest class provides testing for
|
64
|
+
# the CachedNestedFileReader class.
|
65
|
+
#
|
66
|
+
class CachedNestedFileReaderTest < Minitest::Test
|
67
|
+
def setup
|
68
|
+
@file2 = Tempfile.new('test2.txt')
|
69
|
+
@file2.write("ImportedLine1\nImportedLine2")
|
70
|
+
@file2.rewind
|
71
|
+
|
72
|
+
@file1 = Tempfile.new('test1.txt')
|
73
|
+
@file1.write("Line1\nLine2\n #insert #{@file2.path}\nLine3")
|
74
|
+
@file1.rewind
|
75
|
+
# binding.pry
|
76
|
+
@reader = CachedNestedFileReader.new(import_pattern: /^ *#insert (.+)$/)
|
77
|
+
end
|
78
|
+
|
79
|
+
def teardown
|
80
|
+
@file1.close
|
81
|
+
@file1.unlink
|
82
|
+
|
83
|
+
@file2.close
|
84
|
+
@file2.unlink
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_readlines_without_imports
|
88
|
+
result = []
|
89
|
+
@reader.readlines(@file2.path) { |line| result << line }
|
90
|
+
assert_equal %w[ImportedLine1 ImportedLine2], result
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_readlines_with_imports
|
94
|
+
result = []
|
95
|
+
@reader.readlines(@file1.path) { |line| result << line }
|
96
|
+
assert_equal %w[Line1 Line2 ImportedLine1 ImportedLine2 Line3], result
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_caching_functionality
|
100
|
+
# First read
|
101
|
+
result1 = []
|
102
|
+
@reader.readlines(@file2.path) { |line| result1 << line }
|
103
|
+
|
104
|
+
# Simulate file content change
|
105
|
+
@file2.reopen(@file2.path, 'w') { |f| f.write('ChangedLine') }
|
106
|
+
|
107
|
+
# Second read (should read from cache, not the changed file)
|
108
|
+
result2 = []
|
109
|
+
@reader.readlines(@file2.path) { |line| result2 << line }
|
110
|
+
|
111
|
+
assert_equal result1, result2
|
112
|
+
assert_equal %w[ImportedLine1 ImportedLine2], result2
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/colorize.rb
CHANGED
data/lib/env.rb
CHANGED
@@ -10,17 +10,22 @@ module Env
|
|
10
10
|
# :reek:NilCheck
|
11
11
|
# :reek:UtilityFunction
|
12
12
|
def env_bool(name, default: false)
|
13
|
-
return default if name.nil? || (val = ENV
|
13
|
+
return default if name.nil? || (val = ENV.fetch(name, nil)).nil?
|
14
14
|
return false if val.empty? || val == '0'
|
15
15
|
|
16
16
|
true
|
17
17
|
end
|
18
18
|
|
19
|
+
# :reek:UtilityFunction
|
20
|
+
def env_bool_false(name)
|
21
|
+
!(val = (name && ENV.fetch(name, nil))).nil? && !(val.empty? || val == '0')
|
22
|
+
end
|
23
|
+
|
19
24
|
# skip :reek:DataClump
|
20
25
|
# skip :reek:NilCheck
|
21
26
|
# skip :reek:UtilityFunction
|
22
27
|
def env_int(name, default: 0)
|
23
|
-
return default if name.nil? || (val = ENV
|
28
|
+
return default if name.nil? || (val = ENV.fetch(name, nil)).nil?
|
24
29
|
return default if val.empty?
|
25
30
|
|
26
31
|
val.to_i
|
@@ -30,7 +35,7 @@ module Env
|
|
30
35
|
# skip :reek:NilCheck
|
31
36
|
# skip :reek:UtilityFunction
|
32
37
|
def env_str(name, default: '')
|
33
|
-
return default if name.nil? || (val = ENV
|
38
|
+
return default if name.nil? || (val = ENV.fetch(name, nil)).nil?
|
34
39
|
|
35
40
|
val || default
|
36
41
|
end
|
data/lib/env_opts.rb
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# encoding=utf-8
|
4
|
+
|
5
|
+
require_relative 'tap'
|
6
|
+
|
7
|
+
include Tap #; tap_config
|
8
|
+
|
9
|
+
# define options with initial values
|
10
|
+
# option to read value from environmnt variables
|
11
|
+
# option to cast input values
|
12
|
+
# value priority: default < environment < argument
|
13
|
+
#
|
14
|
+
# :reek:TooManyMethods
|
15
|
+
class EnvOpts
|
16
|
+
attr_reader :opts, :values
|
17
|
+
|
18
|
+
def initialize(opts_raw = {}, argv = ARGV)
|
19
|
+
@opts = {}
|
20
|
+
@values = {}
|
21
|
+
add_options(opts_raw)
|
22
|
+
# parse(argv, &block) if block_given?
|
23
|
+
block_given? ? parse(argv, &block) : parse(argv)
|
24
|
+
|
25
|
+
self # rubocop:disable Lint/Void
|
26
|
+
end
|
27
|
+
|
28
|
+
# add options to menu
|
29
|
+
# calculate help text
|
30
|
+
#
|
31
|
+
# :reek:NestedIterators
|
32
|
+
def add_options(opts_raw)
|
33
|
+
return self if opts_raw.nil?
|
34
|
+
|
35
|
+
help_rows = opts_raw.map do |key, opt_raw|
|
36
|
+
opt_name = key_name_to_option_name(key)
|
37
|
+
|
38
|
+
# set_per_options(opt_name, opt_raw)
|
39
|
+
@opts[opt_name] = (opt_raw ||= {})
|
40
|
+
set_key_value_as_cast(opt_name, EnvOpts.optdefault(opt_raw))
|
41
|
+
set_key_value_per_environment_as_cast(opt_name, opt_raw)
|
42
|
+
|
43
|
+
[
|
44
|
+
[20, '-', "--#{opt_name}"],
|
45
|
+
[16, '-',
|
46
|
+
if @opts[opt_name][:env].present?
|
47
|
+
option_name_to_environment_name(opt_name, @opts[opt_name])
|
48
|
+
else
|
49
|
+
''
|
50
|
+
end],
|
51
|
+
# [24, '-', get_environment_value_from_option(opt_name, @opts[opt_name])],
|
52
|
+
[24, '-', @opts[opt_name][:default]],
|
53
|
+
[6, '-', if (fixed = opt_raw.fetch(:fixed, nil)).nil?
|
54
|
+
":#{option_cast(@opts[opt_name])}"
|
55
|
+
else
|
56
|
+
fixed.to_s
|
57
|
+
end]
|
58
|
+
]
|
59
|
+
end
|
60
|
+
|
61
|
+
max_widths = help_rows.reduce([0, 0, 0, 0]) do |memo, vals|
|
62
|
+
vals.map.with_index do |val, ind|
|
63
|
+
[memo[ind], val[2].to_s.length].max
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
@values['help'] = help_rows.map do |row|
|
68
|
+
row.map.with_index do |cell, ind|
|
69
|
+
format("%#{cell[1]}#{max_widths[ind]}s", cell[2])
|
70
|
+
end.join(' ')
|
71
|
+
end.join("\n")
|
72
|
+
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
# accept :d or :default option
|
77
|
+
#
|
78
|
+
def self.optdefault(opt_raw)
|
79
|
+
return opt_raw[:d] unless opt_raw[:d].nil?
|
80
|
+
|
81
|
+
opt_raw[:default]
|
82
|
+
end
|
83
|
+
|
84
|
+
def output_help
|
85
|
+
puts @values['help']
|
86
|
+
end
|
87
|
+
|
88
|
+
# process arguments as mostly pairs of option name and value
|
89
|
+
#
|
90
|
+
def parse(argv = ARGV)
|
91
|
+
return self if argv.nil? || !(argv&.count || 0).positive?
|
92
|
+
|
93
|
+
args_ind = 0
|
94
|
+
while args_ind < argv.count
|
95
|
+
args_consumed = 0
|
96
|
+
arg = argv.fetch(args_ind, '') #.tap_inspect 'argument', source: 'EnvOpts'
|
97
|
+
if arg.start_with? '--'
|
98
|
+
opt_name = arg[2..-1] #.tap_inspect 'opt_name', source: 'EnvOpts'
|
99
|
+
args_consumed = consume_arguments(opt_name,
|
100
|
+
argv.fetch(args_ind + 1, nil))
|
101
|
+
end
|
102
|
+
|
103
|
+
if args_consumed.zero?
|
104
|
+
if arg == '--help'
|
105
|
+
output_help
|
106
|
+
exit
|
107
|
+
elsif block_given?
|
108
|
+
yield 'NAO', [arg]
|
109
|
+
args_consumed = 1
|
110
|
+
else
|
111
|
+
warn "Invalid argument: #{arg.inspect} in #{argv.inspect}"
|
112
|
+
exit 1
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
args_ind += args_consumed
|
117
|
+
end
|
118
|
+
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
# set option current values per environment values
|
123
|
+
#
|
124
|
+
def options_per_environment_as_cast(opts_raw)
|
125
|
+
return self if opts_raw.nil?
|
126
|
+
|
127
|
+
opts_raw.each do |key, opt_raw|
|
128
|
+
set_key_value_per_environment_as_cast(key_name_to_option_name(key),
|
129
|
+
opt_raw)
|
130
|
+
end
|
131
|
+
|
132
|
+
self
|
133
|
+
end
|
134
|
+
|
135
|
+
# symbol name to option name
|
136
|
+
# option names use hyphens
|
137
|
+
#
|
138
|
+
def self.symbol_name_to_option_name(name)
|
139
|
+
name.to_s.gsub('_', '-') #.tap_inspect
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
# convert key name or symbol to an option name
|
145
|
+
#
|
146
|
+
def key_name_to_option_name(key)
|
147
|
+
(key.is_a?(Symbol) ? EnvOpts.symbol_name_to_option_name(key) : key) #.tap_inspect
|
148
|
+
end
|
149
|
+
|
150
|
+
# get cast of environment variable
|
151
|
+
#
|
152
|
+
def option_cast(opt_raw)
|
153
|
+
(opt_raw[:cast].present? ? opt_raw[:cast].to_s : 'to_s')
|
154
|
+
end
|
155
|
+
|
156
|
+
# update value for named option
|
157
|
+
# return number of arguments used
|
158
|
+
#
|
159
|
+
def consume_arguments(opt_name, value)
|
160
|
+
return 0 if (opt_raw = @opts.fetch(opt_name, nil)).nil?
|
161
|
+
|
162
|
+
return 0 unless opt_raw.fetch(:option, true)
|
163
|
+
|
164
|
+
if !(fixed = opt_raw.fetch(:fixed, nil)).nil?
|
165
|
+
set_key_value_as_cast(opt_name, fixed)
|
166
|
+
1
|
167
|
+
elsif value.nil?
|
168
|
+
0
|
169
|
+
else
|
170
|
+
set_key_value_as_cast(opt_name, value)
|
171
|
+
2
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# option names use hyphens
|
176
|
+
#
|
177
|
+
def method_name_to_option_name(name)
|
178
|
+
name.to_s.gsub('_', '-') #.tap_inspect
|
179
|
+
end
|
180
|
+
|
181
|
+
# read and write options using the option name as a method
|
182
|
+
#
|
183
|
+
def method_missing(method_name, *args)
|
184
|
+
if method_name.to_s.end_with?('=')
|
185
|
+
value = args.first
|
186
|
+
name = method_name_to_option_name(method_name.to_s[0..-2])
|
187
|
+
set_key_value_as_cast(name, value)
|
188
|
+
else
|
189
|
+
@values[method_name_to_option_name(method_name)]
|
190
|
+
end #.tap_inspect "ref #{method_name}", source: 'EnvOpts'
|
191
|
+
end
|
192
|
+
|
193
|
+
# option name to environment name
|
194
|
+
# if true or empty, compute from option name
|
195
|
+
#
|
196
|
+
def option_name_to_environment_name(opt_name, opt_raw)
|
197
|
+
case env_name = opt_raw.fetch(:env, '')
|
198
|
+
when true, ''
|
199
|
+
"#{@values['env-prefix']}#{opt_name.upcase.gsub('-', '_')}"
|
200
|
+
else
|
201
|
+
env_name
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# get environment value from option
|
206
|
+
#
|
207
|
+
def get_environment_value_from_option(opt_name, opt_raw)
|
208
|
+
ENV.fetch(option_name_to_environment_name(opt_name, opt_raw),
|
209
|
+
nil)
|
210
|
+
end
|
211
|
+
|
212
|
+
# option names are available as methods
|
213
|
+
#
|
214
|
+
# :reek:BooleanParameter
|
215
|
+
def respond_to_missing?(method_name, include_private = false)
|
216
|
+
(@opts.keys.include?(method_name_to_option_name(method_name)) || super)
|
217
|
+
end
|
218
|
+
|
219
|
+
def set_key_value_as_cast(key, value)
|
220
|
+
opt = @opts[key]
|
221
|
+
set_key_value_raw(key, (opt[:cast] ? value.send(opt[:cast]) : value))
|
222
|
+
end
|
223
|
+
|
224
|
+
# set key value_per environment as cast
|
225
|
+
#
|
226
|
+
def set_key_value_per_environment_as_cast(key, opt_raw)
|
227
|
+
return if opt_raw[:env].nil?
|
228
|
+
|
229
|
+
value = get_environment_value_from_option(key, opt_raw)
|
230
|
+
|
231
|
+
return unless value
|
232
|
+
|
233
|
+
set_key_value_as_cast(key,
|
234
|
+
opt_raw[:cast] ? value.send(opt_raw[:cast]) : value)
|
235
|
+
end
|
236
|
+
|
237
|
+
# set key value (raw)
|
238
|
+
#
|
239
|
+
def set_key_value_raw(key, value)
|
240
|
+
@values[key] = value
|
241
|
+
end
|
242
|
+
end
|