markdown_exec 1.3.1 → 1.3.3.1

Sign up to get free protection for your applications and to get access to all the features.
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[spec test reek rubocop rubocopminitest]
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: "Output display level (#{DISPLAY_LEVEL_BASE} to #{DISPLAY_LEVEL_MAX} [data, +context, +info])",
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-z]\S*))',
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-z]\S*))',
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]*) *(?<name>.*)$',
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} (#{MarkdownExec::VERSION})\n" +
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::value_for_cli(svh[:default]) if svh[:compreply].nil?
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, ERB.new(File.read(filespec = File.join(BF, 'tab_completion.sh.erb'))).result(binding)
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
 
@@ -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: 2022-10-29 23:24:15 UTC"
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
@@ -14,6 +14,10 @@
14
14
  # │ │ │
15
15
  # │5 │ for flashing text
16
16
  class String
17
+ def plain
18
+ self
19
+ end
20
+
17
21
  def black
18
22
  "\033[30m#{self}\033[0m"
19
23
  end
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[name]).nil?
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[name]).nil?
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[name]).nil?
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