#!/usr/bin/env bash # validate-refine-plan-io.sh # Validates input and output paths for the refine-plan command # Exit codes: # 0 - Success, all validations passed # 1 - Input file does not exist # 2 - Input file is empty # 3 - Input file has no valid comment blocks or has malformed comment syntax # 4 - Input file missing required gen-plan sections # 5 - Output directory does not exist or is not writable, or input directory is not writable for in-place mode # 6 - QA directory not writable # 7 - Invalid arguments set -e scan_cmt_blocks() { local input_file="$1" awk ' function trim(value) { sub(/^[[:space:]]+/, "", value) sub(/[[:space:]]+$/, "", value) return value } function has_non_ws(value) { return value ~ /[^[:space:]]/ } function current_heading() { return nearest_heading == "" ? "Preamble" : nearest_heading } function context_excerpt(line_text, column, excerpt) { excerpt = substr(line_text, column) excerpt = trim(excerpt) if (excerpt == "") { excerpt = trim(line_text) } gsub(/[[:cntrl:]]/, " ", excerpt) gsub(/[[:space:]]+/, " ", excerpt) if (length(excerpt) > 80) { excerpt = substr(excerpt, 1, 77) "..." } return excerpt } function emit_error(kind, line_num, column, excerpt, heading) { fatal = 1 fatal_code = 2 heading = current_heading() if (kind == "nested") { printf "Comment parse error: nested comment block at line %d, column %d near \"%s\" (context: \"%s\")\n", line_num, column, heading, excerpt > "/dev/stderr" } else if (kind == "stray_end") { printf "Comment parse error: stray comment end marker at line %d, column %d near \"%s\" (context: \"%s\")\n", line_num, column, heading, excerpt > "/dev/stderr" } exit fatal_code } function find_comment_markers(text, start_pos, markers, i, pos, min_pos, closest_marker, closest_pos) { # Initialize markers array markers["CMT:"] = "classic_start" markers[""] = "cmt_tag_start" markers[""] = "comment_tag_start" markers["ENDCMT"] = "classic_end" markers[""] = "cmt_tag_end" markers[""] = "comment_tag_end" markers[""] = "html_end" closest_marker = "" closest_pos = 0 min_pos = length(text) + 1 for (marker in markers) { pos = index(substr(text, start_pos), marker) if (pos > 0) { pos = start_pos + pos - 1 if (pos < min_pos) { min_pos = pos closest_marker = marker closest_pos = pos } } } if (closest_marker == "") { return "" } else { return closest_marker "|" closest_pos } } function get_end_marker_for_format(format) { if (format == "classic") return "ENDCMT" if (format == "cmt_tag") return "" if (format == "comment_tag") return "" return "" } function get_marker_length(marker) { if (marker == "CMT:") return 4 if (marker == "") return 5 if (marker == "") return 9 if (marker == "ENDCMT") return 6 if (marker == "") return 6 if (marker == "") return 10 if (marker == "") return 3 return 0 } BEGIN { count = 0 in_fence = 0 in_html = 0 in_cmt = 0 fence_marker = "" nearest_heading = "Preamble" cmt_open_line = 0 cmt_open_col = 0 cmt_open_heading = "Preamble" cmt_open_excerpt = "" cmt_has_text = 0 cmt_format = "" # Track format: "classic", "cmt_tag", "comment_tag" fatal = 0 fatal_code = 0 } { line = $0 if (!in_fence && !in_html && !in_cmt && line ~ /^[[:space:]]*#[#]*[[:space:]]+/) { nearest_heading = trim(line) } if (in_fence) { if ((fence_marker == "```" && line ~ /^[[:space:]]*```/) || (fence_marker == "~~~" && line ~ /^[[:space:]]*~~~/)) { in_fence = 0 fence_marker = "" } next } if (!in_html && !in_cmt) { if (line ~ /^[[:space:]]*```/) { in_fence = 1 fence_marker = "```" next } if (line ~ /^[[:space:]]*~~~/) { in_fence = 1 fence_marker = "~~~" next } } pos = 1 line_length = length(line) while (pos <= line_length) { rest = substr(line, pos) if (in_html) { close_rel = index(rest, "-->") if (in_cmt && has_non_ws(rest)) { cmt_has_text = 1 } if (close_rel > 0) { pos += close_rel + 2 in_html = 0 continue } pos = line_length + 1 break } if (in_cmt) { marker_info = find_comment_markers(line, pos) if (marker_info == "") { if (has_non_ws(rest)) { cmt_has_text = 1 } pos = line_length + 1 break } split(marker_info, parts, "|") found_marker = parts[1] marker_pos = parts[2] token_rel = marker_pos - pos + 1 segment = substr(rest, 1, token_rel - 1) if (has_non_ws(segment)) { cmt_has_text = 1 } if (found_marker == "") if (close_rel > 0) { pos += close_rel + 2 in_html = 0 continue } pos = line_length + 1 break } if (in_cmt) { html_rel = index(rest, "