markdown_exec 1.3.3.5 → 1.3.6

Sign up to get free protection for your applications and to get access to all the features.
data/lib/markdown_exec.rb CHANGED
@@ -12,10 +12,17 @@ require 'shellwords'
12
12
  require 'tty-prompt'
13
13
  require 'yaml'
14
14
 
15
+ require_relative 'block_label'
15
16
  require_relative 'cached_nested_file_reader'
16
17
  require_relative 'cli'
17
18
  require_relative 'colorize'
18
19
  require_relative 'env'
20
+ require_relative 'fcb'
21
+ require_relative 'filter'
22
+ require_relative 'mdoc'
23
+ require_relative 'option_value'
24
+ require_relative 'saved_assets'
25
+ require_relative 'saved_files_matcher'
19
26
  require_relative 'shared'
20
27
  require_relative 'tap'
21
28
  require_relative 'markdown_exec/version'
@@ -90,14 +97,14 @@ public
90
97
 
91
98
  # :reek:UtilityFunction
92
99
  def list_recent_output(saved_stdout_folder, saved_stdout_glob, list_count)
93
- Sfiles.new(saved_stdout_folder,
94
- saved_stdout_glob).most_recent_list(list_count)
100
+ SavedFilesMatcher.most_recent_list(saved_stdout_folder,
101
+ saved_stdout_glob, list_count)
95
102
  end
96
103
 
97
104
  # :reek:UtilityFunction
98
105
  def list_recent_scripts(saved_script_folder, saved_script_glob, list_count)
99
- Sfiles.new(saved_script_folder,
100
- saved_script_glob).most_recent_list(list_count)
106
+ SavedFilesMatcher.most_recent_list(saved_script_folder,
107
+ saved_script_glob, list_count)
101
108
  end
102
109
 
103
110
  # convert regex match groups to a hash with symbol keys
@@ -111,427 +118,9 @@ end
111
118
  #
112
119
  module MarkdownExec
113
120
  # :reek:IrresponsibleModule
114
- class Error < StandardError; end
115
-
116
- # fenced code block
117
- #
118
- class FCB
119
- def initialize(options = {})
120
- @attrs = {
121
- body: nil,
122
- call: nil,
123
- headings: [],
124
- name: nil,
125
- reqs: [],
126
- shell: '',
127
- title: '',
128
- random: Random.new.rand,
129
- text: nil # displayable in menu
130
- }.merge options
131
- end
132
-
133
- def to_h
134
- @attrs
135
- end
136
-
137
- def to_yaml
138
- @attrs.to_yaml
139
- end
140
-
141
- private
142
-
143
- # :reek:ManualDispatch
144
- def method_missing(method, *args, &block)
145
- method_name = method.to_s
146
-
147
- if @attrs.respond_to?(method_name)
148
- @attrs.send(method_name, *args, &block)
149
- elsif method_name[-1] == '='
150
- @attrs[method_name.chop.to_sym] = args[0]
151
- else
152
- @attrs[method_name.to_sym]
153
- end
154
- rescue StandardError => err
155
- warn(error = "ERROR ** FCB.method_missing(method: #{method_name}," \
156
- " *args: #{args.inspect}, &block)")
157
- warn err.inspect
158
- warn(caller[0..4])
159
- raise StandardError, error
160
- end
161
-
162
- # option names are available as methods
163
- #
164
- def respond_to_missing?(_method_name, _include_private = false)
165
- true # recognize all hash methods, rest are treated as hash keys
166
- end
167
- end
168
-
169
- # select fcb per options
170
- #
171
- # :reek:UtilityFunction
172
- class Filter
173
- # def self.fcb_title_parse(opts, fcb_title)
174
- # fcb_title.match(Regexp.new(opts[:fenced_start_ex_match])).named_captures.sym_keys
175
- # end
176
-
177
- def self.fcb_select?(options, fcb)
178
- # options.tap_yaml 'options'
179
- # fcb.tap_inspect 'fcb'
180
- name = fcb.fetch(:name, '').tap_inspect 'name'
181
- shell = fcb.fetch(:shell, '').tap_inspect 'shell'
182
-
183
- ## include hidden blocks for later use
184
- #
185
- name_default = true
186
- name_exclude = nil
187
- name_select = nil
188
- shell_default = true
189
- shell_exclude = nil
190
- shell_select = nil
191
- hidden_name = nil
192
-
193
- if name.present? && options[:block_name]
194
- if name =~ /#{options[:block_name]}/
195
- '=~ block_name'.tap_puts
196
- name_select = true
197
- name_exclude = false
198
- else
199
- '!~ block_name'.tap_puts
200
- name_exclude = true
201
- name_select = false
202
- end
203
- end
204
-
205
- if name.present? && name_select.nil? && options[:select_by_name_regex].present?
206
- '+select_by_name_regex'.tap_puts
207
- name_select = (!!(name =~ /#{options[:select_by_name_regex]}/)).tap_inspect 'name_select'
208
- end
209
-
210
- if shell.present? && options[:select_by_shell_regex].present?
211
- '+select_by_shell_regex'.tap_puts
212
- shell_select = (!!(shell =~ /#{options[:select_by_shell_regex]}/)).tap_inspect 'shell_select'
213
- end
214
-
215
- if name.present? && name_exclude.nil? && options[:exclude_by_name_regex].present?
216
- '-exclude_by_name_regex'.tap_puts
217
- name_exclude = (!!(name =~ /#{options[:exclude_by_name_regex]}/)).tap_inspect 'name_exclude'
218
- end
219
-
220
- if shell.present? && options[:exclude_by_shell_regex].present?
221
- '-exclude_by_shell_regex'.tap_puts
222
- shell_exclude = (!!(shell =~ /#{options[:exclude_by_shell_regex]}/)).tap_inspect 'shell_exclude'
223
- end
224
-
225
- if name.present? && options[:hide_blocks_by_name] &&
226
- options[:block_name_hidden_match].present?
227
- '+block_name_hidden_match'.tap_puts
228
- hidden_name = (!!(name =~ /#{options[:block_name_hidden_match]}/)).tap_inspect 'hidden_name'
229
- end
230
-
231
- if shell.present? && options[:hide_blocks_by_shell] &&
232
- options[:block_shell_hidden_match].present?
233
- '-hide_blocks_by_shell'.tap_puts
234
- (!!(shell =~ /#{options[:block_shell_hidden_match]}/)).tap_inspect 'hidden_shell'
235
- end
236
-
237
- if options[:bash_only]
238
- '-bash_only'.tap_puts
239
- shell_default = (shell == 'bash').tap_inspect 'shell_default'
240
- end
241
-
242
- ## name matching does not filter hidden blocks
243
- #
244
- case
245
- when options[:no_chrome] && fcb.fetch(:chrome, false)
246
- '-no_chrome'.tap_puts
247
- false
248
- when options[:exclude_expect_blocks] && shell == 'expect'
249
- '-exclude_expect_blocks'.tap_puts
250
- false
251
- when hidden_name == true
252
- true
253
- when name_exclude == true, shell_exclude == true,
254
- name_select == false, shell_select == false
255
- false
256
- when name_select == true, shell_select == true
257
- true
258
- when name_default == false, shell_default == false
259
- false
260
- else
261
- true
262
- end.tap_inspect
263
- rescue StandardError => err
264
- warn("ERROR ** Filter::fcb_select?(); #{err.inspect}")
265
- raise err
266
- end
267
- end # class Filter
268
-
269
- ## an imported markdown document
270
- #
271
- class MDoc
272
- attr_reader :table
273
-
274
- # convert block name to fcb_parse
275
- #
276
- def initialize(table)
277
- @table = table
278
- end
279
-
280
- def collect_recursively_required_code(name)
281
- get_required_blocks(name)
282
- .map do |fcb|
283
- body = fcb[:body].join("\n")
284
-
285
- if fcb[:cann]
286
- xcall = fcb[:cann][1..-2]
287
- mstdin = xcall.match(/<(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/)
288
- mstdout = xcall.match(/>(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/)
289
-
290
- yqcmd = if mstdin[:type]
291
- "echo \"$#{mstdin[:name]}\" | yq '#{body}'"
292
- else
293
- "yq e '#{body}' '#{mstdin[:name]}'"
294
- end
295
- if mstdout[:type]
296
- "export #{mstdout[:name]}=$(#{yqcmd})"
297
- else
298
- "#{yqcmd} > '#{mstdout[:name]}'"
299
- end
300
- elsif fcb[:stdout]
301
- stdout = fcb[:stdout]
302
- body = fcb[:body].join("\n")
303
- if stdout[:type]
304
- %(export #{stdout[:name]}=$(cat <<"EOF"\n#{body}\nEOF\n))
305
- else
306
- "cat > '#{stdout[:name]}' <<\"EOF\"\n" \
307
- "#{body}\n" \
308
- "EOF\n"
309
- end
310
- else
311
- fcb[:body]
312
- end
313
- end.flatten(1)
314
- end
315
-
316
- def get_block_by_name(name, default = {})
317
- @table.select { |fcb| fcb.fetch(:name, '') == name }.fetch(0, default)
318
- end
319
-
320
- def get_required_blocks(name)
321
- name_block = get_block_by_name(name)
322
- raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
323
-
324
- all = [name_block.fetch(:name, '')] + recursively_required(name_block[:reqs])
325
-
326
- # in order of appearance in document
327
- # insert function blocks
328
- @table.select { |fcb| all.include? fcb.fetch(:name, '') }
329
- .map do |fcb|
330
- if (call = fcb[:call])
331
- [get_block_by_name("[#{call.match(/^%\((\S+) |\)/)[1]}]")
332
- .merge({ cann: call })]
333
- else
334
- []
335
- end + [fcb]
336
- end.flatten(1)
337
- end
338
-
339
- # :reek:UtilityFunction
340
- def hide_menu_block_per_options(opts, block)
341
- (opts[:hide_blocks_by_name] &&
342
- block[:name]&.match(Regexp.new(opts[:block_name_hidden_match])) &&
343
- (block[:name]&.present? || block[:label]&.present?)
344
- ).tap_inspect
345
- end
346
-
347
- # def blocks_for_menu(opts)
348
- # if opts[:hide_blocks_by_name]
349
- # @table.reject { |block| hide_menu_block_per_options opts, block }
350
- # else
351
- # @table
352
- # end
353
- # end
354
-
355
- def fcbs_per_options(opts = {})
356
- options = opts.merge(block_name_hidden_match: nil)
357
- selrows = @table.select do |fcb_title_groups|
358
- Filter.fcb_select? options, fcb_title_groups
359
- end
360
-
361
- ### hide rows correctly
362
-
363
- if opts[:hide_blocks_by_name]
364
- selrows.reject { |block| hide_menu_block_per_options opts, block }
365
- else
366
- selrows
367
- end.map do |block|
368
- # block[:name] = block[:text] if block[:name].nil?
369
- block
370
- end
371
- end
372
-
373
- def recursively_required(reqs)
374
- return [] unless reqs
375
-
376
- rem = reqs
377
- memo = []
378
- while rem.count.positive?
379
- rem = rem.map do |req|
380
- next if memo.include? req
381
-
382
- memo += [req]
383
- get_block_by_name(req).fetch(:reqs, [])
384
- end
385
- .compact
386
- .flatten(1)
387
- end
388
- memo
389
- end
390
- end # class MDoc
391
-
392
- # format option defaults and values
393
- #
394
- # :reek:TooManyInstanceVariables
395
- class BlockLabel
396
- def initialize(filename:, headings:, menu_blocks_with_docname:,
397
- menu_blocks_with_headings:, title:, body:, text:)
398
- @filename = filename
399
- @headings = headings
400
- @menu_blocks_with_docname = menu_blocks_with_docname
401
- @menu_blocks_with_headings = menu_blocks_with_headings
402
- # @title = title.present? ? title : body
403
- @title = title
404
- @body = body
405
- @text = text
406
- rescue StandardError => err
407
- warn(error = "ERROR ** BlockLabel.initialize(); #{err.inspect}")
408
- binding.pry if $tap_enable
409
- raise ArgumentError, error
410
- end
411
-
412
- # join title, headings, filename
413
- #
414
- def make
415
- label = @title
416
- label = @body unless label.present?
417
- label = @text unless label.present?
418
- label.tap_inspect
419
- ([label] +
420
- (if @menu_blocks_with_headings
421
- [@headings.compact.join(' # ')]
422
- else
423
- []
424
- end) +
425
- (
426
- if @menu_blocks_with_docname
427
- [@filename]
428
- else
429
- []
430
- end
431
- )).join(' ')
432
- rescue StandardError => err
433
- warn(error = "ERROR ** BlockLabel.make(); #{err.inspect}")
434
- binding.pry if $tap_enable
435
- raise ArgumentError, error
436
- end
437
- end # class BlockLabel
438
-
439
121
  FNR11 = '/'
440
122
  FNR12 = ',~'
441
123
 
442
- # format option defaults and values
443
- #
444
- class SavedAsset
445
- def initialize(filename:, prefix:, time:, blockname:)
446
- @filename = filename
447
- @prefix = prefix
448
- @time = time
449
- @blockname = blockname
450
- end
451
-
452
- def script_name
453
- fne = @filename.gsub(FNR11, FNR12)
454
- "#{[@prefix, @time.strftime('%F-%H-%M-%S'), fne, ',',
455
- @blockname].join('_')}.sh"
456
- end
457
-
458
- def stdout_name
459
- "#{[@prefix, @time.strftime('%F-%H-%M-%S'), @filename,
460
- @blockname].join('_')}.out.txt"
461
- end
462
- end # class SavedAsset
463
-
464
- # format option defaults and values
465
- #
466
- class OptionValue
467
- def initialize(value)
468
- @value = value
469
- end
470
-
471
- # as default value in env_str()
472
- #
473
- def for_hash(default = nil)
474
- return default if @value.nil?
475
-
476
- case @value.class.to_s
477
- when 'String', 'Integer'
478
- @value
479
- when 'FalseClass', 'TrueClass'
480
- @value ? true : false
481
- when @value.empty?
482
- default
483
- else
484
- @value.to_s
485
- end
486
- end
487
-
488
- # for output as default value in list_default_yaml()
489
- #
490
- def for_yaml(default = nil)
491
- return default if @value.nil?
492
-
493
- case @value.class.to_s
494
- when 'String'
495
- "'#{@value}'"
496
- when 'Integer'
497
- @value
498
- when 'FalseClass', 'TrueClass'
499
- @value ? true : false
500
- when @value.empty?
501
- default
502
- else
503
- @value.to_s
504
- end
505
- end
506
- end # class OptionValue
507
-
508
- # a generated list of saved files
509
- #
510
- class Sfiles
511
- def initialize(folder, glob)
512
- @folder = folder
513
- @glob = glob
514
- end
515
-
516
- def list_all
517
- Dir.glob(File.join(@folder, @glob))
518
- end
519
-
520
- def most_recent(arr = nil)
521
- arr = list_all if arr.nil?
522
- return if arr.count < 1
523
-
524
- arr.max
525
- end
526
-
527
- def most_recent_list(list_count, arr = nil)
528
- arr = list_all if arr.nil?
529
- return if (ac = arr.count) < 1
530
-
531
- arr.sort[-[ac, list_count].min..].reverse
532
- end
533
- end # class Sfiles
534
-
535
124
  ##
536
125
  #
537
126
  # rubocop:disable Layout/LineLength
@@ -570,7 +159,7 @@ module MarkdownExec
570
159
  []
571
160
  else
572
161
  argv[0..ind - 1]
573
- end #.tap_inspect
162
+ end
574
163
  end
575
164
 
576
165
  # return arguments after `--`
@@ -581,7 +170,7 @@ module MarkdownExec
581
170
  []
582
171
  else
583
172
  argv[ind + 1..-1]
584
- end #.tap_inspect
173
+ end
585
174
  end
586
175
 
587
176
  ##
@@ -589,16 +178,14 @@ module MarkdownExec
589
178
  #
590
179
  def base_options
591
180
  menu_iter do |item|
592
- # noisy item.tap_yaml name: :item
593
181
  next unless item[:opt_name].present?
594
182
 
595
183
  item_default = item[:default]
596
- # noisy item_default.tap_inspect name: :item_default
597
184
  value = if item_default.nil?
598
185
  item_default
599
186
  else
600
187
  env_str(item[:env_var],
601
- default: OptionValue.new(item_default).for_hash)
188
+ default: OptionValue.for_hash(item_default))
602
189
  end
603
190
  [item[:opt_name], item[:proccode] ? item[:proccode].call(value) : value]
604
191
  end.compact.to_h
@@ -757,7 +344,7 @@ module MarkdownExec
757
344
 
758
345
  # :reek:DuplicateMethodCall
759
346
  def exec_block(options, _block_name = '')
760
- options = calculated_options.merge(options).tap_yaml 'options'
347
+ options = calculated_options.merge(options)
761
348
  update_options options, over: false
762
349
 
763
350
  # document and block reports
@@ -935,9 +522,11 @@ module MarkdownExec
935
522
  # return body if not struct
936
523
  #
937
524
  def list_blocks_in_file(call_options = {}, &options_block)
938
- opts = optsmerge(call_options, options_block) #.tap_yaml 'opts'
525
+ opts = optsmerge(call_options, options_block)
526
+ use_chrome = !opts[:no_chrome]
527
+
939
528
  blocks = []
940
- if opts[:menu_initial_divider].present?
529
+ if opts[:menu_initial_divider].present? && use_chrome
941
530
  blocks.push FCB.new({
942
531
  # name: '',
943
532
  chrome: true,
@@ -961,20 +550,26 @@ module MarkdownExec
961
550
  #
962
551
  if opts[:menu_divider_match].present? &&
963
552
  (mbody = fcb.body[0].match opts[:menu_divider_match])
964
- blocks.push FCB.new(
965
- { chrome: true,
966
- disabled: '',
967
- name: format(opts[:menu_divider_format],
968
- mbody[:name]).send(opts[:menu_divider_color].to_sym) }
969
- )
553
+ if use_chrome
554
+ blocks.push FCB.new(
555
+ { chrome: true,
556
+ disabled: '',
557
+ name: format(opts[:menu_divider_format],
558
+ mbody[:name]).send(opts[:menu_divider_color].to_sym) }
559
+ )
560
+ end
970
561
  elsif opts[:menu_task_match].present? &&
971
562
  (mbody = fcb.body[0].match opts[:menu_task_match])
972
- blocks.push FCB.new(
973
- { chrome: true,
974
- disabled: '',
975
- name: format(opts[:menu_task_format],
976
- mbody[:name]).send(opts[:menu_task_color].to_sym) }
977
- )
563
+ if use_chrome
564
+ blocks.push FCB.new(
565
+ { chrome: true,
566
+ disabled: '',
567
+ name: format(
568
+ opts[:menu_task_format],
569
+ $~.named_captures.transform_keys(&:to_sym)
570
+ ).send(opts[:menu_task_color].to_sym) }
571
+ )
572
+ end
978
573
  else
979
574
  # line not added
980
575
  end
@@ -985,7 +580,7 @@ module MarkdownExec
985
580
  end
986
581
  end
987
582
 
988
- if opts[:menu_divider_format].present? && opts[:menu_final_divider].present?
583
+ if opts[:menu_divider_format].present? && opts[:menu_final_divider].present? && use_chrome && use_chrome
989
584
  blocks.push FCB.new(
990
585
  { chrome: true,
991
586
  disabled: '',
@@ -994,7 +589,7 @@ module MarkdownExec
994
589
  .send(opts[:menu_divider_color].to_sym) }
995
590
  )
996
591
  end
997
- blocks.tap_inspect
592
+ blocks
998
593
  rescue StandardError => err
999
594
  warn(error = "ERROR ** MarkParse.list_blocks_in_file(); #{err.inspect}")
1000
595
  warn(caller[0..4])
@@ -1017,7 +612,7 @@ module MarkdownExec
1017
612
  next unless item[:opt_name].present? && item[:default].present?
1018
613
 
1019
614
  [
1020
- "#{item[:opt_name]}: #{OptionValue.new(item[:default]).for_yaml}",
615
+ "#{item[:opt_name]}: #{OptionValue.for_yaml(item[:default])}",
1021
616
  item[:description].present? ? item[:description] : nil
1022
617
  ].compact.join(' # ')
1023
618
  end.compact.sort
@@ -1074,7 +669,7 @@ module MarkdownExec
1074
669
  blocks.map do |block|
1075
670
  block.fetch(:text, nil) || block.fetch(:name, nil)
1076
671
  end
1077
- end.compact.reject(&:empty?).tap_inspect
672
+ end.compact.reject(&:empty?)
1078
673
  end
1079
674
 
1080
675
  ## output type (body string or full object) per option struct and bash
@@ -1086,19 +681,21 @@ module MarkdownExec
1086
681
  # fcb.fetch(:name, '') != '' && Filter.fcb_select?(opts, fcb)
1087
682
  Filter.fcb_select?(opts.merge(no_chrome: true), fcb)
1088
683
  end
1089
- blocks_per_opts(blocks, opts).tap_inspect
684
+ blocks_per_opts(blocks, opts)
1090
685
  end
1091
686
 
1092
687
  def make_block_labels(call_options = {})
1093
688
  opts = options.merge(call_options)
1094
689
  list_blocks_in_file(opts).map do |fcb|
1095
- BlockLabel.new(filename: opts[:filename],
1096
- headings: fcb.fetch(:headings, []),
1097
- menu_blocks_with_docname: opts[:menu_blocks_with_docname],
1098
- menu_blocks_with_headings: opts[:menu_blocks_with_headings],
1099
- title: fcb[:title],
1100
- text: fcb[:text],
1101
- body: fcb[:body]).make
690
+ BlockLabel.make(
691
+ filename: opts[:filename],
692
+ headings: fcb.fetch(:headings, []),
693
+ menu_blocks_with_docname: opts[:menu_blocks_with_docname],
694
+ menu_blocks_with_headings: opts[:menu_blocks_with_headings],
695
+ title: fcb[:title],
696
+ text: fcb[:text],
697
+ body: fcb[:body]
698
+ )
1102
699
  end.compact
1103
700
  end
1104
701
 
@@ -1352,8 +949,8 @@ module MarkdownExec
1352
949
  end
1353
950
 
1354
951
  def run_last_script
1355
- filename = Sfiles.new(@options[:saved_script_folder],
1356
- @options[:saved_script_glob]).most_recent
952
+ filename = SavedFilesMatcher.most_recent(@options[:saved_script_folder],
953
+ @options[:saved_script_glob])
1357
954
  return unless filename
1358
955
 
1359
956
  saved_name_split filename
@@ -1389,7 +986,7 @@ module MarkdownExec
1389
986
 
1390
987
  def select_approve_and_execute_block(call_options, &options_block)
1391
988
  opts = optsmerge call_options, options_block
1392
- blocks_in_file = list_blocks_in_file(opts.merge(struct: true)).tap_inspect
989
+ blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
1393
990
  mdoc = MDoc.new(blocks_in_file) do |nopts|
1394
991
  opts.merge!(nopts)
1395
992
  end
@@ -1405,7 +1002,7 @@ module MarkdownExec
1405
1002
  # next unless fcb.fetch(:name, '').present?
1406
1003
 
1407
1004
  fcb.merge!(
1408
- label: BlockLabel.new(
1005
+ label: BlockLabel.make(
1409
1006
  body: fcb[:body],
1410
1007
  filename: opts[:filename],
1411
1008
  headings: fcb.fetch(:headings, []),
@@ -1413,7 +1010,7 @@ module MarkdownExec
1413
1010
  menu_blocks_with_headings: opts[:menu_blocks_with_headings],
1414
1011
  text: fcb[:text],
1415
1012
  title: fcb[:title]
1416
- ).make
1013
+ )
1417
1014
  )
1418
1015
 
1419
1016
  fcb.to_h
@@ -1529,12 +1126,12 @@ module MarkdownExec
1529
1126
 
1530
1127
  dirname = File.dirname(@options[:saved_filespec])
1531
1128
  FileUtils.mkdir_p dirname
1532
- (shebang = if @options[:shebang]&.present?
1533
- "#{@options[:shebang]} #{@options[:shell]}\n"
1534
- else
1535
- ''
1536
- end
1537
- ).tap_inspect name: :shebang
1129
+ shebang = if @options[:shebang]&.present?
1130
+ "#{@options[:shebang]} #{@options[:shell]}\n"
1131
+ else
1132
+ ''
1133
+ end
1134
+
1538
1135
  File.write(@options[:saved_filespec], shebang +
1539
1136
  "# file_name: #{opts[:filename]}\n" \
1540
1137
  "# block_name: #{opts[:block_name]}\n" \
@@ -1546,3 +1143,5 @@ module MarkdownExec
1546
1143
  end
1547
1144
  end # class MarkParse
1548
1145
  end # module MarkdownExec
1146
+
1147
+ require 'minitest/autorun' if $PROGRAM_NAME == __FILE__