markdown_exec 0.0.5 → 0.0.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 812f458e15000e04f1ee08e1073e809fa752ab01f04f242ecfe55bb2eb61d0b0
4
- data.tar.gz: 264d7a0a8a53e0b862c7a1877e6b6fe41b0cb163d66eaacdf3208865a0eb4a8e
3
+ metadata.gz: 8c89f954d1fc114cb22e9222ebc2c7d0575848d597899e1385edbf7b5963f08b
4
+ data.tar.gz: f14d0e24a3c348c303e96014b68a477d560ed1c3c707ac486173ffe19513df1d
5
5
  SHA512:
6
- metadata.gz: 3ada17fe22a83f07d4ca841b1ded1a50ea89028f203572c8020e85f8d63db9e6e4d04b9ddd41b9ddb3852d405f8e5e2db88d16340600a995ec2090c6c057c94d
7
- data.tar.gz: 3c935e05c24c4756843d23c5b7bc6c74831337c3d2991c1dc3ff9959b50106dadd7ded470c550f2581fb17991cd1b8881520362ce942e811a7db4f858febc134
6
+ metadata.gz: b78cd2cfbef3c47acd318c173fa953891f83c29ea2ac59b062dafc2e2ce8573c4f862f2e681cacb6ebd827dc77b10b984dd8a33a1349849357e9c64ab564932d
7
+ data.tar.gz: 8f8282f7a5a171dee4c83f9fce9974f48b7ee55b43d25651bfebaec0f46b97028c1ed58bd23ea85a796f949cd1eeae923d6899a5efb448879de96aa37287915d
data/Rakefile CHANGED
@@ -16,7 +16,7 @@ RuboCop::RakeTask.new
16
16
  task default: %i[test rubocop]
17
17
 
18
18
  GEM_NAME = 'markdown_exec'
19
- GEM_VERSION = '0.0.5'
19
+ GEM_VERSION = '0.0.6'
20
20
 
21
21
  # task :default => :build
22
22
 
@@ -32,6 +32,10 @@ task publish: :build do
32
32
  system "gem push #{GEM_NAME}-#{GEM_VERSION}.gem"
33
33
  end
34
34
 
35
+ task uninstall: :build do
36
+ system "gem uninstall #{GEM_NAME}"
37
+ end
38
+
35
39
  task :clean do
36
40
  system 'rm *.gem'
37
41
  end
data/bin/mde CHANGED
@@ -2,528 +2,9 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # encoding=utf-8
5
- # rubocop:disable Style/GlobalVars
6
-
7
- env_debug = ENV['MARKDOWN_EXEC_DEBUG']
8
- $pdebug = !(env_debug || '').empty?
9
-
10
- APP_NAME = 'MDExec'
11
- APP_DESC = 'Markdown block executor'
12
- VERSION = '0.0.5'
13
5
 
14
6
  require 'markdown_exec'
15
- # puts MarkdownExec::MDExec.echo(ARGV[0])
16
-
17
- require 'optparse'
18
- require 'pathname'
19
- require 'tty-prompt'
20
- require 'yaml'
21
-
22
- # require_relative 'mdlib'
23
- # #!/usr/bin/env ruby
24
- # # frozen_string_literal: true
25
- # # encoding=utf-8
26
-
27
- # env_debug = ENV['MARKDOWN_EXEC_DEBUG']
28
- # $pdebug = !(env_debug || '').empty?
29
-
30
- require 'open3'
31
- # require 'tty-prompt'
32
-
33
- BLOCK_SIZE = 1024
34
- SELECT_PAGE_HEIGHT = 12
35
-
36
- ##
37
- #
38
- class MarkParse
39
- attr_accessor :options
40
-
41
- def initialize(options)
42
- @options = options
43
- end
44
-
45
- def count_blocks
46
- cnt = 0
47
- File.readlines(options[:mdfilename]).each do |line|
48
- cnt += 1 if line.match(/^```/)
49
- end
50
- cnt / 2
51
- end
52
-
53
- def find_files
54
- puts "pwd: #{`pwd`}" if $pdebug
55
- # `ls -1 *.md`.split("\n").tap { |ret| puts "find_files() ret: #{ret.inspect}" if $pdebug }
56
- `ls -1 #{File.join options[:mdfolder], '*.md'}`.split("\n").tap do |ret|
57
- puts "find_files() ret: #{ret.inspect}" if $pdebug
58
- end
59
- end
60
-
61
- def fout(str)
62
- puts str # to stdout
63
- end
64
-
65
- def copts(call_options = {}, options_block = nil)
66
- class_call_options = options.merge(call_options || {})
67
- if options_block
68
- options_block.call class_call_options
69
- else
70
- class_call_options
71
- end.tap { |ret| puts "copts() ret: #{ret.inspect}" if $pdebug }
72
- end
73
-
74
- def bsr(headings, title)
75
- # puts "bsr() headings: #{headings.inspect}"
76
- { headings: headings, name: title, title: title }
77
- end
78
-
79
- def block_summary(opts, headings, block_title, current)
80
- puts "block_summary() block_title: #{block_title.inspect}" if $pdebug
81
- return [current] unless opts[:struct]
82
-
83
- # return [{ body: current, name: block_title, title: block_title }] unless opts[:bash]
84
- return [bsr(headings, block_title).merge({ body: current })] unless opts[:bash]
85
-
86
- bm = block_title.match(/:(\S+)( |$)/)
87
- reqs = block_title.scan(/\+\S+/).map { |s| s[1..] }
88
-
89
- if $pdebug
90
- puts ["block_summary() bm: #{bm.inspect}",
91
- "block_summary() reqs: #{reqs.inspect}"]
92
- end
93
-
94
- if bm && bm[1]
95
- # [{ body: current, name: bm[1], reqs: reqs, title: bm[1] }]
96
- [bsr(headings, bm[1]).merge({ body: current, reqs: reqs })]
97
- else
98
- # [{ body: current, name: block_title, reqs: reqs, title: block_title }]
99
- [bsr(headings, block_title).merge({ body: current, reqs: reqs })]
100
- end
101
- end
102
-
103
- def get_blocks(call_options = {}, &options_block)
104
- opts = copts call_options, options_block
105
-
106
- blocks = []
107
- current = nil
108
- in_block = false
109
- block_title = ''
110
-
111
- headings = []
112
- File.readlines(opts[:mdfilename]).each do |line|
113
- puts "get_blocks() line: #{line.inspect}" if $pdebug
114
- continue unless line
115
-
116
- if opts[:mdheadings]
117
- if (lm = line.match(/^### *(.+?) *$/))
118
- headings = [headings[0], headings[1], lm[1]]
119
- elsif (lm = line.match(/^## *([^#]*?) *$/))
120
- headings = [headings[0], lm[1]]
121
- elsif (lm = line.match(/^# *([^#]*?) *$/))
122
- headings = [lm[1]]
123
- end
124
- puts "get_blocks() headings: #{headings.inspect}" if $pdebug
125
- end
126
-
127
- if line.match(/^`{3,}/)
128
- if in_block
129
- puts 'get_blocks() in_block' if $pdebug
130
- if current
131
-
132
- # block_title ||= current.join(' ').gsub(/ +/, ' ')[0..64]
133
- block_title = current.join(' ').gsub(/ +/, ' ')[0..64] if block_title.nil? || block_title.empty?
134
-
135
- blocks += block_summary opts, headings, block_title, current
136
- current = nil
137
- end
138
- in_block = false
139
- block_title = ''
140
- else
141
- ## new block
142
- #
143
-
144
- # lm = line.match(/^`{3,}([^`\s]+)( .+)?$/)
145
- lm = line.match(/^`{3,}([^`\s]*) *(.*)$/)
146
-
147
- do1 = false
148
- if opts[:bash_only]
149
- do1 = true if lm && (lm[1] == 'bash')
150
- elsif opts[:exclude_expect_blocks]
151
- do1 = true unless lm && (lm[1] == 'expect')
152
- else
153
- do1 = true
154
- end
155
- if $pdebug
156
- puts ["get_blocks() lm: #{lm.inspect}",
157
- "get_blocks() opts: #{opts.inspect}",
158
- "get_blocks() do1: #{do1}"]
159
- end
160
-
161
- if do1 && (!opts[:title_match] || (lm && lm[2] && lm[2].match(opts[:title_match])))
162
- current = []
163
- in_block = true
164
- block_title = (lm && lm[2])
165
- end
166
-
167
- end
168
- elsif current
169
- current += [line.chomp]
170
- end
171
- end
172
- blocks.tap { |ret| puts "get_blocks() ret: #{ret.inspect}" if $pdebug }
173
- end
174
-
175
- def make_block_label(block, call_options = {})
176
- opts = options.merge(call_options)
177
- puts "make_block_label() opts: #{opts.inspect}" if $pdebug
178
- puts "make_block_label() block: #{block.inspect}" if $pdebug
179
- if opts[:mdheadings]
180
- heads = block.fetch(:headings, []).compact.join(' # ')
181
- "#{block[:title]} [#{heads}] (#{opts[:mdfilename]})"
182
- else
183
- "#{block[:title]} (#{opts[:mdfilename]})"
184
- end
185
- end
186
-
187
- def make_block_labels(call_options = {})
188
- opts = options.merge(call_options)
189
- get_blocks(opts).map do |block|
190
- make_block_label block, opts
191
- end
192
- end
193
-
194
- def select_block(call_options = {}, &options_block)
195
- opts = copts call_options, options_block
196
-
197
- blocks = get_blocks(opts.merge(struct: true))
198
- puts "select_block() blocks: #{blocks.to_yaml}" if $pdebug
199
-
200
- prompt = TTY::Prompt.new(interrupt: :exit)
201
- pt = "#{opts.fetch(:prompt, nil) || 'Pick one'}:"
202
- puts "select_block() pt: #{pt.inspect}" if $pdebug
203
-
204
- blocks.each { |block| block.merge! label: make_block_label(block, opts) }
205
- block_labels = blocks.map { |block| block[:label] }
206
- puts "select_block() block_labels: #{block_labels.inspect}" if $pdebug
207
-
208
- if opts[:preview_options]
209
- select_per_page = 3
210
- block_labels.each do |bn|
211
- fout " - #{bn}"
212
- end
213
- else
214
- select_per_page = SELECT_PAGE_HEIGHT
215
- end
216
-
217
- return nil if block_labels.count.zero?
218
-
219
- sel = prompt.select(pt, block_labels, per_page: select_per_page)
220
- puts "select_block() sel: #{sel.inspect}" if $pdebug
221
- # catch
222
- # # catch TTY::Reader::InputInterrupt
223
- # puts "InputInterrupt"
224
- # end
225
-
226
- label_block = blocks.select { |block| block[:label] == sel }.fetch(0, nil)
227
- puts "select_block() label_block: #{label_block.inspect}" if $pdebug
228
- sel = label_block[:name]
229
- puts "select_block() sel: #{sel.inspect}" if $pdebug
230
-
231
- cbs = code_blocks(blocks, sel)
232
- puts "select_block() cbs: #{cbs.inspect}" if $pdebug
233
-
234
- ## display code blocks for approval
235
- #
236
- cbs.each { |cb| fout cb } if opts[:display] || opts[:approve]
237
-
238
- allow = true
239
- allow = prompt.yes? 'Process?' if opts[:approve]
240
-
241
- selected = block_by_name blocks, sel
242
- puts "select_block() selected: #{selected.inspect}" if $pdebug
243
- if allow && opts[:execute]
244
-
245
- ## process in script, to handle line continuations
246
- #
247
- cmd2 = cbs.flatten.join("\n")
248
- fout "$ #{cmd2.to_yaml}"
249
-
250
- # Open3.popen3(cmd2) do |stdin, stdout, stderr, wait_thr|
251
- # cnt += 1
252
- # # stdin.puts "This is sent to the command"
253
- # # stdin.close # we're done
254
- # stdout_str = stdout.read # read stdout to string. note that this will block until the command is done!
255
- # stderr_str = stderr.read # read stderr to string
256
- # status = wait_thr.value # will block until the command finishes; returns status that responds to .success?
257
- # fout "#{stdout_str}"
258
- # fout "#{cnt}: err: #{stderr_str}" if stderr_str != ''
259
- # # fout "#{cnt}: stt: #{status}"
260
- # end
261
-
262
- Open3.popen3(cmd2) do |stdin, stdout, stderr|
263
- stdin.close_write
264
- begin
265
- files = [stdout, stderr]
266
-
267
- until all_eof(files)
268
- ready = IO.select(files)
269
-
270
- next unless ready
271
-
272
- readable = ready[0]
273
- # writable = ready[1]
274
- # exceptions = ready[2]
275
-
276
- readable.each do |f|
277
- # fileno = f.fileno
278
-
279
- data = f.read_nonblock(BLOCK_SIZE)
280
- # fout "- fileno: #{fileno}\n#{data}"
281
- fout data
282
- rescue EOFError #=> e
283
- # fout "fileno: #{fileno} EOF"
284
- end
285
- end
286
- rescue IOError => e
287
- fout "IOError: #{e}"
288
- end
289
- end
290
- end
291
-
292
- selected[:name]
293
- end
294
-
295
- def select_md_file
296
- opts = options
297
- files = find_files
298
- if files.count == 1
299
- sel = files[0]
300
- elsif files.count >= 2
301
-
302
- if opts[:preview_options]
303
- select_per_page = 3
304
- files.each do |file|
305
- fout " - #{file}"
306
- end
307
- else
308
- select_per_page = SELECT_PAGE_HEIGHT
309
- end
310
-
311
- prompt = TTY::Prompt.new
312
- sel = prompt.select("#{opts.fetch(:prompt, 'Pick one')}:", files, per_page: select_per_page)
313
- end
314
-
315
- sel
316
- end
317
-
318
- # Returns true if all files are EOF
319
- #
320
- def all_eof(files)
321
- files.find { |f| !f.eof }.nil?
322
- end
323
-
324
- def code(table, block)
325
- puts "code() table: #{table.inspect}" if $pdebug
326
- puts "code() block: #{block.inspect}" if $pdebug
327
- all = [block[:name]] + unroll(table, block[:reqs])
328
- puts "code() all: #{all.inspect}" if $pdebug
329
- all.reverse.map do |req|
330
- puts "code() req: #{req.inspect}" if $pdebug
331
- block_by_name(table, req).fetch(:body, '')
332
- end
333
- .flatten(1)
334
- .tap { |ret| puts "code() ret: #{ret.inspect}" if $pdebug }
335
- end
336
-
337
- def block_by_name(table, name, default = {})
338
- table.select { |block| block[:name] == name }.fetch(0, default)
339
- end
340
-
341
- def code_blocks(table, name)
342
- puts "code_blocks() table: #{table.inspect}" if $pdebug
343
- puts "code_blocks() name: #{name.inspect}" if $pdebug
344
- name_block = block_by_name(table, name)
345
- puts "code_blocks() name_block: #{name_block.inspect}" if $pdebug
346
- all = [name_block[:name]] + unroll(table, name_block[:reqs])
347
- puts "code_blocks() all: #{all.inspect}" if $pdebug
348
-
349
- # in order of appearance in document
350
- table.select { |block| all.include? block[:name] }
351
- .map { |block| block.fetch(:body, '') }
352
- .flatten(1)
353
- .tap { |ret| puts "code_blocks() ret: #{ret.inspect}" if $pdebug }
354
- end
355
-
356
- def unroll(table, reqs)
357
- puts "unroll() table: #{table.inspect}" if $pdebug
358
- puts "unroll() reqs: #{reqs.inspect}" if $pdebug
359
- all = []
360
- rem = reqs
361
- while rem.count.positive?
362
- puts "unrol() rem: #{rem.inspect}" if $pdebug
363
- rem = rem.map do |req|
364
- puts "unrol() req: #{req.inspect}" if $pdebug
365
- next if all.include? req
366
-
367
- all += [req]
368
- puts "unrol() all: #{all.inspect}" if $pdebug
369
- block_by_name(table, req).fetch(:reqs, [])
370
- end
371
- .compact
372
- .flatten(1)
373
- .tap { |_ret| puts "unroll() rem: #{rem.inspect}" if $pdebug }
374
- end
375
- all.tap { |ret| puts "unroll() ret: #{ret.inspect}" if $pdebug }
376
-
377
- $stderr.sync = true
378
- $stdout.sync = true
379
-
380
- def fout(str)
381
- puts str # to stdout
382
- end
383
-
384
- ## configuration file
385
- #
386
- def read_configuration!(options, configuration_path)
387
- if Pathname.new(configuration_path).exist?
388
- # rubocop:disable Security/YAMLLoad
389
- options.merge!((YAML.load(File.open(configuration_path)) || {})
390
- .transform_keys(&:to_sym))
391
- # rubocop:enable Security/YAMLLoad
392
- end
393
- options
394
- end
395
-
396
- ## default configuration
397
- #
398
- options = {
399
- mdheadings: true,
400
- list_blocks: false,
401
- list_docs: false,
402
- mdfilename: 'README.md',
403
- mdfolder: '.'
404
- }
405
-
406
- def options_finalize!(options); end
407
-
408
- # read local configuration file
409
- #
410
- read_configuration! options, ".#{APP_NAME.downcase}.yml"
411
-
412
- ## read current details for aws resources from app_data_file
413
- #
414
- # load_resources! options
415
- # puts "q31 options: #{options.to_yaml}" if $pdebug
416
-
417
- # rubocop:disable Metrics/BlockLength
418
- option_parser = OptionParser.new do |opts|
419
- executable_name = File.basename($PROGRAM_NAME)
420
- opts.banner = [
421
- "#{APP_NAME} - #{APP_DESC} (#{VERSION})".freeze,
422
- "Usage: #{executable_name} [options]"
423
- ].join("\n")
424
-
425
- ## menu top: on_head appear in reverse order added
426
- #
427
- opts.on('--config PATH', 'Read configuration file') do |value|
428
- read_configuration! options, value
429
- end
430
-
431
- ## menu body: items appear in order added
432
- #
433
- opts.on('-f RELATIVE', '--mdfilename', 'Name of document') do |value|
434
- options[:mdfilename] = value
435
- end
436
-
437
- opts.on('-p PATH', '--mdfolder', 'Path to documents') do |value|
438
- options[:mdfolder] = value
439
- end
440
-
441
- opts.on('--list-blocks', 'List blocks') do |_value|
442
- options[:list_blocks] = true
443
- end
444
-
445
- opts.on('--list-docs', 'List docs in current folder') do |_value|
446
- options[:list_docs] = true
447
- end
448
-
449
- ## menu bottom: items appear in order added
450
- #
451
- opts.on_tail('-h', '--help', 'App help') do |_value|
452
- puts option_parser.help
453
- exit
454
- end
455
-
456
- opts.on_tail('-v', '--version', 'App version') do |_value|
457
- puts VERSION
458
- exit
459
- end
460
-
461
- opts.on_tail('-x', '--exit', 'Exit app') do |_value|
462
- exit
463
- end
464
-
465
- opts.on_tail('-0', 'Show configuration') do |_v|
466
- options_finalize! options
467
- puts options.to_yaml
468
- end
469
- end
470
- # rubocop:enable Metrics/BlockLength
471
-
472
- option_parser.load # filename defaults to basename of the program without suffix in a directory ~/.options
473
- option_parser.environment # env defaults to the basename of the program.
474
- option_parser.parse! # (into: options)
475
- options_finalize! options
476
-
477
- ## process
478
- #
479
- # rubocop:disable Metrics/BlockLength
480
- loop do # once
481
- mp = MarkParse.new options
482
- options.merge!(
483
- {
484
- approve: true,
485
- bash: true,
486
- display: true,
487
- exclude_expect_blocks: true,
488
- execute: true,
489
- prompt: 'Execute',
490
- struct: true
491
- }
492
- )
493
-
494
- ## show
495
- #
496
- if options[:list_docs]
497
- fout mp.find_files
498
- break
499
- end
500
-
501
- if options[:list_blocks]
502
- fout (mp.find_files.map do |file|
503
- mp.make_block_labels(mdfilename: file, struct: true)
504
- end).flatten(1).to_yaml
505
- break
506
- end
507
-
508
- ## process
509
- #
510
- mp.select_block(bash: true, struct: true) if options[:mdfilename]
511
-
512
- # rubocop:disable Style/BlockComments
513
- =begin
514
- # rescue ArgumentError => e
515
- # puts "User abort: #{e}"
516
-
517
- # rescue StandardError => e
518
- # puts "ERROR: #{e}"
519
- # raise StandardError, e
520
-
521
- # ensure
522
- # exit
523
- =end
524
- # rubocop:enable Style/BlockComments
525
7
 
526
- break unless false # rubocop:disable Lint/LiteralAsCondition
527
- end
528
- # rubocop:enable Metrics/BlockLength
529
- # rubocop:enable Style/GlobalVars
8
+ mp = MarkdownExec::MarkParse.new
9
+ mp.fout Time.now
10
+ # mp.run
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MarkdownExec
4
- VERSION = '0.0.5'
4
+ VERSION = '0.0.6'
5
5
  end
data/lib/markdown_exec.rb CHANGED
@@ -2,13 +2,535 @@
2
2
 
3
3
  require_relative 'markdown_exec/version'
4
4
 
5
+ #!/usr/bin/env ruby
6
+ # frozen_string_literal: true
7
+
8
+ # encoding=utf-8
9
+ # rubocop:disable Style/GlobalVars
10
+
11
+ env_debug = ENV['MARKDOWN_EXEC_DEBUG']
12
+ $pdebug = !(env_debug || '').empty?
13
+
14
+ # APP_NAME = 'MDExec'
15
+ # APP_DESC = 'Markdown block executor'
16
+ # VERSION = '0.0.6'
17
+
18
+ # require 'markdown_exec'
19
+ # # puts MarkdownExec::MarkParse.echo(ARGV[0])
20
+
21
+ require 'optparse'
22
+ require 'pathname'
23
+ require 'tty-prompt'
24
+ require 'yaml'
25
+
26
+ # require_relative 'mdlib'
27
+ # #!/usr/bin/env ruby
28
+ # # frozen_string_literal: true
29
+ # # encoding=utf-8
30
+
31
+ # env_debug = ENV['MARKDOWN_EXEC_DEBUG']
32
+ # $pdebug = !(env_debug || '').empty?
33
+
34
+ require 'open3'
35
+ # require 'tty-prompt'
36
+
37
+ BLOCK_SIZE = 1024
38
+ SELECT_PAGE_HEIGHT = 12
39
+
5
40
  module MarkdownExec
6
41
  class Error < StandardError; end
7
42
 
8
43
  # Markdown Exec
9
- class MDExec
10
- def self.echo(str = '')
11
- "#{str}#{str}"
44
+ # class MarkParse
45
+ # def self.echo(str = '')
46
+ # "#{str}#{str}"
47
+ # end
48
+ # end
49
+ ##
50
+ #
51
+ class MarkParse
52
+ attr_accessor :options
53
+
54
+ def initialize(options = {})
55
+ @options = options
56
+ end
57
+
58
+ def count_blocks
59
+ cnt = 0
60
+ File.readlines(options[:mdfilename]).each do |line|
61
+ cnt += 1 if line.match(/^```/)
62
+ end
63
+ cnt / 2
64
+ end
65
+
66
+ def find_files
67
+ puts "pwd: #{`pwd`}" if $pdebug
68
+ # `ls -1 *.md`.split("\n").tap { |ret| puts "find_files() ret: #{ret.inspect}" if $pdebug }
69
+ `ls -1 #{File.join options[:mdfolder], '*.md'}`.split("\n").tap do |ret|
70
+ puts "find_files() ret: #{ret.inspect}" if $pdebug
71
+ end
72
+ end
73
+
74
+ def fout(str)
75
+ puts str # to stdout
76
+ end
77
+
78
+ def copts(call_options = {}, options_block = nil)
79
+ class_call_options = options.merge(call_options || {})
80
+ if options_block
81
+ options_block.call class_call_options
82
+ else
83
+ class_call_options
84
+ end.tap { |ret| puts "copts() ret: #{ret.inspect}" if $pdebug }
85
+ end
86
+
87
+ def bsr(headings, title)
88
+ # puts "bsr() headings: #{headings.inspect}"
89
+ { headings: headings, name: title, title: title }
90
+ end
91
+
92
+ def block_summary(opts, headings, block_title, current)
93
+ puts "block_summary() block_title: #{block_title.inspect}" if $pdebug
94
+ return [current] unless opts[:struct]
95
+
96
+ # return [{ body: current, name: block_title, title: block_title }] unless opts[:bash]
97
+ return [bsr(headings, block_title).merge({ body: current })] unless opts[:bash]
98
+
99
+ bm = block_title.match(/:(\S+)( |$)/)
100
+ reqs = block_title.scan(/\+\S+/).map { |s| s[1..] }
101
+
102
+ if $pdebug
103
+ puts ["block_summary() bm: #{bm.inspect}",
104
+ "block_summary() reqs: #{reqs.inspect}"]
105
+ end
106
+
107
+ if bm && bm[1]
108
+ # [{ body: current, name: bm[1], reqs: reqs, title: bm[1] }]
109
+ [bsr(headings, bm[1]).merge({ body: current, reqs: reqs })]
110
+ else
111
+ # [{ body: current, name: block_title, reqs: reqs, title: block_title }]
112
+ [bsr(headings, block_title).merge({ body: current, reqs: reqs })]
113
+ end
114
+ end
115
+
116
+ def get_blocks(call_options = {}, &options_block)
117
+ opts = copts call_options, options_block
118
+
119
+ blocks = []
120
+ current = nil
121
+ in_block = false
122
+ block_title = ''
123
+
124
+ headings = []
125
+ File.readlines(opts[:mdfilename]).each do |line|
126
+ puts "get_blocks() line: #{line.inspect}" if $pdebug
127
+ continue unless line
128
+
129
+ if opts[:mdheadings]
130
+ if (lm = line.match(/^### *(.+?) *$/))
131
+ headings = [headings[0], headings[1], lm[1]]
132
+ elsif (lm = line.match(/^## *([^#]*?) *$/))
133
+ headings = [headings[0], lm[1]]
134
+ elsif (lm = line.match(/^# *([^#]*?) *$/))
135
+ headings = [lm[1]]
136
+ end
137
+ puts "get_blocks() headings: #{headings.inspect}" if $pdebug
138
+ end
139
+
140
+ if line.match(/^`{3,}/)
141
+ if in_block
142
+ puts 'get_blocks() in_block' if $pdebug
143
+ if current
144
+
145
+ # block_title ||= current.join(' ').gsub(/ +/, ' ')[0..64]
146
+ block_title = current.join(' ').gsub(/ +/, ' ')[0..64] if block_title.nil? || block_title.empty?
147
+
148
+ blocks += block_summary opts, headings, block_title, current
149
+ current = nil
150
+ end
151
+ in_block = false
152
+ block_title = ''
153
+ else
154
+ ## new block
155
+ #
156
+
157
+ # lm = line.match(/^`{3,}([^`\s]+)( .+)?$/)
158
+ lm = line.match(/^`{3,}([^`\s]*) *(.*)$/)
159
+
160
+ do1 = false
161
+ if opts[:bash_only]
162
+ do1 = true if lm && (lm[1] == 'bash')
163
+ elsif opts[:exclude_expect_blocks]
164
+ do1 = true unless lm && (lm[1] == 'expect')
165
+ else
166
+ do1 = true
167
+ end
168
+ if $pdebug
169
+ puts ["get_blocks() lm: #{lm.inspect}",
170
+ "get_blocks() opts: #{opts.inspect}",
171
+ "get_blocks() do1: #{do1}"]
172
+ end
173
+
174
+ if do1 && (!opts[:title_match] || (lm && lm[2] && lm[2].match(opts[:title_match])))
175
+ current = []
176
+ in_block = true
177
+ block_title = (lm && lm[2])
178
+ end
179
+
180
+ end
181
+ elsif current
182
+ current += [line.chomp]
183
+ end
184
+ end
185
+ blocks.tap { |ret| puts "get_blocks() ret: #{ret.inspect}" if $pdebug }
186
+ end
187
+
188
+ def make_block_label(block, call_options = {})
189
+ opts = options.merge(call_options)
190
+ puts "make_block_label() opts: #{opts.inspect}" if $pdebug
191
+ puts "make_block_label() block: #{block.inspect}" if $pdebug
192
+ if opts[:mdheadings]
193
+ heads = block.fetch(:headings, []).compact.join(' # ')
194
+ "#{block[:title]} [#{heads}] (#{opts[:mdfilename]})"
195
+ else
196
+ "#{block[:title]} (#{opts[:mdfilename]})"
197
+ end
198
+ end
199
+
200
+ def make_block_labels(call_options = {})
201
+ opts = options.merge(call_options)
202
+ get_blocks(opts).map do |block|
203
+ make_block_label block, opts
204
+ end
205
+ end
206
+
207
+ def select_block(call_options = {}, &options_block)
208
+ opts = copts call_options, options_block
209
+
210
+ blocks = get_blocks(opts.merge(struct: true))
211
+ puts "select_block() blocks: #{blocks.to_yaml}" if $pdebug
212
+
213
+ prompt = TTY::Prompt.new(interrupt: :exit)
214
+ pt = "#{opts.fetch(:prompt, nil) || 'Pick one'}:"
215
+ puts "select_block() pt: #{pt.inspect}" if $pdebug
216
+
217
+ blocks.each { |block| block.merge! label: make_block_label(block, opts) }
218
+ block_labels = blocks.map { |block| block[:label] }
219
+ puts "select_block() block_labels: #{block_labels.inspect}" if $pdebug
220
+
221
+ if opts[:preview_options]
222
+ select_per_page = 3
223
+ block_labels.each do |bn|
224
+ fout " - #{bn}"
225
+ end
226
+ else
227
+ select_per_page = SELECT_PAGE_HEIGHT
228
+ end
229
+
230
+ return nil if block_labels.count.zero?
231
+
232
+ sel = prompt.select(pt, block_labels, per_page: select_per_page)
233
+ puts "select_block() sel: #{sel.inspect}" if $pdebug
234
+ # catch
235
+ # # catch TTY::Reader::InputInterrupt
236
+ # puts "InputInterrupt"
237
+ # end
238
+
239
+ label_block = blocks.select { |block| block[:label] == sel }.fetch(0, nil)
240
+ puts "select_block() label_block: #{label_block.inspect}" if $pdebug
241
+ sel = label_block[:name]
242
+ puts "select_block() sel: #{sel.inspect}" if $pdebug
243
+
244
+ cbs = code_blocks(blocks, sel)
245
+ puts "select_block() cbs: #{cbs.inspect}" if $pdebug
246
+
247
+ ## display code blocks for approval
248
+ #
249
+ cbs.each { |cb| fout cb } if opts[:display] || opts[:approve]
250
+
251
+ allow = true
252
+ allow = prompt.yes? 'Process?' if opts[:approve]
253
+
254
+ selected = block_by_name blocks, sel
255
+ puts "select_block() selected: #{selected.inspect}" if $pdebug
256
+ if allow && opts[:execute]
257
+
258
+ ## process in script, to handle line continuations
259
+ #
260
+ cmd2 = cbs.flatten.join("\n")
261
+ fout "$ #{cmd2.to_yaml}"
262
+
263
+ # Open3.popen3(cmd2) do |stdin, stdout, stderr, wait_thr|
264
+ # cnt += 1
265
+ # # stdin.puts "This is sent to the command"
266
+ # # stdin.close # we're done
267
+ # stdout_str = stdout.read # read stdout to string. note that this will block until the command is done!
268
+ # stderr_str = stderr.read # read stderr to string
269
+ # status = wait_thr.value # will block until the command finishes; returns status that responds to .success?
270
+ # fout "#{stdout_str}"
271
+ # fout "#{cnt}: err: #{stderr_str}" if stderr_str != ''
272
+ # # fout "#{cnt}: stt: #{status}"
273
+ # end
274
+
275
+ Open3.popen3(cmd2) do |stdin, stdout, stderr|
276
+ stdin.close_write
277
+ begin
278
+ files = [stdout, stderr]
279
+
280
+ until all_eof(files)
281
+ ready = IO.select(files)
282
+
283
+ next unless ready
284
+
285
+ readable = ready[0]
286
+ # writable = ready[1]
287
+ # exceptions = ready[2]
288
+
289
+ readable.each do |f|
290
+ # fileno = f.fileno
291
+
292
+ data = f.read_nonblock(BLOCK_SIZE)
293
+ # fout "- fileno: #{fileno}\n#{data}"
294
+ fout data
295
+ rescue EOFError #=> e
296
+ # fout "fileno: #{fileno} EOF"
297
+ end
298
+ end
299
+ rescue IOError => e
300
+ fout "IOError: #{e}"
301
+ end
302
+ end
303
+ end
304
+
305
+ selected[:name]
306
+ end
307
+
308
+ def select_md_file
309
+ opts = options
310
+ files = find_files
311
+ if files.count == 1
312
+ sel = files[0]
313
+ elsif files.count >= 2
314
+
315
+ if opts[:preview_options]
316
+ select_per_page = 3
317
+ files.each do |file|
318
+ fout " - #{file}"
319
+ end
320
+ else
321
+ select_per_page = SELECT_PAGE_HEIGHT
322
+ end
323
+
324
+ prompt = TTY::Prompt.new
325
+ sel = prompt.select("#{opts.fetch(:prompt, 'Pick one')}:", files, per_page: select_per_page)
326
+ end
327
+
328
+ sel
329
+ end
330
+
331
+ # Returns true if all files are EOF
332
+ #
333
+ def all_eof(files)
334
+ files.find { |f| !f.eof }.nil?
335
+ end
336
+
337
+ def code(table, block)
338
+ puts "code() table: #{table.inspect}" if $pdebug
339
+ puts "code() block: #{block.inspect}" if $pdebug
340
+ all = [block[:name]] + unroll(table, block[:reqs])
341
+ puts "code() all: #{all.inspect}" if $pdebug
342
+ all.reverse.map do |req|
343
+ puts "code() req: #{req.inspect}" if $pdebug
344
+ block_by_name(table, req).fetch(:body, '')
345
+ end
346
+ .flatten(1)
347
+ .tap { |ret| puts "code() ret: #{ret.inspect}" if $pdebug }
348
+ end
349
+
350
+ def block_by_name(table, name, default = {})
351
+ table.select { |block| block[:name] == name }.fetch(0, default)
352
+ end
353
+
354
+ def code_blocks(table, name)
355
+ puts "code_blocks() table: #{table.inspect}" if $pdebug
356
+ puts "code_blocks() name: #{name.inspect}" if $pdebug
357
+ name_block = block_by_name(table, name)
358
+ puts "code_blocks() name_block: #{name_block.inspect}" if $pdebug
359
+ all = [name_block[:name]] + unroll(table, name_block[:reqs])
360
+ puts "code_blocks() all: #{all.inspect}" if $pdebug
361
+
362
+ # in order of appearance in document
363
+ table.select { |block| all.include? block[:name] }
364
+ .map { |block| block.fetch(:body, '') }
365
+ .flatten(1)
366
+ .tap { |ret| puts "code_blocks() ret: #{ret.inspect}" if $pdebug }
367
+ end
368
+
369
+ def unroll(table, reqs)
370
+ puts "unroll() table: #{table.inspect}" if $pdebug
371
+ puts "unroll() reqs: #{reqs.inspect}" if $pdebug
372
+ all = []
373
+ rem = reqs
374
+ while rem.count.positive?
375
+ puts "unrol() rem: #{rem.inspect}" if $pdebug
376
+ rem = rem.map do |req|
377
+ puts "unrol() req: #{req.inspect}" if $pdebug
378
+ next if all.include? req
379
+
380
+ all += [req]
381
+ puts "unrol() all: #{all.inspect}" if $pdebug
382
+ block_by_name(table, req).fetch(:reqs, [])
383
+ end
384
+ .compact
385
+ .flatten(1)
386
+ .tap { |_ret| puts "unroll() rem: #{rem.inspect}" if $pdebug }
387
+ end
388
+ all.tap { |ret| puts "unroll() ret: #{ret.inspect}" if $pdebug }
389
+ end
390
+ end
391
+
392
+ # $stderr.sync = true
393
+ # $stdout.sync = true
394
+
395
+ def fout(str)
396
+ puts str # to stdout
397
+ end
398
+
399
+ ## configuration file
400
+ #
401
+ def read_configuration!(options, configuration_path)
402
+ if Pathname.new(configuration_path).exist?
403
+ # rubocop:disable Security/YAMLLoad
404
+ options.merge!((YAML.load(File.open(configuration_path)) || {})
405
+ .transform_keys(&:to_sym))
406
+ # rubocop:enable Security/YAMLLoad
407
+ end
408
+ options
409
+ end
410
+
411
+ def run
412
+ ## default configuration
413
+ #
414
+ options = {
415
+ mdheadings: true,
416
+ list_blocks: false,
417
+ list_docs: false,
418
+ mdfilename: 'README.md',
419
+ mdfolder: '.'
420
+ }
421
+
422
+ def options_finalize!(options); end
423
+
424
+ # read local configuration file
425
+ #
426
+ read_configuration! options, ".#{APP_NAME.downcase}.yml"
427
+
428
+ ## read current details for aws resources from app_data_file
429
+ #
430
+ # load_resources! options
431
+ # puts "q31 options: #{options.to_yaml}" if $pdebug
432
+
433
+ # rubocop:disable Metrics/BlockLength
434
+ option_parser = OptionParser.new do |opts|
435
+ executable_name = File.basename($PROGRAM_NAME)
436
+ opts.banner = [
437
+ "#{APP_NAME} - #{APP_DESC} (#{VERSION})".freeze,
438
+ "Usage: #{executable_name} [options]"
439
+ ].join("\n")
440
+
441
+ ## menu top: on_head appear in reverse order added
442
+ #
443
+ opts.on('--config PATH', 'Read configuration file') do |value|
444
+ read_configuration! options, value
445
+ end
446
+
447
+ ## menu body: items appear in order added
448
+ #
449
+ opts.on('-f RELATIVE', '--mdfilename', 'Name of document') do |value|
450
+ options[:mdfilename] = value
451
+ end
452
+
453
+ opts.on('-p PATH', '--mdfolder', 'Path to documents') do |value|
454
+ options[:mdfolder] = value
455
+ end
456
+
457
+ opts.on('--list-blocks', 'List blocks') do |_value|
458
+ options[:list_blocks] = true
459
+ end
460
+
461
+ opts.on('--list-docs', 'List docs in current folder') do |_value|
462
+ options[:list_docs] = true
463
+ end
464
+
465
+ ## menu bottom: items appear in order added
466
+ #
467
+ opts.on_tail('-h', '--help', 'App help') do |_value|
468
+ puts option_parser.help
469
+ exit
470
+ end
471
+
472
+ opts.on_tail('-v', '--version', 'App version') do |_value|
473
+ puts VERSION
474
+ exit
475
+ end
476
+
477
+ opts.on_tail('-x', '--exit', 'Exit app') do |_value|
478
+ exit
479
+ end
480
+
481
+ opts.on_tail('-0', 'Show configuration') do |_v|
482
+ options_finalize! options
483
+ puts options.to_yaml
484
+ end
485
+ end
486
+ # rubocop:enable Metrics/BlockLength
487
+
488
+ option_parser.load # filename defaults to basename of the program without suffix in a directory ~/.options
489
+ option_parser.environment # env defaults to the basename of the program.
490
+ option_parser.parse! # (into: options)
491
+ options_finalize! options
492
+
493
+ ## process
494
+ #
495
+ # rubocop:disable Metrics/BlockLength
496
+ loop do # once
497
+ mp = MarkParse.new options
498
+ options.merge!(
499
+ {
500
+ approve: true,
501
+ bash: true,
502
+ display: true,
503
+ exclude_expect_blocks: true,
504
+ execute: true,
505
+ prompt: 'Execute',
506
+ struct: true
507
+ }
508
+ )
509
+
510
+ ## show
511
+ #
512
+ if options[:list_docs]
513
+ fout mp.find_files
514
+ break
515
+ end
516
+
517
+ if options[:list_blocks]
518
+ fout (mp.find_files.map do |file|
519
+ mp.make_block_labels(mdfilename: file, struct: true)
520
+ end).flatten(1).to_yaml
521
+ break
522
+ end
523
+
524
+ ## process
525
+ #
526
+ mp.select_block(bash: true, struct: true) if options[:mdfilename]
527
+
528
+ # rubocop:disable Style/BlockComments
529
+ # rubocop:enable Style/BlockComments
530
+
531
+ break unless false # rubocop:disable Lint/LiteralAsCondition
12
532
  end
13
533
  end
534
+ # rubocop:enable Metrics/BlockLength
535
+ # rubocop:enable Style/GlobalVars
14
536
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: markdown_exec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fareed Stevenson