| 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) |
| 143 | } |
| 144 | |
| 145 | # Queue of directives: (line_number, key, value) |
| 146 | directive_queue = [] |
| 147 | |
| 148 | with open(self.filepath, 'r') as f: |
| 149 | content = f.read() |
| 150 | |
| 151 | # Pre-scan for md-exec directives before stripping comments |
| 152 | directive_pattern = r'<!--\s*md-exec:\s*(.*?)\s*-->' |
| 153 | for match in re.finditer(directive_pattern, content): |
| 154 | line_num = content[:match.start()].count('\n') |
| 155 | raw_text = match.group(1) |
| 156 | |
| 157 | # Split by comma to support multiple directives: "abort_on_fail, print_command_output" |
| 158 | parts = [p.strip() for p in raw_text.split(',')] |
| 159 | |
| 160 | for part in parts: |
| 161 | if '=' in part: |
| 162 | key, val = part.split('=', 1) |
| 163 | key = key.strip() |
| 164 | val = val.strip().lower() |
| 165 | parsed_val = True if val == 'true' else False if val == 'false' else val |
| 166 | else: |
| 167 | key = part.strip() |
| 168 | parsed_val = True |
| 169 | |
| 170 | directive_queue.append((line_num, key, parsed_val)) |
| 171 | |
| 172 | # Remove HTML comments but preserve line numbers |
| 173 | content = re.sub( |
| 174 | r'<!--.*?-->', |
| 175 | lambda m: '\n' * m.group(0).count('\n'), |
| 176 | content, |
| 177 | flags=re.DOTALL |