markdown_exec 3.5.1 → 3.6.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/.ai-agent-instructions +54 -0
- data/.cursorrules +198 -0
- data/.rubocop.wide.yml +5 -0
- data/.rubocop.yml +7 -2
- data/CHANGELOG.md +19 -1
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/Rakefile +2 -0
- data/ai-principles.md +516 -0
- data/architecture-decisions.md +190 -0
- data/bats/block-hide.bats +1 -1
- data/bats/block-type-bash.bats +5 -5
- data/bats/block-type-link.bats +1 -1
- data/bats/block-type-opts.bats +3 -3
- data/bats/block-type-port.bats +2 -2
- data/bats/block-type-shell-context-eval.bats +48 -0
- data/bats/block-type-shell-require-ux.bats +2 -2
- data/bats/block-type-ux-allowed.bats +4 -4
- data/bats/block-type-ux-auto.bats +1 -1
- data/bats/block-type-ux-chained.bats +1 -1
- data/bats/block-type-ux-default.bats +1 -1
- data/bats/block-type-ux-echo-hash-transform.bats +1 -1
- data/bats/block-type-ux-echo-hash.bats +2 -2
- data/bats/block-type-ux-echo.bats +4 -4
- data/bats/block-type-ux-exec-hash-transform.bats +1 -1
- data/bats/block-type-ux-exec-hash.bats +2 -2
- data/bats/block-type-ux-exec.bats +1 -1
- data/bats/block-type-ux-force.bats +2 -2
- data/bats/block-type-ux-formats.bats +1 -1
- data/bats/block-type-ux-hidden.bats +1 -1
- data/bats/block-type-ux-invalid.bats +2 -2
- data/bats/block-type-ux-readonly.bats +1 -1
- data/bats/block-type-ux-require-chained.bats +2 -2
- data/bats/block-type-ux-require-context.bats +2 -2
- data/bats/block-type-ux-require.bats +3 -3
- data/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 +4 -4
- data/bats/block-type-ux-transform.bats +1 -1
- data/bats/block-type-vars.bats +3 -3
- data/bats/border.bats +1 -1
- data/bats/cli.bats +11 -11
- data/bats/command-substitution-options.bats +2 -2
- data/bats/command-substitution.bats +1 -1
- data/bats/document-shell.bats +3 -3
- data/bats/history.bats +5 -5
- data/bats/import-conflict.bats +1 -1
- data/bats/import-directive-line-continuation.bats +1 -1
- data/bats/import-directive-parameter-symbols.bats +1 -1
- data/bats/import-duplicates.bats +6 -6
- data/bats/import-parameter-symbols.bats +1 -1
- data/bats/import-with-text-substitution.bats +1 -1
- data/bats/import.bats +4 -4
- data/bats/indented-block-type-vars.bats +1 -1
- data/bats/indented-multi-line-output.bats +1 -1
- data/bats/line-decor-dynamic.bats +1 -1
- data/bats/line-wrapping.bats +1 -1
- data/bats/load-vars-state-demo.bats +8 -8
- data/bats/markup.bats +4 -4
- data/bats/mde.bats +4 -4
- data/bats/option-expansion.bats +1 -1
- data/bats/options-collapse.bats +4 -4
- data/bats/options.bats +47 -17
- data/bats/plain.bats +1 -1
- data/bats/publish.bats +2 -2
- data/bats/table-column-truncate.bats +1 -1
- data/bats/table.bats +2 -2
- data/bats/variable-expansion-multiline.bats +1 -1
- data/bats/variable-expansion.bats +6 -6
- data/bin/tab_completion.sh +3 -3
- data/conversation-template.md +611 -0
- data/docs/block-execution-modes.md +177 -0
- data/docs/block-filtering.md +252 -0
- data/docs/block-naming-patterns.md +210 -0
- data/docs/block-scanning-patterns.md +248 -0
- data/docs/cli-reference.md +370 -0
- data/docs/dev/bats-document-configuration.md +1 -1
- data/docs/dev/block-hide.md +1 -1
- data/docs/dev/block-type-shell-context-eval.md +52 -0
- data/docs/dev/block-type-shell-require-ux.md +6 -2
- data/docs/dev/block-type-ux-echo.md +3 -3
- data/docs/dev/block-type-ux-force.md +1 -1
- data/docs/dev/block-type-ux-require-chained.md +1 -1
- data/docs/dev/block-type-ux-require.md +2 -2
- data/docs/dev/block-type-ux-transform.md +5 -4
- data/docs/dev/import-parameter-symbols.md +1 -1
- data/docs/dev/linked-file.md +1 -1
- data/docs/dev/load-vars-state-demo.md +1 -1
- data/docs/dev/print_bytes.md +3 -0
- data/docs/dev/requiring-blocks.md +1 -1
- data/docs/dev/shebang.md +6 -0
- data/docs/dev/specs.md +2 -2
- data/docs/docker-testing.md +5 -0
- data/docs/execution-control.md +384 -0
- data/docs/getting-started.md +209 -0
- data/docs/import-options.md +391 -0
- data/docs/shell-script-evaluation.md +78 -0
- data/docs/tab-completion.md +7 -0
- data/docs/ux-blocks.md +376 -0
- data/examples/link-blocks-vars.md +2 -2
- data/examples/linked.md +1 -1
- data/examples/linked1.md +1 -1
- data/examples/opts-blocks.md +2 -2
- data/examples/port-blocks.md +1 -1
- data/examples/variable-expansion.md +1 -1
- data/implementation-decisions.md +212 -0
- data/lib/cached_nested_file_reader.rb +145 -5
- data/lib/command_result.rb +27 -6
- data/lib/executed_shell_command.rb +512 -0
- data/lib/filter.rb +7 -7
- data/lib/hash_delegator.rb +699 -605
- data/lib/input_sequencer.rb +4 -3
- data/lib/link_history.rb +95 -36
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/mdoc.rb +138 -51
- data/lib/menu.src.yml +115 -88
- data/lib/menu.yml +154 -88
- data/lib/transformed_shell_command.rb +449 -0
- data/lib/wl.rb +15 -0
- data/lib/ww.rb +17 -6
- data/requirements.md +111 -0
- data/semantic-tokens.md +132 -0
- data/tasks.md +69 -0
- metadata +29 -4
- data/docs/ux-blocks-examples.md +0 -120
- data/docs/ux-blocks-init-act.md +0 -100
|
@@ -33,7 +33,8 @@ class CachedNestedFileReader
|
|
|
33
33
|
symbol_evaluated_expression:,
|
|
34
34
|
symbol_force_quoted_literal:,
|
|
35
35
|
symbol_raw_literal:,
|
|
36
|
-
symbol_variable_reference
|
|
36
|
+
symbol_variable_reference:,
|
|
37
|
+
hide_shebang: true
|
|
37
38
|
)
|
|
38
39
|
@file_cache = {}
|
|
39
40
|
@import_directive_line_pattern = import_directive_line_pattern
|
|
@@ -46,16 +47,18 @@ class CachedNestedFileReader
|
|
|
46
47
|
@symbol_force_quoted_literal = symbol_force_quoted_literal
|
|
47
48
|
@symbol_raw_literal = symbol_raw_literal
|
|
48
49
|
@symbol_variable_reference = symbol_variable_reference
|
|
50
|
+
@hide_shebang = hide_shebang
|
|
49
51
|
end
|
|
50
52
|
|
|
51
53
|
def error_handler(name = '', opts = {})
|
|
52
54
|
Exceptions.error_handler(
|
|
53
55
|
"CachedNestedFileReader.#{name} -- #{$!}",
|
|
56
|
+
caller.deref(6),
|
|
54
57
|
opts
|
|
55
58
|
)
|
|
56
59
|
end
|
|
57
60
|
|
|
58
|
-
def warn_format(name, message, opts = {})
|
|
61
|
+
def self.warn_format(name, message, opts = {})
|
|
59
62
|
Exceptions.warn_format(
|
|
60
63
|
"CachedNestedFileReader.#{name} -- #{message}",
|
|
61
64
|
opts
|
|
@@ -94,6 +97,11 @@ class CachedNestedFileReader
|
|
|
94
97
|
wwt :readline, 'depth:', depth, 'filename:', filename, 'ind:', ind,
|
|
95
98
|
'segment:', segment
|
|
96
99
|
|
|
100
|
+
# [REQ:SHEBANG_HIDING] Filter shebang lines from processed output
|
|
101
|
+
# [IMPL:SHEBANG_FILTERING] [ARCH:SHEBANG_EXTRACTION] [REQ:SHEBANG_HIDING]
|
|
102
|
+
# first line is a shebang line if it starts with '#!'
|
|
103
|
+
next if @hide_shebang && is_shebang_line?(segment, ind == 0)
|
|
104
|
+
|
|
97
105
|
if continued_line || (Regexp.new(@import_directive_line_pattern) =~ segment)
|
|
98
106
|
line = (continued_line || '') + segment
|
|
99
107
|
# if segment ends in a continuation, prepend to next line
|
|
@@ -106,7 +114,7 @@ class CachedNestedFileReader
|
|
|
106
114
|
|
|
107
115
|
# apply substitutions to the @import line
|
|
108
116
|
line_sub1 = apply_line_substitutions(line, substitutions,
|
|
109
|
-
|
|
117
|
+
use_template_delimiters)
|
|
110
118
|
|
|
111
119
|
# parse the @import line
|
|
112
120
|
Regexp.new(@import_directive_line_pattern) =~ line_sub1
|
|
@@ -190,8 +198,10 @@ class CachedNestedFileReader
|
|
|
190
198
|
wwt :read_document_code, 'processed_lines:', processed_lines
|
|
191
199
|
@file_cache[cache_key] = processed_lines
|
|
192
200
|
rescue Errno::ENOENT => err
|
|
193
|
-
warn_format(
|
|
194
|
-
|
|
201
|
+
CachedNestedFileReader.warn_format(
|
|
202
|
+
'readlines', "#{err} @@ #{context}",
|
|
203
|
+
{ abort: true }
|
|
204
|
+
)
|
|
195
205
|
rescue StandardError
|
|
196
206
|
wwe $!
|
|
197
207
|
end
|
|
@@ -314,6 +324,15 @@ class CachedNestedFileReader
|
|
|
314
324
|
sorted_params = substitutions.sort.to_h.hash
|
|
315
325
|
"#{filename}##{name_strip}##{params_string}##{sorted_params}"
|
|
316
326
|
end
|
|
327
|
+
|
|
328
|
+
# [REQ:SHEBANG_HIDING] Detect shebang lines at the start of file content
|
|
329
|
+
# [IMPL:SHEBANG_DETECTION] [ARCH:SHEBANG_EXTRACTION] [REQ:SHEBANG_HIDING]
|
|
330
|
+
# Matches the beginning of the first line as '#!' - anything after that matches a shebang
|
|
331
|
+
def is_shebang_line?(line, is_first_line)
|
|
332
|
+
return false unless is_first_line
|
|
333
|
+
|
|
334
|
+
line.start_with?('#!')
|
|
335
|
+
end
|
|
317
336
|
end
|
|
318
337
|
|
|
319
338
|
return if $PROGRAM_NAME != __FILE__
|
|
@@ -501,4 +520,125 @@ class CachedNestedFileReaderTest < Minitest::Test
|
|
|
501
520
|
importing_file.close
|
|
502
521
|
importing_file.unlink
|
|
503
522
|
end
|
|
523
|
+
|
|
524
|
+
# [REQ:SHEBANG_HIDING] Test shebang line detection and filtering
|
|
525
|
+
# [IMPL:SHEBANG_DETECTION] [IMPL:SHEBANG_FILTERING] [ARCH:SHEBANG_EXTRACTION] [REQ:SHEBANG_HIDING]
|
|
526
|
+
def test_readlines_with_shebang_hidden
|
|
527
|
+
file_with_shebang = Tempfile.new('test_shebang.txt')
|
|
528
|
+
file_with_shebang.write("#!/usr/bin/env mde\nLine1\nLine2")
|
|
529
|
+
file_with_shebang.rewind
|
|
530
|
+
|
|
531
|
+
reader_with_hide = CachedNestedFileReader.new(
|
|
532
|
+
import_directive_line_pattern:
|
|
533
|
+
/^(?<indention> *)@import +(?<name>\S+)(?<params>(?: +[A-Za-z_]\w*=(?:"[^"]*"|'[^']*'|\S+))*) *$/,
|
|
534
|
+
import_directive_parameter_scan:
|
|
535
|
+
/([A-Za-z_]\w*)(:=|\?=|!=|=)(?:"([^"]*)"|'([^']*)'|(\S+))/,
|
|
536
|
+
import_parameter_variable_assignment: '%{key}=%{value}',
|
|
537
|
+
shell: 'bash',
|
|
538
|
+
shell_block_name: '(document_shell)',
|
|
539
|
+
symbol_command_substitution: ':c=',
|
|
540
|
+
symbol_evaluated_expression: ':e=',
|
|
541
|
+
symbol_raw_literal: '=',
|
|
542
|
+
symbol_force_quoted_literal: ':q=',
|
|
543
|
+
symbol_variable_reference: ':v=',
|
|
544
|
+
hide_shebang: true
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
result = reader_with_hide.readlines(file_with_shebang.path).map(&:to_s)
|
|
548
|
+
assert_equal %w[Line1 Line2], result, 'Shebang line should be filtered when hide_shebang is true'
|
|
549
|
+
|
|
550
|
+
file_with_shebang.close
|
|
551
|
+
file_with_shebang.unlink
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
def test_readlines_with_shebang_shown
|
|
555
|
+
file_with_shebang = Tempfile.new('test_shebang.txt')
|
|
556
|
+
file_with_shebang.write("#!/usr/bin/env mde\nLine1\nLine2")
|
|
557
|
+
file_with_shebang.rewind
|
|
558
|
+
|
|
559
|
+
reader_without_hide = CachedNestedFileReader.new(
|
|
560
|
+
import_directive_line_pattern:
|
|
561
|
+
/^(?<indention> *)@import +(?<name>\S+)(?<params>(?: +[A-Za-z_]\w*=(?:"[^"]*"|'[^']*'|\S+))*) *$/,
|
|
562
|
+
import_directive_parameter_scan:
|
|
563
|
+
/([A-Za-z_]\w*)(:=|\?=|!=|=)(?:"([^"]*)"|'([^']*)'|(\S+))/,
|
|
564
|
+
import_parameter_variable_assignment: '%{key}=%{value}',
|
|
565
|
+
shell: 'bash',
|
|
566
|
+
shell_block_name: '(document_shell)',
|
|
567
|
+
symbol_command_substitution: ':c=',
|
|
568
|
+
symbol_evaluated_expression: ':e=',
|
|
569
|
+
symbol_raw_literal: '=',
|
|
570
|
+
symbol_force_quoted_literal: ':q=',
|
|
571
|
+
symbol_variable_reference: ':v=',
|
|
572
|
+
hide_shebang: false
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
result = reader_without_hide.readlines(file_with_shebang.path).map(&:to_s)
|
|
576
|
+
assert_equal ['#!/usr/bin/env mde', 'Line1', 'Line2'], result, 'Shebang line should be included when hide_shebang is false'
|
|
577
|
+
|
|
578
|
+
file_with_shebang.close
|
|
579
|
+
file_with_shebang.unlink
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
def test_readlines_with_shebang_in_imported_file
|
|
583
|
+
imported_file = Tempfile.new('imported_shebang.txt')
|
|
584
|
+
imported_file.write("#!/usr/bin/env mde\nImportedLine1\nImportedLine2")
|
|
585
|
+
imported_file.rewind
|
|
586
|
+
|
|
587
|
+
main_file = Tempfile.new('main_file.txt')
|
|
588
|
+
main_file.write("Line1\n @import #{imported_file.path}\nLine2")
|
|
589
|
+
main_file.rewind
|
|
590
|
+
|
|
591
|
+
reader_with_hide = CachedNestedFileReader.new(
|
|
592
|
+
import_directive_line_pattern:
|
|
593
|
+
/^(?<indention> *)@import +(?<name>\S+)(?<params>(?: +[A-Za-z_]\w*=(?:"[^"]*"|'[^']*'|\S+))*) *$/,
|
|
594
|
+
import_directive_parameter_scan:
|
|
595
|
+
/([A-Za-z_]\w*)(:=|\?=|!=|=)(?:"([^"]*)"|'([^']*)'|(\S+))/,
|
|
596
|
+
import_parameter_variable_assignment: '%{key}=%{value}',
|
|
597
|
+
shell: 'bash',
|
|
598
|
+
shell_block_name: '(document_shell)',
|
|
599
|
+
symbol_command_substitution: ':c=',
|
|
600
|
+
symbol_evaluated_expression: ':e=',
|
|
601
|
+
symbol_raw_literal: '=',
|
|
602
|
+
symbol_force_quoted_literal: ':q=',
|
|
603
|
+
symbol_variable_reference: ':v=',
|
|
604
|
+
hide_shebang: true
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
result = reader_with_hide.readlines(main_file.path).map(&:to_s)
|
|
608
|
+
assert_equal ['Line1', ' ImportedLine1', ' ImportedLine2', 'Line2'], result,
|
|
609
|
+
'Shebang in imported file should be filtered when hide_shebang is true'
|
|
610
|
+
|
|
611
|
+
imported_file.close
|
|
612
|
+
imported_file.unlink
|
|
613
|
+
main_file.close
|
|
614
|
+
main_file.unlink
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def test_readlines_without_shebang
|
|
618
|
+
file_without_shebang = Tempfile.new('test_no_shebang.txt')
|
|
619
|
+
file_without_shebang.write("Line1\nLine2\nLine3")
|
|
620
|
+
file_without_shebang.rewind
|
|
621
|
+
|
|
622
|
+
reader_with_hide = CachedNestedFileReader.new(
|
|
623
|
+
import_directive_line_pattern:
|
|
624
|
+
/^(?<indention> *)@import +(?<name>\S+)(?<params>(?: +[A-Za-z_]\w*=(?:"[^"]*"|'[^']*'|\S+))*) *$/,
|
|
625
|
+
import_directive_parameter_scan:
|
|
626
|
+
/([A-Za-z_]\w*)(:=|\?=|!=|=)(?:"([^"]*)"|'([^']*)'|(\S+))/,
|
|
627
|
+
import_parameter_variable_assignment: '%{key}=%{value}',
|
|
628
|
+
shell: 'bash',
|
|
629
|
+
shell_block_name: '(document_shell)',
|
|
630
|
+
symbol_command_substitution: ':c=',
|
|
631
|
+
symbol_evaluated_expression: ':e=',
|
|
632
|
+
symbol_raw_literal: '=',
|
|
633
|
+
symbol_force_quoted_literal: ':q=',
|
|
634
|
+
symbol_variable_reference: ':v=',
|
|
635
|
+
hide_shebang: true
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
result = reader_with_hide.readlines(file_without_shebang.path).map(&:to_s)
|
|
639
|
+
assert_equal %w[Line1 Line2 Line3], result, 'File without shebang should work normally'
|
|
640
|
+
|
|
641
|
+
file_without_shebang.close
|
|
642
|
+
file_without_shebang.unlink
|
|
643
|
+
end
|
|
504
644
|
end
|
data/lib/command_result.rb
CHANGED
|
@@ -3,11 +3,18 @@
|
|
|
3
3
|
|
|
4
4
|
# encoding=utf-8
|
|
5
5
|
|
|
6
|
+
ALL = [
|
|
7
|
+
BASH = 'bash',
|
|
8
|
+
FISH = 'fish',
|
|
9
|
+
SH = 'sh'
|
|
10
|
+
].freeze
|
|
11
|
+
|
|
6
12
|
# Encapsulates the result of executing a system command, storing its output,
|
|
7
13
|
# exit status, and any number of additional, arbitrary attributes.
|
|
8
14
|
#
|
|
9
15
|
# @example
|
|
10
|
-
# result = CommandResult.new(stdout: output, exit_status: $?.exitstatus,
|
|
16
|
+
# result = CommandResult.new(stdout: output, exit_status: $?.exitstatus,
|
|
17
|
+
# duration: 1.23)
|
|
11
18
|
# result.stdout # => output
|
|
12
19
|
# result.exit_status # => 0
|
|
13
20
|
# result.duration # => 1.23
|
|
@@ -15,13 +22,19 @@
|
|
|
15
22
|
# result.new_field # => 42
|
|
16
23
|
# result.success? # => true
|
|
17
24
|
class CommandResult
|
|
25
|
+
ALL = [
|
|
26
|
+
EXIT_STATUS_OK = 0,
|
|
27
|
+
EXIT_STATUS_FAIL = 127,
|
|
28
|
+
EXIT_STATUS_REQUIRED_EMPTY = 248
|
|
29
|
+
].freeze
|
|
30
|
+
|
|
18
31
|
# @param attributes [Hash{Symbol=>Object}] initial named attributes
|
|
19
32
|
def initialize(**attributes)
|
|
20
|
-
@attributes = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
33
|
+
@attributes = {
|
|
34
|
+
exit_status: EXIT_STATUS_OK,
|
|
35
|
+
stdout: '',
|
|
36
|
+
warning: ''
|
|
37
|
+
}.merge(attributes)
|
|
25
38
|
end
|
|
26
39
|
|
|
27
40
|
def failure?
|
|
@@ -39,6 +52,14 @@ class CommandResult
|
|
|
39
52
|
value
|
|
40
53
|
end
|
|
41
54
|
|
|
55
|
+
def stdout
|
|
56
|
+
@attributes[:stdout]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def transformed
|
|
60
|
+
@attributes[:transformed]
|
|
61
|
+
end
|
|
62
|
+
|
|
42
63
|
# # trap assignment to new_lines
|
|
43
64
|
# def new_lines=(value)
|
|
44
65
|
# ww caller.deref[0..4], value
|