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