markdown_exec 2.2.0 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +16 -4
- data/CHANGELOG.md +28 -0
- data/Gemfile.lock +1 -1
- data/Rakefile +32 -8
- data/bats/bats.bats +33 -0
- data/bats/block-types.bats +56 -0
- data/bats/cli.bats +74 -0
- data/bats/fail.bats +11 -0
- data/bats/history.bats +34 -0
- data/bats/markup.bats +66 -0
- data/bats/mde.bats +29 -0
- data/bats/options.bats +92 -0
- data/bats/test_helper.bash +152 -0
- data/bin/tab_completion.sh +44 -20
- data/docs/dev/block-type-opts.md +10 -0
- data/docs/dev/block-type-port.md +24 -0
- data/docs/dev/block-type-vars.md +7 -0
- data/docs/dev/pass-through-arguments.md +8 -0
- data/docs/dev/specs-import.md +9 -0
- data/docs/dev/specs.md +83 -0
- data/docs/dev/text-decoration.md +7 -0
- data/examples/bash-blocks.md +4 -4
- data/examples/block-names.md +40 -5
- data/examples/import0.md +23 -0
- data/examples/import1.md +13 -0
- data/examples/link-blocks-vars.md +3 -3
- data/examples/opts-blocks-require.md +6 -6
- data/examples/table-markup.md +31 -0
- data/examples/text-markup.md +58 -0
- data/examples/vars-blocks.md +2 -2
- data/examples/wrap.md +87 -9
- data/lib/ansi_formatter.rb +12 -6
- data/lib/ansi_string.rb +153 -0
- data/lib/argument_processor.rb +160 -0
- data/lib/cached_nested_file_reader.rb +4 -2
- data/lib/ce_get_cost_and_usage.rb +4 -3
- data/lib/cli.rb +1 -1
- data/lib/colorize.rb +41 -0
- data/lib/constants.rb +17 -0
- data/lib/directory_searcher.rb +4 -2
- data/lib/doh.rb +190 -0
- data/lib/env.rb +1 -1
- data/lib/exceptions.rb +9 -6
- data/lib/fcb.rb +0 -199
- data/lib/filter.rb +18 -5
- data/lib/find_files.rb +8 -3
- data/lib/format_table.rb +406 -0
- data/lib/hash_delegator.rb +939 -611
- data/lib/hierarchy_string.rb +221 -0
- data/lib/input_sequencer.rb +19 -11
- data/lib/instance_method_wrapper.rb +2 -1
- data/lib/layered_hash.rb +143 -0
- data/lib/link_history.rb +22 -8
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +420 -165
- data/lib/mdoc.rb +38 -38
- data/lib/menu.src.yml +832 -680
- data/lib/menu.yml +814 -689
- data/lib/namer.rb +6 -12
- data/lib/object_present.rb +1 -1
- data/lib/option_value.rb +7 -3
- data/lib/poly.rb +33 -14
- data/lib/resize_terminal.rb +60 -52
- data/lib/saved_assets.rb +45 -34
- data/lib/saved_files_matcher.rb +6 -3
- data/lib/streams_out.rb +7 -1
- data/lib/table_extractor.rb +166 -0
- data/lib/tap.rb +5 -6
- data/lib/text_analyzer.rb +236 -0
- metadata +28 -3
- data/lib/std_out_err_logger.rb +0 -119
@@ -0,0 +1,31 @@
|
|
1
|
+
# Demonstrate Tables
|
2
|
+
|
3
|
+
Table flush at left.
|
4
|
+
Centered columns.
|
5
|
+
| Common Name| Species| Genus| Family| Year Discovered
|
6
|
+
|:-:|:-:|:-:|:-:|:-:
|
7
|
+
| Tapanuli Orangutan| Pongo tapanuliensis| Pongo| Hominidae| 2017
|
8
|
+
| Psychedelic Frogfish| Histiophryne psychedelica| Histiophryne| Antennariidae| 2009
|
9
|
+
| Ruby Seadragon| Phyllopteryx dewysea| Phyllopteryx| Syngnathidae| 2015
|
10
|
+
|
11
|
+
Table indented with two spaces.
|
12
|
+
Left-justified columns.
|
13
|
+
| Common Name| Species| Genus| Family| Year Discovered
|
14
|
+
|:-|:-|:-|:-|:-
|
15
|
+
| Illacme tobini (Millipede)| Illacme tobini| Illacme| Siphonorhinidae| 2016
|
16
|
+
| Cappuccino Snake| Hydrodynastes bicinctus| Hydrodynastes| Colubridae| 2021
|
17
|
+
| Homo luzonensis| Homo luzonensis| Homo| Hominidae| 2019
|
18
|
+
|
19
|
+
Table indented with one tab.
|
20
|
+
Right-justified columns.
|
21
|
+
| Common Name| Species| Genus| Family| Year Discovered
|
22
|
+
|-:|-:|-:|-:|-:
|
23
|
+
| Spiny Dandelion| Taraxacum japonicum| Taraxacum| Asteraceae| 2022
|
24
|
+
| Mythical Monkey| Cercopithecus lomamiensis| Cercopithecus| Cercopithecidae| 2012
|
25
|
+
| Yeti Crab| Kiwa hirsuta| Kiwa| Kiwaidae| 2005
|
26
|
+
|
27
|
+
## Non-table text is not modified
|
28
|
+
1. |
|
29
|
+
2. ||
|
30
|
+
|
|
31
|
+
||
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Demonstrate Decoration of Text
|
2
|
+
|
3
|
+
## LINE_DECOR_MAIN
|
4
|
+
These are the Main decorations predefined:
|
5
|
+
|
6
|
+
- **_Bold-Underline_**
|
7
|
+
- **Bold**
|
8
|
+
- **~Bold-Italic~**
|
9
|
+
- __Underline__
|
10
|
+
- _~Underline-Italic~_
|
11
|
+
- `Italic`
|
12
|
+
- ~~Strikethrough~~
|
13
|
+
|
14
|
+
## LINE_DECOR_PRE
|
15
|
+
These decorations are performed before Main, allowing for overrides without redefining Main.
|
16
|
+
|
17
|
+
### Override Bold Underline
|
18
|
+
This **_text_** is bold and underlined by default.
|
19
|
+
::: Select below to trigger. If it changes to yellow, the option was processed.
|
20
|
+
```opts :[bold-underline]
|
21
|
+
line_decor_pre:
|
22
|
+
- :color_method: :yellow
|
23
|
+
:pattern: '\*\*_([^_]{0,64})_\*\*'
|
24
|
+
```
|
25
|
+
This **_text_** is yellow when the rule takes precedence over the Main decorations.
|
26
|
+
|
27
|
+
## LINE_DECOR_POST
|
28
|
+
These decorations are performed after the Main decorations.
|
29
|
+
This !!text!! is not decorated by default.
|
30
|
+
::: Select below to trigger. If it changes to green, the option was processed.
|
31
|
+
```opts :[green]
|
32
|
+
line_decor_post:
|
33
|
+
- :color_method: :green
|
34
|
+
:pattern: '!!([^!]{0,64})!!'
|
35
|
+
```
|
36
|
+
|
37
|
+
## MDE Non-standard Markdown Configuration
|
38
|
+
- `_text_`: A single underscore delimeter is a standard way of underlining text. Because this occurs in code often, this decoration is not a default for MDE.
|
39
|
+
|
40
|
+
## Precedence
|
41
|
+
The order decorations are processed affects results.
|
42
|
+
A Bold delimiter `__` has to be processed before the Underline delimeter `_`. If reversed, bold `text` appears as underlined `_text_`.
|
43
|
+
|
44
|
+
# Related MDE Options
|
45
|
+
line_decor_main | Line-oriented text decoration (Main)
|
46
|
+
line_decor_post | Line-oriented text decoration (Post)
|
47
|
+
line_decor_pre | Line-oriented text decoration (Pre)
|
48
|
+
menu_note_match | Pattern for notes in block selection menu
|
49
|
+
|
50
|
+
```opts :(document_options)
|
51
|
+
line_decor_post:
|
52
|
+
- :color_method: blue
|
53
|
+
:pattern: '!([^!]{0,64})!blue!'
|
54
|
+
- :color_method: green
|
55
|
+
:pattern: '!([^!]{0,64})!green!'
|
56
|
+
- :color_method: red
|
57
|
+
:pattern: '!([^!]{0,64})!red!'
|
58
|
+
```
|
data/examples/vars-blocks.md
CHANGED
@@ -9,7 +9,7 @@ The hidden block "(defaults)" sets the environment variable VAULT to "default" i
|
|
9
9
|
: ${VAULT:=default}
|
10
10
|
```
|
11
11
|
|
12
|
-
:::
|
12
|
+
::: Select below to trigger. If it prints "VAULT: default", the shell block was processed.
|
13
13
|
The named block prints the environment variable VAULT. It requires hidden block "(defaults)" before printing.
|
14
14
|
```bash :show_vars +(defaults)
|
15
15
|
source bin/colorize_env_vars.sh
|
@@ -24,7 +24,7 @@ When clicked, it adds the variable to the inherited code. It does not output.
|
|
24
24
|
|
25
25
|
# DOES NOT WORK 2024-07-20
|
26
26
|
## This does not evaluate the shell block.
|
27
|
-
:::
|
27
|
+
::: Select below to trigger. If it prints "VAULT: 22", the shell block was processed.
|
28
28
|
The block sets the environment variable VAULT to "22". It requires block "show_vars". Notice block "show_vars" is called after the variable is set.
|
29
29
|
```vars :[set_with_show] +show_vars
|
30
30
|
VAULT: 22
|
data/examples/wrap.md
CHANGED
@@ -1,17 +1,27 @@
|
|
1
1
|
# Demo block wrapping
|
2
2
|
|
3
|
-
|
3
|
+
## Wrapped block
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
This block is wrapped by the `{outer*}` pair of blocks.
|
6
|
+
Expect output: "outer-before", "single body", and "outer-after".
|
7
|
+
::: Select below to test a block wrapped by a named pair of blocks.
|
8
|
+
```bash :[single] +{outer}
|
9
|
+
echo single body
|
10
|
+
```
|
8
11
|
|
9
|
-
|
10
|
-
::: and nested inside, the `{inner*}` pair of blocks.
|
12
|
+
## Nested wraps
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
This block is wrapped first by the `{outer*}` pair of blocks and then the `{inner*}` pair of blocks.
|
15
|
+
Expect output: "outer-before", "inner-before", "nested body", "inner-after", and "outer-after".
|
16
|
+
Blocks for the left-most included wrapper are first and last.
|
17
|
+
::: Select below to test a block wrapped by nested named pair of blocks.
|
18
|
+
```bash :[nested] +{outer} +{inner}
|
19
|
+
echo nested body
|
20
|
+
```
|
21
|
+
Expect output: "inner-before", "outer-before", "nested body", "outer-after", and "inner-after".
|
22
|
+
```bash :[inverted-nesting] +{inner} +{outer}
|
23
|
+
echo inverted nesting
|
24
|
+
```
|
15
25
|
|
16
26
|
::: This pair of hidden blocks are the `{inner*}` set.
|
17
27
|
```bash :{inner}
|
@@ -31,3 +41,71 @@ echo outer-before
|
|
31
41
|
```bash :{outer-after}
|
32
42
|
echo outer-after
|
33
43
|
```
|
44
|
+
|
45
|
+
## Requiring additional Bash blocks
|
46
|
+
|
47
|
+
```bash :(inc1)
|
48
|
+
echo included1
|
49
|
+
```
|
50
|
+
```bash :inc2
|
51
|
+
echo included2
|
52
|
+
```
|
53
|
+
Main block without a name.
|
54
|
+
Inc2 + Outer
|
55
|
+
```bash +{outer} +inc2
|
56
|
+
echo expecting11
|
57
|
+
```
|
58
|
+
Inc1 + Outer
|
59
|
+
```bash +{outer} +(inc1)
|
60
|
+
echo expecting12
|
61
|
+
```
|
62
|
+
Main block with a name.
|
63
|
+
Inc2 + Outer
|
64
|
+
```bash :ex21 +{outer} +inc2
|
65
|
+
echo expecting21
|
66
|
+
```
|
67
|
+
Inc1 + Outer
|
68
|
+
```bash :ex22 +{outer} +(inc1)
|
69
|
+
echo expecting22
|
70
|
+
```
|
71
|
+
|
72
|
+
## Requiring additional Bash blocks from wrapper block
|
73
|
+
|
74
|
+
::: Does not work
|
75
|
+
|
76
|
+
Inc1 + wrap-with-req
|
77
|
+
```bash :ex31 +{wrap-with-req}
|
78
|
+
echo expecting31
|
79
|
+
```
|
80
|
+
|
81
|
+
```bash :{wrap-with-req} +(inc1)
|
82
|
+
echo wrap-with-req-before
|
83
|
+
```
|
84
|
+
|
85
|
+
```bash :{wrap-with-req-after}
|
86
|
+
echo wrap-with-req-after
|
87
|
+
```
|
88
|
+
|
89
|
+
::: Debug Inherited Code
|
90
|
+
```opts
|
91
|
+
dump_blocks_in_file: true
|
92
|
+
dump_dependencies: true
|
93
|
+
dump_inherited_block_names: true
|
94
|
+
dump_inherited_dependencies: true
|
95
|
+
dump_menu_blocks: true
|
96
|
+
```
|
97
|
+
|
98
|
+
```opts :add_shell_code_labels
|
99
|
+
shell_code_label_format_above: "# -^-"
|
100
|
+
shell_code_label_format_below: "# -v- +%{block_name} -o- %{document_filename} -o- %{time_now_date} -v-"
|
101
|
+
```
|
102
|
+
|
103
|
+
```opts :(document_options)
|
104
|
+
execute_in_own_window: false
|
105
|
+
output_execution_report: false
|
106
|
+
output_execution_summary: false
|
107
|
+
pause_after_script_execution: true
|
108
|
+
line_decor_pre:
|
109
|
+
- :color_method: :underline_italic
|
110
|
+
:pattern: '"([^"]{0,64})"'
|
111
|
+
```
|
data/lib/ansi_formatter.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
|
4
4
|
# encoding=utf-8
|
5
5
|
|
6
|
+
require_relative 'ansi_string'
|
7
|
+
|
6
8
|
class AnsiFormatter
|
7
9
|
def initialize(options = {})
|
8
10
|
@options = options
|
@@ -18,10 +20,10 @@ class AnsiFormatter
|
|
18
20
|
line_postfix: '',
|
19
21
|
detail_sep: ''
|
20
22
|
)
|
21
|
-
|
23
|
+
data&.map do |item|
|
22
24
|
scan_and_process_multiple_substrings(item, highlight, plain_color_sym,
|
23
25
|
highlight_color_sym).join
|
24
|
-
end || []
|
26
|
+
end || []
|
25
27
|
end
|
26
28
|
|
27
29
|
# Function to scan a string and process its segments based on multiple substrings
|
@@ -30,8 +32,10 @@ class AnsiFormatter
|
|
30
32
|
# @param plain_sym [Symbol] The symbol for non-matching segments.
|
31
33
|
# @param color_sym [Symbol] The symbol for matching segments.
|
32
34
|
# @return [Array<String>] The processed segments.
|
33
|
-
def scan_and_process_multiple_substrings(str, substrings, plain_sym,
|
34
|
-
|
35
|
+
def scan_and_process_multiple_substrings(str, substrings, plain_sym,
|
36
|
+
color_sym)
|
37
|
+
return string_send_color(str,
|
38
|
+
plain_sym) if substrings.empty? || substrings.any?(&:empty?)
|
35
39
|
|
36
40
|
substring_patterns = substrings.map do |value|
|
37
41
|
[value, Regexp.new(value, Regexp::IGNORECASE)]
|
@@ -41,7 +45,9 @@ class AnsiFormatter
|
|
41
45
|
remaining_str = str.dup
|
42
46
|
|
43
47
|
while remaining_str.length.positive?
|
44
|
-
match_indices = substring_patterns.map
|
48
|
+
match_indices = substring_patterns.map do |_, pattern|
|
49
|
+
remaining_str.index(pattern)
|
50
|
+
end.compact
|
45
51
|
earliest_match = match_indices.min
|
46
52
|
|
47
53
|
if earliest_match
|
@@ -112,6 +118,6 @@ class AnsiFormatter
|
|
112
118
|
# @return [String] The string with the applied color method.
|
113
119
|
def string_send_color(string, color_sym, default: 'plain')
|
114
120
|
color_method = @options.fetch(color_sym, default).to_sym
|
115
|
-
string.to_s.send(color_method)
|
121
|
+
AnsiString.new(string.to_s).send(color_method)
|
116
122
|
end
|
117
123
|
end
|
data/lib/ansi_string.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# encoding=utf-8
|
4
|
+
|
5
|
+
# Extends Ruby's native String class to include ANSI coloring functionality.
|
6
|
+
# Adds methods to apply RGB colors, named colors, and other formatting to strings.
|
7
|
+
class AnsiString < String
|
8
|
+
# Handles dynamic method calls to create RGB colors.
|
9
|
+
#
|
10
|
+
# @param method_name [Symbol] The name of the method being called.
|
11
|
+
# @param args [Array] The arguments passed to the method.
|
12
|
+
# @param block [Proc] An optional block.
|
13
|
+
# @return [AnsiString] The formatted string.
|
14
|
+
def method_missing(method_name, *args, &block)
|
15
|
+
if dynamic_color_method?(method_name)
|
16
|
+
case method_name.to_s
|
17
|
+
when /^fg_bg_rgb_/
|
18
|
+
bytes = $'.split('_')
|
19
|
+
fg_bg_rgb_color(bytes[0..2].join(';'), bytes[3..5].join(';'))
|
20
|
+
when /^fg_bg_rgbh_/
|
21
|
+
hex_to_fg_bg_rgb($')
|
22
|
+
when /^fg_rgb_/
|
23
|
+
fg_rgb_color($'.gsub('_', ';'))
|
24
|
+
when /^fg_rgbh_/
|
25
|
+
hex_to_rgb($')
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Checks if the AnsiString instance responds to a particular method.
|
35
|
+
#
|
36
|
+
# @param method_name [Symbol] The name of the method being checked.
|
37
|
+
# @param include_private [Boolean] Whether to include private methods in the check.
|
38
|
+
# @return [Boolean] True if the method is supported, otherwise false.
|
39
|
+
def respond_to_missing?(method_name, include_private = false)
|
40
|
+
dynamic_color_method?(method_name) || super
|
41
|
+
end
|
42
|
+
|
43
|
+
# Generates an ANSI control sequence for the string.
|
44
|
+
#
|
45
|
+
# @return [AnsiString] The string wrapped in an ANSI control sequence.
|
46
|
+
def ansi_control_sequence
|
47
|
+
self.class.new("\033[#{self}\033[0m")
|
48
|
+
end
|
49
|
+
|
50
|
+
# Applies a 24-bit RGB foreground and background color to the string.
|
51
|
+
#
|
52
|
+
# @param fg_rgb [String] The RGB foreground color, expressed as a string like "1;2;3".
|
53
|
+
# @param bg_rgb [String] The RGB background color, expressed as a string like "4;5;6".
|
54
|
+
# @return [AnsiString] The string with the applied RGB foreground and background colors.
|
55
|
+
def fg_bg_rgb_color(fg_rgb, bg_rgb)
|
56
|
+
self.class.new("38;2;#{fg_rgb}m\033[48;2;#{bg_rgb}m#{self}").ansi_control_sequence
|
57
|
+
end
|
58
|
+
|
59
|
+
# Applies a 24-bit RGB foreground color to the string.
|
60
|
+
#
|
61
|
+
# @param rgb [String] The RGB color, expressed as a string like "1;2;3".
|
62
|
+
# @return [AnsiString] The string with the applied RGB foreground color.
|
63
|
+
def fg_rgb_color(rgb)
|
64
|
+
self.class.new("38;2;#{rgb}m#{self}").ansi_control_sequence
|
65
|
+
end
|
66
|
+
|
67
|
+
# Converts hex color codes to RGB and applies them to the string.
|
68
|
+
#
|
69
|
+
# @param hex_str [String] The RGB color, expressed as a hex string like "FF00FF".
|
70
|
+
# @return [AnsiString] The string with the applied RGB foreground color.
|
71
|
+
def hex_to_fg_bg_rgb(hex_str)
|
72
|
+
values = hex_str.split('_').map { |hex| hex.to_i(16).to_s }
|
73
|
+
fg_bg_rgb_color(
|
74
|
+
values[0..2].join(';'),
|
75
|
+
values[3..5].join(';')
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Converts hex color codes to RGB and applies them to the string.
|
80
|
+
#
|
81
|
+
# @param hex_str [String] The RGB color, expressed as a hex string like "FF00FF".
|
82
|
+
# @return [AnsiString] The string with the applied RGB foreground color.
|
83
|
+
def hex_to_rgb(hex_str)
|
84
|
+
self.class.new(
|
85
|
+
fg_rgb_color(
|
86
|
+
hex_str.split('_').map { |hex| hex.to_i(16).to_s }.join(';')
|
87
|
+
)
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Provides a plain, unmodified version of the string.
|
92
|
+
#
|
93
|
+
# @return [AnsiString] The original string.
|
94
|
+
def plain
|
95
|
+
self.class.new(self)
|
96
|
+
end
|
97
|
+
|
98
|
+
# A collection of methods for applying named colors.
|
99
|
+
#
|
100
|
+
# For example, #black applies a black foreground color to the string.
|
101
|
+
# These are provided for convenience and easy readability.
|
102
|
+
def black; self.class.new("30m#{self}").ansi_control_sequence; end
|
103
|
+
def bred; self.class.new("1;31m#{self}").ansi_control_sequence; end
|
104
|
+
def bgreen; self.class.new("1;32m#{self}").ansi_control_sequence; end
|
105
|
+
def byellow; self.class.new("1;33m#{self}").ansi_control_sequence; end
|
106
|
+
def magenta; self.class.new("35m#{self}").ansi_control_sequence; end
|
107
|
+
def cyan; self.class.new("36m#{self}").ansi_control_sequence; end
|
108
|
+
def white; self.class.new("37m#{self}").ansi_control_sequence; end
|
109
|
+
def bwhite; self.class.new("1;37m#{self}").ansi_control_sequence; end
|
110
|
+
|
111
|
+
# More named colors using RGB hex values
|
112
|
+
def blue; fg_rgbh_00_00_FF; end
|
113
|
+
def green; fg_rgbh_00_FF_00; end
|
114
|
+
def indigo; fg_rgbh_4B_00_82; end
|
115
|
+
def orange; fg_rgbh_FF_7F_00; end
|
116
|
+
def red; fg_rgbh_FF_00_00; end
|
117
|
+
def violet; fg_rgbh_94_00_D3; end
|
118
|
+
def yellow; fg_rgbh_FF_FF_00; end
|
119
|
+
|
120
|
+
# Graphics modes
|
121
|
+
def bold; self.class.new("\033[1m#{self}\033[22m"); end
|
122
|
+
|
123
|
+
def bold_italic;
|
124
|
+
self.class.new("\033[1m\033[3m#{self}\033[22m\033[23m");
|
125
|
+
end
|
126
|
+
|
127
|
+
def bold_underline;
|
128
|
+
self.class.new("\033[1m\033[4m#{self}\033[22m\033[24m");
|
129
|
+
end
|
130
|
+
|
131
|
+
def dim; self.class.new("\033[2m#{self}\033[22m"); end
|
132
|
+
def italic; self.class.new("\033[3m#{self}\033[23m"); end
|
133
|
+
def underline; self.class.new("\033[4m#{self}\033[24m"); end
|
134
|
+
|
135
|
+
def underline_italic;
|
136
|
+
self.class.new("\033[4m\033[3m#{self}\033[23m\033[24m");
|
137
|
+
end
|
138
|
+
|
139
|
+
def blinking; self.class.new("\033[5m#{self}\033[25m"); end
|
140
|
+
def inverse; self.class.new("\033[7m#{self}\033[27m"); end
|
141
|
+
def hidden; self.class.new("\033[8m#{self}\033[28m"); end
|
142
|
+
def strikethrough; self.class.new("\033[9m#{self}\033[29m"); end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
# Checks if the method name matches any of the dynamic color methods.
|
147
|
+
#
|
148
|
+
# @param method_name [Symbol] The name of the method being checked.
|
149
|
+
# @return [Boolean] True if the method name matches a dynamic color method.
|
150
|
+
def dynamic_color_method?(method_name)
|
151
|
+
method_name.to_s =~ /^(fg_bg_rgb_|fg_bg_rgbh_|fg_rgb_|fg_rgbh_)/
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# encoding=utf-8
|
4
|
+
require_relative 'constants'
|
5
|
+
require_relative 'object_present'
|
6
|
+
|
7
|
+
def process_arguments(arguments, loose_args, options_parsed)
|
8
|
+
# &bt arguments, loose_args, options_parsed
|
9
|
+
# loose_args will be empty first command contains pass-through arguments
|
10
|
+
while loose_args.any?
|
11
|
+
if arguments.first == loose_args.first
|
12
|
+
yield ArgPro::ArgIsPosition, arguments.shift
|
13
|
+
|
14
|
+
loose_args.shift
|
15
|
+
next
|
16
|
+
end
|
17
|
+
|
18
|
+
yield ArgPro::ArgIsOption, options_parsed.first
|
19
|
+
|
20
|
+
arguments.shift(options_parsed.first[:procname].present? ? 2 : 1)
|
21
|
+
options_parsed.shift
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def process_commands(options_parsed:, arguments:, enable_search:,
|
26
|
+
named_procs:, rest:)
|
27
|
+
# &bt arguments,options_parsed
|
28
|
+
command_processed = false
|
29
|
+
block_executed = false
|
30
|
+
requested_menu = false
|
31
|
+
position = 0
|
32
|
+
|
33
|
+
process_arguments(arguments.dup, rest.dup,
|
34
|
+
options_parsed.dup) do |type, item|
|
35
|
+
# &bt type,item
|
36
|
+
case type
|
37
|
+
when ArgPro::ArgIsOption
|
38
|
+
if named_procs.include?(item[:name])
|
39
|
+
command_processed = true
|
40
|
+
yield ArgPro::CallProcess, item[:name]
|
41
|
+
else
|
42
|
+
converted = if item[:proccode]
|
43
|
+
yield ArgPro::ConvertValue, [item[:proccode],
|
44
|
+
item[:value]]
|
45
|
+
else
|
46
|
+
item[:value]
|
47
|
+
end
|
48
|
+
if item[:name]
|
49
|
+
yield ArgPro::ActSetOption, [item[:name], converted]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
when ArgPro::ArgIsPosition
|
53
|
+
case position
|
54
|
+
when 0
|
55
|
+
# position 0: file, folder, or search term (optional)
|
56
|
+
if Dir.exist?(item)
|
57
|
+
yield ArgPro::ActSetPath, item
|
58
|
+
elsif File.exist?(item)
|
59
|
+
yield ArgPro::ActSetFileName, item
|
60
|
+
elsif enable_search
|
61
|
+
yield ArgPro::ActFind, item
|
62
|
+
else
|
63
|
+
yield ArgPro::ActFileIsMissing, item
|
64
|
+
end
|
65
|
+
else
|
66
|
+
# position 1: block (optional)
|
67
|
+
if item == '.'
|
68
|
+
requested_menu = true
|
69
|
+
else
|
70
|
+
block_executed = true
|
71
|
+
yield ArgPro::ActSetBlockName, item
|
72
|
+
end
|
73
|
+
end
|
74
|
+
position += 1
|
75
|
+
rest.shift
|
76
|
+
else
|
77
|
+
raise
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
return if $PROGRAM_NAME != __FILE__
|
83
|
+
|
84
|
+
require 'minitest/autorun'
|
85
|
+
|
86
|
+
class ArgumentProcessorTest < Minitest::Test
|
87
|
+
def setup
|
88
|
+
@arguments = ['fixtures/sample1.md', 'block', '--option', 'value',
|
89
|
+
'ignored']
|
90
|
+
@rest = ['fixtures/sample1.md', 'block', 'ignored']
|
91
|
+
@options_parsed = [{ name: '--option', procname: 'VAL', value: 'value' }]
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_process_arguments_with_position
|
95
|
+
result = []
|
96
|
+
process_arguments(@arguments,
|
97
|
+
@rest, @options_parsed) do |type, item|
|
98
|
+
result << [type, item]
|
99
|
+
end
|
100
|
+
|
101
|
+
expected = [
|
102
|
+
[ArgPro::ArgIsPosition, 'fixtures/sample1.md'],
|
103
|
+
[ArgPro::ArgIsPosition, 'block'],
|
104
|
+
[ArgPro::ArgIsOption, { name: '--option', procname: 'VAL', value: 'value' }],
|
105
|
+
[ArgPro::ArgIsPosition, 'ignored']
|
106
|
+
]
|
107
|
+
assert_equal expected, result
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class CommandProcessorTest < Minitest::Test
|
112
|
+
def setup
|
113
|
+
@exisiting_file_name = Dir.glob('fixtures/*').first
|
114
|
+
@missing_file_name = 'missing-file.tmp'
|
115
|
+
@arguments = [@exisiting_file_name, 'process', '--option', 'value',
|
116
|
+
'ignored']
|
117
|
+
@named_procs = []
|
118
|
+
@options_parsed = [{ name: '--option', procname: 'VAL', value: 'value' }]
|
119
|
+
@rest = [@exisiting_file_name, 'process', 'ignored']
|
120
|
+
@enable_search = true
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_process_commands_with_valid_file
|
124
|
+
result = []
|
125
|
+
process_commands(
|
126
|
+
arguments: @arguments, named_procs: @named_procs,
|
127
|
+
options_parsed: @options_parsed,
|
128
|
+
rest: @rest, enable_search: @enable_search
|
129
|
+
) do |type, item|
|
130
|
+
result << [type, item]
|
131
|
+
end
|
132
|
+
|
133
|
+
expected = [
|
134
|
+
[ArgPro::ActSetFileName, @exisiting_file_name],
|
135
|
+
[ArgPro::ActSetBlockName, 'process'],
|
136
|
+
[ArgPro::ActSetOption, ['--option', 'value']],
|
137
|
+
[ArgPro::ActSetBlockName, 'ignored']
|
138
|
+
]
|
139
|
+
|
140
|
+
assert_equal expected, result
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_process_commands_with_search
|
144
|
+
result = []
|
145
|
+
process_commands(arguments: [@missing_file_name, 'process'],
|
146
|
+
named_procs: @named_procs,
|
147
|
+
options_parsed: [],
|
148
|
+
rest: [@missing_file_name, 'process'],
|
149
|
+
enable_search: @enable_search) do |type, item|
|
150
|
+
result << [type, item]
|
151
|
+
end
|
152
|
+
|
153
|
+
expected = [
|
154
|
+
[ArgPro::ActFind, @missing_file_name],
|
155
|
+
[ArgPro::ActSetBlockName, 'process']
|
156
|
+
]
|
157
|
+
|
158
|
+
assert_equal expected, result
|
159
|
+
end
|
160
|
+
end
|
@@ -53,7 +53,8 @@ class CachedNestedFileReader
|
|
53
53
|
included_file_path = if name_strip =~ %r{^/}
|
54
54
|
name_strip
|
55
55
|
elsif import_paths
|
56
|
-
find_files(name_strip,
|
56
|
+
find_files(name_strip,
|
57
|
+
import_paths + [directory_path])&.first
|
57
58
|
else
|
58
59
|
File.join(directory_path, name_strip)
|
59
60
|
end
|
@@ -72,7 +73,8 @@ class CachedNestedFileReader
|
|
72
73
|
@file_cache[filename] = processed_lines
|
73
74
|
rescue Errno::ENOENT
|
74
75
|
# Exceptions.error_handler('readlines', { abort: true })
|
75
|
-
warn_format('readlines', "No such file -- #{filename} @@ #{context}",
|
76
|
+
warn_format('readlines', "No such file -- #{filename} @@ #{context}",
|
77
|
+
{ abort: true })
|
76
78
|
end
|
77
79
|
end
|
78
80
|
|
@@ -8,15 +8,16 @@ text = system(cmd)
|
|
8
8
|
data = YAML.load(text)
|
9
9
|
|
10
10
|
# Extracting the relevant information
|
11
|
-
services = data[
|
12
|
-
service_name = group[
|
11
|
+
services = data['ResultsByTime'][0]['Groups'].map do |group|
|
12
|
+
service_name = group['Keys'][0]
|
13
13
|
unblended_cost = "#{group['Metrics']['UnblendedCost']['Amount']} #{group['Metrics']['UnblendedCost']['Unit']}"
|
14
14
|
usage_quantity = "#{group['Metrics']['UsageQuantity']['Amount']} #{group['Metrics']['UsageQuantity']['Unit']}"
|
15
15
|
[service_name, unblended_cost, usage_quantity]
|
16
16
|
end
|
17
17
|
|
18
18
|
# Create a table
|
19
|
-
table = Terminal::Table.new :
|
19
|
+
table = Terminal::Table.new headings: ['Service', 'Unblended Cost', 'Usage Quantity'],
|
20
|
+
rows: services
|
20
21
|
|
21
22
|
# Output the table
|
22
23
|
puts table
|