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.
- checksums.yaml +4 -4
- data/.reek +24 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +7 -4
- data/README.md +4 -0
- data/Rakefile +24 -4
- data/bin/tab_completion.sh +11 -3
- data/lib/colorize.rb +64 -0
- data/lib/env.rb +37 -0
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +352 -313
- data/lib/shared.rb +1 -103
- data/lib/tap.rb +36 -0
- metadata +6 -16
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
|
-
|
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
|
-
|
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
|
-
|
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:
|
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
|
-
|
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,
|
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
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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',
|
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
|
-
|
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
|
386
|
+
rescue IOError
|
184
387
|
# thread killed, do nothing
|
185
388
|
end
|
186
389
|
|
187
|
-
|
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
|
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] += [
|
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
|
-
|
440
|
+
frame = ' #=#=#'.yellow
|
441
|
+
fout frame
|
230
442
|
required_blocks.each { |cb| fout cb }
|
231
|
-
fout
|
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
|
-
|
406
|
-
|
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]))
|
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[:
|
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
|
-
|
583
|
+
block_allow = false
|
484
584
|
if opts[:bash_only]
|
485
|
-
|
585
|
+
block_allow = true if lm && (lm[:shell] == 'bash')
|
486
586
|
else
|
487
|
-
|
488
|
-
|
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
|
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]}: #{
|
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
|
-
|
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
|
-
|
603
|
-
|
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
|
-
|
608
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
(
|
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
|
-
|
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 =
|
1201
|
-
|
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
|
-
|
1217
|
-
|
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"]
|
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
|
-
|
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
|
-
|
1243
|
-
|
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
|
-
#
|
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
|
1260
|
-
approve_block opts,
|
1328
|
+
# if repeat_menu
|
1329
|
+
approve_block opts, mdoc
|
1261
1330
|
# end
|
1262
1331
|
|
1263
|
-
break unless
|
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(
|
1338
|
+
def select_md_file(files = list_markdown_files_in_path)
|
1270
1339
|
opts = options
|
1271
|
-
|
1272
|
-
if files.count == 1
|
1340
|
+
if (count = files.count) == 1
|
1273
1341
|
files[0]
|
1274
|
-
elsif
|
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
|
1331
|
-
return
|
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
|
-
|
1361
|
-
|
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: #{
|
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
|