diff --git a/app/cli_pages.py b/app/cli_pages.py new file mode 100644 index 0000000..729fe3d --- /dev/null +++ b/app/cli_pages.py @@ -0,0 +1,125 @@ +""" +CLI commands for checking and fixing image instruction page order. + +Lives in its own module rather than in cli.py so the page-ordering work stays +together: the logic is in app/services/page_order.py and the commands that +drive it are here. + + flask pages audit which sets have broken numbering + flask pages fix --dry-run what would be repaired + flask pages fix repair every set that needs it + flask pages fix --set 60123 repair one set + flask pages sort 60123 --by filename reorder one set + flask pages sort 60123 --by page_hint --reverse + +Created by: Jess Rogerson (yelling commands at Claude.AI) +""" + +import click +from flask.cli import AppGroup + +pages_cli = AppGroup( + 'pages', help='Check and fix the page order of image instructions.') + + +def _sets_with_images(): + """Every set that has at least one image page, in set-number order.""" + from app import db + from app.models.set import Set + from app.models.instruction import Instruction + + ids = [row[0] for row in db.session.query(Instruction.set_id) + .filter(Instruction.file_type == 'IMAGE').distinct()] + return Set.query.filter(Set.id.in_(ids)).order_by(Set.set_number).all() + + +@pages_cli.command('audit') +@click.option('--all/--problems', 'show_all', default=False, + help='List every set, or only the ones with a numbering problem ' + '(default).') +def pages_audit(show_all): + """Report duplicate, missing or unnumbered pages across the library.""" + from app.services import page_order + + problems = 0 + for lego_set in _sets_with_images(): + report = page_order.audit(lego_set.id) + if report['is_clean'] and not show_all: + continue + if not report['is_clean']: + problems += 1 + + state = 'ok' if report['is_clean'] else 'NEEDS FIXING' + colour = 'green' if report['is_clean'] else 'yellow' + click.secho(f"{lego_set.set_number:<12} {report['total']:>4} pages " + f"{state}", fg=colour) + if report['duplicates']: + click.echo(f" duplicates : {report['duplicates']}") + if report['gaps']: + click.echo(f" gaps : {report['gaps']}") + if report['unnumbered']: + click.echo(f" unnumbered : {report['unnumbered']}") + + click.echo(f"\n{problems} set(s) need fixing.") + + +@pages_cli.command('fix') +@click.option('--set', 'set_number', default=None, + help='One set number. Omit to fix every set that needs it.') +@click.option('--dry-run', is_flag=True, help='Report without writing.') +def pages_fix(set_number, dry_run): + """Renumber pages 1..n, closing gaps and clearing duplicates.""" + from app.models.set import Set + from app.services import page_order + + if set_number: + targets = Set.query.filter_by(set_number=set_number).all() + if not targets: + raise click.ClickException(f'No set numbered {set_number}') + else: + targets = _sets_with_images() + + fixed = 0 + for lego_set in targets: + report = page_order.audit(lego_set.id) + if report['is_clean']: + continue + fixed += 1 + if dry_run: + click.echo(f"would fix {lego_set.set_number} " + f"({report['total']} pages)") + continue + page_order.renumber(lego_set.id) + click.secho(f"fixed {lego_set.set_number} ({report['total']} pages)", + fg='green') + + click.echo(f"{fixed} set(s) {'would be ' if dry_run else ''}fixed.") + + +@pages_cli.command('sort') +@click.argument('set_number') +@click.option('--by', 'strategy', default='filename', + help='filename, page_hint, uploaded or size.') +@click.option('--reverse', is_flag=True, help='Sort the other way round.') +def pages_sort(set_number, strategy, reverse): + """Reorder one set's image pages by filename, upload order or size.""" + from app.models.set import Set + from app.services import page_order + + lego_set = Set.query.filter_by(set_number=set_number).first() + if lego_set is None: + raise click.ClickException(f'No set numbered {set_number}') + + try: + pages = page_order.auto_sort(lego_set.id, strategy, reverse=reverse) + except ValueError as e: + raise click.ClickException(str(e)) + + for page in pages: + click.echo(f" {page.page_number:>4} {page.file_name}") + click.secho(f'Sorted {len(pages)} pages.', fg='green') + + +def register_pages_cli(app): + """Attach the `flask pages` group to `app`.""" + app.cli.add_command(pages_cli)