markdown_exec 3.5.1 → 3.5.2
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 +12 -1
- data/Gemfile.lock +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-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 +3 -3
- 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 +1 -1
- 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 +1 -1
- 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 +2 -2
- 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 +1 -1
- 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 +3 -3
- 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 +4 -4
- 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/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/block-hide.md +1 -1
- data/docs/dev/block-type-ux-transform.md +5 -4
- data/docs/dev/print_bytes.md +3 -0
- data/docs/dev/shebang.md +6 -0
- 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/tab-completion.md +7 -0
- data/docs/ux-blocks.md +376 -0
- data/examples/linked1.md +8 -1
- data/implementation-decisions.md +212 -0
- data/lib/cached_nested_file_reader.rb +138 -1
- 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 +403 -350
- data/lib/link_history.rb +22 -11
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/mdoc.rb +103 -44
- data/lib/menu.src.yml +110 -83
- data/lib/menu.yml +149 -83
- data/lib/transformed_shell_command.rb +449 -0
- data/lib/wl.rb +15 -0
- data/lib/ww.rb +16 -5
- data/requirements.md +111 -0
- data/semantic-tokens.md +132 -0
- data/tasks.md +69 -0
- metadata +26 -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,6 +47,7 @@ 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 = {})
|
|
@@ -94,6 +96,11 @@ class CachedNestedFileReader
|
|
|
94
96
|
wwt :readline, 'depth:', depth, 'filename:', filename, 'ind:', ind,
|
|
95
97
|
'segment:', segment
|
|
96
98
|
|
|
99
|
+
# [REQ:SHEBANG_HIDING] Filter shebang lines from processed output
|
|
100
|
+
# [IMPL:SHEBANG_FILTERING] [ARCH:SHEBANG_EXTRACTION] [REQ:SHEBANG_HIDING]
|
|
101
|
+
# first line is a shebang line if it starts with '#!'
|
|
102
|
+
next if @hide_shebang && is_shebang_line?(segment, ind == 0)
|
|
103
|
+
|
|
97
104
|
if continued_line || (Regexp.new(@import_directive_line_pattern) =~ segment)
|
|
98
105
|
line = (continued_line || '') + segment
|
|
99
106
|
# if segment ends in a continuation, prepend to next line
|
|
@@ -314,6 +321,15 @@ class CachedNestedFileReader
|
|
|
314
321
|
sorted_params = substitutions.sort.to_h.hash
|
|
315
322
|
"#{filename}##{name_strip}##{params_string}##{sorted_params}"
|
|
316
323
|
end
|
|
324
|
+
|
|
325
|
+
# [REQ:SHEBANG_HIDING] Detect shebang lines at the start of file content
|
|
326
|
+
# [IMPL:SHEBANG_DETECTION] [ARCH:SHEBANG_EXTRACTION] [REQ:SHEBANG_HIDING]
|
|
327
|
+
# Matches the beginning of the first line as '#!' - anything after that matches a shebang
|
|
328
|
+
def is_shebang_line?(line, is_first_line)
|
|
329
|
+
return false unless is_first_line
|
|
330
|
+
|
|
331
|
+
line.start_with?('#!')
|
|
332
|
+
end
|
|
317
333
|
end
|
|
318
334
|
|
|
319
335
|
return if $PROGRAM_NAME != __FILE__
|
|
@@ -501,4 +517,125 @@ class CachedNestedFileReaderTest < Minitest::Test
|
|
|
501
517
|
importing_file.close
|
|
502
518
|
importing_file.unlink
|
|
503
519
|
end
|
|
520
|
+
|
|
521
|
+
# [REQ:SHEBANG_HIDING] Test shebang line detection and filtering
|
|
522
|
+
# [IMPL:SHEBANG_DETECTION] [IMPL:SHEBANG_FILTERING] [ARCH:SHEBANG_EXTRACTION] [REQ:SHEBANG_HIDING]
|
|
523
|
+
def test_readlines_with_shebang_hidden
|
|
524
|
+
file_with_shebang = Tempfile.new('test_shebang.txt')
|
|
525
|
+
file_with_shebang.write("#!/usr/bin/env mde\nLine1\nLine2")
|
|
526
|
+
file_with_shebang.rewind
|
|
527
|
+
|
|
528
|
+
reader_with_hide = CachedNestedFileReader.new(
|
|
529
|
+
import_directive_line_pattern:
|
|
530
|
+
/^(?<indention> *)@import +(?<name>\S+)(?<params>(?: +[A-Za-z_]\w*=(?:"[^"]*"|'[^']*'|\S+))*) *$/,
|
|
531
|
+
import_directive_parameter_scan:
|
|
532
|
+
/([A-Za-z_]\w*)(:=|\?=|!=|=)(?:"([^"]*)"|'([^']*)'|(\S+))/,
|
|
533
|
+
import_parameter_variable_assignment: '%{key}=%{value}',
|
|
534
|
+
shell: 'bash',
|
|
535
|
+
shell_block_name: '(document_shell)',
|
|
536
|
+
symbol_command_substitution: ':c=',
|
|
537
|
+
symbol_evaluated_expression: ':e=',
|
|
538
|
+
symbol_raw_literal: '=',
|
|
539
|
+
symbol_force_quoted_literal: ':q=',
|
|
540
|
+
symbol_variable_reference: ':v=',
|
|
541
|
+
hide_shebang: true
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
result = reader_with_hide.readlines(file_with_shebang.path).map(&:to_s)
|
|
545
|
+
assert_equal ['Line1', 'Line2'], result, 'Shebang line should be filtered when hide_shebang is true'
|
|
546
|
+
|
|
547
|
+
file_with_shebang.close
|
|
548
|
+
file_with_shebang.unlink
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def test_readlines_with_shebang_shown
|
|
552
|
+
file_with_shebang = Tempfile.new('test_shebang.txt')
|
|
553
|
+
file_with_shebang.write("#!/usr/bin/env mde\nLine1\nLine2")
|
|
554
|
+
file_with_shebang.rewind
|
|
555
|
+
|
|
556
|
+
reader_without_hide = CachedNestedFileReader.new(
|
|
557
|
+
import_directive_line_pattern:
|
|
558
|
+
/^(?<indention> *)@import +(?<name>\S+)(?<params>(?: +[A-Za-z_]\w*=(?:"[^"]*"|'[^']*'|\S+))*) *$/,
|
|
559
|
+
import_directive_parameter_scan:
|
|
560
|
+
/([A-Za-z_]\w*)(:=|\?=|!=|=)(?:"([^"]*)"|'([^']*)'|(\S+))/,
|
|
561
|
+
import_parameter_variable_assignment: '%{key}=%{value}',
|
|
562
|
+
shell: 'bash',
|
|
563
|
+
shell_block_name: '(document_shell)',
|
|
564
|
+
symbol_command_substitution: ':c=',
|
|
565
|
+
symbol_evaluated_expression: ':e=',
|
|
566
|
+
symbol_raw_literal: '=',
|
|
567
|
+
symbol_force_quoted_literal: ':q=',
|
|
568
|
+
symbol_variable_reference: ':v=',
|
|
569
|
+
hide_shebang: false
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
result = reader_without_hide.readlines(file_with_shebang.path).map(&:to_s)
|
|
573
|
+
assert_equal ['#!/usr/bin/env mde', 'Line1', 'Line2'], result, 'Shebang line should be included when hide_shebang is false'
|
|
574
|
+
|
|
575
|
+
file_with_shebang.close
|
|
576
|
+
file_with_shebang.unlink
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
def test_readlines_with_shebang_in_imported_file
|
|
580
|
+
imported_file = Tempfile.new('imported_shebang.txt')
|
|
581
|
+
imported_file.write("#!/usr/bin/env mde\nImportedLine1\nImportedLine2")
|
|
582
|
+
imported_file.rewind
|
|
583
|
+
|
|
584
|
+
main_file = Tempfile.new('main_file.txt')
|
|
585
|
+
main_file.write("Line1\n @import #{imported_file.path}\nLine2")
|
|
586
|
+
main_file.rewind
|
|
587
|
+
|
|
588
|
+
reader_with_hide = CachedNestedFileReader.new(
|
|
589
|
+
import_directive_line_pattern:
|
|
590
|
+
/^(?<indention> *)@import +(?<name>\S+)(?<params>(?: +[A-Za-z_]\w*=(?:"[^"]*"|'[^']*'|\S+))*) *$/,
|
|
591
|
+
import_directive_parameter_scan:
|
|
592
|
+
/([A-Za-z_]\w*)(:=|\?=|!=|=)(?:"([^"]*)"|'([^']*)'|(\S+))/,
|
|
593
|
+
import_parameter_variable_assignment: '%{key}=%{value}',
|
|
594
|
+
shell: 'bash',
|
|
595
|
+
shell_block_name: '(document_shell)',
|
|
596
|
+
symbol_command_substitution: ':c=',
|
|
597
|
+
symbol_evaluated_expression: ':e=',
|
|
598
|
+
symbol_raw_literal: '=',
|
|
599
|
+
symbol_force_quoted_literal: ':q=',
|
|
600
|
+
symbol_variable_reference: ':v=',
|
|
601
|
+
hide_shebang: true
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
result = reader_with_hide.readlines(main_file.path).map(&:to_s)
|
|
605
|
+
assert_equal ['Line1', ' ImportedLine1', ' ImportedLine2', 'Line2'], result,
|
|
606
|
+
'Shebang in imported file should be filtered when hide_shebang is true'
|
|
607
|
+
|
|
608
|
+
imported_file.close
|
|
609
|
+
imported_file.unlink
|
|
610
|
+
main_file.close
|
|
611
|
+
main_file.unlink
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
def test_readlines_without_shebang
|
|
615
|
+
file_without_shebang = Tempfile.new('test_no_shebang.txt')
|
|
616
|
+
file_without_shebang.write("Line1\nLine2\nLine3")
|
|
617
|
+
file_without_shebang.rewind
|
|
618
|
+
|
|
619
|
+
reader_with_hide = CachedNestedFileReader.new(
|
|
620
|
+
import_directive_line_pattern:
|
|
621
|
+
/^(?<indention> *)@import +(?<name>\S+)(?<params>(?: +[A-Za-z_]\w*=(?:"[^"]*"|'[^']*'|\S+))*) *$/,
|
|
622
|
+
import_directive_parameter_scan:
|
|
623
|
+
/([A-Za-z_]\w*)(:=|\?=|!=|=)(?:"([^"]*)"|'([^']*)'|(\S+))/,
|
|
624
|
+
import_parameter_variable_assignment: '%{key}=%{value}',
|
|
625
|
+
shell: 'bash',
|
|
626
|
+
shell_block_name: '(document_shell)',
|
|
627
|
+
symbol_command_substitution: ':c=',
|
|
628
|
+
symbol_evaluated_expression: ':e=',
|
|
629
|
+
symbol_raw_literal: '=',
|
|
630
|
+
symbol_force_quoted_literal: ':q=',
|
|
631
|
+
symbol_variable_reference: ':v=',
|
|
632
|
+
hide_shebang: true
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
result = reader_with_hide.readlines(file_without_shebang.path).map(&:to_s)
|
|
636
|
+
assert_equal ['Line1', 'Line2', 'Line3'], result, 'File without shebang should work normally'
|
|
637
|
+
|
|
638
|
+
file_without_shebang.close
|
|
639
|
+
file_without_shebang.unlink
|
|
640
|
+
end
|
|
504
641
|
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
|