lib/markdown_exec.rb in markdown_exec-1.0.0 vs lib/markdown_exec.rb in markdown_exec-1.1.0

- old
+ new

@@ -1,42 +1,18 @@ #!/usr/bin/env ruby # frozen_string_literal: true # encoding=utf-8 +require 'English' +require 'clipboard' require 'open3' require 'optparse' require 'tty-prompt' require 'yaml' -## -# default if nil -# false if empty or '0' -# else true - -def env_bool(name, default: false) - return default if name.nil? || (val = ENV[name]).nil? - return false if val.empty? || val == '0' - - true -end - -def env_int(name, default: 0) - return default if name.nil? || (val = ENV[name]).nil? - return default if val.empty? - - val.to_i -end - -def env_str(name, default: '') - return default if name.nil? || (val = ENV[name]).nil? - - val || default -end - -$pdebug = env_bool 'MDE_DEBUG' - +require_relative 'shared' require_relative 'markdown_exec/version' $stderr.sync = true $stdout.sync = true @@ -60,29 +36,22 @@ end end public -# debug output -# -def tap_inspect(format: nil, name: 'return') - return self unless $pdebug +# 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 - cvt = { - json: :to_json, - string: :to_s, - yaml: :to_yaml, - else: :inspect - } - fn = cvt.fetch(format, cvt[:else]) +# @execute_files[ind] = @execute_files[ind] + [block] +EF_STDOUT = 0 +EF_STDERR = 1 +EF_STDIN = 2 - puts "-> #{caller[0].scan(/in `?(\S+)'$/)[0][0]}()" \ - " #{name}: #{method(fn).call}" - - self -end - module MarkdownExec class Error < StandardError; end ## # @@ -96,19 +65,26 @@ ## # options necessary to start, parse input, defaults for cli options def base_options - menu_data - .map do |_long_name, _short_name, env_var, _arg_name, _description, opt_name, default, proc1| # rubocop:disable Metrics/ParameterLists - next unless opt_name.present? + menu_iter do |item| + item.tap_inspect name: :item, format: :yaml + next unless item[:opt_name].present? - value = env_str(env_var, default: value_for_hash(default)) - [opt_name, proc1 ? proc1.call(value) : value] + item_default = item[:default] + item_default.tap_inspect name: :item_default + value = if item_default.nil? + item_default + else + env_str(item[:env_var], default: value_for_hash(item_default)) + end + [item[:opt_name], item[:proc1] ? item[:proc1].call(value) : value] end.compact.to_h.merge( { mdheadings: true, # use headings (levels 1,2,3) in block lable + menu_exit_at_top: true, menu_with_exit: true } ).tap_inspect format: :yaml end @@ -136,19 +112,47 @@ def approve_block(opts, blocks_in_file) required_blocks = list_recursively_required_blocks(blocks_in_file, opts[:block_name]) display_command(opts, required_blocks) if opts[:output_script] || opts[:user_must_approve] allow = true - allow = @prompt.yes? opts[:prompt_approve_block] if opts[:user_must_approve] - opts[:ir_approve] = allow + if opts[:user_must_approve] + loop do + # (sel = @prompt.select(opts[:prompt_approve_block], %w(Yes No Copy_script_to_clipboard Save_script), cycle: true)).tap_inspect name: :sel + (sel = @prompt.select(opts[:prompt_approve_block], filter: true) do |menu| + menu.default 1 + # menu.enum '.' + # menu.filter true + + menu.choice 'Yes', 1 + menu.choice 'No', 2 + menu.choice 'Copy script to clipboard', 3 + menu.choice 'Save script', 4 + end).tap_inspect name: :sel + allow = (sel == 1) + if sel == 3 + text = required_blocks.flatten.join($INPUT_RECORD_SEPARATOR) + Clipboard.copy(text) + fout "Clipboard updated: #{required_blocks.count} blocks, #{required_blocks.flatten.count} lines, #{text.length} characters" + end + if sel == 4 + # opts[:saved_script_filename] = saved_name_make(opts) + write_command_file(opts.merge(save_executed_script: true), required_blocks) + fout "File saved: #{@options[:saved_filespec]}" + end + break if [1, 2].include? sel + end + end + (opts[:ir_approve] = allow).tap_inspect name: :allow + selected = get_block_by_name blocks_in_file, opts[:block_name] if opts[:ir_approve] write_command_file opts, required_blocks command_execute opts, required_blocks.flatten.join("\n") save_execution_output output_execution_summary + output_execution_result end selected[:name] end @@ -163,44 +167,49 @@ def command_execute(opts, cmd2) @execute_files = Hash.new([]) @execute_options = opts @execute_started_at = Time.now.utc - Open3.popen3(cmd2) do |stdin, stdout, stderr| - stdin.close_write - begin - files = [stdout, stderr] - until all_at_eof(files) - ready = IO.select(files) + Open3.popen3(@options[:shell], '-c', cmd2) do |stdin, stdout, stderr, exec_thr| + # pid = exec_thr.pid # pid of the started process - next unless ready + t1 = 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? + end + end - # readable = ready[0] - # # writable = ready[1] - # # exceptions = ready[2] - ready.each.with_index do |readable, ind| - readable.each do |f| - block = f.read_nonblock(BLOCK_SIZE) - @execute_files[ind] = @execute_files[ind] + [block] - print block if opts[:output_stdout] - rescue EOFError #=> e - # do nothing at EOF - end - end + t2 = Thread.new do + until (line = stderr.gets).nil? + @execute_files[EF_STDERR] = @execute_files[EF_STDERR] + [line] + print line if opts[:output_stdout] + yield nil, nil, line, exec_thr if block_given? end - rescue IOError => e - fout "IOError: #{e}" end - @execute_completed_at = Time.now.utc + + in_thr = Thread.new do + while exec_thr.alive? # reading input until the child process ends + stdin.puts(line = $stdin.gets) + @execute_files[EF_STDIN] = @execute_files[EF_STDIN] + [line] + yield line, nil, nil, exec_thr if block_given? + end + end + + exec_thr.join + in_thr.kill + # @return_code = exec_thr.value end + @execute_completed_at = Time.now.utc rescue Errno::ENOENT => e # error triggered by missing command in script @execute_aborted_at = Time.now.utc @execute_error_message = e.message @execute_error = e - @execute_files[1] = e.message + @execute_files[EF_STDERR] += [e.message] fout "Error ENOENT: #{e.inspect}" end def count_blocks_in_filename fenced_start_and_end_match = Regexp.new @options[:fenced_start_and_end_match] @@ -210,11 +219,13 @@ end cnt / 2 end def display_command(_opts, required_blocks) + fout ' #=#=#'.yellow required_blocks.each { |cb| fout cb } + fout ' #=#=#'.yellow end def exec_block(options, _block_name = '') options = default_options.merge options update_options options, over: false @@ -237,11 +248,12 @@ list_recent_scripts: -> { fout_list list_recent_scripts }, 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 } + tab_completions: -> { fout tab_completions }, + menu_export: -> { fout menu_export } } simple_commands.each_key do |key| if @options[key] simple_commands[key].call return # rubocop:disable Lint/NonLocalExitFromIterator @@ -290,10 +302,23 @@ else [summarize_block(headings, block_title).merge({ body: current, reqs: reqs })] end end + def approved_fout?(level) + level <= @options[:display_level] + end + + # 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 + def list_blocks_in_file(call_options = {}, &options_block) opts = optsmerge call_options, options_block unless opts[:filename]&.present? fout 'No blocks found.' @@ -358,29 +383,27 @@ end blocks.tap_inspect end def list_default_env - menu_data - .map do |_long_name, _short_name, env_var, _arg_name, description, _opt_name, default, _proc1| # rubocop:disable Metrics/ParameterLists - next unless env_var.present? + menu_iter do |item| + next unless item[:env_var].present? [ - "#{env_var}=#{value_for_cli default}", - description.present? ? description : nil + "#{item[:env_var]}=#{value_for_cli item[:default]}", + item[:description].present? ? item[:description] : nil ].compact.join(' # ') end.compact.sort end def list_default_yaml - menu_data - .map do |_long_name, _short_name, _env_var, _arg_name, description, opt_name, default, _proc1| # rubocop:disable Metrics/ParameterLists - next unless opt_name.present? && default.present? + menu_iter do |item| + next unless item[:opt_name].present? && item[:default].present? [ - "#{opt_name}: #{value_for_yaml default}", - description.present? ? description : nil + "#{item[:opt_name]}: #{value_for_yaml item[:default]}", + item[:description].present? ? item[:description] : nil ].compact.join(' # ') end.compact.sort end def list_files_per_options(options) @@ -485,87 +508,404 @@ make_block_label block, opts end.compact.tap_inspect end - def menu_data + 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 } - - summary_head = [ - ['config', nil, nil, 'PATH', 'Read configuration file', nil, '.', lambda { |value| - read_configuration_file! options, value - }], - ['debug', 'd', 'MDE_DEBUG', 'BOOL', 'Debug output', nil, false, ->(value) { $pdebug = value.to_i != 0 }] + # val_true = ->(_value) { true } # for commands, sets option to true + set1 = [ + { + 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| + $pdebug = 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 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 sorted_keys(options).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 + (set1.reject { |v1| v1[:arg_name] }) + (set1.select { |v1| v1[:arg_name] }) + end - # rubocop:disable Layout/LineLength - summary_body = [ - ['block-name', 'f', 'MDE_BLOCK_NAME', 'RELATIVE', 'Name of block', :block_name, nil, val_as_str], - ['filename', 'f', 'MDE_FILENAME', 'RELATIVE', 'Name of document', :filename, nil, val_as_str], - ['list-blocks', nil, nil, nil, 'List blocks', :list_blocks, false, val_as_bool], - ['list-count', nil, 'MDE_LIST_COUNT', 'NUM', 'Max. items to return in list', :list_count, 16, val_as_int], - ['list-default-env', nil, nil, nil, 'List default configuration as environment variables', :list_default_env, false, val_as_bool], - ['list-default-yaml', nil, nil, nil, 'List default configuration as YAML', :list_default_yaml, false, val_as_bool], - ['list-docs', nil, nil, nil, 'List docs in current folder', :list_docs, false, val_as_bool], - ['list-recent-output', nil, nil, nil, 'List recent saved output', :list_recent_output, false, val_as_bool], - ['list-recent-scripts', nil, nil, nil, 'List recent saved scripts', :list_recent_scripts, false, val_as_bool], - ['logged-stdout-filename-prefix', nil, 'MDE_LOGGED_STDOUT_FILENAME_PREFIX', 'NAME', 'Name prefix for stdout files', :logged_stdout_filename_prefix, 'mde', val_as_str], - ['output-execution-summary', nil, 'MDE_OUTPUT_EXECUTION_SUMMARY', 'BOOL', 'Display summary for execution', :output_execution_summary, false, val_as_bool], - ['output-script', nil, 'MDE_OUTPUT_SCRIPT', 'BOOL', 'Display script prior to execution', :output_script, false, val_as_bool], - ['output-stdout', nil, 'MDE_OUTPUT_STDOUT', 'BOOL', 'Display standard output from execution', :output_stdout, true, val_as_bool], - ['path', 'p', 'MDE_PATH', 'PATH', 'Path to documents', :path, nil, val_as_str], - ['pwd', nil, nil, nil, 'Gem home folder', :pwd, false, val_as_bool], - ['run-last-script', nil, nil, nil, 'Run most recently saved script', :run_last_script, false, val_as_bool], - ['save-executed-script', nil, 'MDE_SAVE_EXECUTED_SCRIPT', 'BOOL', 'Save executed script', :save_executed_script, false, val_as_bool], - ['save-execution-output', nil, 'MDE_SAVE_EXECUTION_OUTPUT', 'BOOL', 'Save standard output of the executed script', :save_execution_output, false, val_as_bool], - ['saved-script-filename-prefix', nil, 'MDE_SAVED_SCRIPT_FILENAME_PREFIX', 'NAME', 'Name prefix for saved scripts', :saved_script_filename_prefix, 'mde', val_as_str], - ['saved-script-folder', nil, 'MDE_SAVED_SCRIPT_FOLDER', 'SPEC', 'Saved script folder', :saved_script_folder, 'logs', val_as_str], - ['saved-script-glob', nil, 'MDE_SAVED_SCRIPT_GLOB', 'SPEC', 'Glob matching saved scripts', :saved_script_glob, 'mde_*.sh', val_as_str], - ['saved-stdout-folder', nil, 'MDE_SAVED_STDOUT_FOLDER', 'SPEC', 'Saved stdout folder', :saved_stdout_folder, 'logs', val_as_str], - ['saved-stdout-glob', nil, 'MDE_SAVED_STDOUT_GLOB', 'SPEC', 'Glob matching saved outputs', :saved_stdout_glob, 'mde_*.out.txt', val_as_str], - ['select-recent-output', nil, nil, nil, 'Select and execute a recently saved output', :select_recent_output, false, val_as_bool], - ['select-recent-script', nil, nil, nil, 'Select and execute a recently saved script', :select_recent_script, false, val_as_bool], - ['tab-completions', nil, nil, nil, 'List tab completions', :tab_completions, false, val_as_bool], - ['user-must-approve', nil, 'MDE_USER_MUST_APPROVE', 'BOOL', 'Pause for user to approve script', :user_must_approve, true, val_as_bool] - ] - # rubocop:enable Layout/LineLength - - # rubocop:disable Style/Semicolon - summary_tail = [ - [nil, '0', nil, nil, 'Show current configuration values', - nil, nil, ->(_) { options_finalize options; fout sorted_keys(options).to_yaml }], - ['help', 'h', nil, nil, 'App help', - nil, nil, ->(_) { fout menu_help; exit }], - ['version', 'v', nil, nil, "Print the gem's version", - nil, nil, ->(_) { fout MarkdownExec::VERSION; exit }], - ['exit', 'x', nil, nil, 'Exit app', - nil, nil, ->(_) { exit }] - ] - # rubocop:enable Style/Semicolon - - env_vars = [ - [nil, nil, 'MDE_BLOCK_NAME_EXCLUDED_MATCH', nil, 'Pattern for blocks to hide from user-selection', - :block_name_excluded_match, '^\(.*\)$', val_as_str], - [nil, nil, 'MDE_BLOCK_NAME_MATCH', nil, '', :block_name_match, ':(?<title>\S+)( |$)', val_as_str], - [nil, nil, 'MDE_BLOCK_REQUIRED_SCAN', nil, '', :block_required_scan, '\+\S+', val_as_str], - [nil, nil, 'MDE_FENCED_START_AND_END_MATCH', nil, '', :fenced_start_and_end_match, '^`{3,}', val_as_str], - [nil, nil, 'MDE_FENCED_START_EX_MATCH', nil, '', :fenced_start_ex_match, - '^`{3,}(?<shell>[^`\s]*) *(?<name>.*)$', val_as_str], - [nil, nil, 'MDE_HEADING1_MATCH', nil, '', :heading1_match, '^# *(?<name>[^#]*?) *$', val_as_str], - [nil, nil, 'MDE_HEADING2_MATCH', nil, '', :heading2_match, '^## *(?<name>[^#]*?) *$', val_as_str], - [nil, nil, 'MDE_HEADING3_MATCH', nil, '', :heading3_match, '^### *(?<name>.+?) *$', val_as_str], - [nil, nil, 'MDE_MD_FILENAME_GLOB', nil, '', :md_filename_glob, '*.[Mm][Dd]', val_as_str], - [nil, nil, 'MDE_MD_FILENAME_MATCH', nil, '', :md_filename_match, '.+\\.md', val_as_str], - [nil, nil, 'MDE_OUTPUT_VIEWER_OPTIONS', nil, 'Options for viewing saved output file', :output_viewer_options, - '', val_as_str], - [nil, nil, 'MDE_SELECT_PAGE_HEIGHT', nil, '', :select_page_height, 12, val_as_int] - # [nil, nil, 'MDE_', nil, '', nil, '', nil], - ] - - summary_head + summary_body + summary_tail + env_vars + def menu_iter(data = menu_data1, &block) + data.map(&block) end def menu_help @option_parser.help end @@ -607,10 +947,28 @@ else class_call_options end.tap_inspect end + def output_execution_result + oq = [['Block', @options[:block_name], DISPLAY_LEVEL_ADMIN], + ['Command', + [MarkdownExec::BIN_NAME, + @options[:filename], + @options[:block_name]].join(' '), + DISPLAY_LEVEL_ADMIN]] + + [['Script', :saved_filespec], + ['StdOut', :logged_stdout_filespec]].each do |label, name| + oq << [label, @options[name], DISPLAY_LEVEL_ADMIN] if @options[name] + end + + oq.map do |label, value, level| + lout ["#{label}:".yellow, value.to_s].join(' '), level: level + end + end + def output_execution_summary return unless @options[:output_execution_summary] fout_section 'summary', { execute_aborted_at: @execute_aborted_at, @@ -624,13 +982,16 @@ } end def prompt_with_quit(prompt_text, items, opts = {}) exit_option = '* Exit' - sel = @prompt.select prompt_text, - items + (@options[:menu_with_exit] ? [exit_option] : []), - opts + all_items = if @options[:menu_exit_at_top] + (@options[:menu_with_exit] ? [exit_option] : []) + items + else + items + (@options[:menu_with_exit] ? [exit_option] : []) + end + sel = @prompt.select(prompt_text, all_items, opts.merge(filter: true)) sel == exit_option ? nil : sel end def read_configuration_file!(options, configuration_path) return unless File.exist?(configuration_path) @@ -673,23 +1034,23 @@ "#{MarkdownExec::APP_NAME}" \ " - #{MarkdownExec::APP_DESC} (#{MarkdownExec::VERSION})", "Usage: #{executable_name} [(path | filename [block_name])] [options]" ].join("\n") - menu_data - .map do |long_name, short_name, _env_var, arg_name, description, opt_name, default, proc1| # rubocop:disable Metrics/ParameterLists - next unless long_name.present? || short_name.present? + menu_iter do |item| + next unless item[:long_name].present? || item[:short_name].present? - opts.on(*[if long_name.present? - "--#{long_name}#{arg_name.present? ? " #{arg_name}" : ''}" + opts.on(*[if item[:long_name].present? + "--#{item[:long_name]}#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}" end, - short_name.present? ? "-#{short_name}" : nil, - [description, - default.present? ? "[#{value_for_cli default}]" : nil].compact.join(' '), + item[:short_name].present? ? "-#{item[:short_name]}" : nil, + [item[:description], + item[:default].present? ? "[#{value_for_cli item[:default]}]" : nil].compact.join(' '), lambda { |value| - ret = proc1.call(value) - options[opt_name] = ret if opt_name + # ret = item[:proc1].call(value) + ret = item[:proc1] ? item[:proc1].call(value) : value + options[item[:opt_name]] = ret if item[:opt_name] ret }].compact) end end option_parser.load # filename defaults to basename of the program without suffix in a directory ~/.options @@ -700,11 +1061,11 @@ exec_block options, options[:block_name] end FNR11 = '/' - FNR12 = ',;' + FNR12 = ',~' def saved_name_make(opts) fne = opts[:filename].gsub(FNR11, FNR12) "#{[opts[:saved_script_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne, ',', opts[:block_name]].join('_')}.sh" @@ -739,32 +1100,55 @@ @options[:block_name]].join('_')}.out.txt" @options[:logged_stdout_filespec] = File.join @options[:saved_stdout_folder], @options[:logged_stdout_filename] @logged_stdout_filespec = @options[:logged_stdout_filespec] dirname = File.dirname(@options[:logged_stdout_filespec]) Dir.mkdir dirname unless File.exist?(dirname) - File.write(@options[:logged_stdout_filespec], @execute_files&.fetch(0, '')) + + # File.write(@options[:logged_stdout_filespec], @execute_files&.fetch(EF_STDOUT, '')) + ol = ["-STDOUT-\n"] + ol += @execute_files&.fetch(EF_STDOUT, []) + ol += ["-STDERR-\n"].tap_inspect name: :ol3 + ol += @execute_files&.fetch(EF_STDERR, []) + ol += ["-STDIN-\n"] + ol += @execute_files&.fetch(EF_STDIN, []) + 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)) - unless opts[:block_name].present? - pt = (opts[:prompt_select_block]).to_s - blocks_in_file.each { |block| block.merge! label: make_block_label(block, opts) } - block_labels = option_exclude_blocks(opts, blocks_in_file).map { |block| block[:label] } + loop1 = true && !opts[:block_name].present? - return nil if block_labels.count.zero? + loop do + unless opts[:block_name].present? + pt = (opts[:prompt_select_block]).to_s + blocks_in_file.each { |block| block.merge! label: make_block_label(block, opts) } + block_labels = option_exclude_blocks(opts, blocks_in_file).map { |block| block[:label] } - sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height] - return nil if sel.nil? + return nil if block_labels.count.zero? - label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil) - opts[:block_name] = @options[:block_name] = label_block[:name] - end + sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height] + return nil if sel.nil? - approve_block opts, blocks_in_file + # if sel.nil? + # loop1 = 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 loop1 + approve_block opts, blocks_in_file + # end + + break unless loop1 + + opts[:block_name] = '' + end end def select_md_file(files_ = nil) opts = options files = files_ || list_markdown_files_in_path @@ -802,13 +1186,20 @@ def summarize_block(headings, title) { headings: headings, name: title, title: title } end - def tab_completions(data = menu_data) + def menu_export(data = menu_data1) data.map do |item| - "--#{item[0]}" if item[0] + item.delete(:proc1) + item + end.to_yaml + end + + def tab_completions(data = menu_data1) + data.map do |item| + "--#{item[:long_name]}" if item[:long_name] end.compact end def update_options(opts = {}, over: true) if over @@ -817,23 +1208,10 @@ @options.merge! opts end @options.tap_inspect format: :yaml end - def value_for_cli(value) - case value.class.to_s - when 'String' - "'#{value}'" - when 'FalseClass', 'TrueClass' - value ? '1' : '0' - when 'Integer' - value - else - value.to_s - end - end - def value_for_hash(value, default = nil) return default if value.nil? case value.class.to_s when 'String', 'Integer', 'FalseClass', 'TrueClass' @@ -870,13 +1248,26 @@ @options[:saved_filespec] = File.join opts[:saved_script_folder], opts[:saved_script_filename] dirname = File.dirname(@options[:saved_filespec]) Dir.mkdir dirname unless File.exist?(dirname) - File.write(@options[:saved_filespec], "#!/usr/bin/env bash\n" \ + (shebang = if @options[:shebang]&.present? + "#{@options[:shebang]} #{@options[:shell]}\n" + else + '' + end + ).tap_inspect name: :shebang + File.write(@options[:saved_filespec], shebang + "# file_name: #{opts[:filename]}\n" \ "# block_name: #{opts[:block_name]}\n" \ "# time: #{Time.now.utc}\n" \ "#{required_blocks.flatten.join("\n")}\n") + + @options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod + return if @options[:saved_script_chmod].zero? + + @options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod + File.chmod @options[:saved_script_chmod], @options[:saved_filespec] + @options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod end end end