Top Level Namespace

Includes:
CLI, Tap

Defined Under Namespace

Modules: ArrayUtil, CLI, ClassMethodWrapper, CompactionHelpers, Env, Exceptions, HashDelegatorSelf, ImwUx, InstanceMethodWrapper, MarkdownExec, MarkdownTableFormatter, PathUtils, StringUtil, Tap, TextAnalyzer Classes: AnsiFormatter, AnsiString, AppInterrupt, ArgPro, ArgumentProcessorTest, Array, BashCommentFormatter, BashCommentFormatterTest, BlockLabel, BlockLabelTest, BlockMissing, BlockType, CachedNestedFileReader, CachedNestedFileReaderTest, ColorScheme, CommandProcessorTest, DOH, DirectorySearcher, DirectorySearcherTest, DirectorySearcherTest2, ExecutionStreams, FCBTest, FOut, FalseClass, FileMissingError, Hash, HierarchyString, IndexedLine, InputSequencer, LayeredHash, LinkKeys, LoadFile, LoadFileLinkState, MDE, MenuOptions, MenuState, NamedCaptureExtractor, NestedLine, Object, OptionValueTest, Poly, Regexp, RegexpGsubFormatTest, ResizeTerminalTest, SavedAssetTest, SavedFilesMatcherTest, SelectedBlockMenuState, ShellType, StreamsOut, String, StringWrapper, TableExtractor, TestFindFiles, TestFormatTable, TestFormatTable2, TestHierarchyString, TestMarkdownTableFormatter, TestObjectMethods, TestShellExpressionEvaluator, TestStringMethods, TestTableExtractor, TestTextAnalyzer, TtyMenu

Constant Summary collapse

BF =
'bin'
DISPLAY_LEVEL_BASE =

display_level values

0
DISPLAY_LEVEL_ADMIN =

required output

1
DISPLAY_LEVEL_DEBUG =

monit

2
DISPLAY_LEVEL_DUMP =
3
DISPLAY_LEVEL_DEFAULT =
DISPLAY_LEVEL_ADMIN
DISPLAY_LEVEL_MAX =
DISPLAY_LEVEL_DUMP
LOCAL_YML =
'menu.yml'
"lib/#{LOCAL_YML}"
BLOCK_TYPE_COLOR_OPTIONS =
{
  BlockType::EDIT => :menu_edit_color,
  BlockType::HISTORY => :menu_history_color,
  BlockType::LINK => :menu_link_color,
  BlockType::LOAD => :menu_load_color,
  BlockType::OPTS => :menu_opts_color,
  BlockType::SAVE => :menu_save_color,
  BlockType::SHELL => :menu_bash_color,
  BlockType::VARS => :menu_vars_color
}.freeze
SAVED_ASSET_FORMAT =
'%{prefix}%{join}%{time}%{join}%{filename}%{join}' \
'%{mark}%{join}%{blockname}%{join}%{exts}'
ARGV_SEP =
'--'

Constants included from Tap

Tap::ALL, Tap::ALL2, Tap::CVT, Tap::DN, Tap::NONE, Tap::T1, Tap::T2, Tap::T3, Tap::T4, Tap::TB1, Tap::TB2, Tap::TB3, Tap::TD, Tap::TD0, Tap::TD1, Tap::TD2, Tap::TDD, Tap::TP, Tap::TP0, Tap::TP1, Tap::TP2

Instance Method Summary collapse

Methods included from Tap

#tap_config, #tap_inspect, #tap_print, #tap_pry, #tap_puts, #tap_yaml

Methods included from Env

#env_bool, #env_bool_false, #env_int, #env_str

Methods included from CLI

#value_for_cli

Instance Method Details

#append_to_bash_history(line) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/append_to_bash_history.rb', line 6

def append_to_bash_history(line)
  # Get the bash history file from the HISTFILE environment variable or default to ~/.bash_history
  histfile = ENV['HISTFILE'] || File.expand_path("~/.bash_history")

  if File.exist?(histfile)
    # Open the file in append mode and write the line
    File.open(histfile, 'a') do |file|
      file.puts line
    end
    puts "Appended to bash history: #{line}"
  else
    puts "Bash history file does not exist."
  end
end

#assert_equal_hash(expected, actual, message = nil) ⇒ Object



189
190
191
192
193
# File 'lib/fcb.rb', line 189

def assert_equal_hash(expected, actual, message = nil)
  sorted_expected = sort_hash_recursively(expected)
  sorted_actual = sort_hash_recursively(actual)
  assert_equal sorted_expected, sorted_actual, message
end

#block_type_selected?(selected_types, type) ⇒ Boolean

Determines if a given block type is selected based on a list.

check against. If nil, all types are considered selected. selected_types is nil (indicating all types are selected).

Parameters:

  • selected_types (Array<String>, nil)

    An array of block types to

  • type (String)

    The block type to check for selection.

Returns:

  • (Boolean)

    Returns true if the type is selected or if



13
14
15
# File 'lib/filter.rb', line 13

def block_type_selected?(selected_types, type)
  !selected_types || selected_types.include?(type)
end

#bpp(*args) ⇒ Object



71
72
73
74
75
# File 'lib/markdown_exec.rb', line 71

def bpp(*args)
  pp '+ bpp()'
  pp(*args.map.with_index { |line, ind| "  - #{ind}: #{line}" })
  rbi
end

#cil(caller_info) ⇒ Object



25
26
27
28
# File 'lib/ww.rb', line 25

def cil(caller_info)
  "#{caller_info.lineno} : #{File.basename(caller_info.path)} : " \
    "#{caller_info.label}"
end

#display_terminal_rectangle(width, height) ⇒ Object

This function draws a rectangle of the given width and height with stars on the edges and empty space inside.



77
78
79
80
81
# File 'lib/resize_terminal.rb', line 77

def display_terminal_rectangle(width, height)
  puts '*' * width
  (height - 2).times { puts "*#{' ' * (width - 2)}*" }
  puts '*' * width
end

#dp(str) ⇒ Object



56
57
58
# File 'lib/markdown_exec.rb', line 56

def dp(str)
  lout " => #{str}", level: DISPLAY_LEVEL_DEBUG
end

#evaluate_shell_expressions(initial_code, expressions, shell: '/bin/bash', key_format: "%%<%s>") ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/evaluate_shell_expressions.rb', line 8

def evaluate_shell_expressions(initial_code, expressions, shell: '/bin/bash',
                               key_format: "%%<%s>")
  # !!p initial_code expressions key_format shell
  return if initial_code.nil? || initial_code.empty? ||
            expressions.nil? || expressions.empty? ||
            key_format.nil? || key_format.empty?

  # token to separate output
  token = "__TOKEN__#{Time.now.to_i}__"

  # Construct a single shell script
  script = initial_code.dup
  expressions.each_with_index do |(key, expression), index|
    script << "\necho #{token}#{index}\n"
    script << expression << "\n"
  end
  # !!v script

  # Execute
  stdout_str, stderr_str, status = Open3.capture3(shell, "-c", script)
  # !!v stdout_str, stderr_str, status

  unless status.success?
    raise "Shell script execution failed: #{stderr_str}"
  end

  # Extract output for expressions
  result_hash = {}
  stdout_str.split(/\n?#{token}\d+\n/)[1..-1].tap do |output_parts|
    expressions.each_with_index do |(key, _expression), index|
      result_hash[sprintf(key_format, key)] = output_parts[index].chomp
    end
  end
  # !!v result_hash

  result_hash
end

#find_files(pattern, paths = ['', Dir.pwd], base_dir: Dir.pwd, exclude_dirs: false, use_relative_paths: true) ⇒ Object

Finds files matching a given pattern within specified directory paths while optionally excluding “.” and “..” entries and directory names from the results.

The function takes a pattern (filename or pattern with wildcards), an array of paths, and options to exclude directory entries and special entries “.” and “..”, and to use relative paths. It searches for files matching the pattern within each of the specified paths. Hidden files are included in the search. The search can include subdirectories depending on the path specification (e.g., ‘dir/**’ for recursive search).

Args:

pattern (String): A filename or a pattern string with wildcards.
paths (Array<String>): An array of directory paths where the search will be performed.
  Paths can include wildcards for recursive search.
exclude_dirs (Boolean): If true, excludes "." and ".." and directory names from the results.
use_relative_paths (Boolean): If true, removes the app's base directory from the file names
  if present.

Returns:

Array<String>: A unique list of file paths that match the given pattern in the specified paths,
excluding directories if exclude_dirs is true. Paths are relative if use_relative_paths is true.

Example:

find_files('version.rb', ['lib/**', 'spec'], true, true)
# This might return file paths like ['markdown_exec/version.rb', 'spec/version_spec.rb'].


31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/find_files.rb', line 31

def find_files(pattern, paths = ['', Dir.pwd], base_dir: Dir.pwd,
               exclude_dirs: false, use_relative_paths: true)
  matched_files = []

  paths.each do |path_with_wildcard|
    # Combine the path with the wildcard and the pattern
    search_pattern = File.join(path_with_wildcard, pattern)

    # Use Dir.glob with the File::FNM_DOTMATCH flag to include hidden files
    files = Dir.glob(search_pattern, File::FNM_DOTMATCH)

    # Optionally exclude "." and ".." and directory names
    files.reject! { |file|
      file.end_with?('/.', '/..') || File.directory?(file)
    } if exclude_dirs

    # Optionally use relative paths
    files.map! { |file|
      file.sub(/^#{Regexp.escape(base_dir)}\//, '')
    } if use_relative_paths

    matched_files += files
  end

  matched_files.uniq
end

#format_and_highlight_dependencies(dependencies, highlight_color_sym: :exception_color_detail, plain_color_sym: :menu_chrome_color, label: 'Dependencies:', highlight: [], line_prefix: ' ', line_postfix: '', detail_sep: ' ') ⇒ String

Formats and highlights a list of dependencies. Dependencies are presented with indentation, and specific items can be highlighted in a specified color, while others are shown in a plain color.

Parameters:

  • dependencies (Hash)

    A hash of dependencies, where each key is a dependency name, and its value is an array of sub-items.

  • highlight_color_sym (Symbol) (defaults to: :exception_color_detail)

    The color method to apply to highlighted items. Default is :exception_color_detail.

  • plain_color_sym (Symbol) (defaults to: :menu_chrome_color)

    The color method for non-highlighted items. Default is :menu_chrome_color.

  • label (String) (defaults to: 'Dependencies:')

    The label to prefix the list of dependencies with. Default is ‘Dependencies:’.

  • highlight (Array) (defaults to: [])

    An array of items to highlight. Each item in this array will be formatted with the specified highlight color.

  • line_prefix (String) (defaults to: ' ')

    Prefix for each line. Default is ‘ ’.

  • line_postfix (String) (defaults to: '')

    Postfix for each line. Default is ”.

  • detail_sep (String) (defaults to: ' ')

    Separator for items in the sub-list. Default is ‘ ’.

Returns:

  • (String)

    A formatted string representation of the dependencies with highlighted items.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/directory_searcher.rb', line 50

def format_and_highlight_dependencies(
  dependencies,
  highlight_color_sym: :exception_color_detail,
  plain_color_sym: :menu_chrome_color,
  label: 'Dependencies:',
  highlight: [],
  line_prefix: '  ',
  line_postfix: '',
  detail_sep: '  '
)
  formatted_deps = dependencies&.map do |dep_name, sub_items|
    formatted_sub_items = sub_items.map do |item|
      color_sym = highlight.include?(item) ? highlight_color_sym : plain_color_sym
      string_send_color(item, color_sym)
    end.join(detail_sep)

    "#{line_prefix}- #{string_send_color(dep_name,
                                         highlight.include?(dep_name) ? highlight_color_sym : plain_color_sym)}: #{formatted_sub_items}#{line_postfix}"
  end || []

  "#{line_prefix}#{string_send_color(label,
                                     highlight_color_sym)}#{line_postfix}\n" + formatted_deps.join("\n")
end

#format_and_highlight_hash(data, highlight_color_sym: :exception_color_detail, plain_color_sym: :menu_chrome_color, label: 'Data:', highlight: [], line_prefix: ' ', line_postfix: '', key_has_value: ': ') ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/directory_searcher.rb', line 9

def format_and_highlight_hash(
  data,
  highlight_color_sym: :exception_color_detail,
  plain_color_sym: :menu_chrome_color,
  label: 'Data:',
  highlight: [],
  line_prefix: '  ',
  line_postfix: '',
  key_has_value: ': '
)
  formatted_deps = data&.map do |key, value|
    color_sym = highlight.include?(key) ? highlight_color_sym : plain_color_sym
    dkey = string_send_color(key, color_sym)

    "#{line_prefix}#{dkey}#{key_has_value}" \
     "#{string_send_color(value,
                          highlight.include?(value) ? highlight_color_sym : plain_color_sym)}: " \
     "#{formatted_sub_items}#{line_postfix}"
  end

  "#{line_prefix}#{string_send_color(label,
                                     highlight_color_sym)}#{line_postfix}\n" + formatted_deps.join("\n")
end

#format_and_highlight_lines(lines, highlight_color_sym: :exception_color_detail, plain_color_sym: :menu_chrome_color, label: 'Dependencies:', highlight: [], line_prefix: ' ', line_postfix: '') ⇒ Object

warn menu_blocks.to_yaml.sub(/^(?:—n)?/, “MenuBlocks:n”)



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/directory_searcher.rb', line 75

def format_and_highlight_lines(
  lines,
  highlight_color_sym: :exception_color_detail,
  plain_color_sym: :menu_chrome_color,
  label: 'Dependencies:',
  highlight: [],
  line_prefix: '  ',
  line_postfix: ''
)
  formatted_deps = lines&.map do |item|
    "#{line_prefix}- #{string_send_color(dep_name,
                                         highlight.include?(dep_name) ? highlight_color_sym : plain_color_sym)}: #{item}#{line_postfix}"
  end || []

  "#{line_prefix}#{string_send_color(label,
                                     highlight_color_sym)}#{line_postfix}\n" + formatted_deps.join("\n")
end

#mainObject

MDE.prepend(ImwUx)



252
253
254
255
256
257
258
259
260
261
# File 'lib/input_sequencer.rb', line 252

def main
  if ARGV.empty?
    puts "Usage: #{__FILE__} document_filename [block_name...]"
    exit(1)
  end
  document_filename = ARGV.shift
  initial_blocks = ARGV
  mde = MDE.new(document_filename, initial_blocks)
  mde.do_run
end


22
23
24
# File 'lib/shared.rb', line 22

def menu_from_yaml
  YAML.load File.open(File.join(File.expand_path(__dir__), LOCAL_YML))
end

#process_arguments(arguments, loose_args, options_parsed) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/argument_processor.rb', line 7

def process_arguments(arguments, loose_args, options_parsed)
  # !!t arguments, loose_args, options_parsed
  # loose_args will be empty first command contains pass-through arguments
  while loose_args.any?
    if arguments.first == loose_args.first
      yield ArgPro::ArgIsPosition, arguments.shift

      loose_args.shift
      next
    end

    yield ArgPro::ArgIsOption, options_parsed.first

    arguments.shift(options_parsed.first[:procname].present? ? 2 : 1)
    options_parsed.shift
  end
end

#process_commands(options_parsed:, arguments:, enable_search:, named_procs:, rest:) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/argument_processor.rb', line 25

def process_commands(options_parsed:, arguments:, enable_search:,
                     named_procs:, rest:)
  # !!t arguments,options_parsed
  command_processed = false
  block_executed = false
  requested_menu = false
  position = 0

  process_arguments(arguments.dup, rest.dup,
                    options_parsed.dup) do |type, item|
    # !!t type,item
    case type
    when ArgPro::ArgIsOption
      if named_procs.include?(item[:name])
        command_processed = true
        yield ArgPro::CallProcess, item[:name]
      else
        converted = if item[:proccode]
                      yield ArgPro::ConvertValue, [item[:proccode],
                                                   item[:value]]
                    else
                      item[:value]
                    end
        if item[:name]
          yield ArgPro::ActSetOption, [item[:name], converted]
        end
      end
    when ArgPro::ArgIsPosition
      case position
      when 0
        # position 0: file, folder, or search term (optional)
        if Dir.exist?(item)
          yield ArgPro::ActSetPath, item
        elsif File.exist?(item)
          yield ArgPro::ActSetFileName, item
        elsif enable_search
          yield ArgPro::ActFind, item
        else
          yield ArgPro::ActFileIsMissing, item
        end
      else
        # position 1: block (optional)
        if item == '.'
          requested_menu = true
        else
          block_executed = true
          yield ArgPro::ActSetBlockName, item
        end
      end
      position += 1
      rest.shift
    else
      raise
    end
  end
end

#rbiObject



60
61
62
63
# File 'lib/markdown_exec.rb', line 60

def rbi
  pp(caller.take(4).map.with_index { |line, ind| "   - #{ind}: #{line}" })
  binding.irb
end

#rbpObject



65
66
67
68
69
# File 'lib/markdown_exec.rb', line 65

def rbp
  rpry
  pp(caller.take(4).map.with_index { |line, ind| "   - #{ind}: #{line}" })
  binding.pry
end

#resize_terminal(show_dims: false, show_rectangle: false, require_stdout: true) ⇒ Object

This function attempts to resize the terminal to its maximum supported size. It checks if the script is running in an interactive terminal with no arguments. If so, it sends escape sequences to query the terminal size and reads the response. It then compares the current terminal size with the calculated size and adjusts if necessary. If the terminal emulator is unsupported, it prints an error message. 2024-8-23 add require_stdout to allow for testing



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/resize_terminal.rb', line 14

def resize_terminal(show_dims: false, show_rectangle: false,
                    require_stdout: true)
  # Check if running in an interactive terminal and no arguments are provided
  unless $stdin.tty?
    warn 'Usage: resize_terminal'
    return
  end

  return if require_stdout && !$stdout.tty?

  # Save the current state and send the escape sequence to get the cursor position
  print "\e7\e[r\e[999;999H\e[6n\e8"
  $stdout.flush

  # Read the response from the terminal
  response = String.new
  Timeout.timeout(5) do
    loop do
      char = $stdin.getch
      response << char
      break if response.include?('R')
    end
  end

  if response.empty?
    warn "Error: No response received from terminal. Response: #{response.inspect}"
    return 1
  end

  # Match the response to extract the terminal dimensions
  match_data = response.match(/\[(\d+);(\d+)R/)
  unless match_data
    warn "Error: Failed to match terminal response pattern. Response: #{response.inspect}"
    return 1
  end

  calculated_rows, calculated_columns = match_data.captures.map(&:to_i)

  if ENV['COLUMNS'].to_i == calculated_columns && ENV['LINES'].to_i == calculated_rows
    puts "#{ENV.fetch('TERM', nil)} #{calculated_columns}x#{calculated_rows}"
  elsif calculated_columns.positive? && calculated_rows.positive?
    warn "#{ENV.fetch('COLUMNS',
                      nil)}x#{ENV.fetch('LINES',
                                        nil)} -> #{calculated_columns}x#{calculated_rows}" if show_dims
    system("stty cols #{calculated_columns} rows #{calculated_rows}")
  else
    warn "Error: Calculated terminal size is invalid. Columns: #{calculated_columns}, Rows: #{calculated_rows}"
    return 1
  end

  # Display a text rectangle if the option is enabled
  display_terminal_rectangle(calculated_columns,
                             calculated_rows) if show_rectangle
rescue Timeout::Error
  warn 'Error: Timeout while reading terminal response. Unsupported terminal emulator.'
  1
rescue StandardError => err
  warn "Error: #{err.message}. Unsupported terminal emulator."
  1
end

#rpryObject



77
78
79
80
# File 'lib/markdown_exec.rb', line 77

def rpry
  require 'pry-nav'
  require 'pry-stack_explorer'
end

#sort_hash_recursively(hash) ⇒ Object



195
196
197
198
199
# File 'lib/fcb.rb', line 195

def sort_hash_recursively(hash)
  hash.each_with_object({}) do |(k, v), new_hash|
    new_hash[k] = v.is_a?(Hash) ? sort_hash_recursively(v) : v
  end.sort.to_h
end

#spec_source(file, env_var_name = 'SPEC_DEBUG') ⇒ Object

output standard header for file load during testing



5
6
7
8
9
10
# File 'lib/rspec_helpers.rb', line 5

def spec_source(file, env_var_name = 'SPEC_DEBUG')
  if (->(val) { val.nil? ? false : !(val.empty? || val == '0') })
     .call(ENV.fetch(env_var_name, nil))
    puts "#{env_var_name}: #{file}"
  end
end

#ww(*objs, full_backtrace: false, single_line: false, locations: caller_locations) ⇒ Object

def wtr(*objs, full_backtrace: false, single_line: false)

ww(['->'] + objs,
   locations: caller_locations[2..-1],
   full_backtrace: full_backtrace,
   single_line: single_line)

end



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/ww.rb', line 21

def ww(*objs, full_backtrace: false, single_line: false,
       locations: caller_locations)
  return unless $debug

  def cil(caller_info)
    "#{caller_info.lineno} : #{File.basename(caller_info.path)} : " \
      "#{caller_info.label}"
  end

  backtrace = if full_backtrace
                locations.map do |caller_info|
                  cil(caller_info)
                end
              else
                [cil(locations[0, 1].first)]
              end
  trace = backtrace + objs
  if single_line
    PP.singleline_pp(trace, $stderr)
  else
    PP.pp(trace, $stderr)
  end
end