| 83 | self.empty_output = empty_output |
| 84 | |
| 85 | class MarkdownParser: |
| 86 | def __init__(self, filepath, cli_options=None): |
| 87 | self.filepath = filepath |
| 88 | self.cli_options = cli_options or {} |
| 89 | |
| 90 | def _create_test(self, cmd, expected_lines, line_num, timeout, expected_fail, options, strip_output, exit_check, empty_output): |
| 91 | # Reconstruct expected output preserving shell-like behavior. |
| 92 | # If there are expected lines, standard shell commands usually end with a newline. |
| 93 | # ["foo"] -> "foo\n" |
| 94 | # [""] -> "\n" |
| 95 | # [] -> "" |
| 96 | if expected_lines: |
| 97 | expected_str = "\n".join(expected_lines) + "\n" |
| 98 | else: |
| 99 | expected_str = "" |
| 100 | return ShellTest(cmd, expected_str, line_num, timeout, expected_fail, options.copy(), strip_output, exit_check, empty_output) |
| 101 | |
| 102 | def _extract_heredoc_delimiter(self, cmd): |
| 103 | """Extract heredoc delimiter from a command if present. |
| 104 | |
| 105 | Supports patterns like: |
| 106 | - << EOF |
| 107 | - <<EOF |
| 108 | - << 'EOF' |
| 109 | - << "EOF" |
| 110 | - <<- EOF (with tab stripping) |
| 111 | |
| 112 | Returns the delimiter string (unquoted) or None if no heredoc. |
| 113 | """ |
| 114 | # Match heredoc pattern: <<[-][ ]?['"]?WORD['"]? |
| 115 | match = re.search(r'<<-?\s*[\'"]?(\w+)[\'"]?\s*$', cmd) |
| 116 | if match: |
| 117 | return match.group(1) |
| 118 | return None |
| 119 | |
| 120 | def parse(self): |
| 121 | tests = [] |
| 122 | in_block = False |
| 123 | current_cmd = None |
| 124 | current_expected = [] |
| 125 | cmd_line_num = 0 |
| 126 | current_block_timeout = None |
| 127 | current_cmd_expected_fail = False |
| 128 | current_block_expected_fail = False |
| 129 | current_block_strip = False |
| 130 | current_cmd_strip = False |
| 131 | current_block_exit_check = None |
| 132 | current_cmd_exit_check = None |
| 133 | current_block_empty_output = False |
| 134 | current_cmd_empty_output = False |
| 135 | current_block_indent = 0 |
| 136 | heredoc_delimiter = None # Track if we're inside a heredoc |
| 137 | |
| 138 | # Configuration state that applies to tests based on file position |
| 139 | # CLI options provide initial defaults, can be overridden by in-file directives |
| 140 | current_options = { |
| 141 | "abort_on_fail": self.cli_options.get("abort_on_fail", False), |
| 142 | "print_command_output": self.cli_options.get("print_command_output", False) |