160 lines
5.4 KiB
Python
160 lines
5.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Fix markdown linting errors: MD040, MD013, MD060, MD034."""
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
def fix_code_block_languages(content):
|
|
"""Add language specifier to code blocks without one (MD040)."""
|
|
# Pattern: opening fence with no language specifier (``` followed by newline)
|
|
pattern = r'(^```)\n'
|
|
fixed = re.sub(pattern, r'\1text\n', content, flags=re.MULTILINE)
|
|
return fixed
|
|
|
|
def fix_line_length(content):
|
|
"""Break long lines to fit 150 character limit (MD013)."""
|
|
lines = content.split('\n')
|
|
fixed_lines = []
|
|
|
|
for line in lines:
|
|
# Skip code blocks, tables, headings with formatting
|
|
if line.startswith('```') or line.startswith('|') or line.startswith('>'):
|
|
fixed_lines.append(line)
|
|
continue
|
|
|
|
# If line is longer than 150 chars
|
|
if len(line) > 150:
|
|
# For paragraphs, break at word boundaries
|
|
if not line.startswith('#') and not line.startswith('-') and not line.startswith('*'):
|
|
words = line.split(' ')
|
|
current_line = ''
|
|
for word in words:
|
|
test_line = current_line + (' ' if current_line else '') + word
|
|
if len(test_line) <= 150:
|
|
current_line = test_line
|
|
else:
|
|
if current_line:
|
|
fixed_lines.append(current_line)
|
|
current_line = word
|
|
if current_line:
|
|
fixed_lines.append(current_line)
|
|
continue
|
|
|
|
fixed_lines.append(line)
|
|
|
|
return '\n'.join(fixed_lines)
|
|
|
|
def fix_table_formatting(content):
|
|
"""Fix table formatting: ensure spaces around all pipes (MD060)."""
|
|
lines = content.split('\n')
|
|
fixed_lines = []
|
|
|
|
i = 0
|
|
while i < len(lines):
|
|
line = lines[i]
|
|
|
|
# Check if this is a table line (contains pipes)
|
|
if '|' in line:
|
|
# For markdown tables, we need to ensure:
|
|
# 1. Each cell has space after opening | and before closing |
|
|
# 2. Header separator is properly formatted
|
|
|
|
# Split by pipes
|
|
cells = line.split('|')
|
|
|
|
# Rebuild with proper spacing
|
|
# First element might be empty (if line starts with |)
|
|
fixed_cells = []
|
|
for j, cell in enumerate(cells):
|
|
# For separator rows (containing dashes), just trim and preserve
|
|
if '-' in cell and not any(c.isalnum() for c in cell.replace('-', '').replace(' ', '')):
|
|
# This is a separator cell - ensure it's proper format: ---
|
|
trimmed = cell.strip()
|
|
if trimmed and all(c == '-' or c == ':' for c in trimmed):
|
|
fixed_cells.append(' ' + trimmed + ' ')
|
|
else:
|
|
fixed_cells.append(' ' + cell.strip() + ' ')
|
|
else:
|
|
# Regular cell - ensure spaces
|
|
trimmed = cell.strip()
|
|
if trimmed:
|
|
fixed_cells.append(' ' + trimmed + ' ')
|
|
else:
|
|
fixed_cells.append(' ')
|
|
|
|
fixed_line = '|' + '|'.join(fixed_cells) + '|'
|
|
fixed_lines.append(fixed_line)
|
|
else:
|
|
fixed_lines.append(line)
|
|
|
|
i += 1
|
|
|
|
return '\n'.join(fixed_lines)
|
|
|
|
def fix_bare_urls(content):
|
|
"""Convert bare URLs to markdown links (MD034)."""
|
|
# Pattern: URL not already in markdown link or code block
|
|
# This is conservative - only fixes obvious cases
|
|
pattern = r'([^[])(https?://[^\s\)]+)'
|
|
fixed = re.sub(pattern, r'\1[\2](\2)', content)
|
|
return fixed
|
|
|
|
def fix_file(filepath):
|
|
"""Fix all markdown linting errors in a single file."""
|
|
try:
|
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Apply fixes in order
|
|
content = fix_code_block_languages(content)
|
|
content = fix_line_length(content)
|
|
content = fix_table_formatting(content)
|
|
content = fix_bare_urls(content)
|
|
|
|
with open(filepath, 'w', encoding='utf-8') as f:
|
|
f.write(content)
|
|
|
|
return True
|
|
except Exception as e:
|
|
print(f"Error processing {filepath}: {e}", file=sys.stderr)
|
|
return False
|
|
|
|
def main():
|
|
"""Fix markdown linting errors in all AI documentation files."""
|
|
docs_root = Path('provisioning/docs/src')
|
|
|
|
# All AI files (complete list)
|
|
ai_files = [
|
|
'ai/ai-agents.md',
|
|
'ai/ai-assisted-forms.md',
|
|
'ai/architecture.md',
|
|
'ai/config-generation.md',
|
|
'ai/configuration.md',
|
|
'ai/cost-management.md',
|
|
'ai/mcp-integration.md',
|
|
'ai/natural-language-config.md',
|
|
'ai/rag-system.md',
|
|
'ai/README.md',
|
|
'ai/security-policies.md',
|
|
'ai/troubleshooting-with-ai.md',
|
|
]
|
|
|
|
success_count = 0
|
|
for filepath_rel in ai_files:
|
|
filepath = docs_root / filepath_rel
|
|
if filepath.exists():
|
|
if fix_file(filepath):
|
|
print(f"✓ Fixed {filepath_rel}")
|
|
success_count += 1
|
|
else:
|
|
print(f"✗ Failed to fix {filepath_rel}")
|
|
else:
|
|
print(f"⚠ File not found: {filepath_rel}")
|
|
|
|
print(f"\n✓ Fixed {success_count}/{len(ai_files)} AI files")
|
|
return 0 if success_count == len(ai_files) else 1
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|