Skip to main content

scripts-generate-all-md-fix-scripts

#!/usr/bin/env python3 """

title: "MD001: Heading levels should only increment by one level at a time" component_type: script version: "1.0.0" audience: contributor status: stable summary: "Generate all markdown fix scripts based on markdownlint rules. This meta-script creates indiv..." keywords: ['all', 'fix', 'generate', 'scripts'] tokens: ~500 created: 2025-12-22 updated: 2025-12-22 script_name: "generate-all-md-fix-scripts.py" language: python executable: true usage: "python3 scripts/generate-all-md-fix-scripts.py [options]" python_version: "3.10+" dependencies: [] modifies_files: false network_access: false requires_auth: false

Generate all markdown fix scripts based on markdownlint rules. This meta-script creates individual fix scripts for each MD rule. """

SCRIPT_TEMPLATES = { 'MD001': """#!/usr/bin/env python3

MD001: Heading levels should only increment by one level at a time

import re from pathlib import Path

def fix_md001(content): lines = content.split('\n') result = [] last_level = 0 fixed = 0

for line in lines:
if match := re.match(r'^(#{1,6})\\s+', line):
level = len(match.group(1))
if level > last_level + 1:
# Fix: reduce to one level increment
correct_level = last_level + 1
result.append('#' * correct_level + line[level:])
fixed += 1
last_level = correct_level
else:
result.append(line)
last_level = level
else:
result.append(line)

return '\\n'.join(result), fixed

""",

'MD003': """#!/usr/bin/env python3

MD003: Heading style (consistent ATX style)

import re from pathlib import Path

def fix_md003(content): lines = content.split('\n') result = [] fixed = 0

for line in lines:
# Convert setext (underlined) to ATX style
if i > 0 and result:
prev = result[-1]
if re.match(r'^=+$', line.strip()):
result[-1] = '# ' + prev
result.append('')
fixed += 1
continue
elif re.match(r'^-+$', line.strip()):
result[-1] = '## ' + prev
result.append('')
fixed += 1
continue
result.append(line)

return '\\n'.join(result), fixed

""",

'MD007': """#!/usr/bin/env python3

MD007: Unordered list indentation (2 spaces)

import re from pathlib import Path

def fix_md007(content): lines = content.split('\n') result = [] fixed = 0

for line in lines:
if match := re.match(r'^(\\s*)([-*+])\\s+', line):
indent, marker = match.groups()
level = len(indent) // 2
correct_indent = ' ' * level
if indent != correct_indent:
result.append(correct_indent + marker + line[len(indent)+1:])
fixed += 1
else:
result.append(line)
else:
result.append(line)

return '\\n'.join(result), fixed

""",

'MD026': """#!/usr/bin/env python3

MD026: Trailing punctuation in heading

import re from pathlib import Path

def fix_md026(content): lines = content.split('\n') result = [] fixed = 0

for line in lines:
if match := re.match(r'^(#{1,6}\\s+.*?)([.,:;!?]+)$', line):
result.append(match.group(1))
fixed += 1
else:
result.append(line)

return '\\n'.join(result), fixed

""",

'MD027': """#!/usr/bin/env python3

MD027: Multiple spaces after blockquote symbol

import re from pathlib import Path

def fix_md027(content): lines = content.split('\n') result = [] fixed = 0

for line in lines:
if match := re.match(r'^(>+)\\s{2,}', line):
result.append(match.group(1) + ' ' + line[len(match.group(0)):])
fixed += 1
else:
result.append(line)

return '\\n'.join(result), fixed

""",

'MD034': """#!/usr/bin/env python3

MD034: Bare URL used (should use angle brackets or markdown link)

import re from pathlib import Path

def fix_md034(content): lines = content.split('\n') result = [] fixed = 0 url_pattern = r'(?<!\[)(?<!\()https?://[^\s<>)]+(?!\])'

for line in lines:
if re.search(url_pattern, line):
line = re.sub(url_pattern, r'<\\g<0>>', line)
fixed += 1
result.append(line)

return '\\n'.join(result), fixed

""",

'MD037': """#!/usr/bin/env python3

MD037: Spaces inside emphasis markers

import re from pathlib import Path

def fix_md037(content): content, n1 = re.subn(r'\\s+([^]+?)\s+\', r'\1*', content) content, n2 = re.subn(r'\s+([^]+?)\s+', r'\1_', content) content, n3 = re.subn(r'\\\s+([^]+?)\s+\\*', r'\1', content) content, n4 = re.subn(r'\s+([^_]+?)\s+', r'\1', content) return content, n1 + n2 + n3 + n4 """,

'MD042': """#!/usr/bin/env python3

MD042: No empty links

import re from pathlib import Path

def fix_md042(content): fixed = 0 # Remove links with empty text: content, n1 = re.subn(r'\[\]\([^)]+\)', '', content) # Remove reference links with empty text: [] content, n2 = re.subn(r'\[\]\[[^\]]+\]', '', content) return content, n1 + n2 """,

'MD045': """#!/usr:usr/bin/env python3

MD045: Images should have alternate text

import re from pathlib import Path

def fix_md045(content): fixed = 0 def add_alt(match): nonlocal fixed fixed += 1 return f"Image"

content = re.sub(r'!\\[\\]\\(([^)]+)\\)', add_alt, content)
return content, fixed

""",

'MD058': """#!/usr/bin/env python3

MD058: Tables should be surrounded by blank lines

import re from pathlib import Path

def is_table_row(line): return bool(re.match(r'^\s*\|.\|\s$', line.strip()))

def fix_md058(content): lines = content.split('\n') result = [] in_table = False fixed = 0

for i, line in enumerate(lines):
is_table = is_table_row(line)

if is_table and not in_table:
if result and result[-1].strip():
result.append('')
fixed += 1
in_table = True
elif not is_table and in_table:
if line.strip():
result.append('')
fixed += 1
in_table = False

result.append(line)

return '\\n'.join(result), fixed

""", }

import os

def create_fix_script(rule_code, template): script_name = f"fix-{rule_code.lower()}.py" script_path = f"scripts/{script_name}"

# Add standard wrapper
full_script = template + """

def process_file(file_path, dry_run=False): try: with open(file_path, 'r', encoding='utf-8') as f: content = f.read() fixed_content, count = {}(content) if count > 0 and not dry_run: with open(file_path, 'w', encoding='utf-8') as f: f.write(fixed_content) return count except Exception as e: print(f"Error: {{e}}") return 0

if name == 'main': import argparse parser = argparse.ArgumentParser(description='Fix {}') parser.add_argument('paths', nargs='*', default=['.']) parser.add_argument('--dry-run', action='store_true') args = parser.parse_args()

total = 0
for path_str in args.paths:
for md_file in Path(path_str).rglob('*.md'):
count = process_file(md_file, args.dry_run)
if count:
print(f"{{'[DRY RUN] ' if args.dry_run else ''}}Fixed {{count}} issues in {{md_file}}")
total += count
print(f"\\nTotal: {{total}} issues fixed")

""".format(f"fix_{rule_code.lower()}", rule_code)

with open(script_path, 'w') as f:
f.write(full_script)

os.chmod(script_path, 0o755)
return script_name

def main(): """Generate markdown fix scripts based on markdownlint rules.""" import argparse

parser = argparse.ArgumentParser(
description='Generate markdown fix scripts for markdownlint rules',
epilog='Creates individual fix scripts (fix-mdXXX.py) in the scripts/ directory'
)
parser.add_argument(
'--rules', '-r',
nargs='*',
choices=list(SCRIPT_TEMPLATES.keys()),
help='Specific rules to generate scripts for (default: all)'
)
parser.add_argument(
'--list', '-l',
action='store_true',
help='List available rules without generating scripts'
)
parser.add_argument(
'--dry-run',
action='store_true',
help='Show what would be created without writing files'
)

args = parser.parse_args()

# List mode
if args.list:
print("Available markdownlint rules:")
for rule in SCRIPT_TEMPLATES.keys():
print(f" {rule}")
return 0

# Determine which rules to process
rules_to_process = args.rules if args.rules else SCRIPT_TEMPLATES.keys()

created = []
for rule in rules_to_process:
template = SCRIPT_TEMPLATES[rule]
if args.dry_run:
print(f"Would create: fix-{rule.lower()}.py")
created.append(f"fix-{rule.lower()}.py")
else:
script_name = create_fix_script(rule, template)
created.append(script_name)
print(f"Created: {script_name}")

print(f"\nTotal: {len(created)} scripts {'would be ' if args.dry_run else ''}generated")
return 0

if name == 'main': import sys sys.exit(main())