markdown_exec 2.8.5 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -1
- data/Gemfile.lock +1 -1
- data/Rakefile +0 -33
- data/bats/bats.bats +2 -0
- data/bats/block-type-link.bats +1 -1
- data/bats/block-type-ux-allowed.bats +2 -2
- data/bats/block-type-ux-invalid.bats +1 -1
- data/bats/{block-type-ux-preconditions.bats → block-type-ux-required-variables.bats} +1 -1
- data/bats/block-type-ux-row-format.bats +1 -1
- data/bats/block-type-ux-sources.bats +36 -0
- data/bats/border.bats +1 -1
- data/bats/cli.bats +2 -2
- data/bats/command-substitution-options.bats +14 -0
- data/bats/command-substitution.bats +1 -1
- data/bats/fail.bats +5 -2
- data/bats/indented-block-type-vars.bats +1 -1
- data/bats/markup.bats +1 -1
- data/bats/option-expansion.bats +8 -0
- data/bats/table-column-truncate.bats +1 -1
- data/bats/test_helper.bash +50 -5
- data/docs/dev/bats-document-configuration.md +1 -1
- data/docs/dev/block-type-ux-allowed.md +5 -7
- data/docs/dev/block-type-ux-auto.md +8 -5
- data/docs/dev/block-type-ux-chained.md +4 -2
- data/docs/dev/block-type-ux-echo-hash.md +6 -7
- data/docs/dev/block-type-ux-echo.md +2 -2
- data/docs/dev/block-type-ux-exec.md +3 -5
- data/docs/dev/block-type-ux-hidden.md +3 -0
- data/docs/dev/{block-type-ux-preconditions.md → block-type-ux-required-variables.md} +1 -2
- data/docs/dev/block-type-ux-row-format.md +3 -4
- data/docs/dev/block-type-ux-sources.md +57 -0
- data/docs/dev/block-type-ux-transform.md +0 -4
- data/docs/dev/command-substitution-options.md +61 -0
- data/docs/dev/indented-block-type-vars.md +1 -0
- data/docs/dev/menu-pagination-indent.md +123 -0
- data/docs/dev/menu-pagination.md +111 -0
- data/docs/dev/option-expansion.md +10 -0
- data/lib/ansi_formatter.rb +2 -0
- data/lib/block_cache.rb +197 -0
- data/lib/command_result.rb +57 -0
- data/lib/constants.rb +18 -0
- data/lib/error_reporting.rb +38 -0
- data/lib/evaluate_shell_expressions.rb +43 -18
- data/lib/fcb.rb +98 -7
- data/lib/hash_delegator.rb +526 -322
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +136 -45
- data/lib/mdoc.rb +59 -10
- data/lib/menu.src.yml +23 -11
- data/lib/menu.yml +22 -12
- data/lib/value_or_exception.rb +76 -0
- metadata +16 -4
@@ -4,17 +4,20 @@ ENTITY='Pongo tapanuliensis,Pongo'
|
|
4
4
|
```
|
5
5
|
```ux :[SPECIES] +(shell) +(GENUS)
|
6
6
|
echo: "${ENTITY%%,*}"
|
7
|
+
init: false
|
7
8
|
name: SPECIES
|
8
9
|
```
|
9
10
|
/ required ux block requires another
|
10
11
|
```ux :(GENUS) +[NAME]
|
11
12
|
echo: "${ENTITY##*,}"
|
13
|
+
init: false
|
12
14
|
name: GENUS
|
13
15
|
readonly: true
|
14
16
|
```
|
15
17
|
/ executed in context of prior ux blocks, uses their values
|
16
18
|
```ux :[NAME]
|
17
19
|
echo: "$SPECIES - $GENUS"
|
20
|
+
init: false
|
18
21
|
name: NAME
|
19
22
|
readonly: true
|
20
23
|
```
|
@@ -1,9 +1,8 @@
|
|
1
1
|
/ an automatic UX block that has a precondition that must be met before it is executed
|
2
2
|
```ux :[document_ux_SPECIES]
|
3
|
-
default: :exec
|
4
3
|
exec: printf "$MISSING_VARIABLE"
|
5
4
|
name: SPECIES
|
6
|
-
|
5
|
+
required:
|
7
6
|
- MISSING_VARIABLE
|
8
7
|
```
|
9
8
|
@import bats-document-configuration.md
|
@@ -10,7 +10,7 @@ allowed:
|
|
10
10
|
- Pongo tapanuliensis
|
11
11
|
- Histiophryne psychedelica
|
12
12
|
- Phyllopteryx dewysea
|
13
|
-
|
13
|
+
init: Pongo tapanuliensis
|
14
14
|
name: Species
|
15
15
|
prompt: New species?
|
16
16
|
```
|
@@ -24,7 +24,6 @@ allowed:
|
|
24
24
|
- 1. Pongo
|
25
25
|
- 2. Histiophryne
|
26
26
|
- 3. Phyllopteryx
|
27
|
-
default: Pongo
|
28
27
|
menu_format: "| Name: %{name}| Value: ${%{name}}| Prompt: %{prompt}"
|
29
28
|
name: Genus
|
30
29
|
prompt: New genus?
|
@@ -35,13 +34,13 @@ validate: |
|
|
35
34
|
/ default
|
36
35
|
/ auto-load default value
|
37
36
|
```ux :[document_ux_Family]
|
38
|
-
|
37
|
+
init: Hominidae
|
39
38
|
name: Family
|
40
39
|
```
|
41
40
|
@import bats-document-configuration.md
|
42
41
|
```opts :(document_opts)
|
43
42
|
menu_ux_row_format: '| %{name}| ${%{name}}| %{prompt}'
|
44
|
-
screen_width:
|
43
|
+
screen_width: 72
|
45
44
|
table_center: true
|
46
45
|
ux_auto_load_force_default: true
|
47
46
|
```
|
@@ -0,0 +1,57 @@
|
|
1
|
+
/ 1. Simple variable display and edit:
|
2
|
+
```ux
|
3
|
+
name: USER_NAME
|
4
|
+
prompt: Enter your name
|
5
|
+
default: Guest
|
6
|
+
```
|
7
|
+
/Behavior: Displays the USER_NAME variable. When clicked, prompts for input with "Enter your name" and defaults to "Guest" if no input is provided.
|
8
|
+
/
|
9
|
+
/2. Command output initialization:
|
10
|
+
```ux
|
11
|
+
name: CURRENT_DIR
|
12
|
+
init: :exec
|
13
|
+
exec: basename $(pwd)
|
14
|
+
transform: :chomp
|
15
|
+
```
|
16
|
+
/Behavior: Initializes CURRENT_DIR with the output of the `pwd` command when the document loads.
|
17
|
+
/
|
18
|
+
/3. Echo-based initialization:
|
19
|
+
```ux
|
20
|
+
name: SHELL_VERSION
|
21
|
+
init: :echo
|
22
|
+
echo: $SHELL
|
23
|
+
```
|
24
|
+
/Behavior: Sets SHELL_VERSION to the value of the $SHELL environment variable when the document loads.
|
25
|
+
/
|
26
|
+
/4. Selection from allowed values:
|
27
|
+
```ux
|
28
|
+
name: ENVIRONMENT
|
29
|
+
allow:
|
30
|
+
- development
|
31
|
+
- staging
|
32
|
+
- production
|
33
|
+
prompt: Select environment
|
34
|
+
```
|
35
|
+
/Behavior: When activated, presents a menu to select from development, staging, or production environments.
|
36
|
+
/
|
37
|
+
/## Validation Examples
|
38
|
+
/
|
39
|
+
/5. Email validation:
|
40
|
+
```ux
|
41
|
+
name: USER_EMAIL
|
42
|
+
prompt: Enter email address
|
43
|
+
validate: '(?<local>[^@]+)@(?<domain>[^@]+)'
|
44
|
+
transform: '%{local}@%{domain}'
|
45
|
+
```
|
46
|
+
/Behavior: Validates input as an email address, capturing local and domain parts. The transform ensures proper formatting.
|
47
|
+
/
|
48
|
+
/6. Version number validation:
|
49
|
+
```ux
|
50
|
+
name: VERSION
|
51
|
+
prompt: Enter version number
|
52
|
+
validate: '(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)'
|
53
|
+
transform: '%{major}.%{minor}.%{patch}'
|
54
|
+
```
|
55
|
+
/Behavior: Ensures input follows semantic versioning format (e.g., 1.2.3).
|
56
|
+
/
|
57
|
+
@import bats-document-configuration.md
|
@@ -1,7 +1,6 @@
|
|
1
1
|
:::
|
2
2
|
**Execution output has a trailing newline.**
|
3
3
|
```ux :[document_ux_transform_0]
|
4
|
-
default: :exec
|
5
4
|
exec: basename $(pwd)
|
6
5
|
name: Var0
|
7
6
|
```
|
@@ -9,7 +8,6 @@ $(echo -n "$Var0" | hexdump -C)
|
|
9
8
|
:::
|
10
9
|
**With validate and transform, output has no newline.**
|
11
10
|
```ux :[document_ux_transform_1]
|
12
|
-
default: :exec
|
13
11
|
exec: basename $(pwd)
|
14
12
|
name: Var1
|
15
13
|
transform: '%{name}'
|
@@ -19,7 +17,6 @@ $(echo -n "$Var1" | hexdump -C)
|
|
19
17
|
:::
|
20
18
|
**With transform `:chomp`, output has no newline.**
|
21
19
|
```ux :[document_ux_transform_2]
|
22
|
-
default: :exec
|
23
20
|
exec: basename $(pwd)
|
24
21
|
name: Var2
|
25
22
|
transform: :chomp
|
@@ -28,7 +25,6 @@ $(echo -n "$Var2" | hexdump -C)
|
|
28
25
|
:::
|
29
26
|
**With transform `:upcase`, output is in upper case w/ newline.**
|
30
27
|
```ux :[document_ux_transform_3]
|
31
|
-
default: :exec
|
32
28
|
exec: basename $(pwd)
|
33
29
|
name: Var3
|
34
30
|
transform: :upcase
|
@@ -0,0 +1,61 @@
|
|
1
|
+
/ command substitution options to use different patterns
|
2
|
+
/
|
3
|
+
@import bats-document-configuration.md
|
4
|
+
/
|
5
|
+
```vars :(document_vars)
|
6
|
+
Common_Name: Tapanuli Orangutan
|
7
|
+
Species: Pongo tapanuliensis
|
8
|
+
```
|
9
|
+
/
|
10
|
+
::: Command substitution
|
11
|
+
|
12
|
+
The current value of environment variable `Common_Name` is displayed using two different operators.
|
13
|
+
The command `echo $SHLVL` is executed via command substitution, using two different operators.
|
14
|
+
|
15
|
+
| Operator| Variable Expansion| Command Substitution
|
16
|
+
| -| -| -
|
17
|
+
| $| ${Common_Name}| $(echo $Species)
|
18
|
+
| @| @{Common_Name}| @(echo $Species)
|
19
|
+
|
20
|
+
::: Toggle between operators.
|
21
|
+
|
22
|
+
/| MDE Option| Value
|
23
|
+
/| -| -
|
24
|
+
/| command_substitution_regexp| &{command_substitution_regexp}
|
25
|
+
/| menu_ux_row_format| &{menu_ux_row_format}
|
26
|
+
/| variable_expansion_regexp| &{variable_expansion_regexp}
|
27
|
+
|
28
|
+
/ This block requires a hidden block.
|
29
|
+
```opts :operator_$ +(operator_$2)
|
30
|
+
command_substitution_regexp: '(?<expression>\$\((?<command>([^()]*(\([^()]*\))*[^()]*)*)\))'
|
31
|
+
```
|
32
|
+
```opts :(operator_$2)
|
33
|
+
menu_ux_row_format: '%{name}=${%{name}}'
|
34
|
+
variable_expansion_regexp: '(?<expression>\${(?<variable>[A-Z0-9a-z_]+)})'
|
35
|
+
```
|
36
|
+
|
37
|
+
```opts :operator_@
|
38
|
+
command_substitution_regexp: '(?<expression>@\((?<command>([^()]*(\([^()]*\))*[^()]*)*)\))'
|
39
|
+
menu_ux_row_format: '%{name}=@{%{name}}'
|
40
|
+
variable_expansion_regexp: '(?<expression>@{(?<variable>[A-Z0-9a-z_]+)})'
|
41
|
+
```
|
42
|
+
|
43
|
+
```opts :(both)
|
44
|
+
command_substitution_regexp: '(?<expression>[\$\@]\((?<command>([^()]*(\([^()]*\))*[^()]*)*)\))'
|
45
|
+
menu_ux_row_format: '%{name}=${%{name}}'
|
46
|
+
variable_expansion_regexp: '(?<expression>[\$\@]{(?<variable>[A-Z0-9a-z_]+)})'
|
47
|
+
```
|
48
|
+
/
|
49
|
+
/ Require the block that sets @ as the operator.
|
50
|
+
```opts :(document_opts) +operator_@
|
51
|
+
divider4_collapsible: false
|
52
|
+
heading1_center: false
|
53
|
+
heading2_center: false
|
54
|
+
heading3_center: false
|
55
|
+
menu_final_divider:
|
56
|
+
menu_for_saved_lines: false
|
57
|
+
menu_initial_divider:
|
58
|
+
menu_vars_set_format:
|
59
|
+
screen_width: 64
|
60
|
+
table_center: false
|
61
|
+
```
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# Menu pagination indent
|
2
|
+
|
3
|
+
| Option| Value
|
4
|
+
| -| -
|
5
|
+
| select_page_height| &{select_page_height}
|
6
|
+
| screen_width| &{screen_width}
|
7
|
+
|
8
|
+
/ An Opts block
|
9
|
+
::: Indented with 2 spaces
|
10
|
+
```opts
|
11
|
+
option1: 1
|
12
|
+
option2: 2
|
13
|
+
option3: 3
|
14
|
+
option4: 4
|
15
|
+
```
|
16
|
+
::: Indented with 4 spaces
|
17
|
+
```opts
|
18
|
+
option1: 1
|
19
|
+
option2: 2
|
20
|
+
option3: 3
|
21
|
+
option4: 4
|
22
|
+
```
|
23
|
+
::: Indented with 1 tab
|
24
|
+
```opts
|
25
|
+
option1: 1
|
26
|
+
option2: 2
|
27
|
+
option3: 3
|
28
|
+
option4: 4
|
29
|
+
```
|
30
|
+
|
31
|
+
/ A shell block
|
32
|
+
::: Indented with 2 spaces
|
33
|
+
```
|
34
|
+
: 1
|
35
|
+
: 2
|
36
|
+
: 3
|
37
|
+
: 4
|
38
|
+
```
|
39
|
+
::: Indented with 4 spaces
|
40
|
+
```
|
41
|
+
: 1
|
42
|
+
: 2
|
43
|
+
: 3
|
44
|
+
: 4
|
45
|
+
```
|
46
|
+
::: Indented with 1 tab
|
47
|
+
```
|
48
|
+
: 1
|
49
|
+
: 2
|
50
|
+
: 3
|
51
|
+
: 4
|
52
|
+
```
|
53
|
+
|
54
|
+
| Variable| Value
|
55
|
+
| -| -
|
56
|
+
| VARIABLE| ${VARIABLE}
|
57
|
+
|
58
|
+
/ An UX block
|
59
|
+
::: Indented with 2 spaces
|
60
|
+
```ux
|
61
|
+
init: false
|
62
|
+
echo:
|
63
|
+
VARIABLE1: 1
|
64
|
+
VARIABLE2: 2
|
65
|
+
VARIABLE3: 3
|
66
|
+
VARIABLE4: 4
|
67
|
+
name: VARIABLE1
|
68
|
+
```
|
69
|
+
::: Indented with 4 spaces
|
70
|
+
```ux
|
71
|
+
init: false
|
72
|
+
echo:
|
73
|
+
VARIABLE1: 1
|
74
|
+
VARIABLE2: 2
|
75
|
+
VARIABLE3: 3
|
76
|
+
VARIABLE4: 4
|
77
|
+
name: VARIABLE1
|
78
|
+
```
|
79
|
+
::: Indented with 1 tab
|
80
|
+
```ux
|
81
|
+
init: false
|
82
|
+
echo:
|
83
|
+
VARIABLE1: 1
|
84
|
+
VARIABLE2: 2
|
85
|
+
VARIABLE3: 3
|
86
|
+
VARIABLE4: 4
|
87
|
+
name: VARIABLE1
|
88
|
+
```
|
89
|
+
|
90
|
+
|
91
|
+
| Variable| Value
|
92
|
+
| -| -
|
93
|
+
| VARIABLE1| ${VARIABLE1}
|
94
|
+
| VARIABLE2| ${VARIABLE2}
|
95
|
+
|
96
|
+
/ A VARS block
|
97
|
+
::: Indented with 2 spaces
|
98
|
+
```vars
|
99
|
+
VARIABLE1: 1
|
100
|
+
VARIABLE2: 2
|
101
|
+
VARIABLE3: 3
|
102
|
+
VARIABLE4: 4
|
103
|
+
```
|
104
|
+
::: Indented with 4 spaces
|
105
|
+
```vars
|
106
|
+
VARIABLE1: 1
|
107
|
+
VARIABLE2: 2
|
108
|
+
VARIABLE3: 3
|
109
|
+
VARIABLE4: 4
|
110
|
+
```
|
111
|
+
::: Indented with 1 tab
|
112
|
+
```vars
|
113
|
+
VARIABLE1: 1
|
114
|
+
VARIABLE2: 2
|
115
|
+
VARIABLE3: 3
|
116
|
+
VARIABLE4: 4
|
117
|
+
```
|
118
|
+
|
119
|
+
@import bats-document-configuration.md
|
120
|
+
```opts :(document_opts)
|
121
|
+
screen_width: 48
|
122
|
+
select_page_height: 12
|
123
|
+
```
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# Menu pagination
|
2
|
+
|
3
|
+
::: ˆ
|
4
|
+
MDE detects the screen's dimensions: height (lines) and width (characters)
|
5
|
+
|
6
|
+
| Option| Value
|
7
|
+
| -| -
|
8
|
+
| select_page_height| &{select_page_height}
|
9
|
+
| screen_width| &{screen_width}
|
10
|
+
|
11
|
+
Normal document text is displayed as disabled menu lines. The width of these lines is limited according to the screen's width.
|
12
|
+
|
13
|
+
::: ˆ
|
14
|
+
/ A sequence of text blocks
|
15
|
+
: 1
|
16
|
+
: 2
|
17
|
+
: 3
|
18
|
+
: 4
|
19
|
+
: 5
|
20
|
+
: 6
|
21
|
+
: 7
|
22
|
+
: 8
|
23
|
+
: 9
|
24
|
+
: 10
|
25
|
+
: 11
|
26
|
+
: 12
|
27
|
+
|
28
|
+
::: ˆ
|
29
|
+
/ A shell block
|
30
|
+
```
|
31
|
+
: 1
|
32
|
+
: 2
|
33
|
+
: 3
|
34
|
+
: 4
|
35
|
+
: 5
|
36
|
+
: 6
|
37
|
+
: 7
|
38
|
+
: 8
|
39
|
+
: 9
|
40
|
+
: 10
|
41
|
+
: 11
|
42
|
+
: 12
|
43
|
+
```
|
44
|
+
::: ˆ
|
45
|
+
/ An Opts block
|
46
|
+
```opts
|
47
|
+
option1: 1
|
48
|
+
option2: 2
|
49
|
+
option3: 3
|
50
|
+
option4: 4
|
51
|
+
option5: 5
|
52
|
+
option6: 6
|
53
|
+
option7: 7
|
54
|
+
option8: 8
|
55
|
+
option9: 9
|
56
|
+
option10: 10
|
57
|
+
option11: 11
|
58
|
+
option12: 12
|
59
|
+
```
|
60
|
+
::: ˆ
|
61
|
+
|
62
|
+
| Variable| Value
|
63
|
+
| -| -
|
64
|
+
| VARIABLE1| ${VARIABLE1}
|
65
|
+
| VARIABLE2| ${VARIABLE2}
|
66
|
+
|
67
|
+
/ A Vars block
|
68
|
+
```vars
|
69
|
+
VARIABLE1: 1
|
70
|
+
VARIABLE2: 2
|
71
|
+
VARIABLE3: 3
|
72
|
+
VARIABLE4: 4
|
73
|
+
VARIABLE5: 5
|
74
|
+
VARIABLE6: 6
|
75
|
+
VARIABLE7: 7
|
76
|
+
VARIABLE8: 8
|
77
|
+
VARIABLE9: 9
|
78
|
+
VARIABLE10: 10
|
79
|
+
VARIABLE11: 11
|
80
|
+
VARIABLE12: 12
|
81
|
+
```
|
82
|
+
::: ˆ
|
83
|
+
|
84
|
+
| Variable| Value
|
85
|
+
| -| -
|
86
|
+
| VARIABLE| ${VARIABLE}
|
87
|
+
|
88
|
+
/ An UX block
|
89
|
+
```ux
|
90
|
+
init: false
|
91
|
+
echo:
|
92
|
+
VARIABLE1: 1
|
93
|
+
VARIABLE2: 2
|
94
|
+
VARIABLE3: 3
|
95
|
+
VARIABLE4: 4
|
96
|
+
VARIABLE5: 5
|
97
|
+
VARIABLE6: 6
|
98
|
+
VARIABLE7: 7
|
99
|
+
VARIABLE8: 8
|
100
|
+
VARIABLE9: 9
|
101
|
+
VARIABLE10: 10
|
102
|
+
VARIABLE11: 11
|
103
|
+
VARIABLE12: 12
|
104
|
+
name: VARIABLE1
|
105
|
+
```
|
106
|
+
::: ˆ
|
107
|
+
@import bats-document-configuration.md
|
108
|
+
```opts :(document_opts)
|
109
|
+
screen_width: 48
|
110
|
+
select_page_height: 12
|
111
|
+
```
|
@@ -0,0 +1,10 @@
|
|
1
|
+
|
2
|
+
| Option| Description| Value Length| Value| Default
|
3
|
+
| -| -| -| -| -
|
4
|
+
| screen_width| &{screen_width.description}| &{screen_width.length}| &{screen_width}| &{screen_width.default}
|
5
|
+
| table_center| &{table_center.description}| &{table_center.length}| &{table_center}| &{table_center.default}
|
6
|
+
@import bats-document-configuration.md
|
7
|
+
```opts :(document_opts)
|
8
|
+
screen_width: 64
|
9
|
+
table_center: false
|
10
|
+
```
|
data/lib/ansi_formatter.rb
CHANGED
@@ -116,6 +116,8 @@ class AnsiFormatter
|
|
116
116
|
# @param default [String] Default color method to use if color_sym is not found in @options.
|
117
117
|
# @return [String] The string with the applied color method.
|
118
118
|
def string_send_color(string, color_sym, default: 'plain')
|
119
|
+
return string.to_s unless @options.fetch(:ansi_formatter_color, true)
|
120
|
+
|
119
121
|
color_method = @options.fetch(color_sym, default).to_sym
|
120
122
|
AnsiString.new(string.to_s).send(color_method)
|
121
123
|
end
|
data/lib/block_cache.rb
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
#!/usr/bin/env -S bundle exec ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# encoding=utf-8
|
5
|
+
|
6
|
+
# a ruby class contains a list of objects
|
7
|
+
class BlockCache
|
8
|
+
attr_accessor :cache
|
9
|
+
|
10
|
+
def initialize(cache = [])
|
11
|
+
@cache = cache
|
12
|
+
end
|
13
|
+
|
14
|
+
# Find an object by id
|
15
|
+
def find_by_id(id)
|
16
|
+
@cache.find { |obj| obj.id == id }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get an existing object by id or create a new one
|
20
|
+
def get_or_create(id, new_obj_proc = nil)
|
21
|
+
obj = find_by_id(id)
|
22
|
+
return obj if obj
|
23
|
+
|
24
|
+
# Create a new object if not found
|
25
|
+
if block_given?
|
26
|
+
obj = yield(id)
|
27
|
+
elsif new_obj_proc
|
28
|
+
obj = new_obj_proc.call(id)
|
29
|
+
else
|
30
|
+
raise ArgumentError, 'Block or proc required to create new object'
|
31
|
+
end
|
32
|
+
|
33
|
+
@cache << obj
|
34
|
+
obj
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add a new object to the cache
|
38
|
+
def add(obj)
|
39
|
+
raise ArgumentError,
|
40
|
+
'Object must respond to id method' unless obj.respond_to?(:id)
|
41
|
+
|
42
|
+
# Replace existing object with same id
|
43
|
+
if (existing = find_by_id(obj.id))
|
44
|
+
@cache[@cache.index(existing)] = obj
|
45
|
+
else
|
46
|
+
@cache << obj
|
47
|
+
end
|
48
|
+
obj
|
49
|
+
end
|
50
|
+
|
51
|
+
# Remove an object from the cache by id
|
52
|
+
def remove(id)
|
53
|
+
@cache.delete_if { |obj| obj.id == id }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Clear the entire cache
|
57
|
+
def clear
|
58
|
+
@cache = []
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get the size of the cache
|
62
|
+
def size
|
63
|
+
@cache.size
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
return unless $PROGRAM_NAME == __FILE__
|
68
|
+
|
69
|
+
require 'bundler/setup'
|
70
|
+
Bundler.require(:default)
|
71
|
+
|
72
|
+
require 'minitest/autorun'
|
73
|
+
require 'mocha/minitest'
|
74
|
+
|
75
|
+
# require_relative 'ww'
|
76
|
+
|
77
|
+
# A simple dummy object that responds to #id
|
78
|
+
class DummyObject
|
79
|
+
attr_reader :id
|
80
|
+
|
81
|
+
def initialize(id)
|
82
|
+
@id = id
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Unit tests for the BlockCache class
|
87
|
+
#
|
88
|
+
# This test suite verifies correct behavior of:
|
89
|
+
# - initialization (with and without initial cache)
|
90
|
+
# - finding objects by id
|
91
|
+
# - getting or creating objects via block or proc
|
92
|
+
# - adding objects (including replacing existing ones)
|
93
|
+
# - removing objects by id
|
94
|
+
# - clearing the cache
|
95
|
+
# - reporting cache size
|
96
|
+
class TestBlockCache < Minitest::Test
|
97
|
+
def setup
|
98
|
+
@obj1 = DummyObject.new(1)
|
99
|
+
@obj2 = DummyObject.new(2)
|
100
|
+
@initial_cache = [@obj1]
|
101
|
+
@cache = BlockCache.new(@initial_cache.dup)
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_initialize_defaults_to_empty_array
|
105
|
+
empty_cache = BlockCache.new
|
106
|
+
assert_equal [], empty_cache.cache
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_initialize_with_provided_array
|
110
|
+
assert_equal @initial_cache, @cache.cache
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_find_by_id_returns_matching_object
|
114
|
+
assert_equal @obj1, @cache.find_by_id(1)
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_find_by_id_returns_nil_when_not_found
|
118
|
+
assert_nil @cache.find_by_id(999)
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_get_or_create_returns_existing_object
|
122
|
+
found = @cache.get_or_create(1) { |id| DummyObject.new(id) }
|
123
|
+
assert_equal @obj1, found
|
124
|
+
assert_equal 1, @cache.size
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_get_or_create_creates_with_block
|
128
|
+
new_obj = @cache.get_or_create(3) { |id| DummyObject.new(id) }
|
129
|
+
assert_instance_of DummyObject, new_obj
|
130
|
+
assert_equal 3, new_obj.id
|
131
|
+
assert_includes @cache.cache, new_obj
|
132
|
+
assert_equal 2, @cache.size
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_get_or_create_creates_with_proc
|
136
|
+
factory = proc { |id| DummyObject.new(id * 10) }
|
137
|
+
new_obj = @cache.get_or_create(5, factory)
|
138
|
+
assert_instance_of DummyObject, new_obj
|
139
|
+
assert_equal 50, new_obj.id
|
140
|
+
assert_equal new_obj, @cache.find_by_id(50)
|
141
|
+
assert_equal 2, @cache.size
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_get_or_create_raises_without_block_or_proc
|
145
|
+
assert_raises(ArgumentError) do
|
146
|
+
@cache.get_or_create(7)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_add_appends_new_object
|
151
|
+
result = @cache.add(@obj2)
|
152
|
+
assert_equal @obj2, result
|
153
|
+
assert_equal 2, @cache.size
|
154
|
+
assert_equal [@obj1, @obj2], @cache.cache
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_add_replaces_existing_object_with_same_id
|
158
|
+
replacement = DummyObject.new(1)
|
159
|
+
result = @cache.add(replacement)
|
160
|
+
assert_equal replacement, result
|
161
|
+
assert_equal 1, @cache.size
|
162
|
+
assert_equal replacement, @cache.find_by_id(1)
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_add_raises_for_object_without_id_method
|
166
|
+
invalid = Object.new
|
167
|
+
assert_raises(ArgumentError) do
|
168
|
+
@cache.add(invalid)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_remove_deletes_object_by_id
|
173
|
+
@cache.add(@obj2)
|
174
|
+
@cache.remove(1)
|
175
|
+
assert_nil @cache.find_by_id(1)
|
176
|
+
assert_equal 1, @cache.size
|
177
|
+
assert_equal [@obj2], @cache.cache
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_remove_nonexistent_id_leaves_cache_unchanged
|
181
|
+
original = @cache.cache.dup
|
182
|
+
@cache.remove(999)
|
183
|
+
assert_equal original, @cache.cache
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_clear_empties_the_cache
|
187
|
+
@cache.clear
|
188
|
+
assert_equal [], @cache.cache
|
189
|
+
assert_equal 0, @cache.size
|
190
|
+
end
|
191
|
+
|
192
|
+
def test_size_returns_number_of_cached_objects
|
193
|
+
assert_equal 1, @cache.size
|
194
|
+
@cache.add(@obj2)
|
195
|
+
assert_equal 2, @cache.size
|
196
|
+
end
|
197
|
+
end
|