import sys
import re
from pathlib import Path
COMMANDS = {
'arch', 'base32', 'base64', 'basename', 'basenc', 'cat', 'chcon', 'chgrp', 'chmod',
'chown', 'chroot', 'cksum', 'comm', 'cp', 'csplit', 'cut', 'date', 'dd', 'df',
'dir', 'dircolors', 'dirname', 'du', 'echo', 'env', 'expand', 'expr', 'factor',
'false', 'fmt', 'fold', 'groups', 'hashsum', 'head', 'hostid', 'hostname', 'id',
'install', 'join', 'kill', 'link', 'ln', 'logname', 'ls', 'mkdir', 'mkfifo', 'mknod',
'mktemp', 'more', 'mv', 'nice', 'nl', 'nohup', 'nproc', 'numfmt', 'od', 'paste',
'pathchk', 'pinky', 'pr', 'printenv', 'printf', 'ptx', 'pwd', 'readlink', 'realpath',
'rm', 'rmdir', 'runcon', 'seq', 'shred', 'shuf', 'sleep', 'sort', 'split', 'stat',
'stdbuf', 'stty', 'sum', 'sync', 'tac', 'tail', 'tee', 'test', 'timeout', 'touch',
'tr', 'true', 'truncate', 'tsort', 'tty', 'uname', 'unexpand', 'uniq', 'unlink',
'uptime', 'users', 'vdir', 'wc', 'who', 'whoami', 'yes', 'uucore'
}
def split_into_sections(text):
parts = text.split("## What's Changed", 1)
if len(parts) != 2:
print("Error: '## What's Changed' section not found.")
sys.exit(1)
header = parts[0].strip()
rest = parts[1]
next_section_idx = rest.find("##", 1)
if next_section_idx == -1:
whats_changed_block = rest.strip()
tail = ""
else:
whats_changed_block = rest[:next_section_idx].strip()
tail = rest[next_section_idx:].strip()
lines = [line.strip() for line in whats_changed_block.splitlines() if line.strip()]
return header, lines, tail
def categorize_lines(lines):
dep_pattern = re.compile(
r'^\* (?:chore|fix)\(deps\): update .+? by @.+? in https://github.com/.+?$', re.IGNORECASE
)
deps = []
command_sections = {cmd: [] for cmd in COMMANDS}
general = []
for line in lines:
if dep_pattern.match(line):
deps.append(line)
continue
matched = False
for cmd in sorted(COMMANDS, key=len, reverse=True):
if re.search(rf"\b{re.escape(cmd)}\b", line, re.IGNORECASE):
command_sections[cmd].append(line)
matched = True
break
if not matched:
general.append(line)
command_sections = {cmd: items for cmd, items in command_sections.items() if items}
return general, deps, command_sections
def build_output(header, general, deps, command_sections, tail):
output = [header, "## What's Changed", ""]
output.extend(general)
if deps:
output.append("\n## Dependency Updates")
output.extend(deps)
for cmd in sorted(command_sections):
output.append(f"\n## {cmd}")
output.extend(command_sections[cmd])
if tail:
output.append("\n" + tail)
return "\n".join(output).strip() + "\n"
def main():
if len(sys.argv) != 2:
print(f"Usage: {Path(sys.argv[0]).name} <changelog-file>")
sys.exit(1)
path = Path(sys.argv[1])
if not path.is_file():
print(f"Error: File '{path}' not found.")
sys.exit(1)
text = path.read_text(encoding="utf-8")
header, lines, tail = split_into_sections(text)
general, deps, command_sections = categorize_lines(lines)
result = build_output(header, general, deps, command_sections, tail)
output_path = path.with_name(path.stem + ".with-commands.md")
output_path.write_text(result, encoding="utf-8")
print(f"Wrote organized changelog to: {output_path}")
if __name__ == "__main__":
main()