markdown_exec 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/markdown_exec.rb CHANGED
@@ -5,21 +5,36 @@
5
5
 
6
6
  require 'English'
7
7
  require 'clipboard'
8
- require 'mrdialog'
9
8
  require 'open3'
10
9
  require 'optparse'
11
10
  require 'tty-prompt'
12
11
  require 'yaml'
13
12
 
13
+ require_relative 'colorize'
14
+ require_relative 'env'
14
15
  require_relative 'shared'
16
+ require_relative 'tap'
15
17
  require_relative 'markdown_exec/version'
16
18
 
19
+ include Tap # rubocop:disable Style/MixinUsage
20
+
17
21
  $stderr.sync = true
18
22
  $stdout.sync = true
19
23
 
20
24
  BLOCK_SIZE = 1024
21
25
 
22
- 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
23
38
  def present?
24
39
  case self.class.to_s
25
40
  when 'FalseClass', 'TrueClass'
@@ -30,7 +45,9 @@ class Object # rubocop:disable Style/Documentation
30
45
  end
31
46
  end
32
47
 
33
- class String # rubocop:disable Style/Documentation
48
+ # is value empty?
49
+ #
50
+ class String
34
51
  BLANK_RE = /\A[[:space:]]*\z/.freeze
35
52
  def blank?
36
53
  empty? || BLANK_RE.match?(self)
@@ -51,17 +68,216 @@ EF_STDOUT = 0
51
68
  EF_STDERR = 1
52
69
  EF_STDIN = 2
53
70
 
71
+ # execute markdown documents
72
+ #
54
73
  module MarkdownExec
74
+ # :reek:IrresponsibleModule
55
75
  class Error < StandardError; end
56
76
 
77
+ ## an imported markdown document
78
+ #
79
+ class MDoc
80
+ def initialize(table)
81
+ @table = table
82
+ end
83
+
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
92
+
93
+ def get_block_by_name(name, default = {})
94
+ @table.select { |block| block[:name] == name }.fetch(0, default)
95
+ end
96
+
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
259
+
57
260
  ##
58
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
59
266
  class MarkParse
60
- attr_accessor :options
267
+ attr_reader :options
61
268
 
62
269
  def initialize(options = {})
63
270
  @options = options
64
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
65
281
  end
66
282
 
67
283
  ##
@@ -69,20 +285,19 @@ module MarkdownExec
69
285
 
70
286
  def base_options
71
287
  menu_iter do |item|
72
- item.tap_inspect name: :item, format: :yaml
288
+ # noisy item.tap_inspect name: :item, format: :yaml
73
289
  next unless item[:opt_name].present?
74
290
 
75
291
  item_default = item[:default]
76
- item_default.tap_inspect name: :item_default
292
+ # noisy item_default.tap_inspect name: :item_default
77
293
  value = if item_default.nil?
78
294
  item_default
79
295
  else
80
- env_str(item[:env_var], default: value_for_hash(item_default))
296
+ env_str(item[:env_var], default: OptionValue.new(item_default).for_hash)
81
297
  end
82
298
  [item[:opt_name], item[:proc1] ? item[:proc1].call(value) : value]
83
299
  end.compact.to_h.merge(
84
300
  {
85
- mdheadings: true, # use headings (levels 1,2,3) in block lable
86
301
  menu_exit_at_top: true,
87
302
  menu_with_exit: true
88
303
  }
@@ -104,20 +319,13 @@ module MarkdownExec
104
319
  }
105
320
  end
106
321
 
107
- # Returns true if all files are EOF
108
- #
109
- def all_at_eof(files)
110
- files.find { |f| !f.eof }.nil?
111
- end
112
-
113
- def approve_block(opts, blocks_in_file)
114
- 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])
115
324
  display_command(opts, required_blocks) if opts[:output_script] || opts[:user_must_approve]
116
325
 
117
326
  allow = true
118
327
  if opts[:user_must_approve]
119
328
  loop do
120
- # (sel = @prompt.select(opts[:prompt_approve_block], %w(Yes No Copy_script_to_clipboard Save_script), cycle: true)).tap_inspect name: :sel
121
329
  (sel = @prompt.select(opts[:prompt_approve_block], filter: true) do |menu|
122
330
  menu.default 1
123
331
  # menu.enum '.'
@@ -132,10 +340,11 @@ module MarkdownExec
132
340
  if sel == 3
133
341
  text = required_blocks.flatten.join($INPUT_RECORD_SEPARATOR)
134
342
  Clipboard.copy(text)
135
- fout "Clipboard updated: #{required_blocks.count} blocks, #{required_blocks.flatten.count} lines, #{text.length} characters"
343
+ fout "Clipboard updated: #{required_blocks.count} blocks," /
344
+ " #{required_blocks.flatten.count} lines," /
345
+ " #{text.length} characters"
136
346
  end
137
347
  if sel == 4
138
- # opts[:saved_script_filename] = saved_name_make(opts)
139
348
  write_command_file(opts.merge(save_executed_script: true), required_blocks)
140
349
  fout "File saved: #{@options[:saved_filespec]}"
141
350
  end
@@ -144,7 +353,7 @@ module MarkdownExec
144
353
  end
145
354
  (opts[:ir_approve] = allow).tap_inspect name: :allow
146
355
 
147
- selected = get_block_by_name blocks_in_file, opts[:block_name]
356
+ selected = mdoc.get_block_by_name opts[:block_name]
148
357
 
149
358
  if opts[:ir_approve]
150
359
  write_command_file opts, required_blocks
@@ -157,40 +366,34 @@ module MarkdownExec
157
366
  selected[:name]
158
367
  end
159
368
 
160
- def code(table, block)
161
- all = [block[:name]] + recursively_required(table, block[:reqs])
162
- all.reverse.map do |req|
163
- get_block_by_name(table, req).fetch(:body, '')
164
- end
165
- .flatten(1)
166
- .tap_inspect
167
- end
168
-
169
- def command_execute(opts, cmd2)
369
+ # :reek:DuplicateMethodCall
370
+ # :reek:UncommunicativeVariableName { exclude: [ e ] }
371
+ # :reek:LongYieldList
372
+ def command_execute(opts, command)
170
373
  @execute_files = Hash.new([])
171
374
  @execute_options = opts
172
375
  @execute_started_at = Time.now.utc
173
376
 
174
- Open3.popen3(@options[:shell], '-c', cmd2) do |stdin, stdout, stderr, exec_thr|
377
+ Open3.popen3(@options[:shell], '-c', command) do |stdin, stdout, stderr, exec_thr|
175
378
  # pid = exec_thr.pid # pid of the started process
176
379
 
177
- t1 = Thread.new do
380
+ Thread.new do
178
381
  until (line = stdout.gets).nil?
179
382
  @execute_files[EF_STDOUT] = @execute_files[EF_STDOUT] + [line]
180
383
  print line if opts[:output_stdout]
181
384
  yield nil, line, nil, exec_thr if block_given?
182
385
  end
183
- rescue IOError => e
386
+ rescue IOError
184
387
  # thread killed, do nothing
185
388
  end
186
389
 
187
- t2 = Thread.new do
390
+ Thread.new do
188
391
  until (line = stderr.gets).nil?
189
392
  @execute_files[EF_STDERR] = @execute_files[EF_STDERR] + [line]
190
393
  print line if opts[:output_stdout]
191
394
  yield nil, nil, line, exec_thr if block_given?
192
395
  end
193
- rescue IOError => e
396
+ rescue IOError
194
397
  # thread killed, do nothing
195
398
  end
196
399
 
@@ -212,7 +415,14 @@ module MarkdownExec
212
415
  @execute_aborted_at = Time.now.utc
213
416
  @execute_error_message = e.message
214
417
  @execute_error = e
215
- @execute_files[EF_STDERR] += [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]
216
426
  fout "Error ENOENT: #{e.inspect}"
217
427
  end
218
428
 
@@ -225,12 +435,15 @@ module MarkdownExec
225
435
  cnt / 2
226
436
  end
227
437
 
438
+ # :reek:DuplicateMethodCall
228
439
  def display_command(_opts, required_blocks)
229
- fout ' #=#=#'.yellow
440
+ frame = ' #=#=#'.yellow
441
+ fout frame
230
442
  required_blocks.each { |cb| fout cb }
231
- fout ' #=#=#'.yellow
443
+ fout frame
232
444
  end
233
445
 
446
+ # :reek:DuplicateMethodCall
234
447
  def exec_block(options, _block_name = '')
235
448
  options = default_options.merge options
236
449
  update_options options, over: false
@@ -252,118 +465,6 @@ module MarkdownExec
252
465
  list_recent_output: -> { fout_list list_recent_output },
253
466
  list_recent_scripts: -> { fout_list list_recent_scripts },
254
467
  pwd: -> { fout File.expand_path('..', __dir__) },
255
- pwd3: lambda {
256
- text = 'A B C'
257
- items = []
258
- Struct.new('BuildListData', :tag, :item, :status)
259
- data = Struct::BuildListData.new
260
-
261
- data.tag = '1'
262
- data.item = 'Item number 1'
263
- data.status = true
264
- items.push(data.to_a)
265
-
266
- data = Struct::BuildListData.new
267
- data.tag = '2'
268
- data.item = 'Item number 2'
269
- data.status = false
270
- items.push(data.to_a)
271
-
272
- data = Struct::BuildListData.new
273
- data.tag = '3'
274
- data.item = 'Item number 3'
275
- data.status = false
276
- items.push(data.to_a)
277
-
278
- data = Struct::BuildListData.new
279
- data.tag = '4'
280
- data.item = 'Item number 4'
281
- data.status = true
282
- items.push(data.to_a)
283
-
284
- data = Struct::BuildListData.new
285
- data.tag = '5'
286
- data.item = 'Item number 5'
287
- data.status = false
288
- items.push(data.to_a)
289
-
290
- data = Struct::BuildListData.new
291
- data.tag = '6'
292
- data.item = 'Item number 6'
293
- data.status = true
294
- items.push(data.to_a)
295
-
296
- dialog = MRDialog.new
297
- dialog.clear = true
298
- dialog.shadow = false
299
- dialog.title = 'BUILDLIST'
300
- # dialog.logger = Logger.new(ENV["HOME"] + "/dialog_" + ME + ".log")
301
-
302
- height = 0
303
- width = 0
304
- listheight = 0
305
-
306
- selected_items = dialog.buildlist(text, items, height, width, listheight)
307
- exit_code = dialog.exit_code
308
- puts "Exit code: #{exit_code}"
309
- puts 'Selecetd tags:'
310
- selected_items.each do |item|
311
- puts " '#{item}'"
312
- end
313
- },
314
- pwd1: lambda {
315
- dialog = MRDialog.new
316
- dialog.clear = true
317
- dialog.title = 'YES/NO BOX'
318
- puts "yesno: #{dialog.yesno('ABC', 0, 0)}"
319
- },
320
- pwd2: lambda {
321
- dialog = MRDialog.new
322
- # dialog.logger = Logger.new(ENV["HOME"] + "/dialog_" + ME + ".log")
323
- dialog.clear = true
324
- dialog.title = 'MENU BOX'
325
- text = 'textextst'
326
- items = []
327
- menu_data = Struct.new(:tag, :item)
328
- data = menu_data.new
329
- data.tag = 'Linux'
330
- data.item = 'The Great Unix Clone for 386/486'
331
- items.push(data.to_a)
332
-
333
- data = menu_data.new
334
- data.tag = 'NetBSD'
335
- data.item = 'Another free Unix Clone for 386/486'
336
- items.push(data.to_a)
337
-
338
- data = menu_data.new
339
- data.tag = 'OS/2'
340
- data.item = 'IBM OS/2'
341
- items.push(data.to_a)
342
-
343
- data = menu_data.new
344
- data.tag = 'WIN NT'
345
- data.item = 'Microsoft Windows NT'
346
- items.push(data.to_a)
347
-
348
- data = menu_data.new
349
- data.tag = 'PCDOS'
350
- data.item = 'IBM PC DOS'
351
- items.push(data.to_a)
352
-
353
- data = menu_data.new
354
- data.tag = 'MSDOS'
355
- data.item = 'Microsoft DOS'
356
- items.push(data.to_a)
357
-
358
- height = 0
359
- width = 0
360
- menu_height = 4
361
-
362
- selected_item = dialog.menu(text, items, height, width, menu_height)
363
-
364
- puts "Selected item: #{selected_item}"
365
- },
366
- # pwd: -> { fout `dialog --yesno "ABC" 99 99` },
367
468
  run_last_script: -> { run_last_script },
368
469
  select_recent_output: -> { select_recent_output },
369
470
  select_recent_script: -> { select_recent_script },
@@ -402,17 +503,15 @@ module MarkdownExec
402
503
  puts data.to_yaml
403
504
  end
404
505
 
405
- def get_block_by_name(table, name, default = {})
406
- table.select { |block| block[:name] == name }.fetch(0, default)
407
- end
408
-
409
- def get_block_summary(opts, headings, block_title, current)
506
+ # :reek:LongParameterList
507
+ def get_block_summary(opts, headings:, block_title:, current:)
410
508
  return [current] unless opts[:struct]
411
509
 
412
510
  return [summarize_block(headings, block_title).merge({ body: current })] unless opts[:bash]
413
511
 
414
512
  bm = block_title.match(Regexp.new(opts[:block_name_match]))
415
- 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..] }
416
515
 
417
516
  if bm && bm[1]
418
517
  [summarize_block(headings, bm[:title]).merge({ body: current, reqs: reqs })]
@@ -434,6 +533,7 @@ module MarkdownExec
434
533
  fout level == DISPLAY_LEVEL_BASE ? str : @options[:display_level_xbase_prefix] + str
435
534
  end
436
535
 
536
+ # :reek:DuplicateMethodCall
437
537
  def list_blocks_in_file(call_options = {}, &options_block)
438
538
  opts = optsmerge call_options, options_block
439
539
 
@@ -457,7 +557,7 @@ module MarkdownExec
457
557
  File.readlines(opts[:filename]).each do |line|
458
558
  continue unless line
459
559
 
460
- if opts[:mdheadings]
560
+ if opts[:menu_blocks_with_headings]
461
561
  if (lm = line.match(Regexp.new(opts[:heading3_match])))
462
562
  headings = [headings[0], headings[1], lm[:name]]
463
563
  elsif (lm = line.match(Regexp.new(opts[:heading2_match])))
@@ -471,7 +571,7 @@ module MarkdownExec
471
571
  if in_block
472
572
  if current
473
573
  block_title = current.join(' ').gsub(/ +/, ' ')[0..64] if block_title.nil? || block_title.empty?
474
- blocks += get_block_summary opts, headings, block_title, current
574
+ blocks += get_block_summary opts, headings: headings, block_title: block_title, current: current
475
575
  current = nil
476
576
  end
477
577
  in_block = false
@@ -480,16 +580,16 @@ module MarkdownExec
480
580
  # new block
481
581
  #
482
582
  lm = line.match(fenced_start_ex)
483
- do1 = false
583
+ block_allow = false
484
584
  if opts[:bash_only]
485
- do1 = true if lm && (lm[:shell] == 'bash')
585
+ block_allow = true if lm && (lm[:shell] == 'bash')
486
586
  else
487
- do1 = true
488
- 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]
489
589
  end
490
590
 
491
591
  in_block = true
492
- 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])))
493
593
  current = []
494
594
  block_title = (lm && lm[:name])
495
595
  end
@@ -517,7 +617,7 @@ module MarkdownExec
517
617
  next unless item[:opt_name].present? && item[:default].present?
518
618
 
519
619
  [
520
- "#{item[:opt_name]}: #{value_for_yaml item[:default]}",
620
+ "#{item[:opt_name]}: #{OptionValue.new(item[:default]).for_yaml}",
521
621
  item[:description].present? ? item[:description] : nil
522
622
  ].compact.join(' # ')
523
623
  end.compact.sort
@@ -525,14 +625,16 @@ module MarkdownExec
525
625
 
526
626
  def list_files_per_options(options)
527
627
  list_files_specified(
528
- options[:filename]&.present? ? options[:filename] : nil,
529
- options[:path],
530
- 'README.md',
531
- '.'
628
+ specified_filename: options[:filename]&.present? ? options[:filename] : nil,
629
+ specified_folder: options[:path],
630
+ default_filename: 'README.md',
631
+ default_folder: '.'
532
632
  ).tap_inspect
533
633
  end
534
634
 
535
- 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)
536
638
  fn = File.join(if specified_filename&.present?
537
639
  if specified_folder&.present?
538
640
  [specified_folder, specified_filename]
@@ -571,51 +673,14 @@ module MarkdownExec
571
673
  end.compact.tap_inspect
572
674
  end
573
675
 
574
- def list_recursively_required_blocks(table, name)
575
- name_block = get_block_by_name(table, name)
576
- raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
577
-
578
- all = [name_block[:name]] + recursively_required(table, name_block[:reqs])
579
-
580
- # in order of appearance in document
581
- table.select { |block| all.include? block[:name] }
582
- .map { |block| block.fetch(:body, '') }
583
- .flatten(1)
584
- .tap_inspect
585
- end
586
-
587
- def most_recent(arr)
588
- return unless arr
589
- return if arr.count < 1
590
-
591
- arr.max.tap_inspect
592
- end
593
-
594
- def most_recent_list(arr)
595
- return unless arr
596
- return if (ac = arr.count) < 1
597
-
598
- arr.sort[-[ac, options[:list_count]].min..].reverse.tap_inspect
599
- end
600
-
601
676
  def list_recent_output
602
- most_recent_list(Dir.glob(File.join(@options[:saved_stdout_folder],
603
- @options[:saved_stdout_glob]))).tap_inspect
677
+ Sfiles.new(@options[:saved_stdout_folder],
678
+ @options[:saved_stdout_glob]).most_recent_list
604
679
  end
605
680
 
606
681
  def list_recent_scripts
607
- most_recent_list(Dir.glob(File.join(@options[:saved_script_folder],
608
- @options[:saved_script_glob]))).tap_inspect
609
- end
610
-
611
- def make_block_label(block, call_options = {})
612
- opts = options.merge(call_options)
613
- if opts[:mdheadings]
614
- heads = block.fetch(:headings, []).compact.join(' # ')
615
- "#{block[:title]} [#{heads}] (#{opts[:filename]})"
616
- else
617
- "#{block[:title]} (#{opts[:filename]})"
618
- end
682
+ Sfiles.new(@options[:saved_script_folder],
683
+ @options[:saved_script_glob]).most_recent_list
619
684
  end
620
685
 
621
686
  def make_block_labels(call_options = {})
@@ -623,16 +688,22 @@ module MarkdownExec
623
688
  list_blocks_in_file(opts).map do |block|
624
689
  # next if opts[:hide_blocks_by_name] && block[:name].match(%r{^:\(.+\)$})
625
690
 
626
- 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
627
696
  end.compact.tap_inspect
628
697
  end
629
698
 
699
+ # :reek:DuplicateMethodCall
700
+ # :reek:UncommunicativeMethodName ### temp
630
701
  def menu_data1
631
702
  val_as_bool = ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value }
632
703
  val_as_int = ->(value) { value.to_i }
633
704
  val_as_str = ->(value) { value.to_s }
634
705
  # val_true = ->(_value) { true } # for commands, sets option to true
635
- set1 = [
706
+ menu_options = [
636
707
  {
637
708
  arg_name: 'PATH',
638
709
  default: '.',
@@ -650,7 +721,7 @@ module MarkdownExec
650
721
  long_name: 'debug',
651
722
  short_name: 'd',
652
723
  proc1: lambda { |value|
653
- $pdebug = value.to_i != 0
724
+ tap_config value.to_i != 0
654
725
  }
655
726
  },
656
727
  {
@@ -734,6 +805,24 @@ module MarkdownExec
734
805
  opt_name: :logged_stdout_filename_prefix,
735
806
  proc1: val_as_str
736
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
+ },
737
826
  {
738
827
  arg_name: 'BOOL',
739
828
  default: false,
@@ -893,7 +982,7 @@ module MarkdownExec
893
982
  short_name: '0',
894
983
  proc1: lambda { |_|
895
984
  options_finalize options
896
- fout sorted_keys(options).to_yaml
985
+ fout options.sort_by_key.to_yaml
897
986
  }
898
987
  },
899
988
  {
@@ -1016,7 +1105,8 @@ module MarkdownExec
1016
1105
  }
1017
1106
  ]
1018
1107
  # commands first, options second
1019
- (set1.reject { |v1| v1[:arg_name] }) + (set1.select { |v1| v1[:arg_name] })
1108
+ (menu_options.reject { |option| option[:arg_name] }) +
1109
+ (menu_options.select { |option| option[:arg_name] })
1020
1110
  end
1021
1111
 
1022
1112
  def menu_iter(data = menu_data1, &block)
@@ -1027,15 +1117,6 @@ module MarkdownExec
1027
1117
  @option_parser.help
1028
1118
  end
1029
1119
 
1030
- def option_exclude_blocks(opts, blocks)
1031
- block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
1032
- if opts[:hide_blocks_by_name]
1033
- blocks.reject { |block| block[:name].match(block_name_excluded_match) }
1034
- else
1035
- blocks
1036
- end
1037
- end
1038
-
1039
1120
  ## post-parse options configuration
1040
1121
  #
1041
1122
  def options_finalize(rest)
@@ -1057,6 +1138,7 @@ module MarkdownExec
1057
1138
  @options[:block_name] = block_name if block_name.present?
1058
1139
  end
1059
1140
 
1141
+ # :reek:ControlParameter
1060
1142
  def optsmerge(call_options = {}, options_block = nil)
1061
1143
  class_call_options = @options.merge(call_options || {})
1062
1144
  if options_block
@@ -1110,6 +1192,7 @@ module MarkdownExec
1110
1192
  sel == exit_option ? nil : sel
1111
1193
  end
1112
1194
 
1195
+ # :reek:UtilityFunction ### temp
1113
1196
  def read_configuration_file!(options, configuration_path)
1114
1197
  return unless File.exist?(configuration_path)
1115
1198
 
@@ -1119,23 +1202,7 @@ module MarkdownExec
1119
1202
  # rubocop:enable Security/YAMLLoad
1120
1203
  end
1121
1204
 
1122
- def recursively_required(table, reqs)
1123
- all = []
1124
- rem = reqs
1125
- while rem.count.positive?
1126
- rem = rem.map do |req|
1127
- next if all.include? req
1128
-
1129
- all += [req]
1130
- get_block_by_name(table, req).fetch(:reqs, [])
1131
- end
1132
- .compact
1133
- .flatten(1)
1134
- .tap_inspect(name: 'rem')
1135
- end
1136
- all.tap_inspect
1137
- end
1138
-
1205
+ # :reek:NestedIterators
1139
1206
  def run
1140
1207
  ## default configuration
1141
1208
  #
@@ -1179,15 +1246,6 @@ module MarkdownExec
1179
1246
  exec_block options, options[:block_name]
1180
1247
  end
1181
1248
 
1182
- FNR11 = '/'
1183
- FNR12 = ',~'
1184
-
1185
- def saved_name_make(opts)
1186
- fne = opts[:filename].gsub(FNR11, FNR12)
1187
- "#{[opts[:saved_script_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
1188
- ',', opts[:block_name]].join('_')}.sh"
1189
- end
1190
-
1191
1249
  def saved_name_split(name)
1192
1250
  mf = name.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_,_(?<block>.+)\.sh/)
1193
1251
  return unless mf
@@ -1197,50 +1255,61 @@ module MarkdownExec
1197
1255
  end
1198
1256
 
1199
1257
  def run_last_script
1200
- filename = most_recent Dir.glob(File.join(@options[:saved_script_folder],
1201
- @options[:saved_script_glob]))
1258
+ filename = Sfiles.new(@options[:saved_script_folder],
1259
+ @options[:saved_script_glob]).most_recent
1202
1260
  return unless filename
1203
1261
 
1204
- filename.tap_inspect name: filename
1205
1262
  saved_name_split filename
1206
1263
  @options[:save_executed_script] = false
1207
1264
  select_and_approve_block
1208
1265
  end
1209
1266
 
1210
1267
  def save_execution_output
1268
+ @options.tap_inspect name: :options
1211
1269
  return unless @options[:save_execution_output]
1212
1270
 
1213
- fne = File.basename(@options[:filename], '.*')
1214
-
1215
1271
  @options[:logged_stdout_filename] =
1216
- "#{[@options[:logged_stdout_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
1217
- @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
+
1218
1277
  @options[:logged_stdout_filespec] = File.join @options[:saved_stdout_folder], @options[:logged_stdout_filename]
1219
1278
  @logged_stdout_filespec = @options[:logged_stdout_filespec]
1220
- dirname = File.dirname(@options[:logged_stdout_filespec])
1279
+ (dirname = File.dirname(@options[:logged_stdout_filespec])).tap_inspect name: :dirname
1221
1280
  Dir.mkdir dirname unless File.exist?(dirname)
1222
1281
 
1223
- # File.write(@options[:logged_stdout_filespec], @execute_files&.fetch(EF_STDOUT, ''))
1224
1282
  ol = ["-STDOUT-\n"]
1225
1283
  ol += @execute_files&.fetch(EF_STDOUT, [])
1226
- ol += ["-STDERR-\n"].tap_inspect name: :ol3
1284
+ ol += ["\n-STDERR-\n"]
1227
1285
  ol += @execute_files&.fetch(EF_STDERR, [])
1228
- ol += ["-STDIN-\n"]
1286
+ ol += ["\n-STDIN-\n"]
1229
1287
  ol += @execute_files&.fetch(EF_STDIN, [])
1288
+ ol += ["\n"]
1230
1289
  File.write(@options[:logged_stdout_filespec], ol.join)
1231
1290
  end
1232
1291
 
1233
1292
  def select_and_approve_block(call_options = {}, &options_block)
1234
1293
  opts = optsmerge call_options, options_block
1235
1294
  blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
1295
+ mdoc = MDoc.new(blocks_in_file)
1236
1296
 
1237
- loop1 = true && !opts[:block_name].present?
1297
+ repeat_menu = true && !opts[:block_name].present?
1238
1298
 
1239
1299
  loop do
1240
1300
  unless opts[:block_name].present?
1241
1301
  pt = (opts[:prompt_select_block]).to_s
1242
- blocks_in_file.each { |block| block.merge! label: make_block_label(block, opts) }
1243
- block_labels = option_exclude_blocks(opts, blocks_in_file).map { |block| block[:label] }
1302
+
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
1311
+
1312
+ block_labels = mdoc.option_exclude_blocks(opts).map { |block| block[:label] }
1244
1313
 
1245
1314
  return nil if block_labels.count.zero?
1246
1315
 
@@ -1248,7 +1317,7 @@ module MarkdownExec
1248
1317
  return nil if sel.nil?
1249
1318
 
1250
1319
  # if sel.nil?
1251
- # loop1 = false
1320
+ # repeat_menu = false
1252
1321
  # break
1253
1322
  # end
1254
1323
 
@@ -1256,22 +1325,21 @@ module MarkdownExec
1256
1325
  opts[:block_name] = @options[:block_name] = label_block[:name]
1257
1326
 
1258
1327
  end
1259
- # if loop1
1260
- approve_block opts, blocks_in_file
1328
+ # if repeat_menu
1329
+ approve_block opts, mdoc
1261
1330
  # end
1262
1331
 
1263
- break unless loop1
1332
+ break unless repeat_menu
1264
1333
 
1265
1334
  opts[:block_name] = ''
1266
1335
  end
1267
1336
  end
1268
1337
 
1269
- def select_md_file(files_ = nil)
1338
+ def select_md_file(files = list_markdown_files_in_path)
1270
1339
  opts = options
1271
- files = files_ || list_markdown_files_in_path
1272
- if files.count == 1
1340
+ if (count = files.count) == 1
1273
1341
  files[0]
1274
- elsif files.count >= 2
1342
+ elsif count >= 2
1275
1343
  prompt_with_quit opts[:prompt_select_md].to_s, files, per_page: opts[:select_page_height]
1276
1344
  end
1277
1345
  end
@@ -1297,10 +1365,6 @@ module MarkdownExec
1297
1365
  )
1298
1366
  end
1299
1367
 
1300
- def sorted_keys(hash1)
1301
- hash1.keys.sort.to_h { |k| [k, hash1[k]] }
1302
- end
1303
-
1304
1368
  def summarize_block(headings, title)
1305
1369
  { headings: headings, name: title, title: title }
1306
1370
  end
@@ -1318,6 +1382,8 @@ module MarkdownExec
1318
1382
  end.compact
1319
1383
  end
1320
1384
 
1385
+ # :reek:BooleanParameter
1386
+ # :reek:ControlParameter
1321
1387
  def update_options(opts = {}, over: true)
1322
1388
  if over
1323
1389
  @options = @options.merge opts
@@ -1327,40 +1393,17 @@ module MarkdownExec
1327
1393
  @options.tap_inspect format: :yaml
1328
1394
  end
1329
1395
 
1330
- def value_for_hash(value, default = nil)
1331
- return default if value.nil?
1332
-
1333
- case value.class.to_s
1334
- when 'String', 'Integer', 'FalseClass', 'TrueClass'
1335
- value
1336
- when value.empty?
1337
- default
1338
- else
1339
- value.to_s
1340
- end
1341
- end
1342
-
1343
- def value_for_yaml(value)
1344
- return default if value.nil?
1345
-
1346
- case value.class.to_s
1347
- when 'String'
1348
- "'#{value}'"
1349
- when 'Integer'
1350
- value
1351
- when 'FalseClass', 'TrueClass'
1352
- value ? true : false
1353
- when value.empty?
1354
- default
1355
- else
1356
- value.to_s
1357
- end
1358
- end
1396
+ def write_command_file(call_options, required_blocks)
1397
+ return unless call_options[:save_executed_script]
1359
1398
 
1360
- def write_command_file(opts, required_blocks)
1361
- return unless opts[:save_executed_script]
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
1362
1406
 
1363
- opts[:saved_script_filename] = saved_name_make(opts)
1364
1407
  @execute_script_filespec =
1365
1408
  @options[:saved_filespec] =
1366
1409
  File.join opts[:saved_script_folder], opts[:saved_script_filename]
@@ -1376,15 +1419,11 @@ module MarkdownExec
1376
1419
  File.write(@options[:saved_filespec], shebang +
1377
1420
  "# file_name: #{opts[:filename]}\n" \
1378
1421
  "# block_name: #{opts[:block_name]}\n" \
1379
- "# time: #{Time.now.utc}\n" \
1422
+ "# time: #{time_now}\n" \
1380
1423
  "#{required_blocks.flatten.join("\n")}\n")
1381
-
1382
- @options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
1383
1424
  return if @options[:saved_script_chmod].zero?
1384
1425
 
1385
- @options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
1386
1426
  File.chmod @options[:saved_script_chmod], @options[:saved_filespec]
1387
- @options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
1388
1427
  end
1389
1428
  end
1390
1429
  end