Top Level Namespace

Includes:
CLI, Tap

Defined Under Namespace

Modules: ArrayUtil, AttributeAccess, CLI, ClassMethodWrapper, CompactionHelpers, Env, ErrorReporting, Exceptions, HashDelegatorSelf, ImwUx, InstanceMethodWrapper, MarkdownExec, MarkdownTableFormatter, PathUtils, StringUtil, Tap, TextAnalyzer Classes: AnimationToTTS, AnsiFormatter, AnsiString, AppInterrupt, ArgPro, ArgumentProcessorTest, Array, AudioSegmentGenerator, AudioStitcher, BashCommentFormatter, BashCommentFormatterTest, BlockCache, BlockLabel, BlockLabelTest, BlockMissing, BlockSelection, BlockType, CachedNestedFileReader, CachedNestedFileReaderTest, Collapser, CollapserTest, ColorScheme, CommandProcessorTest, CommandResult, CommandResultDelegator, CommandResultInstanceVars, CommandResultOpenStruct, CommandResultRefined, CommandResultStruct, DirectorySearcher, DirectorySearcherTest, DirectorySearcherTest2, DummyObject, EnvInterface, EvaluateShellExpression, ExecutionStreams, ExportValueSource, FCB, FCBTest, FOut, FalseClass, FileMissingError, Hash, HierarchyString, IndexedLine, InputSequencer, LinkKeys, LoadFile, LoadFileLinkState, LoadMode, LoggedStruct, LoggedStructTest, MDE, MenuOptions, MenuState, NamedCaptureExtractor, NestedLine, NullResult, NullResultTest, Object, OptionValueTest, ParameterExpansion, Regexp, RegexpGsubFormatTest, ResizeTerminalTest, SavedAssetTest, SavedFilesMatcherTest, SelectResponse, SelectedBlockMenuState, ShellSession, ShellSessionTest, ShellType, StreamsOut, String, StringWrapper, SuccessResult, SuccessResultTest, SystemTTSEngine, TTSEngine, TTSEngineFactory, TableExtractor, TestAnimationToTTS, TestBlockCache, TestFindFiles, TestFormatTable, TestFormatTable2, TestHierarchyString, TestMarkdownTableFormatter, TestObjectMethods, TestParameterExpansion, TestShellExpressionEvaluator, TestStringMethods, TestTableExtractor, TestTextAnalyzer, TestWwFunction, TrackedString, TtyMenu, UxActSource, ValueOrException

Constant Summary collapse

DEPTH_ICON =

call depth icons

'›'
LOG_LEVELS =

log levels

i[debug info warn error fatal].freeze
BT_UX_FLD_REQUIRED =
'required'
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}"
OPTIONS =
{
  divider4_collapse: false,
  divider4_collapsible: true,
  heading1_collapse: false,
  heading1_collapsible: false,
  heading2_collapse: true,
  heading2_collapsible: true,
  heading3_collapse: false,
  heading3_collapsible: true
}.freeze
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::UX => {
    :is_allow? => :menu_ux_color_allow,
    :is_echo? => :menu_ux_color_echo,
    :is_edit? => :menu_ux_color_edit,
    :is_exec? => :menu_ux_color_exec,
    :readonly => :menu_ux_color_readonly,
    true => :menu_ux_color
  },
  BlockType::VARS => :menu_vars_color,
  # default for remaining block types
  true => :menu_block_color
}.freeze
COLLAPSIBLE_SYMBOL_COLLAPSED =

‘<+>’ # ‘∆’

'⬢'
COLLAPSIBLE_SYMBOL_EXPANDED =

‘< >’ # ‘…’

'⬡'
COLLAPSIBLE_TOKEN_COLLAPSE =

in regexp (?<collapse>?)

'+'
COLLAPSIBLE_TOKEN_EXPAND =
'-'
COLLAPSIBLE_TYPES =
[BlockType::DIVIDER, BlockType::HEADING].freeze
DEFAULT_NULL_RESULT =

A default instance for cases where no extra details are required.

NullResult.new
SAVED_ASSET_FORMAT =
'%{prefix}%{join}%{time}%{join}%{filename}%{join}' \
'%{mark}%{join}%{blockname}%{join}%{exts}'
ARGV_SEP =
'--'
STATUS_SUCCESS =
0
EXIT_STATUS_REQUIRED_EMPTY =
248
DEFAULT_SUCCESS_RESULT =

Default instance for ease-of-use.

SuccessResult.instance

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

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



519
520
521
522
523
# File 'lib/fcb.rb', line 519

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



73
74
75
76
77
# File 'lib/markdown_exec.rb', line 73

def bpp(*args)
  pp '+ bpp()'
  pp(*args.map.with_index { |line, ind| "  - #{ind}: #{line}" })
  rbi
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.



84
85
86
87
88
# File 'lib/resize_terminal.rb', line 84

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

#dp(str) ⇒ Object



58
59
60
# File 'lib/markdown_exec.rb', line 58

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

#evaluate_shell_expressions(initial_code, expressions, shell: '/bin/bash', initial_code_required: false, occurrence_expressions: nil) ⇒ Object



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
45
46
47
48
49
50
51
52
53
# File 'lib/evaluate_shell_expressions.rb', line 12

def evaluate_shell_expressions(initial_code, expressions, shell: '/bin/bash',
                               initial_code_required: false,
                               occurrence_expressions: nil)
  # !!p initial_code expressions key_format shell
  return if (initial_code_required && (initial_code.nil? || initial_code.empty?)) ||
            expressions.nil? || expressions.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
  wwt :eval, 'script:', script

  # Execute
  stdout_str, _, status = Open3.capture3(shell, '-c', script)

  unless status.success?
    return EvaluateShellExpression::StatusFail
  end

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

  result_hash
rescue StandardError
  ww $@, $!, caller.deref
  ww initial_code, expressions
  raise StandardError, $!
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

#parse_yaml_of_ux_block(data, prompt: nil, validate: nil) ⇒ 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/fcb.rb', line 8

def parse_yaml_of_ux_block(
  data,
  prompt: nil,
  validate: nil
)
  export = data if (export = data['export']).nil?

  # a single variable name is required to display a single value
  menu_format = export['format'] || export['menu_format']
  name = export['name']
  # if name is missing, use the last key in the echo or exec hashes
  if !name&.present?
    name = if export['echo'].is_a? Hash
             export['echo'].keys.last
           elsif export['exec'].is_a? Hash
             export['exec'].keys.last
           end
  end
  raise "Name is missing in UX block: #{data.inspect}" unless name.present? || menu_format.present?

  OpenStruct.new(
    act: export['act'],
    allow: export['allow'] || export['allowed'],
    default: export['default'],
    echo: export['echo'],
    exec: export['exec'],
    force: export['force'],
    init: export['init'],
    menu_format: menu_format,
    name: name,
    prompt: export['prompt'] || prompt,
    readonly: export['readonly'].nil? ? false : export['readonly'],
    required: export['require'] || export['required'] || export[BT_UX_FLD_REQUIRED],
    transform: export['transform'],
    validate: export['validate'] || validate
  )
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



62
63
64
65
# File 'lib/markdown_exec.rb', line 62

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

#rbpObject



67
68
69
70
71
# File 'lib/markdown_exec.rb', line 67

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, debug: $debug) ⇒ 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-08-23 add require_stdout to allow for testing



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
74
75
76
77
78
79
80
# File 'lib/resize_terminal.rb', line 16

def resize_terminal(show_dims: false, show_rectangle: false,
                    require_stdout: true, debug: $debug)
  # 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?
    wwe "Error: No response received from terminal. Response: #{response.inspect}" if debug
    return 1
  end

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

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

  if EnvInterface.get('COLUMNS',
                      transform: lambda(&:to_i)) == calculated_columns &&
     EnvInterface.get('LINES', transform: lambda(&: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
    wwe "Error: Calculated terminal size is invalid. Columns: #{calculated_columns}, Rows: #{calculated_rows}" if debug
    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
  wwe 'Error: Timeout while reading terminal response. Unsupported terminal emulator.' if debug
  1
rescue StandardError => err
  wwe "Error: #{err.message}. Unsupported terminal emulator." if debug
  1
ensure
  EnvInterface.set('COLUMNS', @original_columns)
  EnvInterface.set('LINES', @original_lines)
end

#rpryObject



79
80
81
82
# File 'lib/markdown_exec.rb', line 79

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

#sort_hash_recursively(hash) ⇒ Object



525
526
527
528
529
# File 'lib/fcb.rb', line 525

def sort_hash_recursively(hash)
  hash.transform_values do |v|
    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, **kwargs) ⇒ Object

selectively enabled, for general debugging return the last item in the list



69
70
71
72
73
74
75
76
# File 'lib/ww.rb', line 69

def ww(*objs, **kwargs)
  # assume the final item is the significant one
  # allows prefixing to an existing expression and forwarding the result
  return objs.last unless $debug

  locations = kwargs[:locations] || caller_locations
  ww0(*objs, **kwargs.merge(locations: locations))
end

#ww0(*objs, category: $ww_category, context: nil, full_backtrace: false, level: :debug, locations: caller_locations, log_file: $ww_log_file, output: $ww_output, single_line: false, timestamp: false, location_offset: 0) ⇒ Object

output the formatted data and location

Raises:

  • (ArgumentError)


169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/ww.rb', line 169

def ww0(*objs,
        category: $ww_category,
        context: nil,
        full_backtrace: false,
        level: :debug,
        locations: caller_locations,
        log_file: $ww_log_file,
        output: $ww_output,
        single_line: false,
        timestamp: false,
        location_offset: 0)
  # Format caller information line
  caller_info_line = lambda do |caller_info, ind|
    [
      DEPTH_ICON * (location_offset + locations.count - ind),
      caller_info.path.deref,
      caller_info.lineno,
      caller_info.label
    ].join(' : ')
  end
  # Validate log level
  raise ArgumentError,
        "Invalid log level: #{level}" unless LOG_LEVELS.include?(level)

  # Generate backtrace
  backtrace = if full_backtrace
                locations.map.with_index do |caller_info, ind|
                  caller_info_line.call(caller_info, ind)
                end
              else
                [caller_info_line.call(locations.first, 0)]
              end

  # Add optional timestamp
  time_prefix = timestamp ? "[#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}] " : ''

  # Add log level, category, and context prefix
  level_prefix = "[#{level.to_s.upcase}]"
  category_prefix = category ? "[#{category}] " : ''
  context_prefix = context ? "[#{context}] " : ''

  # Combine all parts into the final message
  header = "#{time_prefix}#{level_prefix} #{category_prefix}#{context_prefix}"
  trace = backtrace + objs
  io = StringIO.new
  formatted_message = if single_line
                        PP.singleline_pp(trace, io)
                        "#{header} #{io.string}"
                      else
                        PP.pp(trace, io)
                        "#{header}\n#{io.string}"
                      end

  # prefix each line in formatted_message
  prefix = (' ' * 8).freeze
  formatted_message = prefix + formatted_message.gsub("\n", "\n#{prefix}")

  # Output to $stderr or specified IO object
  output.puts "\033[38;2;128;191;191m#{formatted_message}\033[0m"
  output.flush

  # Optionally log to a file
  if log_file
    File.open(log_file, 'a') do |file|
      file.puts(formatted_message)
    end
  end

  # Always return the last item in the list, as the label is usually first
  objs.last
end

#wwa(*objs, **kwargs) ⇒ Object

output the object and backtrace for the error abort



80
81
82
83
84
85
86
# File 'lib/ww.rb', line 80

def wwa(*objs, **kwargs)
  ww0(*objs,
      **kwargs.merge(full_backtrace: true,
                     locations: caller_locations))

  exit 1
end

#wwbObject

break into the debugger if enabled



89
90
91
# File 'lib/ww.rb', line 89

def wwb
  binding.irb if $debug
end

#wwe(*objs, **kwargs) ⇒ Object

output the object and backtrace for the error raise the error for the caller to handle

Raises:

  • (StandardError)


95
96
97
98
99
100
101
102
# File 'lib/ww.rb', line 95

def wwe(*objs, **kwargs)
  ww0(*objs,
      **kwargs.merge(full_backtrace: true,
                     locations: caller_locations))

  # raise StandardError, objs.first.fetch(:error) || objs.first
  raise StandardError, objs.first
end

#wwp(*objs, **kwargs) ⇒ Object

selectively enabled, for process tracking output data and the caller’s location



106
107
108
109
110
111
112
113
114
# File 'lib/ww.rb', line 106

def wwp(*objs, **kwargs)
  return objs.last unless $debug

  ww0(*objs,
      **kwargs.merge(
        locations: caller_locations[0..0],
        location_offset: caller_locations.count
      ))
end

#wwr(*objs, **kwargs) ⇒ Object

the return value for a function



117
118
119
120
121
122
123
124
125
126
127
# File 'lib/ww.rb', line 117

def wwr(*objs, **kwargs)
  # assume the final item is the significant one
  # allows prefixing to an existing expression and forwarding the result
  return objs.last unless $debug

  ww0(*objs,
      **kwargs.merge(
        locations: caller_locations[0..0],
        location_offset: caller_locations.count
      ))
end

#wwt(*objs, **kwargs) ⇒ Object

selectively enabled, for tagged data the first item is the tag, the rest is data exclude tags in the list of tags to skip output data and the caller’s location



133
134
135
136
137
138
139
140
141
142
143
# File 'lib/ww.rb', line 133

def wwt(*objs, **kwargs)
  # tags to skip
  return objs.last if !$debug || i[blocks env fcb].include?(objs.first)

  formatted = ['Tagged', objs.first] + objs[1..]
  ww0(*formatted,
      **kwargs.merge(
        locations: caller_locations[0..0],
        location_offset: caller_locations.count
      ))
end

#wwx(expression = nil, **kwargs, &block) ⇒ Object

enhanced expression wrapper with better context usage: wwx { some_expression } or wwx(expression)



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/ww.rb', line 147

def wwx(expression = nil, **kwargs, &block)
  if block_given?
    # Block form: wwx { some_expression }
    result = block.call
    return result unless $debug

    # Capture the source location of the block
    locations = kwargs[:locations] || caller_locations
    ww0(result, **kwargs.merge(locations: locations, context: 'block'))
  elsif expression
    # Direct form: wwx(some_expression)
    return expression unless $debug

    locations = kwargs[:locations] || caller_locations
    ww0(expression, **kwargs.merge(locations: locations, context: 'direct'))
  else
    # No arguments - just return nil
    nil
  end
end