Add flask pages CLI group for auditing and fixing page order
This commit is contained in:
1 parent
42956cff97
commit
95425986d8
1 file changed
+125
@@ -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)
|
||||||
Reference in new issue
Block a user