markdown_exec 0.0.3 → 0.1.0

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: 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: