markdown_exec 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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