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