markdown_exec 1.1.1 → 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
@@ -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