lib/markdown_exec.rb in markdown_exec-1.3.3.5 vs lib/markdown_exec.rb in markdown_exec-1.3.6
- old
+ new
@@ -10,14 +10,21 @@
require 'optparse'
require 'shellwords'
require 'tty-prompt'
require 'yaml'
+require_relative 'block_label'
require_relative 'cached_nested_file_reader'
require_relative 'cli'
require_relative 'colorize'
require_relative 'env'
+require_relative 'fcb'
+require_relative 'filter'
+require_relative 'mdoc'
+require_relative 'option_value'
+require_relative 'saved_assets'
+require_relative 'saved_files_matcher'
require_relative 'shared'
require_relative 'tap'
require_relative 'markdown_exec/version'
include CLI
@@ -88,18 +95,18 @@
public
# :reek:UtilityFunction
def list_recent_output(saved_stdout_folder, saved_stdout_glob, list_count)
- Sfiles.new(saved_stdout_folder,
- saved_stdout_glob).most_recent_list(list_count)
+ SavedFilesMatcher.most_recent_list(saved_stdout_folder,
+ saved_stdout_glob, list_count)
end
# :reek:UtilityFunction
def list_recent_scripts(saved_script_folder, saved_script_glob, list_count)
- Sfiles.new(saved_script_folder,
- saved_script_glob).most_recent_list(list_count)
+ SavedFilesMatcher.most_recent_list(saved_script_folder,
+ saved_script_glob, list_count)
end
# convert regex match groups to a hash with symbol keys
#
# :reek:UtilityFunction
@@ -109,431 +116,13 @@
# execute markdown documents
#
module MarkdownExec
# :reek:IrresponsibleModule
- class Error < StandardError; end
-
- # fenced code block
- #
- class FCB
- def initialize(options = {})
- @attrs = {
- body: nil,
- call: nil,
- headings: [],
- name: nil,
- reqs: [],
- shell: '',
- title: '',
- random: Random.new.rand,
- text: nil # displayable in menu
- }.merge options
- end
-
- def to_h
- @attrs
- end
-
- def to_yaml
- @attrs.to_yaml
- end
-
- private
-
- # :reek:ManualDispatch
- def method_missing(method, *args, &block)
- method_name = method.to_s
-
- if @attrs.respond_to?(method_name)
- @attrs.send(method_name, *args, &block)
- elsif method_name[-1] == '='
- @attrs[method_name.chop.to_sym] = args[0]
- else
- @attrs[method_name.to_sym]
- end
- rescue StandardError => err
- warn(error = "ERROR ** FCB.method_missing(method: #{method_name}," \
- " *args: #{args.inspect}, &block)")
- warn err.inspect
- warn(caller[0..4])
- raise StandardError, error
- end
-
- # option names are available as methods
- #
- def respond_to_missing?(_method_name, _include_private = false)
- true # recognize all hash methods, rest are treated as hash keys
- end
- end
-
- # select fcb per options
- #
- # :reek:UtilityFunction
- class Filter
- # def self.fcb_title_parse(opts, fcb_title)
- # fcb_title.match(Regexp.new(opts[:fenced_start_ex_match])).named_captures.sym_keys
- # end
-
- def self.fcb_select?(options, fcb)
- # options.tap_yaml 'options'
- # fcb.tap_inspect 'fcb'
- name = fcb.fetch(:name, '').tap_inspect 'name'
- shell = fcb.fetch(:shell, '').tap_inspect 'shell'
-
- ## include hidden blocks for later use
- #
- name_default = true
- name_exclude = nil
- name_select = nil
- shell_default = true
- shell_exclude = nil
- shell_select = nil
- hidden_name = nil
-
- if name.present? && options[:block_name]
- if name =~ /#{options[:block_name]}/
- '=~ block_name'.tap_puts
- name_select = true
- name_exclude = false
- else
- '!~ block_name'.tap_puts
- name_exclude = true
- name_select = false
- end
- end
-
- if name.present? && name_select.nil? && options[:select_by_name_regex].present?
- '+select_by_name_regex'.tap_puts
- name_select = (!!(name =~ /#{options[:select_by_name_regex]}/)).tap_inspect 'name_select'
- end
-
- if shell.present? && options[:select_by_shell_regex].present?
- '+select_by_shell_regex'.tap_puts
- shell_select = (!!(shell =~ /#{options[:select_by_shell_regex]}/)).tap_inspect 'shell_select'
- end
-
- if name.present? && name_exclude.nil? && options[:exclude_by_name_regex].present?
- '-exclude_by_name_regex'.tap_puts
- name_exclude = (!!(name =~ /#{options[:exclude_by_name_regex]}/)).tap_inspect 'name_exclude'
- end
-
- if shell.present? && options[:exclude_by_shell_regex].present?
- '-exclude_by_shell_regex'.tap_puts
- shell_exclude = (!!(shell =~ /#{options[:exclude_by_shell_regex]}/)).tap_inspect 'shell_exclude'
- end
-
- if name.present? && options[:hide_blocks_by_name] &&
- options[:block_name_hidden_match].present?
- '+block_name_hidden_match'.tap_puts
- hidden_name = (!!(name =~ /#{options[:block_name_hidden_match]}/)).tap_inspect 'hidden_name'
- end
-
- if shell.present? && options[:hide_blocks_by_shell] &&
- options[:block_shell_hidden_match].present?
- '-hide_blocks_by_shell'.tap_puts
- (!!(shell =~ /#{options[:block_shell_hidden_match]}/)).tap_inspect 'hidden_shell'
- end
-
- if options[:bash_only]
- '-bash_only'.tap_puts
- shell_default = (shell == 'bash').tap_inspect 'shell_default'
- end
-
- ## name matching does not filter hidden blocks
- #
- case
- when options[:no_chrome] && fcb.fetch(:chrome, false)
- '-no_chrome'.tap_puts
- false
- when options[:exclude_expect_blocks] && shell == 'expect'
- '-exclude_expect_blocks'.tap_puts
- false
- when hidden_name == true
- true
- when name_exclude == true, shell_exclude == true,
- name_select == false, shell_select == false
- false
- when name_select == true, shell_select == true
- true
- when name_default == false, shell_default == false
- false
- else
- true
- end.tap_inspect
- rescue StandardError => err
- warn("ERROR ** Filter::fcb_select?(); #{err.inspect}")
- raise err
- end
- end # class Filter
-
- ## an imported markdown document
- #
- class MDoc
- attr_reader :table
-
- # convert block name to fcb_parse
- #
- def initialize(table)
- @table = table
- end
-
- def collect_recursively_required_code(name)
- get_required_blocks(name)
- .map do |fcb|
- body = fcb[:body].join("\n")
-
- if fcb[:cann]
- xcall = fcb[:cann][1..-2]
- mstdin = xcall.match(/<(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/)
- mstdout = xcall.match(/>(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/)
-
- yqcmd = if mstdin[:type]
- "echo \"$#{mstdin[:name]}\" | yq '#{body}'"
- else
- "yq e '#{body}' '#{mstdin[:name]}'"
- end
- if mstdout[:type]
- "export #{mstdout[:name]}=$(#{yqcmd})"
- else
- "#{yqcmd} > '#{mstdout[:name]}'"
- end
- elsif fcb[:stdout]
- stdout = fcb[:stdout]
- body = fcb[:body].join("\n")
- if stdout[:type]
- %(export #{stdout[:name]}=$(cat <<"EOF"\n#{body}\nEOF\n))
- else
- "cat > '#{stdout[:name]}' <<\"EOF\"\n" \
- "#{body}\n" \
- "EOF\n"
- end
- else
- fcb[:body]
- end
- end.flatten(1)
- end
-
- def get_block_by_name(name, default = {})
- @table.select { |fcb| fcb.fetch(:name, '') == name }.fetch(0, default)
- end
-
- def get_required_blocks(name)
- name_block = get_block_by_name(name)
- raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
-
- all = [name_block.fetch(:name, '')] + recursively_required(name_block[:reqs])
-
- # in order of appearance in document
- # insert function blocks
- @table.select { |fcb| all.include? fcb.fetch(:name, '') }
- .map do |fcb|
- if (call = fcb[:call])
- [get_block_by_name("[#{call.match(/^%\((\S+) |\)/)[1]}]")
- .merge({ cann: call })]
- else
- []
- end + [fcb]
- end.flatten(1)
- end
-
- # :reek:UtilityFunction
- def hide_menu_block_per_options(opts, block)
- (opts[:hide_blocks_by_name] &&
- block[:name]&.match(Regexp.new(opts[:block_name_hidden_match])) &&
- (block[:name]&.present? || block[:label]&.present?)
- ).tap_inspect
- end
-
- # def blocks_for_menu(opts)
- # if opts[:hide_blocks_by_name]
- # @table.reject { |block| hide_menu_block_per_options opts, block }
- # else
- # @table
- # end
- # end
-
- def fcbs_per_options(opts = {})
- options = opts.merge(block_name_hidden_match: nil)
- selrows = @table.select do |fcb_title_groups|
- Filter.fcb_select? options, fcb_title_groups
- end
-
- ### hide rows correctly
-
- if opts[:hide_blocks_by_name]
- selrows.reject { |block| hide_menu_block_per_options opts, block }
- else
- selrows
- end.map do |block|
- # block[:name] = block[:text] if block[:name].nil?
- block
- end
- end
-
- def recursively_required(reqs)
- return [] unless reqs
-
- rem = reqs
- memo = []
- while rem.count.positive?
- rem = rem.map do |req|
- next if memo.include? req
-
- memo += [req]
- get_block_by_name(req).fetch(:reqs, [])
- end
- .compact
- .flatten(1)
- end
- memo
- end
- end # class MDoc
-
- # format option defaults and values
- #
- # :reek:TooManyInstanceVariables
- class BlockLabel
- def initialize(filename:, headings:, menu_blocks_with_docname:,
- menu_blocks_with_headings:, title:, body:, text:)
- @filename = filename
- @headings = headings
- @menu_blocks_with_docname = menu_blocks_with_docname
- @menu_blocks_with_headings = menu_blocks_with_headings
- # @title = title.present? ? title : body
- @title = title
- @body = body
- @text = text
- rescue StandardError => err
- warn(error = "ERROR ** BlockLabel.initialize(); #{err.inspect}")
- binding.pry if $tap_enable
- raise ArgumentError, error
- end
-
- # join title, headings, filename
- #
- def make
- label = @title
- label = @body unless label.present?
- label = @text unless label.present?
- label.tap_inspect
- ([label] +
- (if @menu_blocks_with_headings
- [@headings.compact.join(' # ')]
- else
- []
- end) +
- (
- if @menu_blocks_with_docname
- [@filename]
- else
- []
- end
- )).join(' ')
- rescue StandardError => err
- warn(error = "ERROR ** BlockLabel.make(); #{err.inspect}")
- binding.pry if $tap_enable
- raise ArgumentError, error
- end
- end # class BlockLabel
-
FNR11 = '/'
FNR12 = ',~'
- # format option defaults and values
- #
- class SavedAsset
- def initialize(filename:, prefix:, time:, blockname:)
- @filename = filename
- @prefix = prefix
- @time = time
- @blockname = blockname
- end
-
- def script_name
- fne = @filename.gsub(FNR11, FNR12)
- "#{[@prefix, @time.strftime('%F-%H-%M-%S'), fne, ',',
- @blockname].join('_')}.sh"
- end
-
- def stdout_name
- "#{[@prefix, @time.strftime('%F-%H-%M-%S'), @filename,
- @blockname].join('_')}.out.txt"
- end
- end # class SavedAsset
-
- # format option defaults and values
- #
- class OptionValue
- def initialize(value)
- @value = value
- end
-
- # as default value in env_str()
- #
- def for_hash(default = nil)
- return default if @value.nil?
-
- case @value.class.to_s
- when 'String', 'Integer'
- @value
- when 'FalseClass', 'TrueClass'
- @value ? true : false
- when @value.empty?
- default
- else
- @value.to_s
- end
- end
-
- # for output as default value in list_default_yaml()
- #
- def for_yaml(default = nil)
- return default if @value.nil?
-
- case @value.class.to_s
- when 'String'
- "'#{@value}'"
- when 'Integer'
- @value
- when 'FalseClass', 'TrueClass'
- @value ? true : false
- when @value.empty?
- default
- else
- @value.to_s
- end
- end
- end # class OptionValue
-
- # a generated list of saved files
- #
- class Sfiles
- def initialize(folder, glob)
- @folder = folder
- @glob = glob
- end
-
- def list_all
- Dir.glob(File.join(@folder, @glob))
- end
-
- def most_recent(arr = nil)
- arr = list_all if arr.nil?
- return if arr.count < 1
-
- arr.max
- end
-
- def most_recent_list(list_count, arr = nil)
- arr = list_all if arr.nil?
- return if (ac = arr.count) < 1
-
- arr.sort[-[ac, list_count].min..].reverse
- end
- end # class Sfiles
-
##
#
# rubocop:disable Layout/LineLength
# :reek:DuplicateMethodCall { allow_calls: ['block', 'item', 'lm', 'opts', 'option', '@options', 'required_blocks'] }
# rubocop:enable Layout/LineLength
@@ -568,39 +157,37 @@
argv
when 0
[]
else
argv[0..ind - 1]
- end #.tap_inspect
+ end
end
# return arguments after `--`
#
def arguments_for_child(argv = ARGV)
case ind = argv.find_index('--')
when nil, argv.count - 1
[]
else
argv[ind + 1..-1]
- end #.tap_inspect
+ end
end
##
# options necessary to start, parse input, defaults for cli options
#
def base_options
menu_iter do |item|
- # noisy item.tap_yaml name: :item
next unless item[:opt_name].present?
item_default = item[:default]
- # noisy item_default.tap_inspect name: :item_default
value = if item_default.nil?
item_default
else
env_str(item[:env_var],
- default: OptionValue.new(item_default).for_hash)
+ default: OptionValue.for_hash(item_default))
end
[item[:opt_name], item[:proccode] ? item[:proccode].call(value) : value]
end.compact.to_h
end
@@ -755,11 +342,11 @@
fout frame
end
# :reek:DuplicateMethodCall
def exec_block(options, _block_name = '')
- options = calculated_options.merge(options).tap_yaml 'options'
+ options = calculated_options.merge(options)
update_options options, over: false
# document and block reports
#
files = list_files_per_options(options)
@@ -933,13 +520,15 @@
# return body, title if option.struct
# return body if not struct
#
def list_blocks_in_file(call_options = {}, &options_block)
- opts = optsmerge(call_options, options_block) #.tap_yaml 'opts'
+ opts = optsmerge(call_options, options_block)
+ use_chrome = !opts[:no_chrome]
+
blocks = []
- if opts[:menu_initial_divider].present?
+ if opts[:menu_initial_divider].present? && use_chrome
blocks.push FCB.new({
# name: '',
chrome: true,
name: format(
opts[:menu_divider_format],
@@ -959,44 +548,50 @@
when :line
## convert line to block
#
if opts[:menu_divider_match].present? &&
(mbody = fcb.body[0].match opts[:menu_divider_match])
- blocks.push FCB.new(
- { chrome: true,
- disabled: '',
- name: format(opts[:menu_divider_format],
- mbody[:name]).send(opts[:menu_divider_color].to_sym) }
- )
+ if use_chrome
+ blocks.push FCB.new(
+ { chrome: true,
+ disabled: '',
+ name: format(opts[:menu_divider_format],
+ mbody[:name]).send(opts[:menu_divider_color].to_sym) }
+ )
+ end
elsif opts[:menu_task_match].present? &&
(mbody = fcb.body[0].match opts[:menu_task_match])
- blocks.push FCB.new(
- { chrome: true,
- disabled: '',
- name: format(opts[:menu_task_format],
- mbody[:name]).send(opts[:menu_task_color].to_sym) }
- )
+ if use_chrome
+ blocks.push FCB.new(
+ { chrome: true,
+ disabled: '',
+ name: format(
+ opts[:menu_task_format],
+ $~.named_captures.transform_keys(&:to_sym)
+ ).send(opts[:menu_task_color].to_sym) }
+ )
+ end
else
# line not added
end
when :blocks
## enhance fcb with block summary
#
blocks.push get_block_summary(opts, fcb) ### if Filter.fcb_select? opts, fcb
end
end
- if opts[:menu_divider_format].present? && opts[:menu_final_divider].present?
+ if opts[:menu_divider_format].present? && opts[:menu_final_divider].present? && use_chrome && use_chrome
blocks.push FCB.new(
{ chrome: true,
disabled: '',
name: format(opts[:menu_divider_format],
opts[:menu_final_divider])
.send(opts[:menu_divider_color].to_sym) }
)
end
- blocks.tap_inspect
+ blocks
rescue StandardError => err
warn(error = "ERROR ** MarkParse.list_blocks_in_file(); #{err.inspect}")
warn(caller[0..4])
raise StandardError, error
end
@@ -1015,11 +610,11 @@
def list_default_yaml
menu_iter do |item|
next unless item[:opt_name].present? && item[:default].present?
[
- "#{item[:opt_name]}: #{OptionValue.new(item[:default]).for_yaml}",
+ "#{item[:opt_name]}: #{OptionValue.for_yaml(item[:default])}",
item[:description].present? ? item[:description] : nil
].compact.join(' # ')
end.compact.sort
end
@@ -1072,11 +667,11 @@
else
# blocks.map(&:name)
blocks.map do |block|
block.fetch(:text, nil) || block.fetch(:name, nil)
end
- end.compact.reject(&:empty?).tap_inspect
+ end.compact.reject(&:empty?)
end
## output type (body string or full object) per option struct and bash
#
def list_named_blocks_in_file(call_options = {}, &options_block)
@@ -1084,23 +679,25 @@
blocks = list_blocks_in_file(opts.merge(struct: true)).select do |fcb|
# fcb.fetch(:name, '') != '' && Filter.fcb_select?(opts, fcb)
Filter.fcb_select?(opts.merge(no_chrome: true), fcb)
end
- blocks_per_opts(blocks, opts).tap_inspect
+ blocks_per_opts(blocks, opts)
end
def make_block_labels(call_options = {})
opts = options.merge(call_options)
list_blocks_in_file(opts).map do |fcb|
- BlockLabel.new(filename: opts[:filename],
- headings: fcb.fetch(:headings, []),
- menu_blocks_with_docname: opts[:menu_blocks_with_docname],
- menu_blocks_with_headings: opts[:menu_blocks_with_headings],
- title: fcb[:title],
- text: fcb[:text],
- body: fcb[:body]).make
+ BlockLabel.make(
+ filename: opts[:filename],
+ headings: fcb.fetch(:headings, []),
+ menu_blocks_with_docname: opts[:menu_blocks_with_docname],
+ menu_blocks_with_headings: opts[:menu_blocks_with_headings],
+ title: fcb[:title],
+ text: fcb[:text],
+ body: fcb[:body]
+ )
end.compact
end
# :reek:DuplicateMethodCall
# :reek:NestedIterators
@@ -1350,12 +947,12 @@
@options[:block_name] = mf[:block]
@options[:filename] = mf[:file].gsub(FNR12, FNR11)
end
def run_last_script
- filename = Sfiles.new(@options[:saved_script_folder],
- @options[:saved_script_glob]).most_recent
+ filename = SavedFilesMatcher.most_recent(@options[:saved_script_folder],
+ @options[:saved_script_glob])
return unless filename
saved_name_split filename
@options[:save_executed_script] = false
select_approve_and_execute_block({})
@@ -1387,11 +984,11 @@
File.write(@options[:logged_stdout_filespec], ol.join)
end
def select_approve_and_execute_block(call_options, &options_block)
opts = optsmerge call_options, options_block
- blocks_in_file = list_blocks_in_file(opts.merge(struct: true)).tap_inspect
+ blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
mdoc = MDoc.new(blocks_in_file) do |nopts|
opts.merge!(nopts)
end
blocks_menu = mdoc.fcbs_per_options(opts.merge(struct: true))
@@ -1403,19 +1000,19 @@
bm = blocks_menu.map do |fcb|
# next if fcb.fetch(:disabled, false)
# next unless fcb.fetch(:name, '').present?
fcb.merge!(
- label: BlockLabel.new(
+ label: BlockLabel.make(
body: fcb[:body],
filename: opts[:filename],
headings: fcb.fetch(:headings, []),
menu_blocks_with_docname: opts[:menu_blocks_with_docname],
menu_blocks_with_headings: opts[:menu_blocks_with_headings],
text: fcb[:text],
title: fcb[:title]
- ).make
+ )
)
fcb.to_h
end.compact
return nil if bm.count.zero?
@@ -1527,16 +1124,16 @@
@options[:saved_filespec] =
File.join opts[:saved_script_folder], opts[:saved_script_filename]
dirname = File.dirname(@options[:saved_filespec])
FileUtils.mkdir_p dirname
- (shebang = if @options[:shebang]&.present?
- "#{@options[:shebang]} #{@options[:shell]}\n"
- else
- ''
- end
- ).tap_inspect name: :shebang
+ shebang = if @options[:shebang]&.present?
+ "#{@options[:shebang]} #{@options[:shell]}\n"
+ else
+ ''
+ end
+
File.write(@options[:saved_filespec], shebang +
"# file_name: #{opts[:filename]}\n" \
"# block_name: #{opts[:block_name]}\n" \
"# time: #{time_now}\n" \
"#{required_blocks.flatten.join("\n")}\n")
@@ -1544,5 +1141,7 @@
File.chmod @options[:saved_script_chmod], @options[:saved_filespec]
end
end # class MarkParse
end # module MarkdownExec
+
+require 'minitest/autorun' if $PROGRAM_NAME == __FILE__