Skip to content

Commit c8aa182

Browse files
committed
Add validation between the auto-index and the files, update the bash script
1 parent a2fa821 commit c8aa182

File tree

4 files changed

+319
-41
lines changed

4 files changed

+319
-41
lines changed

scripts/README.adoc

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,36 @@ This script generates the _auto-index.adoc_ file by using the template in _templ
1313

1414
What it does::
1515
* Extracts error codes from filenames (e.g., 42007.adoc → 42007).
16-
Extracts the status descriptions from individual error files.
16+
* Extracts the status descriptions from individual error files.
1717
* Extracts page roles from files (`:page-role: changed-2025.04`).
1818
* Creates a new _auto-index.adoc_ file based on the template and populates it with the extracted data.
1919

20-
=== `validate_error_index.py`
20+
=== `validate-error-index.py`
2121

22-
This index validates the consistency between _index.adoc_ and individual error files, identifying discrepancies in Error codes, status descriptions, and page roles.
22+
This index validates the consistency between _index.adoc_ and individual error files, identifying discrepancies in error codes, status descriptions, and page roles.
2323

2424
What it does::
2525
* Checks for error codes mentioned in the index.adoc file that don't have corresponding files.
2626
* Finds error files without index entries.
2727
* Detects status description mismatches between the index and individual files.
2828
* Verifies page role consistency.
2929

30-
=== `update-gql-error-index.sh`
30+
=== `validate-error-auto-index.py`
31+
32+
This index validates the consistency between _auto-index.adoc_ and individual error files, identifying discrepancies in error codes, status descriptions, and page roles.
3133

32-
This script runs the previous two scripts (the generation and validation scripts), and if validation passes, it replaces _index.adoc_ with _auto-index.adoc_.
34+
What it does::
35+
* Checks for error codes mentioned in the index.adoc file that don't have corresponding files.
36+
* Finds error files without index entries.
37+
* Detects status description mismatches between the index and individual files.
38+
* Verifies page role consistency.
39+
40+
=== `update-gql-error-index.sh`
41+
This script orchestrates the execution of the other scripts in a specific order to ensure that the error index is generated and validated correctly.
3342

3443
What it does::
35-
* Runs the `generate-gql-error-index-from-template.py` script to generate the _auto-index.adoc_ file.
36-
* Runs the `validate_error_index.py` script to validate the generated file against the individual error files.
37-
* If validation passes, it replaces the existing _index.adoc_ file with _auto-index.adoc_.
38-
* If validation fails, it prints an error message and does not replace the _index.adoc_ file, while also keeping the _auto-index.adoc_ file for review.
44+
1. Runs the validation script to check for discrepancies between the _index.adoc_ file and the individual error files.
45+
2. If validation passes, it runs the generation script to create the _auto-index.adoc_ file.
46+
3. Runs the validation script again to check for discrepancies between the _auto-index.adoc_ file and the individual error files.
47+
4. If validation passes, it replaces the existing _index.adoc_ file with _auto-index.adoc_.
48+
5. If validation fails, it prints an error message and does not replace the _index.adoc_ file, while also keeping the _auto-index.adoc_ file for manual review.

scripts/update-gql-error-index.sh

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
#!/bin/bash
22

3-
# Script to generate, validate, and update the GQL error index
4-
# This script will:
5-
# 1. Generate auto-index.adoc using the template
6-
# 2. Validate that it matches the content of index.adoc
7-
# 3. If validation passes, replace index.adoc with auto-index.adoc
8-
9-
set -e # Exit on any error
3+
# Script to validate and update the GQL error index
4+
# This script follows a specific workflow to maintain error documentation consistency
105

116
# Define paths
127
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -23,34 +18,50 @@ fi
2318

2419
echo "===== Starting GQL Error Index Update Process ====="
2520

26-
# Step 1: Generate auto-index.adoc
27-
echo "Generating auto-index.adoc from template..."
28-
python3 "$SCRIPT_DIR/generate-gql-error-index-from-template.py"
21+
# Step 1: Validate index.adoc against the individual files
22+
echo "Step 1: Validating index.adoc against individual files..."
23+
python3 "$SCRIPT_DIR/validate-error-index.py"
24+
VALIDATION_RESULT=$?
2925

30-
if [ ! -f "$AUTO_INDEX_FILE" ]; then
31-
echo "Error: Failed to generate auto-index.adoc"
32-
exit 1
33-
fi
26+
# Step 2: Check validation result
27+
if [ $VALIDATION_RESULT -eq 0 ]; then
28+
echo "✅ Validation passed! index.adoc is consistent with individual files."
29+
echo "No further action needed."
30+
exit 0
31+
else
32+
echo "❌ Validation failed. Checking if there are missing entries..."
3433

35-
echo "Generation completed successfully."
34+
# Step 3: Generate auto-index.adoc
35+
echo "Step 3: Generating auto-index.adoc from template..."
36+
python3 "$SCRIPT_DIR/generate-gql-error-index-from-template.py"
37+
GENERATION_RESULT=$?
3638

37-
# Step 2: Validate auto-index.adoc against the files.
38-
echo "Validating auto-index.adoc against the files and index.adoc..."
39-
python3 "$SCRIPT_DIR/validate_error_index.py"
40-
VALIDATION_RESULT=$?
39+
if [ $GENERATION_RESULT -ne 0 ] || [ ! -f "$AUTO_INDEX_FILE" ]; then
40+
echo "Error: Failed to generate auto-index.adoc"
41+
exit 1
42+
fi
4143

42-
# Step 3: If validation passes, replace index.adoc with auto-index.adoc
43-
if [ $VALIDATION_RESULT -eq 0 ]; then
44-
echo "Validation passed! Replacing index.adoc with auto-index.adoc..."
44+
echo "Generation completed successfully."
4545

46-
# Replace index with auto-index
47-
rm "$INDEX_FILE"
48-
mv "$AUTO_INDEX_FILE" "$INDEX_FILE"
46+
# Step 4: Validate auto-index.adoc against individual files
47+
echo "Step 4: Validating auto-index.adoc against individual files..."
48+
python3 "$SCRIPT_DIR/validate-error-auto-index.py"
49+
AUTO_VALIDATION_RESULT=$?
4950

50-
echo "✅ Update completed successfully. index.adoc has been updated."
51-
else
52-
echo "❌ Validation failed with exit code $VALIDATION_RESULT. No changes were made to index.adoc."
53-
echo "Please review the validation errors and fix any discrepancies."
54-
fi
51+
# Step 5: If auto-index validation passes, replace index.adoc
52+
if [ $AUTO_VALIDATION_RESULT -eq 0 ]; then
53+
echo "Step 5: Auto-index validation passed! Replacing index.adoc with auto-index.adoc..."
54+
55+
# Replace index with auto-index
56+
rm "$INDEX_FILE"
57+
mv "$AUTO_INDEX_FILE" "$INDEX_FILE"
5558

56-
exit $VALIDATION_RESULT
59+
echo "✅ Update completed successfully. index.adoc has been updated."
60+
exit 0
61+
else
62+
echo "❌ Auto-index validation failed with exit code $AUTO_VALIDATION_RESULT."
63+
echo "No changes were made to index.adoc."
64+
echo "Please review the validation errors and fix any discrepancies."
65+
exit $AUTO_VALIDATION_RESULT
66+
fi
67+
fi
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import re
5+
from pathlib import Path
6+
import argparse
7+
import sys
8+
from difflib import ndiff
9+
10+
def get_error_codes_from_index(index_file):
11+
"""Extract error codes and their descriptions from the index file."""
12+
error_codes = {}
13+
14+
try:
15+
with open(index_file, 'r') as f:
16+
content = f.read()
17+
18+
# Find all error code entries with descriptions
19+
pattern = r'=== xref:errors/gql-errors/([A-Z0-9]{5})\.adoc\[\1\]\s*\n\s*\n\s*Status description:: (.*?)(?=\n\s*\n|\n===|\Z)'
20+
matches = re.findall(pattern, content, re.DOTALL)
21+
22+
for code, desc in matches:
23+
error_codes[code] = desc.strip()
24+
25+
return error_codes
26+
except Exception as e:
27+
print(f"Error reading index file {index_file}: {e}")
28+
return {}
29+
30+
def get_page_roles_from_index(index_file):
31+
"""Extract page roles for error codes from the index file."""
32+
roles = {}
33+
34+
try:
35+
with open(index_file, 'r') as f:
36+
lines = f.readlines()
37+
38+
for i, line in enumerate(lines):
39+
role_match = re.search(r'\[role=label--([^\]]+)\]', line)
40+
if role_match and i + 1 < len(lines):
41+
code_match = re.search(r'xref:errors/gql-errors/([A-Z0-9]{5})\.adoc', lines[i+1])
42+
if code_match:
43+
roles[code_match.group(1)] = role_match.group(1)
44+
45+
return roles
46+
except Exception as e:
47+
print(f"Error reading index file for roles {index_file}: {e}")
48+
return {}
49+
50+
def get_error_codes_from_files(errors_dir):
51+
"""Get error codes and descriptions from individual error files."""
52+
error_codes = {}
53+
54+
try:
55+
for file in os.listdir(errors_dir):
56+
if file.endswith('.adoc') and file != 'index.adoc' and file != 'auto-index.adoc':
57+
error_code = file[:-5] # Remove .adoc extension
58+
file_path = os.path.join(errors_dir, file)
59+
60+
# Extract description from file
61+
description = extract_description_from_file(file_path)
62+
63+
if description:
64+
error_codes[error_code] = description
65+
else:
66+
error_codes[error_code] = None
67+
68+
return error_codes
69+
except Exception as e:
70+
print(f"Error scanning error files directory {errors_dir}: {e}")
71+
return {}
72+
73+
def extract_description_from_file(file_path):
74+
"""Extract the status description from an error file."""
75+
try:
76+
with open(file_path, 'r') as f:
77+
content = f.read()
78+
79+
# Try different patterns to match the description
80+
patterns = [
81+
r'== Status description\s*\n(.*?)(?=\n\n|\n==|\Z)', # Format with standalone line
82+
r'Status description::\s*(.*?)(?=\n\n|\n==|\Z)' # Format with Status description:: prefix
83+
]
84+
85+
for pattern in patterns:
86+
match = re.search(pattern, content, re.DOTALL)
87+
if match:
88+
description = match.group(1).strip()
89+
if description.startswith('Status description::'):
90+
description = description[len('Status description::'):].strip()
91+
return description
92+
93+
return None
94+
except Exception as e:
95+
print(f"Error reading file {file_path}: {e}")
96+
return None
97+
98+
def get_page_roles_from_files(errors_dir):
99+
"""Get page roles from individual error files."""
100+
roles = {}
101+
102+
try:
103+
for file in os.listdir(errors_dir):
104+
if file.endswith('.adoc') and file != 'index.adoc' and file != 'auto-index.adoc':
105+
error_code = file[:-5] # Remove .adoc extension
106+
file_path = os.path.join(errors_dir, file)
107+
108+
# Extract page role from file
109+
with open(file_path, 'r') as f:
110+
for line in f:
111+
if line.strip().startswith(':page-role:'):
112+
roles[error_code] = line.strip()[11:].strip()
113+
break
114+
115+
return roles
116+
except Exception as e:
117+
print(f"Error scanning error files for roles {errors_dir}: {e}")
118+
return {}
119+
120+
def format_description(desc, max_len=60):
121+
"""Format a description for display, truncating if necessary."""
122+
if not desc:
123+
return "MISSING"
124+
if len(desc) > max_len:
125+
return f"{desc[:max_len]}..."
126+
return desc
127+
128+
def validate_error_parity(errors_dir, auto_index_file, verbose=False):
129+
"""Validate error code parity between auto-index and individual files."""
130+
# Get error codes from auto-index and files
131+
auto_index_codes = get_error_codes_from_index(auto_index_file)
132+
file_codes = get_error_codes_from_files(errors_dir)
133+
134+
# Get page roles
135+
auto_index_roles = get_page_roles_from_index(auto_index_file)
136+
file_roles = get_page_roles_from_files(errors_dir)
137+
138+
# Find missing files and entries
139+
codes_in_auto_index_not_in_files = set(auto_index_codes.keys()) - set(file_codes.keys())
140+
codes_in_files_not_in_auto_index = set(file_codes.keys()) - set(auto_index_codes.keys())
141+
142+
# Find description mismatches
143+
description_mismatches = []
144+
for code in set(auto_index_codes.keys()) & set(file_codes.keys()):
145+
auto_index_desc = auto_index_codes[code] if code in auto_index_codes else None
146+
file_desc = file_codes[code] if code in file_codes else None
147+
148+
# If file has no description, this isn't a mismatch, just incomplete documentation
149+
if file_desc is None:
150+
continue
151+
152+
# Compare normalized descriptions (strip whitespace and standardize spacing)
153+
if auto_index_desc and file_desc:
154+
auto_index_desc_norm = re.sub(r'\s+', ' ', auto_index_desc.strip())
155+
file_desc_norm = re.sub(r'\s+', ' ', file_desc.strip())
156+
157+
if auto_index_desc_norm != file_desc_norm:
158+
description_mismatches.append((code, auto_index_desc, file_desc))
159+
160+
# Find role mismatches
161+
role_mismatches = []
162+
for code in set(auto_index_roles.keys()) | set(file_roles.keys()):
163+
auto_index_role = auto_index_roles.get(code)
164+
file_role = file_roles.get(code)
165+
166+
if auto_index_role != file_role:
167+
role_mismatches.append((code, auto_index_role, file_role))
168+
169+
# Print results
170+
print(f"\n=== Auto-Index Validation Results ===\n")
171+
print(f"Total error codes in auto-index: {len(auto_index_codes)}")
172+
print(f"Total error code files: {len(file_codes)}")
173+
174+
# Missing files
175+
if codes_in_auto_index_not_in_files:
176+
print(f"\n{len(codes_in_auto_index_not_in_files)} error codes in auto-index but missing files:")
177+
for code in sorted(codes_in_auto_index_not_in_files):
178+
print(f" - {code}: {format_description(auto_index_codes.get(code))}")
179+
else:
180+
print("\nNo error codes are missing files. ✓")
181+
182+
# Missing entries
183+
if codes_in_files_not_in_auto_index:
184+
print(f"\n{len(codes_in_files_not_in_auto_index)} error files without auto-index entries:")
185+
for code in sorted(codes_in_files_not_in_auto_index):
186+
print(f" - {code}: {format_description(file_codes.get(code))}")
187+
else:
188+
print("\nNo error files are missing from the auto-index. ✓")
189+
190+
# Description mismatches
191+
if description_mismatches:
192+
print(f"\n{len(description_mismatches)} description mismatches:")
193+
for code, auto_index_desc, file_desc in sorted(description_mismatches):
194+
print(f"\n - {code}:")
195+
print(f" Auto-Index: {format_description(auto_index_desc)}")
196+
print(f" File : {format_description(file_desc)}")
197+
198+
if verbose:
199+
# Show exact differences for detailed debugging
200+
print("\n Detailed differences:")
201+
differences = list(ndiff(auto_index_desc.splitlines(), file_desc.splitlines()))
202+
for line in differences:
203+
if line.startswith('+ ') or line.startswith('- ') or line.startswith('? '):
204+
print(f" {line}")
205+
else:
206+
print("\nNo description mismatches found. ✓")
207+
208+
# Role mismatches
209+
if role_mismatches:
210+
print(f"\n{len(role_mismatches)} page role mismatches:")
211+
for code, auto_index_role, file_role in sorted(role_mismatches):
212+
print(f" - {code}:")
213+
print(f" Auto-Index: {auto_index_role or 'MISSING'}")
214+
print(f" File : {file_role or 'MISSING'}")
215+
else:
216+
print("\nNo page role mismatches found. ✓")
217+
218+
# Final status
219+
if not (codes_in_auto_index_not_in_files or codes_in_files_not_in_auto_index or
220+
description_mismatches or role_mismatches):
221+
print("\n✅ All validations passed! Auto-index is consistent with individual files.")
222+
return True
223+
else:
224+
print("\n❌ Validation failed.")
225+
return False
226+
227+
def main():
228+
parser = argparse.ArgumentParser(description='Validate auto-index.adoc against individual error files')
229+
parser.add_argument('--auto-index', help='Path to auto-index.adoc file')
230+
parser.add_argument('--dir', help='Path to directory containing error files')
231+
parser.add_argument('--verbose', '-v', action='store_true', help='Show detailed differences for mismatches')
232+
args = parser.parse_args()
233+
234+
# Get the script's directory
235+
script_dir = Path(__file__).parent.absolute()
236+
237+
# Default paths if not specified
238+
errors_dir = args.dir if args.dir else script_dir.parent / 'modules' / 'ROOT' / 'pages' / 'errors' / 'gql-errors'
239+
auto_index_file = args.auto_index if args.auto_index else errors_dir / 'auto-index.adoc'
240+
241+
# Validate that the paths exist
242+
if not os.path.exists(errors_dir):
243+
print(f"Error: Directory not found: {errors_dir}")
244+
return 1
245+
246+
if not os.path.exists(auto_index_file):
247+
print(f"Error: Auto-index file not found: {auto_index_file}")
248+
return 1
249+
250+
# Run validation
251+
success = validate_error_parity(errors_dir, auto_index_file, args.verbose)
252+
253+
# Return 0 if success, 1 otherwise
254+
return 0 if success else 1
255+
256+
if __name__ == '__main__':
257+
sys.exit(main())

0 commit comments

Comments
 (0)