#!/usr/bin/env python import argparse import datetime import json import os import pathlib import re import time import requests from fpdf import FPDF from slugify import slugify HERE = pathlib.Path(__file__).resolve().parent class Box: re_title = re.compile(r"Box:\s*(.*)$") re_weight = re.compile(r"\*\s*(.*)kg$") re_content = re.compile(r"\*\s*(.*)\s*$") re_size_2d = re.compile(r"\*\s*(\d+)[x\*](\d+)\s*$") re_size_3d = re.compile(r"\*\s*(\d+)[x\*](\d+)[x\*](\d+)\s*$") def __init__(self, lines): self.title = "" self.slug = "" self.weight = "" self.size = "" self.content = [] for line in lines: title = Box.re_title.match(line) if title: self.title = title[1] self.slug = slugify(self.title) continue weight = Box.re_weight.match(line) if weight: self.weight = weight[1].strip() continue size_2d = Box.re_size_2d.match(line) if size_2d: self.size = f"{size_2d[1]}x{size_2d[2]}" continue size_3d = Box.re_size_3d.match(line) if size_3d: self.size = f"{size_3d[1]}x{size_3d[2]}x{size_3d[3]}" continue content = Box.re_content.match(line) if content: self.content.append(content[1]) def __str__(self): return f"{self.title} ({self.size} | {self.weight} kg)" class Loader: def __init__(self, url="https://pad.stratum0.org/p/inventar_eventfoo/export/txt", max_age=60): self._url = url self.boxes = [] self._raw_lines = [] cache_base = os.getenv("XDG_CACHE_HOME") if not cache_base: cache_base = os.path.join(os.getenv("HOME"), ".cache") cache_fn = os.path.join(cache_base, "hoa-inventory-boxes.json") reload = True if os.path.isfile(cache_fn): try: with open(cache_fn) as fh: cache = json.load(fh) if time.time() - cache["ts"] < max_age: reload = False self._raw_lines = cache["raw"] except json.decoder.JSONDecodeError: pass if reload: self._load_via_http() with open(cache_fn, "w") as fh: json.dump({"ts": time.time(), "raw": self._raw_lines}, fh) else: self._raw_lines = cache["raw"] self._parse() def _load_via_http(self): r = requests.get(self._url) r.raise_for_status() self._raw_lines = [x.strip() for x in r.text.splitlines()] def _parse(self): in_list = False curr = [] for line in self._raw_lines: if not in_list: if line.startswith("Box-Inventar"): in_list = True else: if line.startswith("======="): continue if line.startswith("Box: "): # this starts a new block if curr: self.boxes.append(Box(curr)) curr = [] if line: curr.append(line) class Brother100x62(FPDF): def __init__(self): super().__init__(orientation="l", format=(62, 100)) self.add_font(family="Geo", fname=HERE / "contrib" / "Geo-Regular.ttf") self.add_font(family="Fira", fname=HERE / "contrib" / "FiraSans-Regular.ttf") self.set_margin(0) self.set_auto_page_break(margin=0, auto=True) def header(self): self.image(HERE / "contrib/specht.png", x=100 - 12, y=62 - 25, w=10, h=30, keep_aspect_ratio=True) def add_box(self, box: Box): self.add_page() self.set_font("Geo") self.set_font_size(30) self.set_char_spacing(-1) self.multi_cell(w=0, txt=box.title, align="L") self.rect(x=0, y=20, w=100, h=0.5, style="F") self.set_font("Fira") self.set_font_size(12) self.set_char_spacing(0) self.text(x=1, y=24.5, txt=f"{box.size} cm³| {box.weight} kg") self.set_xy(0, 27) self.multi_cell(w=0, txt="\n".join([f"- {x}" for x in box.content]), align="L") designs = { "brother_100x62": Brother100x62, } def _list(args, loader): for box in loader.boxes: print(f"{box.slug:50} {box}") def _print(args, loader): all_boxes = False if len(args.box_name) == 1 and args.box_name[0] == "all": all_boxes = True boxes = loader.boxes else: # check if all boxes are known boxes = [] for bn in args.box_name: for box in loader.boxes: if bn == box.slug or bn == box.title: boxes.append(box) break else: print(f"Unknown box '{bn}'") exit(1) # generate labels for boxes design = designs[args.label] pdf = design() for box in boxes: pdf.add_box(box) # write result if all_boxes: fn_slug = "all" else: fn_slug = "_".join([x.slug for x in boxes]) fn_slug = fn_slug[0:20] + "..." if len(fn_slug) > 20 else fn_slug fn = HERE / "tmp" / f"{datetime.datetime.now().strftime('%Y-%m-%d-%H-%M')}_{fn_slug}.pdf" pdf.output(fn) print(fn) def main(): loader = Loader() parser = argparse.ArgumentParser() parser.add_argument("--no-cache", help="Do not use cache (if exists)", action="store_true") subparsers = parser.add_subparsers(title="commands") parser_list = subparsers.add_parser("list", help="Lists all defined boxes") parser_list.set_defaults(func=_list) parser_print = subparsers.add_parser("print", help="Prints labels for boxes") parser_print.add_argument("box_name", help="Name of the box", nargs="*") parser_print.add_argument("--label", "-l", help="Label identification", default="brother_100x62") parser_print.set_defaults(func=_print) args = parser.parse_args() if args.no_cache: loader = Loader(max_age=0) else: loader = Loader() if "func" in args: args.func(args, loader) else: parser.print_help() if __name__ == "__main__": main()