lib/markdown_exec.rb in markdown_exec-1.2.0 vs lib/markdown_exec.rb in markdown_exec-1.3.0
- old
+ new
@@ -5,20 +5,22 @@
require 'English'
require 'clipboard'
require 'open3'
require 'optparse'
+require 'shellwords'
require 'tty-prompt'
require 'yaml'
require_relative 'colorize'
require_relative 'env'
require_relative 'shared'
require_relative 'tap'
require_relative 'markdown_exec/version'
-include Tap # rubocop:disable Style/MixinUsage
+include Tap
+tap_config envvar: MarkdownExec::TAP_DEBUG
$stderr.sync = true
$stdout.sync = true
BLOCK_SIZE = 1024
@@ -54,22 +56,10 @@
end
end
public
-# display_level values
-DISPLAY_LEVEL_BASE = 0 # required output
-DISPLAY_LEVEL_ADMIN = 1
-DISPLAY_LEVEL_DEBUG = 2
-DISPLAY_LEVEL_DEFAULT = DISPLAY_LEVEL_ADMIN
-DISPLAY_LEVEL_MAX = DISPLAY_LEVEL_DEBUG
-
-# @execute_files[ind] = @execute_files[ind] + [block]
-EF_STDOUT = 0
-EF_STDERR = 1
-EF_STDIN = 2
-
# execute markdown documents
#
module MarkdownExec
# :reek:IrresponsibleModule
class Error < StandardError; end
@@ -79,40 +69,82 @@
class MDoc
def initialize(table)
@table = table
end
- def code(block)
- all = [block[:name]] + recursively_required(block[:reqs])
- all.reverse.map do |req|
- get_block_by_name(req).fetch(:body, '')
- end
- .flatten(1)
- .tap_inspect
+ def collect_recursively_required_code(name)
+ get_required_blocks(name)
+ .map do |block|
+ block.tap_inspect name: :block, format: :yaml
+ body = block[:body].join("\n")
+
+ if block[:cann]
+ xcall = block[:cann][1..-2].tap_inspect name: :xcall
+ mstdin = xcall.match(/<(?<type>\$)?(?<name>[A-Za-z_-]\S+)/).tap_inspect name: :mstdin
+ mstdout = xcall.match(/>(?<type>\$)?(?<name>[A-Za-z_-]\S+)/).tap_inspect name: :mstdout
+ yqcmd = if mstdin[:type]
+ "echo \"$#{mstdin[:name]}\" | yq '#{body}'"
+ else
+ "yq e '#{body}' '#{mstdin[:name]}'"
+ end.tap_inspect name: :yqcmd
+ if mstdout[:type]
+ "export #{mstdout[:name]}=$(#{yqcmd})"
+ else
+ "#{yqcmd} > '#{mstdout[:name]}'"
+ end
+ elsif block[:stdout]
+ stdout = block[:stdout].tap_inspect name: :stdout
+ body = block[:body].join("\n").tap_inspect name: :body
+ if stdout[:type]
+ # "export #{stdout[:name]}=#{Shellwords.escape body}"
+ %(export #{stdout[:name]}=$(cat <<"EOF"\n#{body}\nEOF\n))
+ else
+ "cat > '#{stdout[:name]}' <<\"EOF\"\n" \
+ "#{body}\n" \
+ "EOF\n"
+ end
+ else
+ block[:body]
+ end
+ end.flatten(1)
+ .tap_inspect format: :yaml
end
def get_block_by_name(name, default = {})
- @table.select { |block| block[:name] == name }.fetch(0, default)
+ name.tap_inspect name: :name
+ @table.select { |block| block[:name] == name }.fetch(0, default).tap_inspect format: :yaml
end
- def list_recursively_required_blocks(name)
+ 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[:name]] + recursively_required(name_block[:reqs])
# in order of appearance in document
- @table.select { |block| all.include? block[:name] }
- .map { |block| block.fetch(:body, '') }
- .flatten(1)
- .tap_inspect
+ sel = @table.select { |block| all.include? block[:name] }
+
+ # insert function blocks
+ sel.map do |block|
+ block.tap_inspect name: :block, format: :yaml
+ if (call = block[:call])
+ [get_block_by_name("[#{call.match(/^\((\S+) |\)/)[1]}]").merge({ cann: call })]
+ else
+ []
+ end + [block]
+ end.flatten(1) # .tap_inspect format: :yaml
end
- def option_exclude_blocks(opts)
- block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
+ # :reek:UtilityFunction
+ def hide_menu_block_per_options(opts, block)
+ (opts[:hide_blocks_by_name] &&
+ block[:name].match(Regexp.new(opts[:block_name_excluded_match]))).tap_inspect
+ end
+
+ def blocks_for_menu(opts)
if opts[:hide_blocks_by_name]
- @table.reject { |block| block[:name].match(block_name_excluded_match) }
+ @table.reject { |block| hide_menu_block_per_options opts, block }
else
@table
end
end
@@ -126,15 +158,14 @@
all += [req]
get_block_by_name(req).fetch(:reqs, [])
end
.compact
.flatten(1)
- .tap_inspect(name: 'rem')
end
- all.tap_inspect
+ all.tap_inspect format: :yaml
end
- end
+ end # class MDoc
# format option defaults and values
#
# :reek:TooManyInstanceVariables
class BlockLabel
@@ -159,11 +190,11 @@
else
[]
end
)).join(' ')
end
- end
+ end # class BlockLabel
FNR11 = '/'
FNR12 = ',~'
# format option defaults and values
@@ -182,11 +213,11 @@
end
def stdout_name
"#{[@prefix, @time.strftime('%F-%H-%M-%S'), @filename, @blockname].join('_')}.out.txt".tap_inspect
end
- end
+ end # class SavedAsset
# format option defaults and values
#
class OptionValue
def initialize(value)
@@ -226,11 +257,11 @@
default
else
@value.to_s
end
end
- end
+ end # class OptionValue
# a generated list of saved files
#
class Sfiles
def initialize(folder, glob)
@@ -240,24 +271,24 @@
def list_all
Dir.glob(File.join(@folder, @glob)).tap_inspect
end
- def most_recent(arr = list_all)
- return unless arr
+ def most_recent(arr = nil)
+ arr = list_all if arr.nil?
return if arr.count < 1
arr.max.tap_inspect
end
- def most_recent_list(arr = list_all)
- return unless arr
+ def most_recent_list(list_count, arr = nil)
+ arr = list_all if arr.nil?
return if (ac = arr.count) < 1
- arr.sort[-[ac, options[:list_count]].min..].reverse.tap_inspect
+ arr.sort[-[ac, list_count].min..].reverse.tap_inspect
end
- end
+ end # class Sfiles
##
#
# :reek:DuplicateMethodCall { allow_calls: ['block', 'item', 'lm', 'opts', 'option', '@options', 'required_blocks'] }
# :reek:MissingSafeMethod { exclude: [ read_configuration_file! ] }
@@ -318,11 +349,11 @@
struct: true # allow get_block_summary()
}
end
def approve_block(opts, mdoc)
- required_blocks = mdoc.list_recursively_required_blocks(opts[:block_name])
+ required_blocks = mdoc.collect_recursively_required_code(opts[:block_name])
display_command(opts, required_blocks) if opts[:output_script] || opts[:user_must_approve]
allow = true
if opts[:user_must_approve]
loop do
@@ -373,12 +404,10 @@
@execute_files = Hash.new([])
@execute_options = opts
@execute_started_at = Time.now.utc
Open3.popen3(@options[:shell], '-c', command) do |stdin, stdout, stderr, exec_thr|
- # pid = exec_thr.pid # pid of the started process
-
Thread.new do
until (line = stdout.gets).nil?
@execute_files[EF_STDOUT] = @execute_files[EF_STDOUT] + [line]
print line if opts[:output_stdout]
yield nil, line, nil, exec_thr if block_given?
@@ -460,12 +489,18 @@
end).flatten(1)
end,
list_default_yaml: -> { fout_list list_default_yaml },
list_docs: -> { fout_list files },
list_default_env: -> { fout_list list_default_env },
- list_recent_output: -> { fout_list list_recent_output },
- list_recent_scripts: -> { fout_list list_recent_scripts },
+ list_recent_output: lambda {
+ fout_list list_recent_output(@options[:saved_stdout_folder],
+ @options[:saved_stdout_glob], @options[:list_count])
+ },
+ list_recent_scripts: lambda {
+ fout_list list_recent_scripts(options[:saved_script_folder],
+ options[:saved_script_glob], options[:list_count])
+ },
pwd: -> { fout File.expand_path('..', __dir__) },
run_last_script: -> { run_last_script },
select_recent_output: -> { select_recent_output },
select_recent_script: -> { select_recent_script },
tab_completions: -> { fout tab_completions },
@@ -502,24 +537,33 @@
puts "# #{name}"
puts data.to_yaml
end
# :reek:LongParameterList
- def get_block_summary(opts, headings:, block_title:, current:)
- return [current] unless opts[:struct]
+ def get_block_summary(call_options = {}, headings:, block_title:, block_body:)
+ opts = optsmerge call_options
+ return [block_body] unless opts[:struct]
+ return [summarize_block(headings, block_title).merge({ body: block_body })] unless opts[:bash]
- return [summarize_block(headings, block_title).merge({ body: current })] unless opts[:bash]
-
- bm = block_title.match(Regexp.new(opts[:block_name_match]))
- reqs = block_title.scan(Regexp.new(opts[:block_required_scan]))
+ block_title.tap_inspect name: :block_title
+ call = block_title.scan(Regexp.new(opts[:block_calls_scan]))
.map { |scanned| scanned[1..] }
+ &.first.tap_inspect name: :call
+ (titlexcall = call ? block_title.sub("%#{call}", '') : block_title).tap_inspect name: :titlexcall
- if bm && bm[1]
- [summarize_block(headings, bm[:title]).merge({ body: current, reqs: reqs })]
- else
- [summarize_block(headings, block_title).merge({ body: current, reqs: reqs })]
- end
+ bm = titlexcall.match(Regexp.new(opts[:block_name_match]))
+ reqs = titlexcall.scan(Regexp.new(opts[:block_required_scan]))
+ .map { |scanned| scanned[1..] }
+ stdin = titlexcall.match(Regexp.new(opts[:block_stdin_scan])).tap_inspect name: :stdin
+ stdout = titlexcall.match(Regexp.new(opts[:block_stdout_scan])).tap_inspect name: :stdout
+
+ title = bm && bm[1] ? bm[:title] : titlexcall
+ [summarize_block(headings, title).merge({ body: block_body,
+ call: call,
+ reqs: reqs,
+ stdin: stdin,
+ stdout: stdout })].tap_inspect format: :yaml
end
def approved_fout?(level)
level <= @options[:display_level]
end
@@ -527,35 +571,37 @@
# display output at level or lower than filter (DISPLAY_LEVEL_DEFAULT)
#
def lout(str, level: DISPLAY_LEVEL_BASE)
return unless approved_fout? level
- # fout level == DISPLAY_LEVEL_BASE ? str : DISPLAY_LEVEL_XBASE_PREFIX + str
fout level == DISPLAY_LEVEL_BASE ? str : @options[:display_level_xbase_prefix] + str
end
# :reek:DuplicateMethodCall
- def list_blocks_in_file(call_options = {}, &options_block)
- opts = optsmerge call_options, options_block
+ # :reek:LongYieldList
+ def iter_blocks_in_file(opts = {})
+ # opts = optsmerge call_options, options_block
unless opts[:filename]&.present?
fout 'No blocks found.'
- exit 1
+ return
end
unless File.exist? opts[:filename]
fout 'Document is missing.'
- exit 1
+ return
end
fenced_start_and_end_match = Regexp.new opts[:fenced_start_and_end_match]
fenced_start_ex = Regexp.new opts[:fenced_start_ex_match]
block_title = ''
- blocks = []
- current = nil
+ block_body = nil
headings = []
in_block = false
+
+ selected_messages = yield :filter
+
File.readlines(opts[:filename]).each do |line|
continue unless line
if opts[:menu_blocks_with_headings]
if (lm = line.match(Regexp.new(opts[:heading3_match])))
@@ -567,19 +613,21 @@
end
end
if line.match(fenced_start_and_end_match)
if in_block
- if current
- block_title = current.join(' ').gsub(/ +/, ' ')[0..64] if block_title.nil? || block_title.empty?
- blocks += get_block_summary opts, headings: headings, block_title: block_title, current: current
- current = nil
+ if block_body
+ # end block
+ #
+ block_title = block_body.join(' ').gsub(/ +/, ' ')[0..64] if block_title.nil? || block_title.empty?
+ yield :blocks, headings, block_title, block_body if block_given? && selected_messages.include?(:blocks)
+ block_body = nil
end
in_block = false
block_title = ''
else
- # new block
+ # start block
#
lm = line.match(fenced_start_ex)
block_allow = false
if opts[:bash_only]
block_allow = true if lm && (lm[:shell] == 'bash')
@@ -588,21 +636,43 @@
block_allow = !(lm && (lm[:shell] == 'expect')) if opts[:exclude_expect_blocks]
end
in_block = true
if block_allow && (!opts[:title_match] || (lm && lm[:name] && lm[:name].match(opts[:title_match])))
- current = []
+ block_body = []
block_title = (lm && lm[:name])
end
end
- elsif current
- current += [line.chomp]
+ elsif block_body
+ block_body += [line.chomp]
+ elsif block_given? && selected_messages.include?(:line)
+ # text outside of block
+ #
+ yield :line, nil, nil, line
end
end
- blocks.tap_inspect
end
+ def list_blocks_in_file(call_options = {}, &options_block)
+ opts = optsmerge call_options, options_block
+
+ blocks = []
+ iter_blocks_in_file(opts) do |btype, headings, block_title, body|
+ case btype
+ when :filter
+ %i[blocks line]
+ when :line
+ if opts[:menu_divider_match] && (mbody = body.match opts[:menu_divider_match])
+ blocks += [{ name: (opts[:menu_divider_format] % mbody[:name]), disabled: '' }]
+ end
+ when :blocks
+ blocks += get_block_summary opts, headings: headings, block_title: block_title, block_body: body
+ end
+ end
+ blocks.tap_inspect format: :yaml
+ end
+
def list_default_env
menu_iter do |item|
next unless item[:env_var].present?
[
@@ -663,455 +733,108 @@
Dir.glob(File.join(@options[:path], @options[:md_filename_glob])).tap_inspect
end
def list_named_blocks_in_file(call_options = {}, &options_block)
opts = optsmerge call_options, options_block
- block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
+ blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
+ mdoc = MDoc.new(blocks_in_file)
+
list_blocks_in_file(opts).map do |block|
- next if opts[:hide_blocks_by_name] && block[:name].match(block_name_excluded_match)
+ next if mdoc.hide_menu_block_per_options(opts, block)
block
end.compact.tap_inspect
end
- def list_recent_output
- Sfiles.new(@options[:saved_stdout_folder],
- @options[:saved_stdout_glob]).most_recent_list
+ 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)
end
- def list_recent_scripts
- Sfiles.new(@options[:saved_script_folder],
- @options[:saved_script_glob]).most_recent_list
+ 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)
end
def make_block_labels(call_options = {})
opts = options.merge(call_options)
list_blocks_in_file(opts).map do |block|
- # next if opts[:hide_blocks_by_name] && block[:name].match(%r{^:\(.+\)$})
-
BlockLabel.new(filename: opts[:filename],
headings: block.fetch(:headings, []),
menu_blocks_with_docname: opts[:menu_blocks_with_docname],
menu_blocks_with_headings: opts[:menu_blocks_with_headings],
title: block[:title]).make
end.compact.tap_inspect
end
# :reek:DuplicateMethodCall
- # :reek:UncommunicativeMethodName ### temp
- def menu_data1
- val_as_bool = ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value }
- val_as_int = ->(value) { value.to_i }
- val_as_str = ->(value) { value.to_s }
- # val_true = ->(_value) { true } # for commands, sets option to true
- menu_options = [
- {
- arg_name: 'PATH',
- default: '.',
- description: 'Read configuration file',
- long_name: 'config',
- proc1: lambda { |value|
- read_configuration_file! options, value
- }
- },
- {
- arg_name: 'BOOL',
- default: false,
- description: 'Debug output',
- env_var: 'MDE_DEBUG',
- long_name: 'debug',
- short_name: 'd',
- proc1: lambda { |value|
- tap_config value.to_i != 0
- }
- },
- {
- arg_name: "INT.#{DISPLAY_LEVEL_BASE}-#{DISPLAY_LEVEL_MAX}",
- default: DISPLAY_LEVEL_DEFAULT,
- description: "Output display level (#{DISPLAY_LEVEL_BASE} to #{DISPLAY_LEVEL_MAX})",
- env_var: 'MDE_DISPLAY_LEVEL',
- long_name: 'display-level',
- opt_name: :display_level,
- proc1: val_as_int
- },
- {
- arg_name: 'NAME',
- compreply: false,
- description: 'Name of block',
- env_var: 'MDE_BLOCK_NAME',
- long_name: 'block-name',
- opt_name: :block_name,
- short_name: 'f',
- proc1: val_as_str
- },
- {
- arg_name: 'RELATIVE_PATH',
- compreply: '.',
- description: 'Name of document',
- env_var: 'MDE_FILENAME',
- long_name: 'filename',
- opt_name: :filename,
- short_name: 'f',
- proc1: val_as_str
- },
- {
- description: 'List blocks',
- long_name: 'list-blocks',
- opt_name: :list_blocks,
- proc1: val_as_bool
- },
- {
- arg_name: 'INT.1-',
- default: 32,
- description: 'Max. items to return in list',
- env_var: 'MDE_LIST_COUNT',
- long_name: 'list-count',
- opt_name: :list_count,
- proc1: val_as_int
- },
- {
- description: 'List default configuration as environment variables',
- long_name: 'list-default-env',
- opt_name: :list_default_env
- },
- {
- description: 'List default configuration as YAML',
- long_name: 'list-default-yaml',
- opt_name: :list_default_yaml
- },
- {
- description: 'List docs in current folder',
- long_name: 'list-docs',
- opt_name: :list_docs,
- proc1: val_as_bool
- },
- {
- description: 'List recent saved output',
- long_name: 'list-recent-output',
- opt_name: :list_recent_output,
- proc1: val_as_bool
- },
- {
- description: 'List recent saved scripts',
- long_name: 'list-recent-scripts',
- opt_name: :list_recent_scripts,
- proc1: val_as_bool
- },
- {
- arg_name: 'PREFIX',
- default: MarkdownExec::BIN_NAME,
- description: 'Name prefix for stdout files',
- env_var: 'MDE_LOGGED_STDOUT_FILENAME_PREFIX',
- long_name: 'logged-stdout-filename-prefix',
- opt_name: :logged_stdout_filename_prefix,
- proc1: val_as_str
- },
- {
- arg_name: 'BOOL',
- default: false,
- description: 'Display document name in block selection menu',
- env_var: 'MDE_MENU_BLOCKS_WITH_DOCNAME',
- long_name: 'menu-blocks-with-docname',
- opt_name: :menu_blocks_with_docname,
- proc1: val_as_bool
- },
- {
- arg_name: 'BOOL',
- default: false,
- description: 'Display headings (levels 1,2,3) in block selection menu',
- env_var: 'MDE_MENU_BLOCKS_WITH_HEADINGS',
- long_name: 'menu-blocks-with-headings',
- opt_name: :menu_blocks_with_headings,
- proc1: val_as_bool
- },
- {
- arg_name: 'BOOL',
- default: false,
- description: 'Display summary for execution',
- env_var: 'MDE_OUTPUT_EXECUTION_SUMMARY',
- long_name: 'output-execution-summary',
- opt_name: :output_execution_summary,
- proc1: val_as_bool
- },
- {
- arg_name: 'BOOL',
- default: false,
- description: 'Display script prior to execution',
- env_var: 'MDE_OUTPUT_SCRIPT',
- long_name: 'output-script',
- opt_name: :output_script,
- proc1: val_as_bool
- },
- {
- arg_name: 'BOOL',
- default: true,
- description: 'Display standard output from execution',
- env_var: 'MDE_OUTPUT_STDOUT',
- long_name: 'output-stdout',
- opt_name: :output_stdout,
- proc1: val_as_bool
- },
- {
- arg_name: 'RELATIVE_PATH',
- default: '.',
- description: 'Path to documents',
- env_var: 'MDE_PATH',
- long_name: 'path',
- opt_name: :path,
- short_name: 'p',
- proc1: val_as_str
- },
- {
- description: 'Gem home folder',
- long_name: 'pwd',
- opt_name: :pwd,
- proc1: val_as_bool
- },
- {
- description: 'Run most recently saved script',
- long_name: 'run-last-script',
- opt_name: :run_last_script,
- proc1: val_as_bool
- },
- {
- arg_name: 'BOOL',
- default: false,
- description: 'Save executed script',
- env_var: 'MDE_SAVE_EXECUTED_SCRIPT',
- long_name: 'save-executed-script',
- opt_name: :save_executed_script,
- proc1: val_as_bool
- },
- {
- arg_name: 'BOOL',
- default: false,
- description: 'Save standard output of the executed script',
- env_var: 'MDE_SAVE_EXECUTION_OUTPUT',
- long_name: 'save-execution-output',
- opt_name: :save_execution_output,
- proc1: val_as_bool
- },
- {
- arg_name: 'INT',
- default: 0o755,
- description: 'chmod for saved scripts',
- env_var: 'MDE_SAVED_SCRIPT_CHMOD',
- long_name: 'saved-script-chmod',
- opt_name: :saved_script_chmod,
- proc1: val_as_int
- },
- {
- arg_name: 'PREFIX',
- default: MarkdownExec::BIN_NAME,
- description: 'Name prefix for saved scripts',
- env_var: 'MDE_SAVED_SCRIPT_FILENAME_PREFIX',
- long_name: 'saved-script-filename-prefix',
- opt_name: :saved_script_filename_prefix,
- proc1: val_as_str
- },
- {
- arg_name: 'RELATIVE_PATH',
- default: 'logs',
- description: 'Saved script folder',
- env_var: 'MDE_SAVED_SCRIPT_FOLDER',
- long_name: 'saved-script-folder',
- opt_name: :saved_script_folder,
- proc1: val_as_str
- },
- {
- arg_name: 'GLOB',
- default: 'mde_*.sh',
- description: 'Glob matching saved scripts',
- env_var: 'MDE_SAVED_SCRIPT_GLOB',
- long_name: 'saved-script-glob',
- opt_name: :saved_script_glob,
- proc1: val_as_str
- },
- {
- arg_name: 'RELATIVE_PATH',
- default: 'logs',
- description: 'Saved stdout folder',
- env_var: 'MDE_SAVED_STDOUT_FOLDER',
- long_name: 'saved-stdout-folder',
- opt_name: :saved_stdout_folder,
- proc1: val_as_str
- },
- {
- arg_name: 'GLOB',
- default: 'mde_*.out.txt',
- description: 'Glob matching saved outputs',
- env_var: 'MDE_SAVED_STDOUT_GLOB',
- long_name: 'saved-stdout-glob',
- opt_name: :saved_stdout_glob,
- proc1: val_as_str
- },
- {
- description: 'Select and execute a recently saved output',
- long_name: 'select-recent-output',
- opt_name: :select_recent_output,
- proc1: val_as_bool
- },
- {
- description: 'Select and execute a recently saved script',
- long_name: 'select-recent-script',
- opt_name: :select_recent_script,
- proc1: val_as_bool
- },
- {
- description: 'YAML export of menu',
- long_name: 'menu-export',
- opt_name: :menu_export,
- proc1: val_as_bool
- },
- {
- description: 'List tab completions',
- long_name: 'tab-completions',
- opt_name: :tab_completions,
- proc1: val_as_bool
- },
- {
- arg_name: 'BOOL',
- default: true,
- description: 'Pause for user to approve script',
- env_var: 'MDE_USER_MUST_APPROVE',
- long_name: 'user-must-approve',
- opt_name: :user_must_approve,
- proc1: val_as_bool
- },
- {
- description: 'Show current configuration values',
- short_name: '0',
- proc1: lambda { |_|
- options_finalize options
- fout options.sort_by_key.to_yaml
- }
- },
- {
- description: 'App help',
- long_name: 'help',
- short_name: 'h',
- proc1: lambda { |_|
- fout menu_help
- exit
- }
- },
- {
- description: "Print the gem's version",
- long_name: 'version',
- short_name: 'v',
- proc1: lambda { |_|
- fout MarkdownExec::VERSION
- exit
- }
- },
- {
- description: 'Exit app',
- long_name: 'exit',
- short_name: 'x',
- proc1: ->(_) { exit }
- },
- {
- default: '^\(.*\)$',
- description: 'Pattern for blocks to hide from user-selection',
- env_var: 'MDE_BLOCK_NAME_EXCLUDED_MATCH',
- opt_name: :block_name_excluded_match,
- proc1: val_as_str
- },
- {
- default: ':(?<title>\S+)( |$)',
- env_var: 'MDE_BLOCK_NAME_MATCH',
- opt_name: :block_name_match,
- proc1: val_as_str
- },
- {
- default: '\+\S+',
- env_var: 'MDE_BLOCK_REQUIRED_SCAN',
- opt_name: :block_required_scan,
- proc1: val_as_str
- },
- {
- default: '> ',
- env_var: 'MDE_DISPLAY_LEVEL_XBASE_PREFIX',
- opt_name: :display_level_xbase_prefix,
- proc1: val_as_str
- },
- {
- default: '^`{3,}',
- env_var: 'MDE_FENCED_START_AND_END_MATCH',
- opt_name: :fenced_start_and_end_match,
- proc1: val_as_str
- },
- {
- default: '^`{3,}(?<shell>[^`\s]*) *(?<name>.*)$',
- env_var: 'MDE_FENCED_START_EX_MATCH',
- opt_name: :fenced_start_ex_match,
- proc1: val_as_str
- },
- {
- default: '^# *(?<name>[^#]*?) *$',
- env_var: 'MDE_HEADING1_MATCH',
- opt_name: :heading1_match,
- proc1: val_as_str
- },
- {
- default: '^## *(?<name>[^#]*?) *$',
- env_var: 'MDE_HEADING2_MATCH',
- opt_name: :heading2_match,
- proc1: val_as_str
- },
- {
- default: '^### *(?<name>.+?) *$',
- env_var: 'MDE_HEADING3_MATCH',
- opt_name: :heading3_match,
- proc1: val_as_str
- },
- {
- default: '*.[Mm][Dd]',
- env_var: 'MDE_MD_FILENAME_GLOB',
- opt_name: :md_filename_glob,
- proc1: val_as_str
- },
- {
- default: '.+\\.md',
- env_var: 'MDE_MD_FILENAME_MATCH',
- opt_name: :md_filename_match,
- proc1: val_as_str
- },
- {
- description: 'Options for viewing saved output file',
- env_var: 'MDE_OUTPUT_VIEWER_OPTIONS',
- opt_name: :output_viewer_options,
- proc1: val_as_str
- },
- {
- default: 24,
- description: 'Maximum # of rows in select list',
- env_var: 'MDE_SELECT_PAGE_HEIGHT',
- opt_name: :select_page_height,
- proc1: val_as_int
- },
- {
- default: '#!/usr/bin/env',
- description: 'Shebang for saved scripts',
- env_var: 'MDE_SHEBANG',
- opt_name: :shebang,
- proc1: val_as_str
- },
- {
- default: 'bash',
- description: 'Shell for launched scripts',
- env_var: 'MDE_SHELL',
- opt_name: :shell,
- proc1: val_as_str
- }
- ]
- # commands first, options second
- (menu_options.reject { |option| option[:arg_name] }) +
- (menu_options.select { |option| option[:arg_name] })
+ # :reek:NestedIterators
+ def menu_for_optparse
+ menu_from_yaml.map do |menu_item|
+ menu_item.merge(
+ {
+ opt_name: menu_item[:opt_name]&.to_sym,
+ proc1: case menu_item[:proc1]
+ when 'debug'
+ lambda { |value|
+ tap_config value: value
+ }
+ when 'exit'
+ lambda { |_|
+ exit
+ }
+ when 'help'
+ lambda { |_|
+ fout menu_help
+ exit
+ }
+ when 'path'
+ lambda { |value|
+ read_configuration_file! options, value
+ }
+ when 'show_config'
+ lambda { |_|
+ options_finalize options
+ fout options.sort_by_key.to_yaml
+ }
+ when 'val_as_bool'
+ ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value }
+ when 'val_as_int'
+ ->(value) { value.to_i }
+ when 'val_as_str'
+ ->(value) { value.to_s }
+ when 'version'
+ lambda { |_|
+ fout MarkdownExec::VERSION
+ exit
+ }
+ else
+ menu_item[:proc1]
+ end
+ }
+ )
+ end
end
- def menu_iter(data = menu_data1, &block)
+ def menu_for_blocks(menu_options)
+ options = default_options.merge menu_options
+ menu = []
+ iter_blocks_in_file(options) do |btype, headings, block_title, body|
+ case btype
+ when :filter
+ %i[blocks line]
+ when :line
+ if options[:menu_divider_match] && (mbody = body.match options[:menu_divider_match])
+ menu += [{ name: mbody[:name], disabled: '' }]
+ end
+ when :blocks
+ summ = get_block_summary options, headings: headings, block_title: block_title, block_body: body
+ menu += [summ[0][:name]]
+ end
+ end
+ menu.tap_inspect format: :yaml
+ end
+
+ def menu_iter(data = menu_for_optparse, &block)
data.map(&block)
end
def menu_help
@option_parser.help
@@ -1143,11 +866,11 @@
class_call_options = @options.merge(call_options || {})
if options_block
options_block.call class_call_options
else
class_call_options
- end.tap_inspect
+ end
end
def output_execution_result
oq = [['Block', @options[:block_name], DISPLAY_LEVEL_ADMIN],
['Command',
@@ -1194,14 +917,12 @@
# :reek:UtilityFunction ### temp
def read_configuration_file!(options, configuration_path)
return unless File.exist?(configuration_path)
- # rubocop:disable Security/YAMLLoad
options.merge!((YAML.load(File.open(configuration_path)) || {})
.transform_keys(&:to_sym))
- # rubocop:enable Security/YAMLLoad
end
# :reek:NestedIterators
def run
## default configuration
@@ -1289,48 +1010,38 @@
File.write(@options[:logged_stdout_filespec], ol.join)
end
def select_and_approve_block(call_options = {}, &options_block)
opts = optsmerge call_options, options_block
- blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
- mdoc = MDoc.new(blocks_in_file)
+ blocks_in_file = list_blocks_in_file(opts.merge(struct: true)).tap_inspect name: :blocks_in_file
+ mdoc = MDoc.new(blocks_in_file) { |nopts| opts.merge!(nopts).tap_inspect name: :infiled_opts, format: :yaml }
+ blocks_menu = mdoc.blocks_for_menu(opts.merge(struct: true)).tap_inspect name: :blocks_menu
repeat_menu = true && !opts[:block_name].present?
-
loop do
unless opts[:block_name].present?
pt = (opts[:prompt_select_block]).to_s
- blocks_in_file.each do |block|
+ blocks_menu.each do |block|
+ next if block.fetch(:disabled, false)
+
block.merge! label:
BlockLabel.new(filename: opts[:filename],
headings: block.fetch(:headings, []),
menu_blocks_with_docname: opts[:menu_blocks_with_docname],
menu_blocks_with_headings: opts[:menu_blocks_with_headings],
title: block[:title]).make
end
+ return nil if blocks_menu.count.zero?
- block_labels = mdoc.option_exclude_blocks(opts).map { |block| block[:label] }
-
- return nil if block_labels.count.zero?
-
- sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height]
+ sel = prompt_with_quit pt, blocks_menu, per_page: opts[:select_page_height]
return nil if sel.nil?
- # if sel.nil?
- # repeat_menu = false
- # break
- # end
-
label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
opts[:block_name] = @options[:block_name] = label_block[:name]
-
end
- # if repeat_menu
approve_block opts, mdoc
- # end
-
break unless repeat_menu
opts[:block_name] = ''
end
end
@@ -1343,42 +1054,57 @@
prompt_with_quit opts[:prompt_select_md].to_s, files, per_page: opts[:select_page_height]
end
end
def select_recent_output
- filename = prompt_with_quit @options[:prompt_select_output].to_s, list_recent_output,
- per_page: @options[:select_page_height]
+ filename = prompt_with_quit(
+ @options[:prompt_select_output].to_s,
+ list_recent_output(
+ @options[:saved_stdout_folder],
+ @options[:saved_stdout_glob],
+ @options[:list_count]
+ ),
+ { per_page: @options[:select_page_height] }
+ )
return unless filename.present?
`open #{filename} #{options[:output_viewer_options]}`
end
def select_recent_script
- filename = prompt_with_quit @options[:prompt_select_md].to_s, list_recent_scripts,
- per_page: @options[:select_page_height]
+ filename = prompt_with_quit(
+ @options[:prompt_select_md].to_s,
+ list_recent_scripts(
+ @options[:saved_script_folder],
+ @options[:saved_script_glob],
+ @options[:list_count]
+ ),
+ { per_page: @options[:select_page_height] }
+ )
return if filename.nil?
- saved_name_split filename
- select_and_approve_block(
- bash: true,
- save_executed_script: false,
- struct: true
- )
+ saved_name_split(filename)
+
+ select_and_approve_block({
+ bash: true,
+ save_executed_script: false,
+ struct: true
+ })
end
def summarize_block(headings, title)
{ headings: headings, name: title, title: title }
end
- def menu_export(data = menu_data1)
+ def menu_export(data = menu_for_optparse)
data.map do |item|
item.delete(:proc1)
item
end.to_yaml
end
- def tab_completions(data = menu_data1)
+ def tab_completions(data = menu_for_optparse)
data.map do |item|
"--#{item[:long_name]}" if item[:long_name]
end.compact
end
@@ -1423,7 +1149,7 @@
"#{required_blocks.flatten.join("\n")}\n")
return if @options[:saved_script_chmod].zero?
File.chmod @options[:saved_script_chmod], @options[:saved_filespec]
end
- end
-end
+ end # class MarkParse
+end # module MarkdownExec