lib/markdown_exec.rb in markdown_exec-0.2.3 vs lib/markdown_exec.rb in markdown_exec-0.2.4
- old
+ new
@@ -96,19 +96,28 @@
def base_options
{
# commands
list_blocks: false, # command
list_docs: false, # command
+ list_recent_scripts: false, # command
+ run_last_script: false, # command
+ select_recent_script: false, # command
# command options
filename: env_str('MDE_FILENAME', default: nil), # option Filename to open
+ list_count: 16,
+ logged_stdout_filename_prefix: 'mde',
output_execution_summary: env_bool('MDE_OUTPUT_EXECUTION_SUMMARY', default: false), # option
output_script: env_bool('MDE_OUTPUT_SCRIPT', default: false), # option
output_stdout: env_bool('MDE_OUTPUT_STDOUT', default: true), # option
path: env_str('MDE_PATH', default: nil), # option Folder to search for files
save_executed_script: env_bool('MDE_SAVE_EXECUTED_SCRIPT', default: false), # option
+ save_execution_output: env_bool('MDE_SAVE_EXECUTION_OUTPUT', default: false), # option
+ saved_script_filename_prefix: 'mde',
saved_script_folder: env_str('MDE_SAVED_SCRIPT_FOLDER', default: 'logs'), # option
+ saved_script_glob: 'mde_*.sh',
+ saved_stdout_folder: env_str('MDE_SAVED_STDOUT_FOLDER', default: 'logs'), # option
user_must_approve: env_bool('MDE_USER_MUST_APPROVE', default: true), # option Pause for user to approve script
# configuration options
block_name_excluded_match: env_str('MDE_BLOCK_NAME_EXCLUDED_MATCH', default: '^\(.+\)$'),
block_name_match: env_str('MDE_BLOCK_NAME_MATCH', default: ':(?<title>\S+)( |$)'),
@@ -127,14 +136,15 @@
def default_options
{
bash: true, # bash block parsing in get_block_summary()
exclude_expect_blocks: true,
- exclude_matching_block_names: true, # exclude hidden blocks
+ hide_blocks_by_name: true,
output_saved_script_filename: false,
- prompt_select_block: 'Choose a block:', # in select_and_approve_block()
- prompt_select_md: 'Choose a file:', # in select_md_file()
+ prompt_approve_block: 'Process?',
+ prompt_select_block: 'Choose a block:',
+ prompt_select_md: 'Choose a file:',
saved_script_filename: nil, # calculated
struct: true # allow get_block_summary()
}
end
@@ -147,11 +157,11 @@
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? 'Process?' if opts[:user_must_approve]
+ 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]
@@ -169,10 +179,11 @@
.flatten(1)
.tap_inspect
end
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
@@ -184,11 +195,10 @@
next unless ready
# readable = ready[0]
# # writable = ready[1]
# # exceptions = ready[2]
- @execute_files = Hash.new([])
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]
@@ -201,13 +211,15 @@
fout "IOError: #{e}"
end
@execute_completed_at = Time.now.utc
end
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
fout "Error ENOENT: #{e.inspect}"
end
def count_blocks_in_filename
fenced_start_and_end_match = Regexp.new @options[:fenced_start_and_end_match]
@@ -220,41 +232,54 @@
def display_command(_opts, required_blocks)
required_blocks.each { |cb| fout cb }
end
- def exec_block(options, block_name = '')
+ def exec_block(options, _block_name = '')
options = default_options.merge options
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_docs]
fout_list files
return
end
- if @options[:list_blocks]
- fout_list (files.map do |file|
- make_block_labels(filename: file, struct: true)
- end).flatten(1)
+ 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,
- block_name: block_name,
- filename: select_md_file(files),
struct: true
)
-
fout "saved_filespec: #{@execute_script_filespec}" if @options[:output_saved_script_filename]
-
- output_execution_summary if @options[:output_execution_summary]
+ save_execution_output
+ output_execution_summary
end
# standard output; not for debug
#
def fout(str)
@@ -392,11 +417,11 @@
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]
list_blocks_in_file(opts).map do |block|
- next if opts[:exclude_matching_block_names] && block[:name].match(block_name_excluded_match)
+ next if opts[:hide_blocks_by_name] && block[:name].match(block_name_excluded_match)
block
end.compact.tap_inspect
end
@@ -411,10 +436,15 @@
.map { |block| block.fetch(:body, '') }
.flatten(1)
.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
+ end
+
def make_block_label(block, call_options = {})
opts = options.merge(call_options)
if opts[:mdheadings]
heads = block.fetch(:headings, []).compact.join(' # ')
"#{block[:title]} [#{heads}] (#{opts[:filename]})"
@@ -424,19 +454,19 @@
end
def make_block_labels(call_options = {})
opts = options.merge(call_options)
list_blocks_in_file(opts).map do |block|
- # next if opts[:exclude_matching_block_names] && block[:name].match(%r{^:\(.+\)$})
+ # next if opts[:hide_blocks_by_name] && block[:name].match(%r{^:\(.+\)$})
make_block_label block, opts
end.compact.tap_inspect
end
def option_exclude_blocks(opts, blocks)
block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
- if opts[:exclude_matching_block_names]
+ if opts[:hide_blocks_by_name]
blocks.reject { |block| block[:name].match(block_name_excluded_match) }
else
blocks
end
end
@@ -449,10 +479,12 @@
class_call_options
end.tap_inspect
end
def output_execution_summary
+ return unless @options[:output_execution_summary]
+
fout_section 'summary', {
execute_aborted_at: @execute_aborted_at,
execute_completed_at: @execute_completed_at,
execute_error: @execute_error,
execute_error_message: @execute_error_message,
@@ -526,30 +558,40 @@
:filename, proc_self],
['list-blocks', nil, nil, nil, 'List blocks',
:list_blocks, proc_true],
['list-docs', nil, nil, nil, 'List docs in current folder',
:list_docs, proc_true],
+ ['list-recent-scripts', nil, nil, nil, 'List recent saved scripts',
+ :list_recent_scripts, proc_true],
['output-execution-summary', nil, 'MDE_OUTPUT_EXECUTION_SUMMARY', 'BOOL', 'Display summary for execution',
:output_execution_summary, proc_to_i],
['output-script', nil, 'MDE_OUTPUT_SCRIPT', 'BOOL', 'Display script',
:output_script, proc_to_i],
['output-stdout', nil, 'MDE_OUTPUT_STDOUT', 'BOOL', 'Display standard output from execution',
:output_stdout, proc_to_i],
['path', 'p', 'MDE_PATH', 'PATH', 'Path to documents',
:path, proc_self],
+ ['run-last-script', nil, nil, nil, 'Run most recently saved script',
+ :run_last_script, proc_true],
+ ['select-recent-script', nil, nil, nil, 'Select and execute a recently saved script',
+ :select_recent_script, proc_true],
+ ['save-execution-output', nil, 'MDE_SAVE_EXECUTION_OUTPUT', 'BOOL', 'Save execution output',
+ :save_execution_output, proc_to_i],
['save-executed-script', nil, 'MDE_SAVE_EXECUTED_SCRIPT', 'BOOL', 'Save executed script',
:save_executed_script, proc_to_i],
['saved-script-folder', nil, 'MDE_SAVED_SCRIPT_FOLDER', 'SPEC', 'Saved script folder',
:saved_script_folder, proc_self],
+ ['saved-stdout-folder', nil, 'MDE_SAVED_STDOUT_FOLDER', 'SPEC', 'Saved stdout folder',
+ :saved_stdout_folder, proc_self],
['user-must-approve', nil, 'MDE_USER_MUST_APPROVE', 'BOOL', 'Pause to approve execution',
:user_must_approve, proc_to_i]
]
# rubocop:disable Style/Semicolon
summary_tail = [
[nil, '0', nil, nil, 'Show configuration',
- nil, ->(_) { options_finalize.call options; fout options.to_yaml }],
+ nil, ->(_) { options_finalize.call options; fout sorted_keys(options).to_yaml }],
['help', 'h', nil, nil, 'App help',
nil, ->(_) { fout option_parser.help; exit }],
['version', 'v', nil, nil, 'App version',
nil, ->(_) { fout MarkdownExec::VERSION; exit }],
['exit', 'x', nil, nil, 'Exit app',
@@ -557,11 +599,13 @@
]
# rubocop:enable Style/Semicolon
(summary_head + summary_body + summary_tail)
.map do |long_name, short_name, env_var, arg_name, description, opt_name, proc1| # rubocop:disable Metrics/ParameterLists
- opts.on(*[long_name.present? ? "--#{long_name}#{arg_name.present? ? (' ' + arg_name) : ''}" : nil,
+ opts.on(*[if long_name.present?
+ "--#{long_name}#{arg_name.present? ? " #{arg_name}" : ''}"
+ end,
short_name.present? ? "-#{short_name}" : nil,
[description,
env_var.present? ? "env: #{env_var}" : nil].compact.join(' - '),
lambda { |value|
ret = proc1.call(value)
@@ -595,10 +639,38 @@
block_name = rest.fetch(1, nil)
exec_block options, block_name
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/)
+
+ @options[:block_name] = mf[:block]
+ @options[:filename] = "#{mf[:file]}.md" ### other extensions
+ @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, ''))
+ 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?
@@ -606,13 +678,13 @@
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] }
return nil if block_labels.count.zero?
- sel = @prompt.select(pt, block_labels, per_page: opts[:select_page_height])
+ sel = @prompt.select pt, block_labels, per_page: opts[:select_page_height]
label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
- opts[:block_name] = label_block[:name]
+ opts[:block_name] = @options[:block_name] = label_block[:name]
end
approve_block opts, blocks_in_file
end
@@ -620,14 +692,34 @@
opts = options
files = files_ || list_markdown_files_in_path
if files.count == 1
files[0]
elsif files.count >= 2
- @prompt.select(opts[:prompt_select_md].to_s, files, per_page: opts[:select_page_height])
+ @prompt.select opts[:prompt_select_md].to_s, files, per_page: opts[:select_page_height]
end
end
+ def select_recent_script
+ filename = @prompt.select @options[:prompt_select_md].to_s, list_recent_scripts,
+ per_page: @options[:select_page_height]
+ 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
+ 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 update_options(opts = {}, over: true)
@@ -638,15 +730,13 @@
end
@options
end
def write_command_file(opts, required_blocks)
- return unless opts[:saved_script_filename].present?
-
- fne = File.basename(opts[:filename], '.*').gsub(/[^a-z0-9]/i, '-') # scan(/[a-z0-9]/i).join
- bne = opts[:block_name].gsub(/[^a-z0-9]/i, '-') # scan(/[a-z0-9]/i).join
- opts[:saved_script_filename] = "mde_#{Time.now.utc.strftime '%F-%H-%M-%S'}_#{fne}_#{bne}.sh"
-
+ 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]
dirname = File.dirname(@options[:saved_filespec])
Dir.mkdir dirname unless File.exist?(dirname)
File.write(@options[:saved_filespec], "#!/usr/bin/env bash\n" \