markdown_exec 1.0.0 → 1.2.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.
data/lib/markdown_exec.rb CHANGED
@@ -3,46 +3,38 @@
3
3
 
4
4
  # encoding=utf-8
5
5
 
6
+ require 'English'
7
+ require 'clipboard'
6
8
  require 'open3'
7
9
  require 'optparse'
8
10
  require 'tty-prompt'
9
11
  require 'yaml'
10
12
 
11
- ##
12
- # default if nil
13
- # false if empty or '0'
14
- # else true
15
-
16
- def env_bool(name, default: false)
17
- return default if name.nil? || (val = ENV[name]).nil?
18
- return false if val.empty? || val == '0'
19
-
20
- true
21
- end
22
-
23
- def env_int(name, default: 0)
24
- return default if name.nil? || (val = ENV[name]).nil?
25
- return default if val.empty?
26
-
27
- val.to_i
28
- end
29
-
30
- def env_str(name, default: '')
31
- return default if name.nil? || (val = ENV[name]).nil?
32
-
33
- val || default
34
- end
35
-
36
- $pdebug = env_bool 'MDE_DEBUG'
37
-
13
+ require_relative 'colorize'
14
+ require_relative 'env'
15
+ require_relative 'shared'
16
+ require_relative 'tap'
38
17
  require_relative 'markdown_exec/version'
39
18
 
19
+ include Tap # rubocop:disable Style/MixinUsage
20
+
40
21
  $stderr.sync = true
41
22
  $stdout.sync = true
42
23
 
43
24
  BLOCK_SIZE = 1024
44
25
 
45
- class Object # rubocop:disable Style/Documentation
26
+ # hash with keys sorted by name
27
+ #
28
+ class Hash
29
+ def sort_by_key
30
+ keys.sort.to_h { |key| [key, self[key]] }
31
+ end
32
+ end
33
+
34
+ # is the value a non-empty string or a binary?
35
+ #
36
+ # :reek:ManualDispatch ### temp
37
+ class Object
46
38
  def present?
47
39
  case self.class.to_s
48
40
  when 'FalseClass', 'TrueClass'
@@ -53,7 +45,9 @@ class Object # rubocop:disable Style/Documentation
53
45
  end
54
46
  end
55
47
 
56
- class String # rubocop:disable Style/Documentation
48
+ # is value empty?
49
+ #
50
+ class String
57
51
  BLANK_RE = /\A[[:space:]]*\z/.freeze
58
52
  def blank?
59
53
  empty? || BLANK_RE.match?(self)
@@ -62,51 +56,249 @@ end
62
56
 
63
57
  public
64
58
 
65
- # debug output
59
+ # display_level values
60
+ DISPLAY_LEVEL_BASE = 0 # required output
61
+ DISPLAY_LEVEL_ADMIN = 1
62
+ DISPLAY_LEVEL_DEBUG = 2
63
+ DISPLAY_LEVEL_DEFAULT = DISPLAY_LEVEL_ADMIN
64
+ DISPLAY_LEVEL_MAX = DISPLAY_LEVEL_DEBUG
65
+
66
+ # @execute_files[ind] = @execute_files[ind] + [block]
67
+ EF_STDOUT = 0
68
+ EF_STDERR = 1
69
+ EF_STDIN = 2
70
+
71
+ # execute markdown documents
66
72
  #
67
- def tap_inspect(format: nil, name: 'return')
68
- return self unless $pdebug
73
+ module MarkdownExec
74
+ # :reek:IrresponsibleModule
75
+ class Error < StandardError; end
69
76
 
70
- cvt = {
71
- json: :to_json,
72
- string: :to_s,
73
- yaml: :to_yaml,
74
- else: :inspect
75
- }
76
- fn = cvt.fetch(format, cvt[:else])
77
+ ## an imported markdown document
78
+ #
79
+ class MDoc
80
+ def initialize(table)
81
+ @table = table
82
+ end
77
83
 
78
- puts "-> #{caller[0].scan(/in `?(\S+)'$/)[0][0]}()" \
79
- " #{name}: #{method(fn).call}"
84
+ def code(block)
85
+ all = [block[:name]] + recursively_required(block[:reqs])
86
+ all.reverse.map do |req|
87
+ get_block_by_name(req).fetch(:body, '')
88
+ end
89
+ .flatten(1)
90
+ .tap_inspect
91
+ end
80
92
 
81
- self
82
- end
93
+ def get_block_by_name(name, default = {})
94
+ @table.select { |block| block[:name] == name }.fetch(0, default)
95
+ end
83
96
 
84
- module MarkdownExec
85
- class Error < StandardError; end
97
+ def list_recursively_required_blocks(name)
98
+ name_block = get_block_by_name(name)
99
+ raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
100
+
101
+ all = [name_block[:name]] + recursively_required(name_block[:reqs])
102
+
103
+ # in order of appearance in document
104
+ @table.select { |block| all.include? block[:name] }
105
+ .map { |block| block.fetch(:body, '') }
106
+ .flatten(1)
107
+ .tap_inspect
108
+ end
109
+
110
+ def option_exclude_blocks(opts)
111
+ block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
112
+ if opts[:hide_blocks_by_name]
113
+ @table.reject { |block| block[:name].match(block_name_excluded_match) }
114
+ else
115
+ @table
116
+ end
117
+ end
118
+
119
+ def recursively_required(reqs)
120
+ all = []
121
+ rem = reqs
122
+ while rem.count.positive?
123
+ rem = rem.map do |req|
124
+ next if all.include? req
125
+
126
+ all += [req]
127
+ get_block_by_name(req).fetch(:reqs, [])
128
+ end
129
+ .compact
130
+ .flatten(1)
131
+ .tap_inspect(name: 'rem')
132
+ end
133
+ all.tap_inspect
134
+ end
135
+ end
136
+
137
+ # format option defaults and values
138
+ #
139
+ # :reek:TooManyInstanceVariables
140
+ class BlockLabel
141
+ def initialize(filename:, headings:, menu_blocks_with_docname:, menu_blocks_with_headings:, title:)
142
+ @filename = filename
143
+ @headings = headings
144
+ @menu_blocks_with_docname = menu_blocks_with_docname
145
+ @menu_blocks_with_headings = menu_blocks_with_headings
146
+ @title = title
147
+ end
148
+
149
+ def make
150
+ ([@title] +
151
+ (if @menu_blocks_with_headings
152
+ [@headings.compact.join(' # ')]
153
+ else
154
+ []
155
+ end) +
156
+ (
157
+ if @menu_blocks_with_docname
158
+ [@filename]
159
+ else
160
+ []
161
+ end
162
+ )).join(' ')
163
+ end
164
+ end
165
+
166
+ FNR11 = '/'
167
+ FNR12 = ',~'
168
+
169
+ # format option defaults and values
170
+ #
171
+ class SavedAsset
172
+ def initialize(filename:, prefix:, time:, blockname:)
173
+ @filename = filename
174
+ @prefix = prefix
175
+ @time = time
176
+ @blockname = blockname
177
+ end
178
+
179
+ def script_name
180
+ fne = @filename.gsub(FNR11, FNR12)
181
+ "#{[@prefix, @time.strftime('%F-%H-%M-%S'), fne, ',', @blockname].join('_')}.sh".tap_inspect
182
+ end
183
+
184
+ def stdout_name
185
+ "#{[@prefix, @time.strftime('%F-%H-%M-%S'), @filename, @blockname].join('_')}.out.txt".tap_inspect
186
+ end
187
+ end
188
+
189
+ # format option defaults and values
190
+ #
191
+ class OptionValue
192
+ def initialize(value)
193
+ @value = value
194
+ end
195
+
196
+ # as default value in env_str()
197
+ #
198
+ def for_hash(default = nil)
199
+ return default if @value.nil?
200
+
201
+ case @value.class.to_s
202
+ when 'String', 'Integer'
203
+ @value
204
+ when 'FalseClass', 'TrueClass'
205
+ @value ? true : false
206
+ when @value.empty?
207
+ default
208
+ else
209
+ @value.to_s
210
+ end
211
+ end
212
+
213
+ # for output as default value in list_default_yaml()
214
+ #
215
+ def for_yaml(default = nil)
216
+ return default if @value.nil?
217
+
218
+ case @value.class.to_s
219
+ when 'String'
220
+ "'#{@value}'"
221
+ when 'Integer'
222
+ @value
223
+ when 'FalseClass', 'TrueClass'
224
+ @value ? true : false
225
+ when @value.empty?
226
+ default
227
+ else
228
+ @value.to_s
229
+ end
230
+ end
231
+ end
232
+
233
+ # a generated list of saved files
234
+ #
235
+ class Sfiles
236
+ def initialize(folder, glob)
237
+ @folder = folder
238
+ @glob = glob
239
+ end
240
+
241
+ def list_all
242
+ Dir.glob(File.join(@folder, @glob)).tap_inspect
243
+ end
244
+
245
+ def most_recent(arr = list_all)
246
+ return unless arr
247
+ return if arr.count < 1
248
+
249
+ arr.max.tap_inspect
250
+ end
251
+
252
+ def most_recent_list(arr = list_all)
253
+ return unless arr
254
+ return if (ac = arr.count) < 1
255
+
256
+ arr.sort[-[ac, options[:list_count]].min..].reverse.tap_inspect
257
+ end
258
+ end
86
259
 
87
260
  ##
88
261
  #
262
+ # :reek:DuplicateMethodCall { allow_calls: ['block', 'item', 'lm', 'opts', 'option', '@options', 'required_blocks'] }
263
+ # :reek:MissingSafeMethod { exclude: [ read_configuration_file! ] }
264
+ # :reek:TooManyInstanceVariables ### temp
265
+ # :reek:TooManyMethods ### temp
89
266
  class MarkParse
90
- attr_accessor :options
267
+ attr_reader :options
91
268
 
92
269
  def initialize(options = {})
93
270
  @options = options
94
271
  @prompt = TTY::Prompt.new(interrupt: :exit)
272
+ @execute_aborted_at = nil
273
+ @execute_completed_at = nil
274
+ @execute_error = nil
275
+ @execute_error_message = nil
276
+ @execute_files = nil
277
+ @execute_options = nil
278
+ @execute_script_filespec = nil
279
+ @execute_started_at = nil
280
+ @option_parser = nil
95
281
  end
96
282
 
97
283
  ##
98
284
  # options necessary to start, parse input, defaults for cli options
99
285
 
100
286
  def base_options
101
- menu_data
102
- .map do |_long_name, _short_name, env_var, _arg_name, _description, opt_name, default, proc1| # rubocop:disable Metrics/ParameterLists
103
- next unless opt_name.present?
104
-
105
- value = env_str(env_var, default: value_for_hash(default))
106
- [opt_name, proc1 ? proc1.call(value) : value]
287
+ menu_iter do |item|
288
+ # noisy item.tap_inspect name: :item, format: :yaml
289
+ next unless item[:opt_name].present?
290
+
291
+ item_default = item[:default]
292
+ # noisy item_default.tap_inspect name: :item_default
293
+ value = if item_default.nil?
294
+ item_default
295
+ else
296
+ env_str(item[:env_var], default: OptionValue.new(item_default).for_hash)
297
+ end
298
+ [item[:opt_name], item[:proc1] ? item[:proc1].call(value) : value]
107
299
  end.compact.to_h.merge(
108
300
  {
109
- mdheadings: true, # use headings (levels 1,2,3) in block lable
301
+ menu_exit_at_top: true,
110
302
  menu_with_exit: true
111
303
  }
112
304
  ).tap_inspect format: :yaml
@@ -127,78 +319,110 @@ module MarkdownExec
127
319
  }
128
320
  end
129
321
 
130
- # Returns true if all files are EOF
131
- #
132
- def all_at_eof(files)
133
- files.find { |f| !f.eof }.nil?
134
- end
135
-
136
- def approve_block(opts, blocks_in_file)
137
- required_blocks = list_recursively_required_blocks(blocks_in_file, opts[:block_name])
322
+ def approve_block(opts, mdoc)
323
+ required_blocks = mdoc.list_recursively_required_blocks(opts[:block_name])
138
324
  display_command(opts, required_blocks) if opts[:output_script] || opts[:user_must_approve]
139
325
 
140
326
  allow = true
141
- allow = @prompt.yes? opts[:prompt_approve_block] if opts[:user_must_approve]
142
- opts[:ir_approve] = allow
143
- selected = get_block_by_name blocks_in_file, opts[:block_name]
327
+ if opts[:user_must_approve]
328
+ loop do
329
+ (sel = @prompt.select(opts[:prompt_approve_block], filter: true) do |menu|
330
+ menu.default 1
331
+ # menu.enum '.'
332
+ # menu.filter true
333
+
334
+ menu.choice 'Yes', 1
335
+ menu.choice 'No', 2
336
+ menu.choice 'Copy script to clipboard', 3
337
+ menu.choice 'Save script', 4
338
+ end).tap_inspect name: :sel
339
+ allow = (sel == 1)
340
+ if sel == 3
341
+ text = required_blocks.flatten.join($INPUT_RECORD_SEPARATOR)
342
+ Clipboard.copy(text)
343
+ fout "Clipboard updated: #{required_blocks.count} blocks," /
344
+ " #{required_blocks.flatten.count} lines," /
345
+ " #{text.length} characters"
346
+ end
347
+ if sel == 4
348
+ write_command_file(opts.merge(save_executed_script: true), required_blocks)
349
+ fout "File saved: #{@options[:saved_filespec]}"
350
+ end
351
+ break if [1, 2].include? sel
352
+ end
353
+ end
354
+ (opts[:ir_approve] = allow).tap_inspect name: :allow
355
+
356
+ selected = mdoc.get_block_by_name opts[:block_name]
144
357
 
145
358
  if opts[:ir_approve]
146
359
  write_command_file opts, required_blocks
147
360
  command_execute opts, required_blocks.flatten.join("\n")
148
361
  save_execution_output
149
362
  output_execution_summary
363
+ output_execution_result
150
364
  end
151
365
 
152
366
  selected[:name]
153
367
  end
154
368
 
155
- def code(table, block)
156
- all = [block[:name]] + recursively_required(table, block[:reqs])
157
- all.reverse.map do |req|
158
- get_block_by_name(table, req).fetch(:body, '')
159
- end
160
- .flatten(1)
161
- .tap_inspect
162
- end
163
-
164
- def command_execute(opts, cmd2)
369
+ # :reek:DuplicateMethodCall
370
+ # :reek:UncommunicativeVariableName { exclude: [ e ] }
371
+ # :reek:LongYieldList
372
+ def command_execute(opts, command)
165
373
  @execute_files = Hash.new([])
166
374
  @execute_options = opts
167
375
  @execute_started_at = Time.now.utc
168
- Open3.popen3(cmd2) do |stdin, stdout, stderr|
169
- stdin.close_write
170
- begin
171
- files = [stdout, stderr]
172
-
173
- until all_at_eof(files)
174
- ready = IO.select(files)
175
-
176
- next unless ready
177
-
178
- # readable = ready[0]
179
- # # writable = ready[1]
180
- # # exceptions = ready[2]
181
- ready.each.with_index do |readable, ind|
182
- readable.each do |f|
183
- block = f.read_nonblock(BLOCK_SIZE)
184
- @execute_files[ind] = @execute_files[ind] + [block]
185
- print block if opts[:output_stdout]
186
- rescue EOFError #=> e
187
- # do nothing at EOF
188
- end
189
- end
376
+
377
+ Open3.popen3(@options[:shell], '-c', command) do |stdin, stdout, stderr, exec_thr|
378
+ # pid = exec_thr.pid # pid of the started process
379
+
380
+ Thread.new do
381
+ until (line = stdout.gets).nil?
382
+ @execute_files[EF_STDOUT] = @execute_files[EF_STDOUT] + [line]
383
+ print line if opts[:output_stdout]
384
+ yield nil, line, nil, exec_thr if block_given?
385
+ end
386
+ rescue IOError
387
+ # thread killed, do nothing
388
+ end
389
+
390
+ Thread.new do
391
+ until (line = stderr.gets).nil?
392
+ @execute_files[EF_STDERR] = @execute_files[EF_STDERR] + [line]
393
+ print line if opts[:output_stdout]
394
+ yield nil, nil, line, exec_thr if block_given?
395
+ end
396
+ rescue IOError
397
+ # thread killed, do nothing
398
+ end
399
+
400
+ in_thr = Thread.new do
401
+ while exec_thr.alive? # reading input until the child process ends
402
+ stdin.puts(line = $stdin.gets)
403
+ @execute_files[EF_STDIN] = @execute_files[EF_STDIN] + [line]
404
+ yield line, nil, nil, exec_thr if block_given?
190
405
  end
191
- rescue IOError => e
192
- fout "IOError: #{e}"
193
406
  end
194
- @execute_completed_at = Time.now.utc
407
+
408
+ exec_thr.join
409
+ in_thr.kill
410
+ # @return_code = exec_thr.value
195
411
  end
412
+ @execute_completed_at = Time.now.utc
196
413
  rescue Errno::ENOENT => e
197
414
  # error triggered by missing command in script
198
415
  @execute_aborted_at = Time.now.utc
199
416
  @execute_error_message = e.message
200
417
  @execute_error = e
201
- @execute_files[1] = e.message
418
+ @execute_files[EF_STDERR] += [@execute_error_message]
419
+ fout "Error ENOENT: #{e.inspect}"
420
+ rescue SignalException => e
421
+ # SIGTERM triggered by user or system
422
+ @execute_aborted_at = Time.now.utc
423
+ @execute_error_message = 'SIGTERM'
424
+ @execute_error = e
425
+ @execute_files[EF_STDERR] += [@execute_error_message]
202
426
  fout "Error ENOENT: #{e.inspect}"
203
427
  end
204
428
 
@@ -211,10 +435,15 @@ module MarkdownExec
211
435
  cnt / 2
212
436
  end
213
437
 
438
+ # :reek:DuplicateMethodCall
214
439
  def display_command(_opts, required_blocks)
440
+ frame = ' #=#=#'.yellow
441
+ fout frame
215
442
  required_blocks.each { |cb| fout cb }
443
+ fout frame
216
444
  end
217
445
 
446
+ # :reek:DuplicateMethodCall
218
447
  def exec_block(options, _block_name = '')
219
448
  options = default_options.merge options
220
449
  update_options options, over: false
@@ -239,7 +468,8 @@ module MarkdownExec
239
468
  run_last_script: -> { run_last_script },
240
469
  select_recent_output: -> { select_recent_output },
241
470
  select_recent_script: -> { select_recent_script },
242
- tab_completions: -> { fout tab_completions }
471
+ tab_completions: -> { fout tab_completions },
472
+ menu_export: -> { fout menu_export }
243
473
  }
244
474
  simple_commands.each_key do |key|
245
475
  if @options[key]
@@ -273,17 +503,15 @@ module MarkdownExec
273
503
  puts data.to_yaml
274
504
  end
275
505
 
276
- def get_block_by_name(table, name, default = {})
277
- table.select { |block| block[:name] == name }.fetch(0, default)
278
- end
279
-
280
- def get_block_summary(opts, headings, block_title, current)
506
+ # :reek:LongParameterList
507
+ def get_block_summary(opts, headings:, block_title:, current:)
281
508
  return [current] unless opts[:struct]
282
509
 
283
510
  return [summarize_block(headings, block_title).merge({ body: current })] unless opts[:bash]
284
511
 
285
512
  bm = block_title.match(Regexp.new(opts[:block_name_match]))
286
- reqs = block_title.scan(Regexp.new(opts[:block_required_scan])).map { |s| s[1..] }
513
+ reqs = block_title.scan(Regexp.new(opts[:block_required_scan]))
514
+ .map { |scanned| scanned[1..] }
287
515
 
288
516
  if bm && bm[1]
289
517
  [summarize_block(headings, bm[:title]).merge({ body: current, reqs: reqs })]
@@ -292,6 +520,20 @@ module MarkdownExec
292
520
  end
293
521
  end
294
522
 
523
+ def approved_fout?(level)
524
+ level <= @options[:display_level]
525
+ end
526
+
527
+ # display output at level or lower than filter (DISPLAY_LEVEL_DEFAULT)
528
+ #
529
+ def lout(str, level: DISPLAY_LEVEL_BASE)
530
+ return unless approved_fout? level
531
+
532
+ # fout level == DISPLAY_LEVEL_BASE ? str : DISPLAY_LEVEL_XBASE_PREFIX + str
533
+ fout level == DISPLAY_LEVEL_BASE ? str : @options[:display_level_xbase_prefix] + str
534
+ end
535
+
536
+ # :reek:DuplicateMethodCall
295
537
  def list_blocks_in_file(call_options = {}, &options_block)
296
538
  opts = optsmerge call_options, options_block
297
539
 
@@ -315,7 +557,7 @@ module MarkdownExec
315
557
  File.readlines(opts[:filename]).each do |line|
316
558
  continue unless line
317
559
 
318
- if opts[:mdheadings]
560
+ if opts[:menu_blocks_with_headings]
319
561
  if (lm = line.match(Regexp.new(opts[:heading3_match])))
320
562
  headings = [headings[0], headings[1], lm[:name]]
321
563
  elsif (lm = line.match(Regexp.new(opts[:heading2_match])))
@@ -329,7 +571,7 @@ module MarkdownExec
329
571
  if in_block
330
572
  if current
331
573
  block_title = current.join(' ').gsub(/ +/, ' ')[0..64] if block_title.nil? || block_title.empty?
332
- blocks += get_block_summary opts, headings, block_title, current
574
+ blocks += get_block_summary opts, headings: headings, block_title: block_title, current: current
333
575
  current = nil
334
576
  end
335
577
  in_block = false
@@ -338,16 +580,16 @@ module MarkdownExec
338
580
  # new block
339
581
  #
340
582
  lm = line.match(fenced_start_ex)
341
- do1 = false
583
+ block_allow = false
342
584
  if opts[:bash_only]
343
- do1 = true if lm && (lm[:shell] == 'bash')
585
+ block_allow = true if lm && (lm[:shell] == 'bash')
344
586
  else
345
- do1 = true
346
- do1 = !(lm && (lm[:shell] == 'expect')) if opts[:exclude_expect_blocks]
587
+ block_allow = true
588
+ block_allow = !(lm && (lm[:shell] == 'expect')) if opts[:exclude_expect_blocks]
347
589
  end
348
590
 
349
591
  in_block = true
350
- if do1 && (!opts[:title_match] || (lm && lm[:name] && lm[:name].match(opts[:title_match])))
592
+ if block_allow && (!opts[:title_match] || (lm && lm[:name] && lm[:name].match(opts[:title_match])))
351
593
  current = []
352
594
  block_title = (lm && lm[:name])
353
595
  end
@@ -360,39 +602,39 @@ module MarkdownExec
360
602
  end
361
603
 
362
604
  def list_default_env
363
- menu_data
364
- .map do |_long_name, _short_name, env_var, _arg_name, description, _opt_name, default, _proc1| # rubocop:disable Metrics/ParameterLists
365
- next unless env_var.present?
605
+ menu_iter do |item|
606
+ next unless item[:env_var].present?
366
607
 
367
608
  [
368
- "#{env_var}=#{value_for_cli default}",
369
- description.present? ? description : nil
609
+ "#{item[:env_var]}=#{value_for_cli item[:default]}",
610
+ item[:description].present? ? item[:description] : nil
370
611
  ].compact.join(' # ')
371
612
  end.compact.sort
372
613
  end
373
614
 
374
615
  def list_default_yaml
375
- menu_data
376
- .map do |_long_name, _short_name, _env_var, _arg_name, description, opt_name, default, _proc1| # rubocop:disable Metrics/ParameterLists
377
- next unless opt_name.present? && default.present?
616
+ menu_iter do |item|
617
+ next unless item[:opt_name].present? && item[:default].present?
378
618
 
379
619
  [
380
- "#{opt_name}: #{value_for_yaml default}",
381
- description.present? ? description : nil
620
+ "#{item[:opt_name]}: #{OptionValue.new(item[:default]).for_yaml}",
621
+ item[:description].present? ? item[:description] : nil
382
622
  ].compact.join(' # ')
383
623
  end.compact.sort
384
624
  end
385
625
 
386
626
  def list_files_per_options(options)
387
627
  list_files_specified(
388
- options[:filename]&.present? ? options[:filename] : nil,
389
- options[:path],
390
- 'README.md',
391
- '.'
628
+ specified_filename: options[:filename]&.present? ? options[:filename] : nil,
629
+ specified_folder: options[:path],
630
+ default_filename: 'README.md',
631
+ default_folder: '.'
392
632
  ).tap_inspect
393
633
  end
394
634
 
395
- def list_files_specified(specified_filename, specified_folder, default_filename, default_folder, filetree = nil)
635
+ # :reek:LongParameterList
636
+ def list_files_specified(specified_filename: nil, specified_folder: nil,
637
+ default_filename: nil, default_folder: nil, filetree: nil)
396
638
  fn = File.join(if specified_filename&.present?
397
639
  if specified_folder&.present?
398
640
  [specified_folder, specified_filename]
@@ -431,51 +673,14 @@ module MarkdownExec
431
673
  end.compact.tap_inspect
432
674
  end
433
675
 
434
- def list_recursively_required_blocks(table, name)
435
- name_block = get_block_by_name(table, name)
436
- raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
437
-
438
- all = [name_block[:name]] + recursively_required(table, name_block[:reqs])
439
-
440
- # in order of appearance in document
441
- table.select { |block| all.include? block[:name] }
442
- .map { |block| block.fetch(:body, '') }
443
- .flatten(1)
444
- .tap_inspect
445
- end
446
-
447
- def most_recent(arr)
448
- return unless arr
449
- return if arr.count < 1
450
-
451
- arr.max.tap_inspect
452
- end
453
-
454
- def most_recent_list(arr)
455
- return unless arr
456
- return if (ac = arr.count) < 1
457
-
458
- arr.sort[-[ac, options[:list_count]].min..].reverse.tap_inspect
459
- end
460
-
461
676
  def list_recent_output
462
- most_recent_list(Dir.glob(File.join(@options[:saved_stdout_folder],
463
- @options[:saved_stdout_glob]))).tap_inspect
677
+ Sfiles.new(@options[:saved_stdout_folder],
678
+ @options[:saved_stdout_glob]).most_recent_list
464
679
  end
465
680
 
466
681
  def list_recent_scripts
467
- most_recent_list(Dir.glob(File.join(@options[:saved_script_folder],
468
- @options[:saved_script_glob]))).tap_inspect
469
- end
470
-
471
- def make_block_label(block, call_options = {})
472
- opts = options.merge(call_options)
473
- if opts[:mdheadings]
474
- heads = block.fetch(:headings, []).compact.join(' # ')
475
- "#{block[:title]} [#{heads}] (#{opts[:filename]})"
476
- else
477
- "#{block[:title]} (#{opts[:filename]})"
478
- end
682
+ Sfiles.new(@options[:saved_script_folder],
683
+ @options[:saved_script_glob]).most_recent_list
479
684
  end
480
685
 
481
686
  def make_block_labels(call_options = {})
@@ -483,102 +688,435 @@ module MarkdownExec
483
688
  list_blocks_in_file(opts).map do |block|
484
689
  # next if opts[:hide_blocks_by_name] && block[:name].match(%r{^:\(.+\)$})
485
690
 
486
- make_block_label block, opts
691
+ BlockLabel.new(filename: opts[:filename],
692
+ headings: block.fetch(:headings, []),
693
+ menu_blocks_with_docname: opts[:menu_blocks_with_docname],
694
+ menu_blocks_with_headings: opts[:menu_blocks_with_headings],
695
+ title: block[:title]).make
487
696
  end.compact.tap_inspect
488
697
  end
489
698
 
490
- def menu_data
699
+ # :reek:DuplicateMethodCall
700
+ # :reek:UncommunicativeMethodName ### temp
701
+ def menu_data1
491
702
  val_as_bool = ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value }
492
703
  val_as_int = ->(value) { value.to_i }
493
704
  val_as_str = ->(value) { value.to_s }
494
-
495
- summary_head = [
496
- ['config', nil, nil, 'PATH', 'Read configuration file', nil, '.', lambda { |value|
497
- read_configuration_file! options, value
498
- }],
499
- ['debug', 'd', 'MDE_DEBUG', 'BOOL', 'Debug output', nil, false, ->(value) { $pdebug = value.to_i != 0 }]
500
- ]
501
-
502
- # rubocop:disable Layout/LineLength
503
- summary_body = [
504
- ['block-name', 'f', 'MDE_BLOCK_NAME', 'RELATIVE', 'Name of block', :block_name, nil, val_as_str],
505
- ['filename', 'f', 'MDE_FILENAME', 'RELATIVE', 'Name of document', :filename, nil, val_as_str],
506
- ['list-blocks', nil, nil, nil, 'List blocks', :list_blocks, false, val_as_bool],
507
- ['list-count', nil, 'MDE_LIST_COUNT', 'NUM', 'Max. items to return in list', :list_count, 16, val_as_int],
508
- ['list-default-env', nil, nil, nil, 'List default configuration as environment variables', :list_default_env, false, val_as_bool],
509
- ['list-default-yaml', nil, nil, nil, 'List default configuration as YAML', :list_default_yaml, false, val_as_bool],
510
- ['list-docs', nil, nil, nil, 'List docs in current folder', :list_docs, false, val_as_bool],
511
- ['list-recent-output', nil, nil, nil, 'List recent saved output', :list_recent_output, false, val_as_bool],
512
- ['list-recent-scripts', nil, nil, nil, 'List recent saved scripts', :list_recent_scripts, false, val_as_bool],
513
- ['logged-stdout-filename-prefix', nil, 'MDE_LOGGED_STDOUT_FILENAME_PREFIX', 'NAME', 'Name prefix for stdout files', :logged_stdout_filename_prefix, 'mde', val_as_str],
514
- ['output-execution-summary', nil, 'MDE_OUTPUT_EXECUTION_SUMMARY', 'BOOL', 'Display summary for execution', :output_execution_summary, false, val_as_bool],
515
- ['output-script', nil, 'MDE_OUTPUT_SCRIPT', 'BOOL', 'Display script prior to execution', :output_script, false, val_as_bool],
516
- ['output-stdout', nil, 'MDE_OUTPUT_STDOUT', 'BOOL', 'Display standard output from execution', :output_stdout, true, val_as_bool],
517
- ['path', 'p', 'MDE_PATH', 'PATH', 'Path to documents', :path, nil, val_as_str],
518
- ['pwd', nil, nil, nil, 'Gem home folder', :pwd, false, val_as_bool],
519
- ['run-last-script', nil, nil, nil, 'Run most recently saved script', :run_last_script, false, val_as_bool],
520
- ['save-executed-script', nil, 'MDE_SAVE_EXECUTED_SCRIPT', 'BOOL', 'Save executed script', :save_executed_script, false, val_as_bool],
521
- ['save-execution-output', nil, 'MDE_SAVE_EXECUTION_OUTPUT', 'BOOL', 'Save standard output of the executed script', :save_execution_output, false, val_as_bool],
522
- ['saved-script-filename-prefix', nil, 'MDE_SAVED_SCRIPT_FILENAME_PREFIX', 'NAME', 'Name prefix for saved scripts', :saved_script_filename_prefix, 'mde', val_as_str],
523
- ['saved-script-folder', nil, 'MDE_SAVED_SCRIPT_FOLDER', 'SPEC', 'Saved script folder', :saved_script_folder, 'logs', val_as_str],
524
- ['saved-script-glob', nil, 'MDE_SAVED_SCRIPT_GLOB', 'SPEC', 'Glob matching saved scripts', :saved_script_glob, 'mde_*.sh', val_as_str],
525
- ['saved-stdout-folder', nil, 'MDE_SAVED_STDOUT_FOLDER', 'SPEC', 'Saved stdout folder', :saved_stdout_folder, 'logs', val_as_str],
526
- ['saved-stdout-glob', nil, 'MDE_SAVED_STDOUT_GLOB', 'SPEC', 'Glob matching saved outputs', :saved_stdout_glob, 'mde_*.out.txt', val_as_str],
527
- ['select-recent-output', nil, nil, nil, 'Select and execute a recently saved output', :select_recent_output, false, val_as_bool],
528
- ['select-recent-script', nil, nil, nil, 'Select and execute a recently saved script', :select_recent_script, false, val_as_bool],
529
- ['tab-completions', nil, nil, nil, 'List tab completions', :tab_completions, false, val_as_bool],
530
- ['user-must-approve', nil, 'MDE_USER_MUST_APPROVE', 'BOOL', 'Pause for user to approve script', :user_must_approve, true, val_as_bool]
531
- ]
532
- # rubocop:enable Layout/LineLength
533
-
534
- # rubocop:disable Style/Semicolon
535
- summary_tail = [
536
- [nil, '0', nil, nil, 'Show current configuration values',
537
- nil, nil, ->(_) { options_finalize options; fout sorted_keys(options).to_yaml }],
538
- ['help', 'h', nil, nil, 'App help',
539
- nil, nil, ->(_) { fout menu_help; exit }],
540
- ['version', 'v', nil, nil, "Print the gem's version",
541
- nil, nil, ->(_) { fout MarkdownExec::VERSION; exit }],
542
- ['exit', 'x', nil, nil, 'Exit app',
543
- nil, nil, ->(_) { exit }]
544
- ]
545
- # rubocop:enable Style/Semicolon
546
-
547
- env_vars = [
548
- [nil, nil, 'MDE_BLOCK_NAME_EXCLUDED_MATCH', nil, 'Pattern for blocks to hide from user-selection',
549
- :block_name_excluded_match, '^\(.*\)$', val_as_str],
550
- [nil, nil, 'MDE_BLOCK_NAME_MATCH', nil, '', :block_name_match, ':(?<title>\S+)( |$)', val_as_str],
551
- [nil, nil, 'MDE_BLOCK_REQUIRED_SCAN', nil, '', :block_required_scan, '\+\S+', val_as_str],
552
- [nil, nil, 'MDE_FENCED_START_AND_END_MATCH', nil, '', :fenced_start_and_end_match, '^`{3,}', val_as_str],
553
- [nil, nil, 'MDE_FENCED_START_EX_MATCH', nil, '', :fenced_start_ex_match,
554
- '^`{3,}(?<shell>[^`\s]*) *(?<name>.*)$', val_as_str],
555
- [nil, nil, 'MDE_HEADING1_MATCH', nil, '', :heading1_match, '^# *(?<name>[^#]*?) *$', val_as_str],
556
- [nil, nil, 'MDE_HEADING2_MATCH', nil, '', :heading2_match, '^## *(?<name>[^#]*?) *$', val_as_str],
557
- [nil, nil, 'MDE_HEADING3_MATCH', nil, '', :heading3_match, '^### *(?<name>.+?) *$', val_as_str],
558
- [nil, nil, 'MDE_MD_FILENAME_GLOB', nil, '', :md_filename_glob, '*.[Mm][Dd]', val_as_str],
559
- [nil, nil, 'MDE_MD_FILENAME_MATCH', nil, '', :md_filename_match, '.+\\.md', val_as_str],
560
- [nil, nil, 'MDE_OUTPUT_VIEWER_OPTIONS', nil, 'Options for viewing saved output file', :output_viewer_options,
561
- '', val_as_str],
562
- [nil, nil, 'MDE_SELECT_PAGE_HEIGHT', nil, '', :select_page_height, 12, val_as_int]
563
- # [nil, nil, 'MDE_', nil, '', nil, '', nil],
705
+ # val_true = ->(_value) { true } # for commands, sets option to true
706
+ menu_options = [
707
+ {
708
+ arg_name: 'PATH',
709
+ default: '.',
710
+ description: 'Read configuration file',
711
+ long_name: 'config',
712
+ proc1: lambda { |value|
713
+ read_configuration_file! options, value
714
+ }
715
+ },
716
+ {
717
+ arg_name: 'BOOL',
718
+ default: false,
719
+ description: 'Debug output',
720
+ env_var: 'MDE_DEBUG',
721
+ long_name: 'debug',
722
+ short_name: 'd',
723
+ proc1: lambda { |value|
724
+ tap_config value.to_i != 0
725
+ }
726
+ },
727
+ {
728
+ arg_name: "INT.#{DISPLAY_LEVEL_BASE}-#{DISPLAY_LEVEL_MAX}",
729
+ default: DISPLAY_LEVEL_DEFAULT,
730
+ description: "Output display level (#{DISPLAY_LEVEL_BASE} to #{DISPLAY_LEVEL_MAX})",
731
+ env_var: 'MDE_DISPLAY_LEVEL',
732
+ long_name: 'display-level',
733
+ opt_name: :display_level,
734
+ proc1: val_as_int
735
+ },
736
+ {
737
+ arg_name: 'NAME',
738
+ compreply: false,
739
+ description: 'Name of block',
740
+ env_var: 'MDE_BLOCK_NAME',
741
+ long_name: 'block-name',
742
+ opt_name: :block_name,
743
+ short_name: 'f',
744
+ proc1: val_as_str
745
+ },
746
+ {
747
+ arg_name: 'RELATIVE_PATH',
748
+ compreply: '.',
749
+ description: 'Name of document',
750
+ env_var: 'MDE_FILENAME',
751
+ long_name: 'filename',
752
+ opt_name: :filename,
753
+ short_name: 'f',
754
+ proc1: val_as_str
755
+ },
756
+ {
757
+ description: 'List blocks',
758
+ long_name: 'list-blocks',
759
+ opt_name: :list_blocks,
760
+ proc1: val_as_bool
761
+ },
762
+ {
763
+ arg_name: 'INT.1-',
764
+ default: 32,
765
+ description: 'Max. items to return in list',
766
+ env_var: 'MDE_LIST_COUNT',
767
+ long_name: 'list-count',
768
+ opt_name: :list_count,
769
+ proc1: val_as_int
770
+ },
771
+ {
772
+ description: 'List default configuration as environment variables',
773
+ long_name: 'list-default-env',
774
+ opt_name: :list_default_env
775
+ },
776
+ {
777
+ description: 'List default configuration as YAML',
778
+ long_name: 'list-default-yaml',
779
+ opt_name: :list_default_yaml
780
+ },
781
+ {
782
+ description: 'List docs in current folder',
783
+ long_name: 'list-docs',
784
+ opt_name: :list_docs,
785
+ proc1: val_as_bool
786
+ },
787
+ {
788
+ description: 'List recent saved output',
789
+ long_name: 'list-recent-output',
790
+ opt_name: :list_recent_output,
791
+ proc1: val_as_bool
792
+ },
793
+ {
794
+ description: 'List recent saved scripts',
795
+ long_name: 'list-recent-scripts',
796
+ opt_name: :list_recent_scripts,
797
+ proc1: val_as_bool
798
+ },
799
+ {
800
+ arg_name: 'PREFIX',
801
+ default: MarkdownExec::BIN_NAME,
802
+ description: 'Name prefix for stdout files',
803
+ env_var: 'MDE_LOGGED_STDOUT_FILENAME_PREFIX',
804
+ long_name: 'logged-stdout-filename-prefix',
805
+ opt_name: :logged_stdout_filename_prefix,
806
+ proc1: val_as_str
807
+ },
808
+ {
809
+ arg_name: 'BOOL',
810
+ default: false,
811
+ description: 'Display document name in block selection menu',
812
+ env_var: 'MDE_MENU_BLOCKS_WITH_DOCNAME',
813
+ long_name: 'menu-blocks-with-docname',
814
+ opt_name: :menu_blocks_with_docname,
815
+ proc1: val_as_bool
816
+ },
817
+ {
818
+ arg_name: 'BOOL',
819
+ default: false,
820
+ description: 'Display headings (levels 1,2,3) in block selection menu',
821
+ env_var: 'MDE_MENU_BLOCKS_WITH_HEADINGS',
822
+ long_name: 'menu-blocks-with-headings',
823
+ opt_name: :menu_blocks_with_headings,
824
+ proc1: val_as_bool
825
+ },
826
+ {
827
+ arg_name: 'BOOL',
828
+ default: false,
829
+ description: 'Display summary for execution',
830
+ env_var: 'MDE_OUTPUT_EXECUTION_SUMMARY',
831
+ long_name: 'output-execution-summary',
832
+ opt_name: :output_execution_summary,
833
+ proc1: val_as_bool
834
+ },
835
+ {
836
+ arg_name: 'BOOL',
837
+ default: false,
838
+ description: 'Display script prior to execution',
839
+ env_var: 'MDE_OUTPUT_SCRIPT',
840
+ long_name: 'output-script',
841
+ opt_name: :output_script,
842
+ proc1: val_as_bool
843
+ },
844
+ {
845
+ arg_name: 'BOOL',
846
+ default: true,
847
+ description: 'Display standard output from execution',
848
+ env_var: 'MDE_OUTPUT_STDOUT',
849
+ long_name: 'output-stdout',
850
+ opt_name: :output_stdout,
851
+ proc1: val_as_bool
852
+ },
853
+ {
854
+ arg_name: 'RELATIVE_PATH',
855
+ default: '.',
856
+ description: 'Path to documents',
857
+ env_var: 'MDE_PATH',
858
+ long_name: 'path',
859
+ opt_name: :path,
860
+ short_name: 'p',
861
+ proc1: val_as_str
862
+ },
863
+ {
864
+ description: 'Gem home folder',
865
+ long_name: 'pwd',
866
+ opt_name: :pwd,
867
+ proc1: val_as_bool
868
+ },
869
+ {
870
+ description: 'Run most recently saved script',
871
+ long_name: 'run-last-script',
872
+ opt_name: :run_last_script,
873
+ proc1: val_as_bool
874
+ },
875
+ {
876
+ arg_name: 'BOOL',
877
+ default: false,
878
+ description: 'Save executed script',
879
+ env_var: 'MDE_SAVE_EXECUTED_SCRIPT',
880
+ long_name: 'save-executed-script',
881
+ opt_name: :save_executed_script,
882
+ proc1: val_as_bool
883
+ },
884
+ {
885
+ arg_name: 'BOOL',
886
+ default: false,
887
+ description: 'Save standard output of the executed script',
888
+ env_var: 'MDE_SAVE_EXECUTION_OUTPUT',
889
+ long_name: 'save-execution-output',
890
+ opt_name: :save_execution_output,
891
+ proc1: val_as_bool
892
+ },
893
+ {
894
+ arg_name: 'INT',
895
+ default: 0o755,
896
+ description: 'chmod for saved scripts',
897
+ env_var: 'MDE_SAVED_SCRIPT_CHMOD',
898
+ long_name: 'saved-script-chmod',
899
+ opt_name: :saved_script_chmod,
900
+ proc1: val_as_int
901
+ },
902
+ {
903
+ arg_name: 'PREFIX',
904
+ default: MarkdownExec::BIN_NAME,
905
+ description: 'Name prefix for saved scripts',
906
+ env_var: 'MDE_SAVED_SCRIPT_FILENAME_PREFIX',
907
+ long_name: 'saved-script-filename-prefix',
908
+ opt_name: :saved_script_filename_prefix,
909
+ proc1: val_as_str
910
+ },
911
+ {
912
+ arg_name: 'RELATIVE_PATH',
913
+ default: 'logs',
914
+ description: 'Saved script folder',
915
+ env_var: 'MDE_SAVED_SCRIPT_FOLDER',
916
+ long_name: 'saved-script-folder',
917
+ opt_name: :saved_script_folder,
918
+ proc1: val_as_str
919
+ },
920
+ {
921
+ arg_name: 'GLOB',
922
+ default: 'mde_*.sh',
923
+ description: 'Glob matching saved scripts',
924
+ env_var: 'MDE_SAVED_SCRIPT_GLOB',
925
+ long_name: 'saved-script-glob',
926
+ opt_name: :saved_script_glob,
927
+ proc1: val_as_str
928
+ },
929
+ {
930
+ arg_name: 'RELATIVE_PATH',
931
+ default: 'logs',
932
+ description: 'Saved stdout folder',
933
+ env_var: 'MDE_SAVED_STDOUT_FOLDER',
934
+ long_name: 'saved-stdout-folder',
935
+ opt_name: :saved_stdout_folder,
936
+ proc1: val_as_str
937
+ },
938
+ {
939
+ arg_name: 'GLOB',
940
+ default: 'mde_*.out.txt',
941
+ description: 'Glob matching saved outputs',
942
+ env_var: 'MDE_SAVED_STDOUT_GLOB',
943
+ long_name: 'saved-stdout-glob',
944
+ opt_name: :saved_stdout_glob,
945
+ proc1: val_as_str
946
+ },
947
+ {
948
+ description: 'Select and execute a recently saved output',
949
+ long_name: 'select-recent-output',
950
+ opt_name: :select_recent_output,
951
+ proc1: val_as_bool
952
+ },
953
+ {
954
+ description: 'Select and execute a recently saved script',
955
+ long_name: 'select-recent-script',
956
+ opt_name: :select_recent_script,
957
+ proc1: val_as_bool
958
+ },
959
+ {
960
+ description: 'YAML export of menu',
961
+ long_name: 'menu-export',
962
+ opt_name: :menu_export,
963
+ proc1: val_as_bool
964
+ },
965
+ {
966
+ description: 'List tab completions',
967
+ long_name: 'tab-completions',
968
+ opt_name: :tab_completions,
969
+ proc1: val_as_bool
970
+ },
971
+ {
972
+ arg_name: 'BOOL',
973
+ default: true,
974
+ description: 'Pause for user to approve script',
975
+ env_var: 'MDE_USER_MUST_APPROVE',
976
+ long_name: 'user-must-approve',
977
+ opt_name: :user_must_approve,
978
+ proc1: val_as_bool
979
+ },
980
+ {
981
+ description: 'Show current configuration values',
982
+ short_name: '0',
983
+ proc1: lambda { |_|
984
+ options_finalize options
985
+ fout options.sort_by_key.to_yaml
986
+ }
987
+ },
988
+ {
989
+ description: 'App help',
990
+ long_name: 'help',
991
+ short_name: 'h',
992
+ proc1: lambda { |_|
993
+ fout menu_help
994
+ exit
995
+ }
996
+ },
997
+ {
998
+ description: "Print the gem's version",
999
+ long_name: 'version',
1000
+ short_name: 'v',
1001
+ proc1: lambda { |_|
1002
+ fout MarkdownExec::VERSION
1003
+ exit
1004
+ }
1005
+ },
1006
+ {
1007
+ description: 'Exit app',
1008
+ long_name: 'exit',
1009
+ short_name: 'x',
1010
+ proc1: ->(_) { exit }
1011
+ },
1012
+ {
1013
+ default: '^\(.*\)$',
1014
+ description: 'Pattern for blocks to hide from user-selection',
1015
+ env_var: 'MDE_BLOCK_NAME_EXCLUDED_MATCH',
1016
+ opt_name: :block_name_excluded_match,
1017
+ proc1: val_as_str
1018
+ },
1019
+ {
1020
+ default: ':(?<title>\S+)( |$)',
1021
+ env_var: 'MDE_BLOCK_NAME_MATCH',
1022
+ opt_name: :block_name_match,
1023
+ proc1: val_as_str
1024
+ },
1025
+ {
1026
+ default: '\+\S+',
1027
+ env_var: 'MDE_BLOCK_REQUIRED_SCAN',
1028
+ opt_name: :block_required_scan,
1029
+ proc1: val_as_str
1030
+ },
1031
+ {
1032
+ default: '> ',
1033
+ env_var: 'MDE_DISPLAY_LEVEL_XBASE_PREFIX',
1034
+ opt_name: :display_level_xbase_prefix,
1035
+ proc1: val_as_str
1036
+ },
1037
+ {
1038
+ default: '^`{3,}',
1039
+ env_var: 'MDE_FENCED_START_AND_END_MATCH',
1040
+ opt_name: :fenced_start_and_end_match,
1041
+ proc1: val_as_str
1042
+ },
1043
+ {
1044
+ default: '^`{3,}(?<shell>[^`\s]*) *(?<name>.*)$',
1045
+ env_var: 'MDE_FENCED_START_EX_MATCH',
1046
+ opt_name: :fenced_start_ex_match,
1047
+ proc1: val_as_str
1048
+ },
1049
+ {
1050
+ default: '^# *(?<name>[^#]*?) *$',
1051
+ env_var: 'MDE_HEADING1_MATCH',
1052
+ opt_name: :heading1_match,
1053
+ proc1: val_as_str
1054
+ },
1055
+ {
1056
+ default: '^## *(?<name>[^#]*?) *$',
1057
+ env_var: 'MDE_HEADING2_MATCH',
1058
+ opt_name: :heading2_match,
1059
+ proc1: val_as_str
1060
+ },
1061
+ {
1062
+ default: '^### *(?<name>.+?) *$',
1063
+ env_var: 'MDE_HEADING3_MATCH',
1064
+ opt_name: :heading3_match,
1065
+ proc1: val_as_str
1066
+ },
1067
+ {
1068
+ default: '*.[Mm][Dd]',
1069
+ env_var: 'MDE_MD_FILENAME_GLOB',
1070
+ opt_name: :md_filename_glob,
1071
+ proc1: val_as_str
1072
+ },
1073
+ {
1074
+ default: '.+\\.md',
1075
+ env_var: 'MDE_MD_FILENAME_MATCH',
1076
+ opt_name: :md_filename_match,
1077
+ proc1: val_as_str
1078
+ },
1079
+ {
1080
+ description: 'Options for viewing saved output file',
1081
+ env_var: 'MDE_OUTPUT_VIEWER_OPTIONS',
1082
+ opt_name: :output_viewer_options,
1083
+ proc1: val_as_str
1084
+ },
1085
+ {
1086
+ default: 24,
1087
+ description: 'Maximum # of rows in select list',
1088
+ env_var: 'MDE_SELECT_PAGE_HEIGHT',
1089
+ opt_name: :select_page_height,
1090
+ proc1: val_as_int
1091
+ },
1092
+ {
1093
+ default: '#!/usr/bin/env',
1094
+ description: 'Shebang for saved scripts',
1095
+ env_var: 'MDE_SHEBANG',
1096
+ opt_name: :shebang,
1097
+ proc1: val_as_str
1098
+ },
1099
+ {
1100
+ default: 'bash',
1101
+ description: 'Shell for launched scripts',
1102
+ env_var: 'MDE_SHELL',
1103
+ opt_name: :shell,
1104
+ proc1: val_as_str
1105
+ }
564
1106
  ]
1107
+ # commands first, options second
1108
+ (menu_options.reject { |option| option[:arg_name] }) +
1109
+ (menu_options.select { |option| option[:arg_name] })
1110
+ end
565
1111
 
566
- summary_head + summary_body + summary_tail + env_vars
1112
+ def menu_iter(data = menu_data1, &block)
1113
+ data.map(&block)
567
1114
  end
568
1115
 
569
1116
  def menu_help
570
1117
  @option_parser.help
571
1118
  end
572
1119
 
573
- def option_exclude_blocks(opts, blocks)
574
- block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
575
- if opts[:hide_blocks_by_name]
576
- blocks.reject { |block| block[:name].match(block_name_excluded_match) }
577
- else
578
- blocks
579
- end
580
- end
581
-
582
1120
  ## post-parse options configuration
583
1121
  #
584
1122
  def options_finalize(rest)
@@ -600,6 +1138,7 @@ module MarkdownExec
600
1138
  @options[:block_name] = block_name if block_name.present?
601
1139
  end
602
1140
 
1141
+ # :reek:ControlParameter
603
1142
  def optsmerge(call_options = {}, options_block = nil)
604
1143
  class_call_options = @options.merge(call_options || {})
605
1144
  if options_block
@@ -609,6 +1148,24 @@ module MarkdownExec
609
1148
  end.tap_inspect
610
1149
  end
611
1150
 
1151
+ def output_execution_result
1152
+ oq = [['Block', @options[:block_name], DISPLAY_LEVEL_ADMIN],
1153
+ ['Command',
1154
+ [MarkdownExec::BIN_NAME,
1155
+ @options[:filename],
1156
+ @options[:block_name]].join(' '),
1157
+ DISPLAY_LEVEL_ADMIN]]
1158
+
1159
+ [['Script', :saved_filespec],
1160
+ ['StdOut', :logged_stdout_filespec]].each do |label, name|
1161
+ oq << [label, @options[name], DISPLAY_LEVEL_ADMIN] if @options[name]
1162
+ end
1163
+
1164
+ oq.map do |label, value, level|
1165
+ lout ["#{label}:".yellow, value.to_s].join(' '), level: level
1166
+ end
1167
+ end
1168
+
612
1169
  def output_execution_summary
613
1170
  return unless @options[:output_execution_summary]
614
1171
 
@@ -626,12 +1183,16 @@ module MarkdownExec
626
1183
 
627
1184
  def prompt_with_quit(prompt_text, items, opts = {})
628
1185
  exit_option = '* Exit'
629
- sel = @prompt.select prompt_text,
630
- items + (@options[:menu_with_exit] ? [exit_option] : []),
631
- opts
1186
+ all_items = if @options[:menu_exit_at_top]
1187
+ (@options[:menu_with_exit] ? [exit_option] : []) + items
1188
+ else
1189
+ items + (@options[:menu_with_exit] ? [exit_option] : [])
1190
+ end
1191
+ sel = @prompt.select(prompt_text, all_items, opts.merge(filter: true))
632
1192
  sel == exit_option ? nil : sel
633
1193
  end
634
1194
 
1195
+ # :reek:UtilityFunction ### temp
635
1196
  def read_configuration_file!(options, configuration_path)
636
1197
  return unless File.exist?(configuration_path)
637
1198
 
@@ -641,23 +1202,7 @@ module MarkdownExec
641
1202
  # rubocop:enable Security/YAMLLoad
642
1203
  end
643
1204
 
644
- def recursively_required(table, reqs)
645
- all = []
646
- rem = reqs
647
- while rem.count.positive?
648
- rem = rem.map do |req|
649
- next if all.include? req
650
-
651
- all += [req]
652
- get_block_by_name(table, req).fetch(:reqs, [])
653
- end
654
- .compact
655
- .flatten(1)
656
- .tap_inspect(name: 'rem')
657
- end
658
- all.tap_inspect
659
- end
660
-
1205
+ # :reek:NestedIterators
661
1206
  def run
662
1207
  ## default configuration
663
1208
  #
@@ -675,19 +1220,19 @@ module MarkdownExec
675
1220
  "Usage: #{executable_name} [(path | filename [block_name])] [options]"
676
1221
  ].join("\n")
677
1222
 
678
- menu_data
679
- .map do |long_name, short_name, _env_var, arg_name, description, opt_name, default, proc1| # rubocop:disable Metrics/ParameterLists
680
- next unless long_name.present? || short_name.present?
1223
+ menu_iter do |item|
1224
+ next unless item[:long_name].present? || item[:short_name].present?
681
1225
 
682
- opts.on(*[if long_name.present?
683
- "--#{long_name}#{arg_name.present? ? " #{arg_name}" : ''}"
1226
+ opts.on(*[if item[:long_name].present?
1227
+ "--#{item[:long_name]}#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
684
1228
  end,
685
- short_name.present? ? "-#{short_name}" : nil,
686
- [description,
687
- default.present? ? "[#{value_for_cli default}]" : nil].compact.join(' '),
1229
+ item[:short_name].present? ? "-#{item[:short_name]}" : nil,
1230
+ [item[:description],
1231
+ item[:default].present? ? "[#{value_for_cli item[:default]}]" : nil].compact.join(' '),
688
1232
  lambda { |value|
689
- ret = proc1.call(value)
690
- options[opt_name] = ret if opt_name
1233
+ # ret = item[:proc1].call(value)
1234
+ ret = item[:proc1] ? item[:proc1].call(value) : value
1235
+ options[item[:opt_name]] = ret if item[:opt_name]
691
1236
  ret
692
1237
  }].compact)
693
1238
  end
@@ -701,15 +1246,6 @@ module MarkdownExec
701
1246
  exec_block options, options[:block_name]
702
1247
  end
703
1248
 
704
- FNR11 = '/'
705
- FNR12 = ',;'
706
-
707
- def saved_name_make(opts)
708
- fne = opts[:filename].gsub(FNR11, FNR12)
709
- "#{[opts[:saved_script_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
710
- ',', opts[:block_name]].join('_')}.sh"
711
- end
712
-
713
1249
  def saved_name_split(name)
714
1250
  mf = name.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_,_(?<block>.+)\.sh/)
715
1251
  return unless mf
@@ -719,58 +1255,91 @@ module MarkdownExec
719
1255
  end
720
1256
 
721
1257
  def run_last_script
722
- filename = most_recent Dir.glob(File.join(@options[:saved_script_folder],
723
- @options[:saved_script_glob]))
1258
+ filename = Sfiles.new(@options[:saved_script_folder],
1259
+ @options[:saved_script_glob]).most_recent
724
1260
  return unless filename
725
1261
 
726
- filename.tap_inspect name: filename
727
1262
  saved_name_split filename
728
1263
  @options[:save_executed_script] = false
729
1264
  select_and_approve_block
730
1265
  end
731
1266
 
732
1267
  def save_execution_output
1268
+ @options.tap_inspect name: :options
733
1269
  return unless @options[:save_execution_output]
734
1270
 
735
- fne = File.basename(@options[:filename], '.*')
736
-
737
1271
  @options[:logged_stdout_filename] =
738
- "#{[@options[:logged_stdout_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
739
- @options[:block_name]].join('_')}.out.txt"
1272
+ SavedAsset.new(blockname: @options[:block_name],
1273
+ filename: File.basename(@options[:filename], '.*'),
1274
+ prefix: @options[:logged_stdout_filename_prefix],
1275
+ time: Time.now.utc).stdout_name
1276
+
740
1277
  @options[:logged_stdout_filespec] = File.join @options[:saved_stdout_folder], @options[:logged_stdout_filename]
741
1278
  @logged_stdout_filespec = @options[:logged_stdout_filespec]
742
- dirname = File.dirname(@options[:logged_stdout_filespec])
1279
+ (dirname = File.dirname(@options[:logged_stdout_filespec])).tap_inspect name: :dirname
743
1280
  Dir.mkdir dirname unless File.exist?(dirname)
744
- File.write(@options[:logged_stdout_filespec], @execute_files&.fetch(0, ''))
1281
+
1282
+ ol = ["-STDOUT-\n"]
1283
+ ol += @execute_files&.fetch(EF_STDOUT, [])
1284
+ ol += ["\n-STDERR-\n"]
1285
+ ol += @execute_files&.fetch(EF_STDERR, [])
1286
+ ol += ["\n-STDIN-\n"]
1287
+ ol += @execute_files&.fetch(EF_STDIN, [])
1288
+ ol += ["\n"]
1289
+ File.write(@options[:logged_stdout_filespec], ol.join)
745
1290
  end
746
1291
 
747
1292
  def select_and_approve_block(call_options = {}, &options_block)
748
1293
  opts = optsmerge call_options, options_block
749
1294
  blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
1295
+ mdoc = MDoc.new(blocks_in_file)
750
1296
 
751
- unless opts[:block_name].present?
752
- pt = (opts[:prompt_select_block]).to_s
753
- blocks_in_file.each { |block| block.merge! label: make_block_label(block, opts) }
754
- block_labels = option_exclude_blocks(opts, blocks_in_file).map { |block| block[:label] }
1297
+ repeat_menu = true && !opts[:block_name].present?
755
1298
 
756
- return nil if block_labels.count.zero?
1299
+ loop do
1300
+ unless opts[:block_name].present?
1301
+ pt = (opts[:prompt_select_block]).to_s
757
1302
 
758
- sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height]
759
- return nil if sel.nil?
1303
+ blocks_in_file.each do |block|
1304
+ block.merge! label:
1305
+ BlockLabel.new(filename: opts[:filename],
1306
+ headings: block.fetch(:headings, []),
1307
+ menu_blocks_with_docname: opts[:menu_blocks_with_docname],
1308
+ menu_blocks_with_headings: opts[:menu_blocks_with_headings],
1309
+ title: block[:title]).make
1310
+ end
760
1311
 
761
- label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
762
- opts[:block_name] = @options[:block_name] = label_block[:name]
763
- end
1312
+ block_labels = mdoc.option_exclude_blocks(opts).map { |block| block[:label] }
1313
+
1314
+ return nil if block_labels.count.zero?
1315
+
1316
+ sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height]
1317
+ return nil if sel.nil?
1318
+
1319
+ # if sel.nil?
1320
+ # repeat_menu = false
1321
+ # break
1322
+ # end
1323
+
1324
+ label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
1325
+ opts[:block_name] = @options[:block_name] = label_block[:name]
1326
+
1327
+ end
1328
+ # if repeat_menu
1329
+ approve_block opts, mdoc
1330
+ # end
1331
+
1332
+ break unless repeat_menu
764
1333
 
765
- approve_block opts, blocks_in_file
1334
+ opts[:block_name] = ''
1335
+ end
766
1336
  end
767
1337
 
768
- def select_md_file(files_ = nil)
1338
+ def select_md_file(files = list_markdown_files_in_path)
769
1339
  opts = options
770
- files = files_ || list_markdown_files_in_path
771
- if files.count == 1
1340
+ if (count = files.count) == 1
772
1341
  files[0]
773
- elsif files.count >= 2
1342
+ elsif count >= 2
774
1343
  prompt_with_quit opts[:prompt_select_md].to_s, files, per_page: opts[:select_page_height]
775
1344
  end
776
1345
  end
@@ -796,20 +1365,25 @@ module MarkdownExec
796
1365
  )
797
1366
  end
798
1367
 
799
- def sorted_keys(hash1)
800
- hash1.keys.sort.to_h { |k| [k, hash1[k]] }
801
- end
802
-
803
1368
  def summarize_block(headings, title)
804
1369
  { headings: headings, name: title, title: title }
805
1370
  end
806
1371
 
807
- def tab_completions(data = menu_data)
1372
+ def menu_export(data = menu_data1)
808
1373
  data.map do |item|
809
- "--#{item[0]}" if item[0]
1374
+ item.delete(:proc1)
1375
+ item
1376
+ end.to_yaml
1377
+ end
1378
+
1379
+ def tab_completions(data = menu_data1)
1380
+ data.map do |item|
1381
+ "--#{item[:long_name]}" if item[:long_name]
810
1382
  end.compact
811
1383
  end
812
1384
 
1385
+ # :reek:BooleanParameter
1386
+ # :reek:ControlParameter
813
1387
  def update_options(opts = {}, over: true)
814
1388
  if over
815
1389
  @options = @options.merge opts
@@ -819,64 +1393,37 @@ module MarkdownExec
819
1393
  @options.tap_inspect format: :yaml
820
1394
  end
821
1395
 
822
- def value_for_cli(value)
823
- case value.class.to_s
824
- when 'String'
825
- "'#{value}'"
826
- when 'FalseClass', 'TrueClass'
827
- value ? '1' : '0'
828
- when 'Integer'
829
- value
830
- else
831
- value.to_s
832
- end
833
- end
834
-
835
- def value_for_hash(value, default = nil)
836
- return default if value.nil?
837
-
838
- case value.class.to_s
839
- when 'String', 'Integer', 'FalseClass', 'TrueClass'
840
- value
841
- when value.empty?
842
- default
843
- else
844
- value.to_s
845
- end
846
- end
1396
+ def write_command_file(call_options, required_blocks)
1397
+ return unless call_options[:save_executed_script]
847
1398
 
848
- def value_for_yaml(value)
849
- return default if value.nil?
1399
+ time_now = Time.now.utc
1400
+ opts = optsmerge call_options
1401
+ opts[:saved_script_filename] =
1402
+ SavedAsset.new(blockname: opts[:block_name],
1403
+ filename: opts[:filename],
1404
+ prefix: opts[:saved_script_filename_prefix],
1405
+ time: time_now).script_name
850
1406
 
851
- case value.class.to_s
852
- when 'String'
853
- "'#{value}'"
854
- when 'Integer'
855
- value
856
- when 'FalseClass', 'TrueClass'
857
- value ? true : false
858
- when value.empty?
859
- default
860
- else
861
- value.to_s
862
- end
863
- end
864
-
865
- def write_command_file(opts, required_blocks)
866
- return unless opts[:save_executed_script]
867
-
868
- opts[:saved_script_filename] = saved_name_make(opts)
869
1407
  @execute_script_filespec =
870
1408
  @options[:saved_filespec] =
871
1409
  File.join opts[:saved_script_folder], opts[:saved_script_filename]
872
1410
 
873
1411
  dirname = File.dirname(@options[:saved_filespec])
874
1412
  Dir.mkdir dirname unless File.exist?(dirname)
875
- File.write(@options[:saved_filespec], "#!/usr/bin/env bash\n" \
1413
+ (shebang = if @options[:shebang]&.present?
1414
+ "#{@options[:shebang]} #{@options[:shell]}\n"
1415
+ else
1416
+ ''
1417
+ end
1418
+ ).tap_inspect name: :shebang
1419
+ File.write(@options[:saved_filespec], shebang +
876
1420
  "# file_name: #{opts[:filename]}\n" \
877
1421
  "# block_name: #{opts[:block_name]}\n" \
878
- "# time: #{Time.now.utc}\n" \
1422
+ "# time: #{time_now}\n" \
879
1423
  "#{required_blocks.flatten.join("\n")}\n")
1424
+ return if @options[:saved_script_chmod].zero?
1425
+
1426
+ File.chmod @options[:saved_script_chmod], @options[:saved_filespec]
880
1427
  end
881
1428
  end
882
1429
  end