diff --git a/.factory/settings.json b/.factory/settings.json index 3ee34bf..fd5ad54 100644 --- a/.factory/settings.json +++ b/.factory/settings.json @@ -51,7 +51,7 @@ "extraArgs": { "parallel_tool_calls": true, "output_config": { - "effort": "max" + "effort": "high" } }, "noImageSupport": true, diff --git a/.factory/skills/rules/SKILL.md b/.factory/skills/rules/SKILL.md index 5949bca..f4d30ce 100644 --- a/.factory/skills/rules/SKILL.md +++ b/.factory/skills/rules/SKILL.md @@ -32,6 +32,24 @@ Read the rule files from `~/.factory/rules/` and apply them as needed. When starting work on a project, run `colgrep init` to build the semantic search index. This enables the colgrep skill for semantic code search across the codebase. The index auto-updates on subsequent searches, so `init` only needs to run once per project. +### Markdown Table Justification + +To justify markdown tables (ASCII character count justified), use the built-in script: + +```bash +# Justify tables in a file (executable with uv shebang) +~/.factory/skills/rules/table_justify.py + +# Or via python +python ~/.factory/skills/rules/table_justify.py + +# Justify and save to new file +~/.factory/skills/rules/table_justify.py -o + +# Justify from stdin +cat table.md | ~/.factory/skills/rules/table_justify.py +``` + ## Research - Back all claims with reference code. diff --git a/.factory/skills/rules/table_justify.py b/.factory/skills/rules/table_justify.py new file mode 100755 index 0000000..da0f8b0 --- /dev/null +++ b/.factory/skills/rules/table_justify.py @@ -0,0 +1,152 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.11" +# /// +""" +Markdown Table Justifier + +ASCII character-justifies markdown tables by padding all cells +to match the maximum width in each column. + +Usage: + ./table_justify.py + ./table_justify.py -o + cat | table-justify +""" + +import argparse +import re +import sys +from pathlib import Path + + +def parse_table(lines: list[str]) -> tuple[list[list[str]], list[int]]: + """ + Parse a markdown table into rows and calculate column widths. + Returns (rows, max_widths). + """ + rows = [] + max_widths = [] + + for line in lines: + line = line.rstrip() + if not line.startswith("|"): + continue + + # Split by | and strip whitespace + cells = [cell.strip() for cell in line.split("|")] + # Remove empty first/last cells from leading/trailing | + cells = [c for c in cells if c or c == ""] + if len(cells) > 0 and cells[0] == "": + cells = cells[1:] + if len(cells) > 0 and cells[-1] == "": + cells = cells[:-1] + + if not cells: + continue + + rows.append(cells) + + # Update max widths + while len(max_widths) < len(cells): + max_widths.append(0) + + for i, cell in enumerate(cells): + max_widths[i] = max(max_widths[i], len(cell)) + + return rows, max_widths + + +def is_separator_row(row: list[str]) -> bool: + """Check if a row is a header separator (all dashes).""" + if not row: + return False + return all(re.match(r"^-+$", cell.strip().replace(" ", "")) or cell.strip() == "" for cell in row) + + +def format_separator(widths: list[int]) -> str: + """Format the separator row.""" + cells = ["-" * w for w in widths] + return "|" + "|".join(f" {c} " for c in cells) + "|" + + +def format_row(row: list[str], widths: list[int]) -> str: + """Format a data row with proper padding.""" + padded = [] + for i, cell in enumerate(row): + if i < len(widths): + padded.append(cell.ljust(widths[i])) + else: + padded.append(cell) + return "|" + "|".join(f" {c} " for c in padded) + "|" + + +def justify_table(lines: list[str]) -> list[str]: + """Justify a markdown table.""" + rows, max_widths = parse_table(lines) + + if not rows: + return lines + + result = [] + for i, row in enumerate(rows): + if i == 1 and is_separator_row(row): + result.append(format_separator(max_widths)) + else: + result.append(format_row(row, max_widths)) + + return result + + +def process_content(content: str) -> str: + """Process content and justify all tables found.""" + lines = content.split("\n") + result = [] + table_lines = [] + in_table = False + + for line in lines: + if line.strip().startswith("|"): + table_lines.append(line) + in_table = True + else: + if in_table: + # End of table, process it + result.extend(justify_table(table_lines)) + table_lines = [] + in_table = False + result.append(line) + + # Process last table if file ends with one + if table_lines: + result.extend(justify_table(table_lines)) + + return "\n".join(result) + + +def main(): + parser = argparse.ArgumentParser( + description="ASCII character-justify markdown tables" + ) + parser.add_argument("input", nargs="?", help="Input file (default: stdin)") + parser.add_argument("-o", "--output", help="Output file (default: stdout)") + args = parser.parse_args() + + # Read input + if args.input: + content = Path(args.input).read_text() + else: + content = sys.stdin.read() + + # Process + result = process_content(content) + + # Write output + if args.output: + Path(args.output).write_text(result) + else: + print(result) + + +if __name__ == "__main__": + main()