lib/markdown_exec.rb in markdown_exec-0.2.6 vs lib/markdown_exec.rb in markdown_exec-1.0.0
- old
+ new
@@ -65,20 +65,17 @@
# debug output
#
def tap_inspect(format: nil, name: 'return')
return self unless $pdebug
- fn = case format
- when :json
- :to_json
- when :string
- :to_s
- when :yaml
- :to_yaml
- else
- :inspect
- end
+ cvt = {
+ json: :to_json,
+ string: :to_s,
+ yaml: :to_yaml,
+ else: :inspect
+ }
+ fn = cvt.fetch(format, cvt[:else])
puts "-> #{caller[0].scan(/in `?(\S+)'$/)[0][0]}()" \
" #{name}: #{method(fn).call}"
self
@@ -100,14 +97,15 @@
##
# 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
+ .map do |_long_name, _short_name, env_var, _arg_name, _description, opt_name, default, proc1| # rubocop:disable Metrics/ParameterLists
next unless opt_name.present?
- [opt_name, env_bool(env_var, default: value_for_hash(default))]
+ value = env_str(env_var, default: value_for_hash(default))
+ [opt_name, proc1 ? proc1.call(value) : value]
end.compact.to_h.merge(
{
mdheadings: true, # use headings (levels 1,2,3) in block lable
menu_with_exit: true
}
@@ -121,10 +119,11 @@
hide_blocks_by_name: true,
output_saved_script_filename: false,
prompt_approve_block: 'Process?',
prompt_select_block: 'Choose a block:',
prompt_select_md: 'Choose a file:',
+ prompt_select_output: 'Choose a file:',
saved_script_filename: nil, # calculated
struct: true # allow get_block_summary()
}
end
@@ -142,12 +141,14 @@
allow = @prompt.yes? opts[:prompt_approve_block] if opts[:user_must_approve]
opts[:ir_approve] = allow
selected = get_block_by_name blocks_in_file, opts[:block_name]
if opts[:ir_approve]
- write_command_file(opts, required_blocks) if opts[:save_executed_script]
+ write_command_file opts, required_blocks
command_execute opts, required_blocks.flatten.join("\n")
+ save_execution_output
+ output_execution_summary
end
selected[:name]
end
@@ -219,57 +220,44 @@
update_options options, over: false
# document and block reports
#
files = list_files_per_options(options)
- if @options[:list_blocks]
- fout_list (files.map do |file|
- make_block_labels(filename: file, struct: true)
- end).flatten(1)
- return
- end
- if @options[:list_default_yaml]
- fout_list list_default_yaml
- return
+ simple_commands = {
+ doc_glob: -> { fout options[:md_filename_glob] },
+ list_blocks: lambda do
+ fout_list (files.map do |file|
+ make_block_labels(filename: file, struct: true)
+ 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 },
+ 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 }
+ }
+ simple_commands.each_key do |key|
+ if @options[key]
+ simple_commands[key].call
+ return # rubocop:disable Lint/NonLocalExitFromIterator
+ end
end
- if @options[:list_docs]
- fout_list files
- return
- end
-
- if @options[:list_default_env]
- fout_list list_default_env
- return
- end
-
- if @options[:list_recent_scripts]
- fout_list list_recent_scripts
- return
- end
-
- if @options[:run_last_script]
- run_last_script
- return
- end
-
- if @options[:select_recent_script]
- select_recent_script
- return
- end
-
# process
#
@options[:filename] = select_md_file(files)
select_and_approve_block(
bash: true,
struct: true
)
fout "saved_filespec: #{@execute_script_filespec}" if @options[:output_saved_script_filename]
- save_execution_output
- output_execution_summary
end
# standard output; not for debug
#
def fout(str)
@@ -310,10 +298,15 @@
unless opts[:filename]&.present?
fout 'No blocks found.'
exit 1
end
+ unless File.exist? opts[:filename]
+ fout 'Document is missing.'
+ exit 1
+ 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
@@ -449,13 +442,32 @@
.map { |block| block.fetch(:body, '') }
.flatten(1)
.tap_inspect
end
+ def most_recent(arr)
+ return unless arr
+ return if arr.count < 1
+
+ arr.max.tap_inspect
+ end
+
+ def most_recent_list(arr)
+ return unless arr
+ return if (ac = arr.count) < 1
+
+ arr.sort[-[ac, options[:list_count]].min..].reverse.tap_inspect
+ end
+
+ def list_recent_output
+ most_recent_list(Dir.glob(File.join(@options[:saved_stdout_folder],
+ @options[:saved_stdout_glob]))).tap_inspect
+ end
+
def list_recent_scripts
- Dir.glob(File.join(@options[:saved_script_folder],
- @options[:saved_script_glob])).sort[0..(options[:list_count] - 1)].reverse.tap_inspect
+ most_recent_list(Dir.glob(File.join(@options[:saved_script_folder],
+ @options[:saved_script_glob]))).tap_inspect
end
def make_block_label(block, call_options = {})
opts = options.merge(call_options)
if opts[:mdheadings]
@@ -474,75 +486,82 @@
make_block_label block, opts
end.compact.tap_inspect
end
def menu_data
- val_as_bool = ->(value) { value.to_i != 0 }
+ 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 = ->(_) { true }
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 }]
]
# 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, nil, val_true],
+ ['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, nil, val_true],
- ['list-default-yaml', nil, nil, nil, 'List default configuration as YAML', :list_default_yaml, nil, val_true],
- ['list-docs', nil, nil, nil, 'List docs in current folder', :list_docs, nil, val_true],
- ['list-recent-scripts', nil, nil, nil, 'List recent saved scripts', :list_recent_scripts, nil, val_true],
+ ['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],
- ['run-last-script', nil, nil, nil, 'Run most recently saved script', :run_last_script, nil, val_true],
- ['select-recent-script', nil, nil, nil, 'Select and execute a recently saved script', :select_recent_script, nil, val_true],
+ ['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, 'App version',
+ ['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, '^\(.+\)$', nil],
- [nil, nil, 'MDE_BLOCK_NAME_MATCH', nil, '', :block_name_match, ':(?<title>\S+)( |$)', nil],
- [nil, nil, 'MDE_BLOCK_REQUIRED_SCAN', nil, '', :block_required_scan, '\+\S+', nil],
- [nil, nil, 'MDE_FENCED_START_AND_END_MATCH', nil, '', :fenced_start_and_end_match, '^`{3,}', nil],
+ :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>.*)$', nil],
- [nil, nil, 'MDE_HEADING1_MATCH', nil, '', :heading1_match, '^# *(?<name>[^#]*?) *$', nil],
- [nil, nil, 'MDE_HEADING2_MATCH', nil, '', :heading2_match, '^## *(?<name>[^#]*?) *$', nil],
- [nil, nil, 'MDE_HEADING3_MATCH', nil, '', :heading3_match, '^### *(?<name>.+?) *$', nil],
- [nil, nil, 'MDE_MD_FILENAME_GLOB', nil, '', :md_filename_glob, '*.[Mm][Dd]', nil],
- [nil, nil, 'MDE_MD_FILENAME_MATCH', nil, '', :md_filename_match, '.+\\.md', nil],
- [nil, nil, 'MDE_SELECT_PAGE_HEIGHT', nil, '', :select_page_height, 12, nil]
+ '^`{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
end
@@ -575,11 +594,12 @@
end
end
## position 1: block name (optional)
#
- @options[:block_name] = rest.fetch(1, nil)
+ block_name = rest.fetch(1, nil)
+ @options[:block_name] = block_name if block_name.present?
end
def optsmerge(call_options = {}, options_block = nil)
class_call_options = @options.merge(call_options || {})
if options_block
@@ -650,11 +670,11 @@
@option_parser = option_parser = OptionParser.new do |opts|
executable_name = File.basename($PROGRAM_NAME)
opts.banner = [
"#{MarkdownExec::APP_NAME}" \
" - #{MarkdownExec::APP_DESC} (#{MarkdownExec::VERSION})",
- "Usage: #{executable_name} [path] [filename] [options]"
+ "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?
@@ -679,42 +699,51 @@
options_finalize rest
exec_block options, options[:block_name]
end
+ FNR11 = '/'
+ 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"
+ end
+
+ def saved_name_split(name)
+ mf = name.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_,_(?<block>.+)\.sh/)
+ return unless mf
+
+ @options[:block_name] = mf[:block].tap_inspect name: :options_block_name
+ @options[:filename] = mf[:file].gsub(FNR12, FNR11).tap_inspect name: :options_filename
+ end
+
def run_last_script
- filename = Dir.glob(File.join(@options[:saved_script_folder],
- @options[:saved_script_glob])).sort[0..(options[:list_count] - 1)].last
- filename.tap_inspect name: filename
- mf = filename.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_(?<block>.+)\.sh/)
+ filename = most_recent Dir.glob(File.join(@options[:saved_script_folder],
+ @options[:saved_script_glob]))
+ return unless filename
- @options[:block_name] = mf[:block]
- @options[:filename] = "#{mf[:file]}.md" ### other extensions
+ filename.tap_inspect name: filename
+ saved_name_split filename
@options[:save_executed_script] = false
select_and_approve_block
- save_execution_output
- output_execution_summary
end
def save_execution_output
return unless @options[:save_execution_output]
fne = File.basename(@options[:filename], '.*')
+
@options[:logged_stdout_filename] =
"#{[@options[:logged_stdout_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
@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, ''))
- # @options[:logged_stderr_filename] =
- # "#{[@options[:logged_stdout_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
- # @options[:block_name]].join('_')}.err.txt"
- # @options[:logged_stderr_filespec] = File.join @options[:saved_stdout_folder], @options[:logged_stderr_filename]
- # @logged_stderr_filespec = @options[:logged_stderr_filespec]
- # File.write(@options[:logged_stderr_filespec], @execute_files&.fetch(1, ''))
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))
@@ -744,36 +773,45 @@
elsif files.count >= 2
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]
+ 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]
return if filename.nil?
- mf = filename.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_(?<block>.+)\.sh/)
-
- @options[:block_name] = mf[:block]
- @options[:filename] = "#{mf[:file]}.md" ### other extensions
+ saved_name_split filename
select_and_approve_block(
bash: true,
save_executed_script: false,
struct: true
)
- save_execution_output
- output_execution_summary
end
def sorted_keys(hash1)
hash1.keys.sort.to_h { |k| [k, hash1[k]] }
end
def summarize_block(headings, title)
{ headings: headings, name: title, title: title }
end
+ def tab_completions(data = menu_data)
+ data.map do |item|
+ "--#{item[0]}" if item[0]
+ end.compact
+ end
+
def update_options(opts = {}, over: true)
if over
@options = @options.merge opts
else
@options.merge! opts
@@ -823,15 +861,16 @@
value.to_s
end
end
def write_command_file(opts, required_blocks)
- fne = File.basename(opts[:filename], '.*')
- opts[:saved_script_filename] =
- "#{[opts[:saved_script_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
- opts[:block_name]].join('_')}.sh"
- @options[:saved_filespec] = File.join opts[:saved_script_folder], opts[:saved_script_filename]
- @execute_script_filespec = @options[:saved_filespec]
+ return unless opts[:save_executed_script]
+
+ opts[:saved_script_filename] = saved_name_make(opts)
+ @execute_script_filespec =
+ @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" \
"# file_name: #{opts[:filename]}\n" \
"# block_name: #{opts[:block_name]}\n" \