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