Skip to content
Snippets Groups Projects
main.py 5.82 KiB
#!/usr/bin/env python
import argparse
import json
import os
import re
import time
import pathlib

import requests
from slugify import slugify
from fpdf import FPDF


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):
    if len(args.box_name) == 1 and args.box_name[0] == "all":
        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)
    pdf.output(HERE / "tmp" / "out.pdf")


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()

    args.func(args, loader)


if __name__ == "__main__":
    main()