#!/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, "