import argparse import sys from rich.console import Console from tmview import version from tmview import client as tm_client from tmview import download as tm_download from tmview import output as tm_output console = Console(stderr=True) # WO is already in EU_OFFICES; extras are non-EU/non-WO offices only ALL_OFFICES_EXTRA = [ "US", "GB", "JP", "CN", "KR", "CH", "NO", "CA", "AU", "MX", "IN", "BR", "RU", "TR", "UA", "MA", "TN", "IL", "ZA", ] def main(): parser = argparse.ArgumentParser( prog="tmview", description="Search EU trademark registries via TMview", ) parser.add_argument("query", nargs="?", help="Trademark name to search for") parser.add_argument( "-o", "--offices", metavar="CODES", help="Comma-separated office codes (default: all EU + WIPO)", ) parser.add_argument( "-l", "--limit", type=int, default=20, metavar="N", help="Max results to return (default: 20, max: 100)", ) parser.add_argument( "-c", "--classes", metavar="NUMS", help="Nice class filter, comma-separated (e.g. 9,35)", ) parser.add_argument( "-s", "--status", metavar="STATUS", help="Status filter: registered|pending|expired", ) parser.add_argument( "-v", "--verbose", action="store_true", help="Show fetch timestamp column in table output", ) parser.add_argument( "--similar-to", metavar="IMAGE", help="Find trademarks with visually similar logos to IMAGE", ) parser.add_argument( "-d", "--download-logos", metavar="DIR", help="Download trademark logo images to DIR", ) parser.add_argument( "--json", action="store_true", dest="json_output", help="Output as JSON instead of table", ) parser.add_argument( "--page", type=int, default=1, metavar="N", help="Page number (default: 1)", ) parser.add_argument( "--all-offices", action="store_true", help="Include non-EU offices (US, JP, GB, etc.)", ) parser.add_argument( "--version", action="version", version=f"tmview {version}", ) args = parser.parse_args() if not args.query and not args.similar_to: parser.error("provide a search query or --similar-to IMAGE") limit = min(args.limit, 100) if args.offices: offices = [o.strip().upper() for o in args.offices.split(",") if o.strip()] elif args.all_offices: offices = tm_client.EU_OFFICES + ALL_OFFICES_EXTRA else: offices = tm_client.EU_OFFICES classes = None if args.classes: try: classes = [int(c.strip()) for c in args.classes.split(",") if c.strip()] except ValueError: console.print("[red]Error: --classes must be comma-separated integers[/red]") sys.exit(1) try: if args.similar_to: status_msg = f"[bold green]Searching TMview for logos similar to {args.similar_to}…[/bold green]" with console.status(status_msg): result = tm_client.search_by_image( image_path=args.similar_to, offices=offices, limit=limit, page=args.page, ) else: with console.status(f'[bold green]Searching TMview for "{args.query}"…[/bold green]'): result = tm_client.search( query=args.query, offices=offices, limit=limit, page=args.page, classes=classes, status=args.status, ) except RuntimeError as exc: console.print(f"[red]Error: {exc}[/red]") console.print("[dim]Suggestion: check your connection or retry in a few seconds.[/dim]") sys.exit(1) if result.get("from_cache"): console.print(f'[dim](cached — fetched {result["fetched_at"]})[/dim]') if args.json_output: tm_output.print_json(result) else: tm_output.print_table(result, args.query, verbose=args.verbose, image_path=args.similar_to) if args.download_logos: trademarks = result["trademarks"] if not trademarks: console.print("[dim]No results to download.[/dim]") else: ok, skipped = tm_download.download_logos(trademarks, args.download_logos, console) console.print(f"[green]Downloaded {ok} logo(s) to {args.download_logos}[/green]" + (f" [yellow]({skipped} skipped)[/yellow]" if skipped else ""))