bobina 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bobina-0.1.0/.github/workflows/ci.yml +54 -0
- bobina-0.1.0/.github/workflows/release.yml +104 -0
- bobina-0.1.0/.gitignore +17 -0
- bobina-0.1.0/AGENTS.md +40 -0
- bobina-0.1.0/CHANGELOG.md +75 -0
- bobina-0.1.0/CONTRIBUTING.md +36 -0
- bobina-0.1.0/LICENSE +21 -0
- bobina-0.1.0/PKG-INFO +204 -0
- bobina-0.1.0/README.md +162 -0
- bobina-0.1.0/contrib/bt-printer-up.sh +69 -0
- bobina-0.1.0/contrib/systemd/bt-printer.service +12 -0
- bobina-0.1.0/docs/CLI.md +472 -0
- bobina-0.1.0/docs/FORMAT.md +515 -0
- bobina-0.1.0/docs/SECURITY.md +71 -0
- bobina-0.1.0/docs/TESTED.md +42 -0
- bobina-0.1.0/docs/diagrams/nudges-flow.md +239 -0
- bobina-0.1.0/docs/guides/batch-printing.md +199 -0
- bobina-0.1.0/docs/guides/templates.md +107 -0
- bobina-0.1.0/docs/pay.md +262 -0
- bobina-0.1.0/docs/printers/mtp-ii-58.md +136 -0
- bobina-0.1.0/docs/problems/B101.md +12 -0
- bobina-0.1.0/docs/problems/B102.md +10 -0
- bobina-0.1.0/docs/problems/E101.md +10 -0
- bobina-0.1.0/docs/problems/E102.md +10 -0
- bobina-0.1.0/docs/problems/E103.md +10 -0
- bobina-0.1.0/docs/problems/E104.md +10 -0
- bobina-0.1.0/docs/problems/E105.md +10 -0
- bobina-0.1.0/docs/problems/E106.md +10 -0
- bobina-0.1.0/docs/problems/E107.md +10 -0
- bobina-0.1.0/docs/problems/E201.md +10 -0
- bobina-0.1.0/docs/problems/E202.md +10 -0
- bobina-0.1.0/docs/problems/E203.md +10 -0
- bobina-0.1.0/docs/problems/E204.md +10 -0
- bobina-0.1.0/docs/problems/E205.md +10 -0
- bobina-0.1.0/docs/problems/E206.md +10 -0
- bobina-0.1.0/docs/problems/E207.md +10 -0
- bobina-0.1.0/docs/problems/E208.md +10 -0
- bobina-0.1.0/docs/problems/E209.md +10 -0
- bobina-0.1.0/docs/problems/E301.md +10 -0
- bobina-0.1.0/docs/problems/E302.md +10 -0
- bobina-0.1.0/docs/problems/E303.md +10 -0
- bobina-0.1.0/docs/problems/E304.md +10 -0
- bobina-0.1.0/docs/problems/E305.md +10 -0
- bobina-0.1.0/docs/problems/E401.md +10 -0
- bobina-0.1.0/docs/problems/E402.md +10 -0
- bobina-0.1.0/docs/problems/E403.md +10 -0
- bobina-0.1.0/docs/problems/E404.md +10 -0
- bobina-0.1.0/docs/problems/E405.md +10 -0
- bobina-0.1.0/docs/problems/E501.md +10 -0
- bobina-0.1.0/docs/problems/F201.md +12 -0
- bobina-0.1.0/docs/problems/F202.md +12 -0
- bobina-0.1.0/docs/problems/F203.md +10 -0
- bobina-0.1.0/docs/problems/F204.md +12 -0
- bobina-0.1.0/docs/problems/F205.md +10 -0
- bobina-0.1.0/docs/problems/F301.md +12 -0
- bobina-0.1.0/docs/problems/F302.md +10 -0
- bobina-0.1.0/docs/problems/F303.md +12 -0
- bobina-0.1.0/docs/problems/I101.md +10 -0
- bobina-0.1.0/docs/problems/I102.md +10 -0
- bobina-0.1.0/docs/problems/I103.md +10 -0
- bobina-0.1.0/docs/problems/I104.md +10 -0
- bobina-0.1.0/docs/problems/I105.md +10 -0
- bobina-0.1.0/docs/problems/I106.md +10 -0
- bobina-0.1.0/docs/problems/I107.md +10 -0
- bobina-0.1.0/docs/problems/L101.md +10 -0
- bobina-0.1.0/docs/problems/L102.md +10 -0
- bobina-0.1.0/docs/problems/L103.md +10 -0
- bobina-0.1.0/docs/problems/L104.md +10 -0
- bobina-0.1.0/docs/problems/L105.md +10 -0
- bobina-0.1.0/docs/problems/M101.md +10 -0
- bobina-0.1.0/docs/problems/M102.md +10 -0
- bobina-0.1.0/docs/problems/M103.md +10 -0
- bobina-0.1.0/docs/problems/M120.md +10 -0
- bobina-0.1.0/docs/problems/M121.md +10 -0
- bobina-0.1.0/docs/problems/M122.md +10 -0
- bobina-0.1.0/docs/problems/M130.md +10 -0
- bobina-0.1.0/docs/problems/M131.md +10 -0
- bobina-0.1.0/docs/problems/M140.md +10 -0
- bobina-0.1.0/docs/problems/M150.md +10 -0
- bobina-0.1.0/docs/problems/M151.md +10 -0
- bobina-0.1.0/docs/problems/M152.md +10 -0
- bobina-0.1.0/docs/problems/M153.md +10 -0
- bobina-0.1.0/docs/problems/M154.md +10 -0
- bobina-0.1.0/docs/problems/M155.md +10 -0
- bobina-0.1.0/docs/problems/M156.md +10 -0
- bobina-0.1.0/docs/problems/M157.md +10 -0
- bobina-0.1.0/docs/problems/M158.md +10 -0
- bobina-0.1.0/docs/problems/M159.md +10 -0
- bobina-0.1.0/docs/problems/M160.md +10 -0
- bobina-0.1.0/docs/problems/M201.md +10 -0
- bobina-0.1.0/docs/problems/M202.md +10 -0
- bobina-0.1.0/docs/problems/M310.md +10 -0
- bobina-0.1.0/docs/problems/M311.md +10 -0
- bobina-0.1.0/docs/problems/M312.md +10 -0
- bobina-0.1.0/docs/problems/M313.md +10 -0
- bobina-0.1.0/docs/problems/M314.md +10 -0
- bobina-0.1.0/docs/problems/M315.md +10 -0
- bobina-0.1.0/docs/problems/P101.md +10 -0
- bobina-0.1.0/docs/problems/P102.md +10 -0
- bobina-0.1.0/docs/problems/P201.md +10 -0
- bobina-0.1.0/docs/problems/P301.md +10 -0
- bobina-0.1.0/docs/problems/P302.md +10 -0
- bobina-0.1.0/docs/problems/Q101.md +12 -0
- bobina-0.1.0/docs/problems/Q102.md +12 -0
- bobina-0.1.0/docs/problems/Q103.md +10 -0
- bobina-0.1.0/docs/problems/Q104.md +10 -0
- bobina-0.1.0/docs/problems/Q105.md +10 -0
- bobina-0.1.0/docs/problems/Q106.md +12 -0
- bobina-0.1.0/docs/problems/Q107.md +12 -0
- bobina-0.1.0/docs/problems/Q108.md +12 -0
- bobina-0.1.0/docs/problems/Q109.md +10 -0
- bobina-0.1.0/docs/problems/Q110.md +10 -0
- bobina-0.1.0/docs/problems/Q111.md +10 -0
- bobina-0.1.0/docs/problems/README.md +100 -0
- bobina-0.1.0/docs/schemas/README.md +12 -0
- bobina-0.1.0/docs/schemas/check.schema.json +54 -0
- bobina-0.1.0/docs/schemas/history.schema.json +108 -0
- bobina-0.1.0/docs/schemas/preview.schema.json +36 -0
- bobina-0.1.0/docs/schemas/print.schema.json +47 -0
- bobina-0.1.0/docs/schemas/problem.schema.json +61 -0
- bobina-0.1.0/drivers/windows/README.md +20 -0
- bobina-0.1.0/drivers/windows/bobina58.gpd +157 -0
- bobina-0.1.0/drivers/windows/bobina58.inf +38 -0
- bobina-0.1.0/drivers/windows/install.ps1 +72 -0
- bobina-0.1.0/drivers/windows/uninstall.ps1 +6 -0
- bobina-0.1.0/examples/batch/all.png +0 -0
- bobina-0.1.0/examples/batch/catalog.json +7 -0
- bobina-0.1.0/examples/batch/jar-label.bob.md +19 -0
- bobina-0.1.0/examples/batch/one.png +0 -0
- bobina-0.1.0/examples/batch/pantry.csv +5 -0
- bobina-0.1.0/examples/batch/pantry.json +6 -0
- bobina-0.1.0/examples/batch/pantry.xlsx +0 -0
- bobina-0.1.0/examples/batch/prices.xlsx +0 -0
- bobina-0.1.0/examples/batch/shelf-tag.bob.md +10 -0
- bobina-0.1.0/examples/batch/shelfA.png +0 -0
- bobina-0.1.0/examples/batch/stock.csv +2 -0
- bobina-0.1.0/examples/templates/clothing-tag.bob.md +10 -0
- bobina-0.1.0/examples/templates/clothing-tag.png +0 -0
- bobina-0.1.0/examples/templates/food-label.bob.md +8 -0
- bobina-0.1.0/examples/templates/food-label.png +0 -0
- bobina-0.1.0/examples/templates/jewelry-tag.bob.md +9 -0
- bobina-0.1.0/examples/templates/jewelry-tag.png +0 -0
- bobina-0.1.0/examples/templates/pay-sign.bob.md +8 -0
- bobina-0.1.0/examples/templates/pay-sign.png +0 -0
- bobina-0.1.0/examples/templates/sale-price.bob.md +8 -0
- bobina-0.1.0/examples/templates/sale-price.png +0 -0
- bobina-0.1.0/examples/templates/shelf-price.bob.md +9 -0
- bobina-0.1.0/examples/templates/shelf-price.png +0 -0
- bobina-0.1.0/examples/templates/shipping-label.bob.md +11 -0
- bobina-0.1.0/examples/templates/shipping-label.png +0 -0
- bobina-0.1.0/install.sh +40 -0
- bobina-0.1.0/llms-full.txt +1553 -0
- bobina-0.1.0/llms.txt +42 -0
- bobina-0.1.0/pyproject.toml +58 -0
- bobina-0.1.0/skills/bobina/SKILL.md +51 -0
- bobina-0.1.0/spec/conformance/README.md +9 -0
- bobina-0.1.0/spec/conformance/assets/ramp.png +0 -0
- bobina-0.1.0/spec/conformance/n1-sizes.bob.md +12 -0
- bobina-0.1.0/spec/conformance/n1-sizes.png +0 -0
- bobina-0.1.0/spec/conformance/n2-styles.bob.md +11 -0
- bobina-0.1.0/spec/conformance/n2-styles.png +0 -0
- bobina-0.1.0/spec/conformance/n4-layout.bob.md +11 -0
- bobina-0.1.0/spec/conformance/n4-layout.png +0 -0
- bobina-0.1.0/spec/conformance/n6-codes.bob.md +16 -0
- bobina-0.1.0/spec/conformance/n6-codes.png +0 -0
- bobina-0.1.0/spec/conformance/r4-drawing.bob.md +15 -0
- bobina-0.1.0/spec/conformance/r4-drawing.png +0 -0
- bobina-0.1.0/spec/conformance/r5-codes-raster.bob.md +12 -0
- bobina-0.1.0/spec/conformance/r5-codes-raster.png +0 -0
- bobina-0.1.0/spec/conformance/r6-image.bob.md +4 -0
- bobina-0.1.0/spec/conformance/r6-image.png +0 -0
- bobina-0.1.0/spec/conformance/templates-card.bob.md +7 -0
- bobina-0.1.0/spec/conformance/templates-card.png +0 -0
- bobina-0.1.0/spec/conformance/templates-label.bob.md +7 -0
- bobina-0.1.0/spec/conformance/templates-label.png +0 -0
- bobina-0.1.0/spec/conformance/templates-receipt.bob.md +11 -0
- bobina-0.1.0/spec/conformance/templates-receipt.png +0 -0
- bobina-0.1.0/spec/conformance/templates-todo.bob.md +6 -0
- bobina-0.1.0/spec/conformance/templates-todo.png +0 -0
- bobina-0.1.0/src/bobina/__init__.py +6 -0
- bobina-0.1.0/src/bobina/__main__.py +5 -0
- bobina-0.1.0/src/bobina/cli/__init__.py +17 -0
- bobina-0.1.0/src/bobina/cli/args.py +911 -0
- bobina-0.1.0/src/bobina/cli/common.py +1264 -0
- bobina-0.1.0/src/bobina/cli/device.py +483 -0
- bobina-0.1.0/src/bobina/cli/documents.py +523 -0
- bobina-0.1.0/src/bobina/cli/history.py +153 -0
- bobina-0.1.0/src/bobina/cli/library.py +277 -0
- bobina-0.1.0/src/bobina/codes.py +158 -0
- bobina-0.1.0/src/bobina/config.py +75 -0
- bobina-0.1.0/src/bobina/datasource.py +441 -0
- bobina-0.1.0/src/bobina/editor.py +58 -0
- bobina-0.1.0/src/bobina/emulator.py +150 -0
- bobina-0.1.0/src/bobina/engine.py +475 -0
- bobina-0.1.0/src/bobina/escpos.py +150 -0
- bobina-0.1.0/src/bobina/export.py +98 -0
- bobina-0.1.0/src/bobina/features.py +45 -0
- bobina-0.1.0/src/bobina/fidelity.py +234 -0
- bobina-0.1.0/src/bobina/fileio.py +219 -0
- bobina-0.1.0/src/bobina/fonts/AtkinsonHyperlegible-Bold.ttf +0 -0
- bobina-0.1.0/src/bobina/fonts/AtkinsonHyperlegible-Regular.ttf +0 -0
- bobina-0.1.0/src/bobina/fonts/BarlowCondensed-Regular.ttf +0 -0
- bobina-0.1.0/src/bobina/fonts/BarlowCondensed-SemiBold.ttf +0 -0
- bobina-0.1.0/src/bobina/fonts/DejaVuSans-Bold.ttf +0 -0
- bobina-0.1.0/src/bobina/fonts/DejaVuSans.ttf +0 -0
- bobina-0.1.0/src/bobina/fonts/IBMPlexMono-Bold.ttf +0 -0
- bobina-0.1.0/src/bobina/fonts/IBMPlexMono-Regular.ttf +0 -0
- bobina-0.1.0/src/bobina/fonts/LICENSES.md +16 -0
- bobina-0.1.0/src/bobina/fonts/TerminusTTF-4.49.3.ttf +0 -0
- bobina-0.1.0/src/bobina/fonts/TerminusTTF-Bold-4.49.3.ttf +0 -0
- bobina-0.1.0/src/bobina/fonts/licenses/AtkinsonHyperlegible-OFL.txt +92 -0
- bobina-0.1.0/src/bobina/fonts/licenses/Barlow-OFL.txt +93 -0
- bobina-0.1.0/src/bobina/fonts/licenses/DejaVu-LICENSE.txt +187 -0
- bobina-0.1.0/src/bobina/fonts/licenses/IBMPlex-OFL.txt +93 -0
- bobina-0.1.0/src/bobina/fonts/licenses/Spleen-BSD-2.txt +24 -0
- bobina-0.1.0/src/bobina/fonts/licenses/Terminus-OFL.txt +93 -0
- bobina-0.1.0/src/bobina/fonts/spleen-12x24.otf +0 -0
- bobina-0.1.0/src/bobina/history.py +296 -0
- bobina-0.1.0/src/bobina/imaging.py +211 -0
- bobina-0.1.0/src/bobina/layout.py +1069 -0
- bobina-0.1.0/src/bobina/library.py +93 -0
- bobina-0.1.0/src/bobina/model.py +96 -0
- bobina-0.1.0/src/bobina/nudges.py +192 -0
- bobina-0.1.0/src/bobina/paper.py +104 -0
- bobina-0.1.0/src/bobina/parser.py +301 -0
- bobina-0.1.0/src/bobina/paths.py +24 -0
- bobina-0.1.0/src/bobina/pay.py +581 -0
- bobina-0.1.0/src/bobina/pdfout.py +86 -0
- bobina-0.1.0/src/bobina/preview.py +91 -0
- bobina-0.1.0/src/bobina/problem_help.py +133 -0
- bobina-0.1.0/src/bobina/profile.py +130 -0
- bobina-0.1.0/src/bobina/profiles/SOURCES.md +10 -0
- bobina-0.1.0/src/bobina/profiles/bixolon-srp-s300.toml +106 -0
- bobina-0.1.0/src/bobina/profiles/citizen-ct-s651.toml +100 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-l90.toml +113 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-m30iii.toml +112 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-p80-42col.toml +113 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-p80.toml +113 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-t20ii-42col.toml +113 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-t20ii.toml +113 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-t20iii.toml +113 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-t70-sa.toml +91 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-t70.toml +99 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-t70ii-ch-58mm.toml +113 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-t70ii-ch.toml +113 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-t70ii-sa.toml +113 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-t70ii.toml +113 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-t88ii.toml +112 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-t88iii.toml +99 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-t88iv-sa.toml +91 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-t88iv.toml +99 -0
- bobina-0.1.0/src/bobina/profiles/epson-tm-t88v.toml +113 -0
- bobina-0.1.0/src/bobina/profiles/metapace-t-1.toml +96 -0
- bobina-0.1.0/src/bobina/profiles/mtp-ii-58.toml +114 -0
- bobina-0.1.0/src/bobina/profiles/netbye-nbtp-5880.toml +91 -0
- bobina-0.1.0/src/bobina/profiles/netbye-nbtp-8040.toml +91 -0
- bobina-0.1.0/src/bobina/profiles/netbye-nbtp-8080.toml +91 -0
- bobina-0.1.0/src/bobina/profiles/netum-nt-5890k.toml +110 -0
- bobina-0.1.0/src/bobina/profiles/netum-nt-80-v-ul.toml +110 -0
- bobina-0.1.0/src/bobina/profiles/seiko-rp-f10-58mm.toml +106 -0
- bobina-0.1.0/src/bobina/profiles/seiko-rp-f10-80mm.toml +106 -0
- bobina-0.1.0/src/bobina/profiles/xprinter-xp-q80k.toml +91 -0
- bobina-0.1.0/src/bobina/profiles/yhdaa-yhd-8390.toml +104 -0
- bobina-0.1.0/src/bobina/profiles/zijiang-zj-5870.toml +98 -0
- bobina-0.1.0/src/bobina/profiles/zjiang-pos-5890.toml +110 -0
- bobina-0.1.0/src/bobina/rasterfont.py +320 -0
- bobina-0.1.0/src/bobina/realistic.py +41 -0
- bobina-0.1.0/src/bobina/rows.py +89 -0
- bobina-0.1.0/src/bobina/sources.py +332 -0
- bobina-0.1.0/src/bobina/state.py +71 -0
- bobina-0.1.0/src/bobina/templates/card.bob.md +19 -0
- bobina-0.1.0/src/bobina/templates/clothing-tag.bob.md +35 -0
- bobina-0.1.0/src/bobina/templates/food-label.bob.md +25 -0
- bobina-0.1.0/src/bobina/templates/jewelry-tag.bob.md +17 -0
- bobina-0.1.0/src/bobina/templates/label.bob.md +24 -0
- bobina-0.1.0/src/bobina/templates/note.bob.md +13 -0
- bobina-0.1.0/src/bobina/templates/pay-sign.bob.md +20 -0
- bobina-0.1.0/src/bobina/templates/receipt.bob.md +37 -0
- bobina-0.1.0/src/bobina/templates/sale-price.bob.md +23 -0
- bobina-0.1.0/src/bobina/templates/shelf-price.bob.md +21 -0
- bobina-0.1.0/src/bobina/templates/shipping-label.bob.md +39 -0
- bobina-0.1.0/src/bobina/templates/todo.bob.md +20 -0
- bobina-0.1.0/src/bobina/templating.py +899 -0
- bobina-0.1.0/src/bobina/transport.py +394 -0
- bobina-0.1.0/src/bobina/view.py +139 -0
- bobina-0.1.0/tests/conftest.py +13 -0
- bobina-0.1.0/tests/fixtures/fake/README.md +1 -0
- bobina-0.1.0/tests/golden/assets/ramp.png +0 -0
- bobina-0.1.0/tests/golden/fonts.bob.md +19 -0
- bobina-0.1.0/tests/golden/fonts.png +0 -0
- bobina-0.1.0/tests/golden/image.bob.md +3 -0
- bobina-0.1.0/tests/golden/image.png +0 -0
- bobina-0.1.0/tests/golden/lists.bob.md +12 -0
- bobina-0.1.0/tests/golden/lists.png +0 -0
- bobina-0.1.0/tests/golden/objects.bob.md +31 -0
- bobina-0.1.0/tests/golden/objects.png +0 -0
- bobina-0.1.0/tests/golden/text.bob.md +20 -0
- bobina-0.1.0/tests/golden/text.png +0 -0
- bobina-0.1.0/tests/test_agent_hints.py +100 -0
- bobina-0.1.0/tests/test_ask.py +87 -0
- bobina-0.1.0/tests/test_bt_tail.py +262 -0
- bobina-0.1.0/tests/test_caches.py +84 -0
- bobina-0.1.0/tests/test_calibrate_feed.py +122 -0
- bobina-0.1.0/tests/test_cli.py +170 -0
- bobina-0.1.0/tests/test_concurrency.py +232 -0
- bobina-0.1.0/tests/test_data.py +108 -0
- bobina-0.1.0/tests/test_decisions.py +73 -0
- bobina-0.1.0/tests/test_docs.py +60 -0
- bobina-0.1.0/tests/test_escpos.py +32 -0
- bobina-0.1.0/tests/test_examples.py +29 -0
- bobina-0.1.0/tests/test_export.py +130 -0
- bobina-0.1.0/tests/test_fidelity.py +81 -0
- bobina-0.1.0/tests/test_gallery.py +109 -0
- bobina-0.1.0/tests/test_gaps.py +111 -0
- bobina-0.1.0/tests/test_golden.py +29 -0
- bobina-0.1.0/tests/test_guide.py +50 -0
- bobina-0.1.0/tests/test_hangs.py +90 -0
- bobina-0.1.0/tests/test_history.py +128 -0
- bobina-0.1.0/tests/test_imported_profiles.py +53 -0
- bobina-0.1.0/tests/test_join.py +215 -0
- bobina-0.1.0/tests/test_layout.py +303 -0
- bobina-0.1.0/tests/test_leakscan.py +64 -0
- bobina-0.1.0/tests/test_left_out_and_json.py +252 -0
- bobina-0.1.0/tests/test_library.py +42 -0
- bobina-0.1.0/tests/test_llms.py +80 -0
- bobina-0.1.0/tests/test_marks.py +205 -0
- bobina-0.1.0/tests/test_memory.py +84 -0
- bobina-0.1.0/tests/test_option_hints.py +191 -0
- bobina-0.1.0/tests/test_p1.py +102 -0
- bobina-0.1.0/tests/test_pay.py +613 -0
- bobina-0.1.0/tests/test_problem_help.py +62 -0
- bobina-0.1.0/tests/test_problems.py +72 -0
- bobina-0.1.0/tests/test_reprint.py +119 -0
- bobina-0.1.0/tests/test_retro_review.py +89 -0
- bobina-0.1.0/tests/test_review_fixes.py +232 -0
- bobina-0.1.0/tests/test_review_leftovers.py +51 -0
- bobina-0.1.0/tests/test_schemas.py +68 -0
- bobina-0.1.0/tests/test_security.py +122 -0
- bobina-0.1.0/tests/test_sources.py +202 -0
- bobina-0.1.0/tests/test_strip.py +75 -0
- bobina-0.1.0/tests/test_strip_rules.py +230 -0
- bobina-0.1.0/tests/test_templating.py +202 -0
- bobina-0.1.0/tests/test_text_filters.py +44 -0
- bobina-0.1.0/tests/test_token_budget.py +44 -0
- bobina-0.1.0/tests/test_transport_errors.py +59 -0
- bobina-0.1.0/tests/test_tty.py +111 -0
- bobina-0.1.0/tests/test_untested_features.py +104 -0
- bobina-0.1.0/tests/test_view_marks.py +40 -0
- bobina-0.1.0/tools/gen_llms_full.py +53 -0
- bobina-0.1.0/tools/gen_pay_docs.py +21 -0
- bobina-0.1.0/tools/gen_problem_docs.py +87 -0
- bobina-0.1.0/tools/import_profiles.py +261 -0
- bobina-0.1.0/tools/install-hooks.sh +24 -0
- bobina-0.1.0/tools/leak-allow.txt +11 -0
- bobina-0.1.0/tools/leakscan.py +169 -0
- bobina-0.1.0/tools/public-files.txt +2 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
python: ["3.11", "3.12", "3.13"]
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python }}
|
|
22
|
+
- name: Install
|
|
23
|
+
run: python -m pip install -e ".[dev]"
|
|
24
|
+
- name: Lint
|
|
25
|
+
run: |
|
|
26
|
+
ruff check src tests tools
|
|
27
|
+
ruff format --check src tests
|
|
28
|
+
- name: Tests (no printer needed)
|
|
29
|
+
run: pytest -q
|
|
30
|
+
|
|
31
|
+
privacy:
|
|
32
|
+
# Generic patterns for everyone; the owner's personal patterns come from a secret, which GitHub does not give
|
|
33
|
+
# to pull requests from forks (so a fork's PR gets the generic scan, and main is scanned again after merge).
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
with:
|
|
38
|
+
fetch-depth: 0
|
|
39
|
+
- uses: actions/setup-python@v5
|
|
40
|
+
with:
|
|
41
|
+
python-version: "3.12"
|
|
42
|
+
- name: Personal-data scan of the files
|
|
43
|
+
env:
|
|
44
|
+
LEAK_PATTERNS: ${{ secrets.LEAK_PATTERNS }}
|
|
45
|
+
run: python tools/leakscan.py
|
|
46
|
+
- name: Personal-data scan of the commit messages
|
|
47
|
+
env:
|
|
48
|
+
LEAK_PATTERNS: ${{ secrets.LEAK_PATTERNS }}
|
|
49
|
+
BEFORE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
|
|
50
|
+
run: |
|
|
51
|
+
if [ -z "$BEFORE" ] || ! git cat-file -e "$BEFORE" 2>/dev/null; then range="HEAD~1..HEAD"; else range="$BEFORE..HEAD"; fi
|
|
52
|
+
git rev-parse -q --verify HEAD~1 >/dev/null || range="HEAD"
|
|
53
|
+
git log --format='%B' "$range" > "$RUNNER_TEMP/messages.txt"
|
|
54
|
+
python tools/leakscan.py "$RUNNER_TEMP/messages.txt"
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# A version is a tag, published to PyPI by trusted publishing (no token) and installed on Linux, macOS and Windows.
|
|
2
|
+
# vX.Y.Z-rc.N is a pre-release (0.1.0rc1): pip and uv only install it when asked by version or with --pre, so
|
|
3
|
+
# nobody gets it by accident; it becomes a GitHub pre-release. vX.Y.Z is the release.
|
|
4
|
+
name: release
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
tags: ["v*"]
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
if: github.event.repository.private == false # only the public repository releases
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
outputs:
|
|
18
|
+
prerelease: ${{ steps.version.outputs.prerelease }}
|
|
19
|
+
version: ${{ steps.version.outputs.version }}
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.12"
|
|
25
|
+
- name: The tag matches the version in pyproject.toml
|
|
26
|
+
id: version
|
|
27
|
+
run: |
|
|
28
|
+
python -m pip install --quiet packaging # not in the runner's Python
|
|
29
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
30
|
+
want=$(python -c "from packaging.version import Version; print(Version('$tag'))")
|
|
31
|
+
have=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
32
|
+
have=$(python -c "from packaging.version import Version; print(Version('$have'))")
|
|
33
|
+
[ "$want" = "$have" ] || { echo "::error::tag $GITHUB_REF_NAME is $want, pyproject.toml says $have"; exit 1; }
|
|
34
|
+
echo "version=$want" >> "$GITHUB_OUTPUT"
|
|
35
|
+
case "$GITHUB_REF_NAME" in *-*) echo "prerelease=true" >> "$GITHUB_OUTPUT";; *) echo "prerelease=false" >> "$GITHUB_OUTPUT";; esac
|
|
36
|
+
- name: Tests and personal-data scan
|
|
37
|
+
env:
|
|
38
|
+
LEAK_PATTERNS: ${{ secrets.LEAK_PATTERNS }}
|
|
39
|
+
run: |
|
|
40
|
+
python -m pip install -e ".[dev]" build
|
|
41
|
+
pytest -q
|
|
42
|
+
python tools/leakscan.py
|
|
43
|
+
- name: Build
|
|
44
|
+
run: python -m build
|
|
45
|
+
- uses: actions/upload-artifact@v4
|
|
46
|
+
with:
|
|
47
|
+
name: dist
|
|
48
|
+
path: dist/
|
|
49
|
+
|
|
50
|
+
pypi:
|
|
51
|
+
needs: build
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
environment: pypi
|
|
54
|
+
permissions:
|
|
55
|
+
id-token: write
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/download-artifact@v4
|
|
58
|
+
with:
|
|
59
|
+
name: dist
|
|
60
|
+
path: dist/
|
|
61
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
62
|
+
|
|
63
|
+
install-test:
|
|
64
|
+
needs: [build, pypi]
|
|
65
|
+
strategy:
|
|
66
|
+
fail-fast: false
|
|
67
|
+
matrix:
|
|
68
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
69
|
+
runs-on: ${{ matrix.os }}
|
|
70
|
+
steps:
|
|
71
|
+
- uses: actions/setup-python@v5
|
|
72
|
+
with:
|
|
73
|
+
python-version: "3.12"
|
|
74
|
+
- name: Install this version from PyPI and run it
|
|
75
|
+
shell: bash
|
|
76
|
+
run: |
|
|
77
|
+
for i in 1 2 3 4 5 6; do
|
|
78
|
+
python -m pip install "bobina==${{ needs.build.outputs.version }}" && break
|
|
79
|
+
sleep 20 # the index takes a moment to show a new upload
|
|
80
|
+
done
|
|
81
|
+
bobina --version
|
|
82
|
+
bobina check "hello from the release check" --json
|
|
83
|
+
|
|
84
|
+
github-release:
|
|
85
|
+
needs: [build, pypi, install-test]
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
permissions:
|
|
88
|
+
contents: write
|
|
89
|
+
steps:
|
|
90
|
+
- uses: actions/checkout@v4
|
|
91
|
+
- uses: actions/download-artifact@v4
|
|
92
|
+
with:
|
|
93
|
+
name: dist
|
|
94
|
+
path: dist/
|
|
95
|
+
- name: Release with the CHANGELOG section
|
|
96
|
+
env:
|
|
97
|
+
GH_TOKEN: ${{ github.token }}
|
|
98
|
+
run: |
|
|
99
|
+
v="${{ needs.build.outputs.version }}"
|
|
100
|
+
base=$(python3 -c "import re,sys; print(re.sub(r'(a|b|rc)\d+$', '', sys.argv[1]))" "$v") # 0.1.0rc1 -> 0.1.0
|
|
101
|
+
awk -v v="$base" '$0 ~ "^## "v" " || $0 ~ "^## "v"$" {on=1; next} on && /^## / {exit} on' CHANGELOG.md > notes.md
|
|
102
|
+
[ -s notes.md ] || echo "bobina $v" > notes.md
|
|
103
|
+
pre=""; [ "${{ needs.build.outputs.prerelease }}" = "true" ] && pre="--prerelease"
|
|
104
|
+
gh release create "$GITHUB_REF_NAME" dist/* --title "bobina $v" --notes-file notes.md $pre
|
bobina-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# artefatos grandes/baixados ficam fora do git; o manifesto registra hash e origem
|
|
2
|
+
artifacts/
|
|
3
|
+
*.apk
|
|
4
|
+
*.xapk
|
|
5
|
+
*.bin
|
|
6
|
+
!teste.bin
|
|
7
|
+
!minimo.bin
|
|
8
|
+
__pycache__/
|
|
9
|
+
.remember/
|
|
10
|
+
local/
|
|
11
|
+
.venv/
|
|
12
|
+
*.egg-info/
|
|
13
|
+
.pytest_cache/
|
|
14
|
+
tests/out/
|
|
15
|
+
security-audit-runs/
|
|
16
|
+
build/
|
|
17
|
+
.claude/
|
bobina-0.1.0/AGENTS.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Notes for coding agents
|
|
2
|
+
|
|
3
|
+
bobina is a Python CLI (src/bobina) that prints Markdown on thermal roll printers. Read CONTRIBUTING.md first: it
|
|
4
|
+
has the rules that tests enforce.
|
|
5
|
+
|
|
6
|
+
## Every change
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
.venv/bin/pytest -q # no printer needed
|
|
10
|
+
.venv/bin/ruff check src tests tools && .venv/bin/ruff format src tests tools
|
|
11
|
+
.venv/bin/python tools/leakscan.py # no personal data: runs in CI
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
- Rendering changes: `BOBINA_UPDATE_GOLDEN=1 pytest tests/test_golden.py`, then look at every changed PNG.
|
|
15
|
+
- Docs have a token budget per page (tests/test_token_budget.py): over it, split or cut the page.
|
|
16
|
+
- Docs that are generated, never edited by hand: `docs/diagrams/nudges-flow.md` and `docs/problems/`
|
|
17
|
+
(`tools/gen_problem_docs.py`),
|
|
18
|
+
`docs/pay.md` (`tools/gen_pay_docs.py`), `llms-full.txt` (`tools/gen_llms_full.py`), imported printer profiles (`tools/import_profiles.py`).
|
|
19
|
+
- A new command or option: `COMMAND_KIND`, `ALSO_KNOWN`/`NO_SYNONYMS` in `src/bobina/cli/args.py` and a hint case
|
|
20
|
+
in `tests/test_option_hints.py` (a test fails until done). A new problem code: `CATALOG` in `src/bobina/nudges.py`,
|
|
21
|
+
what to do in `FIX` (`src/bobina/problem_help.py`), the table in `docs/FORMAT.md`, then `tools/gen_problem_docs.py`.
|
|
22
|
+
|
|
23
|
+
## Principles
|
|
24
|
+
|
|
25
|
+
- Nudges, not blocks: warn with a code, a suggestion and the evidence; refuse only for security.
|
|
26
|
+
- Printer facts come from paper: a profile value needs a printed test (docs/printers/). Imported profiles are
|
|
27
|
+
`measured = false` and conservative.
|
|
28
|
+
- Full command names in docs and output; English in code and docs.
|
|
29
|
+
- Never print on a real printer from tests: use `-o`, `--dry-run`, `check`, `preview` or a file printer
|
|
30
|
+
(`bobina printer add test --path out.bin`).
|
|
31
|
+
|
|
32
|
+
## Map
|
|
33
|
+
|
|
34
|
+
| Path | What |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `src/bobina/engine.py`, `layout.py` | document → rows (native text, raster, codes, feeds) |
|
|
37
|
+
| `src/bobina/escpos.py`, `transport.py` | rows → bytes; Bluetooth, file and command transports |
|
|
38
|
+
| `src/bobina/cli/` | commands (`args.py` parser, `documents.py` print/check/preview, `device.py` printer/calibrate) |
|
|
39
|
+
| `src/bobina/profiles/` | printer profiles (TOML) and their sources |
|
|
40
|
+
| `evals/agent-eval.yaml` | agent tasks for cli-agent-eval (how well agents use this CLI) |
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-11-10
|
|
4
|
+
|
|
5
|
+
First version, calibrated on a Goldensky MTP-II (58 mm).
|
|
6
|
+
|
|
7
|
+
- Printer profiles: 32 more, generated from escpos-printer-db (CC BY 4.0) by `tools/import_profiles.py`: model facts
|
|
8
|
+
only, `measured = false`, conservative (ASCII native text, raster barcodes, safe mode) until `bobina calibrate tear`
|
|
9
|
+
and `bobina calibrate feed`; `bobina profile list` says which are measured. Sources: `src/bobina/profiles/SOURCES.md`.
|
|
10
|
+
- `--marks none|cut|tear|both`: a fine dashed line where one label or order ends (none by default).
|
|
11
|
+
- Payment codes: the ```` ```pay ```` block, one set of fields (`to`, `name`, `amount`, `ref`, `note`) for 15 schemes
|
|
12
|
+
(Pix, UPI, SEPA/EPC, PromptPay, PayNow, bitcoin, lightning, PayPal, Cash App, Venmo, Revolut, Wise, PicPay,
|
|
13
|
+
Mercado Pago, and `other` for a pasted payload). Pix is `pay {scheme=pix}`; template `pay-sign`. `bobina help pay SCHEME` (and
|
|
14
|
+
docs/pay.md) says what each field means in that scheme, its official name and its limit. A value over a limit
|
|
15
|
+
is `Q111`, never cut. Only Pix was scanned with real apps; the others carry the note `P302`.
|
|
16
|
+
- Any ```` ```qr ```` takes `caption=`: `auto` under a payment code ("Pix · R$ 10,00"), `none`, or your own text.
|
|
17
|
+
- Money without a `locale:` is written as the currency's country does (R$ 24,90, ₹120.00), not as the system
|
|
18
|
+
LANG would (R$24.90 on an en_US machine). A document that declares `locale:` keeps its own.
|
|
19
|
+
- Exit code 9: the job went out without its payment code (`Q111`); the rest printed. `--allow Q111` gives 0.
|
|
20
|
+
- `print --json` and `preview --json` write the outcome, the figures and the problems (schemas `bobina.print/1`,
|
|
21
|
+
`bobina.preview/1`); before, `print --json` hid the warnings. `paper` and `calibrate tear|feed` answer in JSON
|
|
22
|
+
too; `view`, `edit`, `send` and `mode` say that `--json` does nothing there.
|
|
23
|
+
- `bobina help CODE` (what a problem means and what to do, docs/problems/), `bobina help --json` (commands,
|
|
24
|
+
options, topics, problems and exit codes for programs), `llms.txt`, `llms-full.txt` and an agent skill
|
|
25
|
+
(`skills/bobina`).
|
|
26
|
+
- Templates for shops: `clothing-tag`, `jewelry-tag`, `shelf-price`, `sale-price`, `food-label`, `shipping-label`
|
|
27
|
+
(docs/guides/templates.md); prices typed as people type them (`24,90`, `R$ 1.890,00`), `M202` when a value is
|
|
28
|
+
not a number. Notes `P301` (a feature not tested on paper yet) and `P302` (a payment code not tested in its app).
|
|
29
|
+
- Safe by default for printers nobody measured: feeds after images as white raster rows, idle wait before the end
|
|
30
|
+
of an image job; `bobina calibrate feed` turns it off for printers that do not need it.
|
|
31
|
+
|
|
32
|
+
- Speed and memory: each QR is encoded once (cache), a batch decodes a logo once (per-file cache keyed by size and
|
|
33
|
+
modification time), big JPEGs are decoded reduced (6000x4000 target: 250 MB -> 72 MB); `--pages` writes the PDF
|
|
34
|
+
page by page at 1:1 (copies share one image); image export draws each label once.
|
|
35
|
+
- Long jobs: previews drawn page by page and split above ~2.5 m (1000 labels: 422 MB -> 88 MB); a print keeps only
|
|
36
|
+
the shown labels' preview for `--last` (425 MB -> 68 MB); 1-bit PNGs. Bounded waits: download deadline and DNS
|
|
37
|
+
timeout, device-file and bluetoothctl timeouts, stdin size caps, `BOBINA_NONINTERACTIVE=1`.
|
|
38
|
+
- Concurrency: a print reserves its seq numbers when it is built (given back when it is not printed and nobody took
|
|
39
|
+
later ones), one job per printer at a time (`busy_timeout`, `E405`), and every state file (counters, paper,
|
|
40
|
+
history, library, config, last job, exports) is written atomically under a lock; a damaged one is `E209`.
|
|
41
|
+
- `.bob.md`: Markdown + Pandoc attributes, fenced objects (QR, barcodes, payment codes, Data Matrix, PDF417, images,
|
|
42
|
+
raw bytes), Jinja variables in a sandbox, CLDR dates/numbers per locale, persistent counters, templates.
|
|
43
|
+
- Engine: one set of print rows feeds the exact preview and the ESC/POS bytes; native when the printer does it
|
|
44
|
+
well, raster per line when it does not; host word wrap; printer-font emulation for previews.
|
|
45
|
+
- CLI: `print` with a confirmation screen (`--yes` to skip), `check`, `preview` (`--realistic`), `view`, `edit`,
|
|
46
|
+
`template`, `seq`, `printer`, `status`, `mode`, `paper` (roll odometer), `calibrate compare|tear|feed`, `fonts`.
|
|
47
|
+
- Spacing attributes: `tracking=` (letter spacing), `indent=` (columns or mm/px), `leading=` (line spacing), in the
|
|
48
|
+
printer font (`ESC SP`, `ESC 3`) and in raster fonts.
|
|
49
|
+
- Text filters: `titlecase` (by language), `deaccent`, `camel`, `pascal`, `snake`, `kebab`, `slug`, `smallcaps`; also
|
|
50
|
+
in export names (`{item|slug}`). Security: filters, string methods and joins keep data marked (no image path
|
|
51
|
+
from data through `| lower` or `'img/' ~ value`).
|
|
52
|
+
- `bobina history` (list, show, stats, clear): every print job recorded privately (schema `bobina.history/1`).
|
|
53
|
+
- `--problems pretty|plain|json|jsonl` (or `BOBINA_PROBLEMS`): every error and warning, also usage errors, in a
|
|
54
|
+
stable schema (`bobina.problem/1`) for scripts and logs.
|
|
55
|
+
- Export: `-o` with `{n}`, `{i}`, `{file}`, `{part}`, `{column}` saves one file per label (printer bytes from
|
|
56
|
+
`print`, images from `preview`); `preview --pages -o x.pdf` makes one page per label.
|
|
57
|
+
- Several data sources: `--data a=FILE --join b=FILE --on KEY` (left join; bare column names when unambiguous,
|
|
58
|
+
`{{ b.col }}` to choose; warnings for unmatched/duplicate keys and shared columns).
|
|
59
|
+
- Every problem has a code: errors `E1xx`–`E5xx` join the nudge catalog; one format for all of them.
|
|
60
|
+
- One strip: several files and `--then` parts in one job (one preview, confirmation and paper count);
|
|
61
|
+
`--compact`/`--no-compact` per part and `--compact-all`; ✂ cut lines inside compact parts and between two
|
|
62
|
+
compact parts, the tear feed elsewhere; counters continue across parts.
|
|
63
|
+
- Data for batches: `--data` reads CSV, TSV/TXT (delimiter detected, cp1252 fallback), Excel `.xlsx`
|
|
64
|
+
(`--sheet`, `bobina[xlsx]`) and JSON; `--rows`, `--where`, `--copies-from` (copies per row).
|
|
65
|
+
- Saved labels: `bobina library add|list|show|rm` and `bobina print @NAME` (with `--set`/`--ask`).
|
|
66
|
+
- `--ask`: the missing template fields (or a document's undeclared `{{ variables }}`) are asked on the
|
|
67
|
+
terminal, with help, examples, defaults and table rows; answers are literal data like `--set`.
|
|
68
|
+
- Images: SVG and PDF pages rendered at the paper width (`bobina[svg]`, `bobina[pdf]`); `bobina print`
|
|
69
|
+
takes a photo, SVG, PDF (`--page N-M`) or https image directly; https images only with `--allow-net`,
|
|
70
|
+
public hosts only (see `docs/SECURITY.md`).
|
|
71
|
+
- Codes fill the paper: `size=full|half` for QR/Data Matrix, stretched barcodes, long barcodes turned vertical.
|
|
72
|
+
- Safety: data from CSV/`--set` is literal (no Markdown, template or printer-command injection), template paths
|
|
73
|
+
confined, sandbox limits; see `docs/SECURITY.md`.
|
|
74
|
+
- Profile `mtp-ii-58` with measured limits (`docs/printers/mtp-ii-58.md`) and conformance tickets
|
|
75
|
+
(`spec/conformance/`).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
- `python -m pip install -e ".[dev]"`, then `pytest -q` and `ruff check src tests && ruff format src tests`.
|
|
4
|
+
No printer is needed: tests compare exact bitmaps (`tests/golden/`, `spec/conformance/`).
|
|
5
|
+
- An intended rendering change regenerates the bitmaps with `BOBINA_UPDATE_GOLDEN=1 pytest tests/test_golden.py`;
|
|
6
|
+
look at every changed PNG before committing.
|
|
7
|
+
- New printer? Copy `src/bobina/profiles/mtp-ii-58.toml`, print the conformance tickets, photograph them, and
|
|
8
|
+
record what works in `docs/printers/<id>.md`. Defaults must come from printed evidence.
|
|
9
|
+
- Principles: nudges, not blocks (warn with a suggestion and the evidence, never refuse — except security);
|
|
10
|
+
full command names in docs and output; English everywhere.
|
|
11
|
+
- Never commit personal data: `python tools/leakscan.py` runs in CI on every push and pull request.
|
|
12
|
+
`tools/install-hooks.sh` installs the same scan as git hooks (staged files and commit messages).
|
|
13
|
+
- Releases are tags (`.github/workflows/release.yml`): each one is published to PyPI, installed on Linux, macOS and
|
|
14
|
+
Windows, and becomes a GitHub release. `vX.Y.Z-rc.N` is a pre-release (only installed when asked for). The tag must match the version
|
|
15
|
+
in `pyproject.toml` (`0.2.0rc1` for `v0.2.0-rc.1`), and CHANGELOG.md gets a `## 0.2.0` section first.
|
|
16
|
+
|
|
17
|
+
## A new command or option
|
|
18
|
+
|
|
19
|
+
A test fails until each step is done, so nothing is forgotten when the CLI grows:
|
|
20
|
+
|
|
21
|
+
1. **Kind of command**: add it to `COMMAND_KIND` in `src/bobina/cli/args.py`: `prints` (it takes `--yes`,
|
|
22
|
+
`--dry-run`, `--editor`) or `reads` (it never prints; those options are not listed in its help and, if given,
|
|
23
|
+
only a note says they do nothing).
|
|
24
|
+
Without it, building the parser raises an error that says this.
|
|
25
|
+
2. **Synonyms of each new long option**: what people type for it (English and Portuguese) in `ALSO_KNOWN`, or the
|
|
26
|
+
option in `NO_SYNONYMS` when its own name is what people type. No word may be a synonym of two options.
|
|
27
|
+
3. **Hint cases**: at least one realistic mistyped option for each new command in `CASES`
|
|
28
|
+
(`tests/test_option_hints.py`).
|
|
29
|
+
4. Typos of every option of every command (swapped neighbours, a dropped letter, `_` for `-`, upper case, one dash)
|
|
30
|
+
are tested automatically; nothing to add.
|
|
31
|
+
|
|
32
|
+
## A new problem code
|
|
33
|
+
|
|
34
|
+
Add it to `CATALOG` (`src/bobina/nudges.py`), say what to do in `FIX` (`src/bobina/problem_help.py`) and add it to
|
|
35
|
+
the table in `docs/FORMAT.md`, then run `python tools/gen_problem_docs.py` (the flow diagram and docs/problems/).
|
|
36
|
+
`tests/test_docs.py` and `tests/test_problem_help.py` check them.
|
bobina-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rafael Bressan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
bobina-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: bobina
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Print Markdown on thermal roll printers, with calibrated defaults, previews you can trust and nudges instead of blocks.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
License-File: src/bobina/fonts/licenses/AtkinsonHyperlegible-OFL.txt
|
|
8
|
+
License-File: src/bobina/fonts/licenses/Barlow-OFL.txt
|
|
9
|
+
License-File: src/bobina/fonts/licenses/DejaVu-LICENSE.txt
|
|
10
|
+
License-File: src/bobina/fonts/licenses/IBMPlex-OFL.txt
|
|
11
|
+
License-File: src/bobina/fonts/licenses/Spleen-BSD-2.txt
|
|
12
|
+
License-File: src/bobina/fonts/licenses/Terminus-OFL.txt
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Printing
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: babel>=2.12
|
|
19
|
+
Requires-Dist: fonttools>=4.40
|
|
20
|
+
Requires-Dist: jinja2>=3.1
|
|
21
|
+
Requires-Dist: markdown-it-py>=3
|
|
22
|
+
Requires-Dist: mdit-py-plugins>=0.4
|
|
23
|
+
Requires-Dist: pdf417gen>=0.7
|
|
24
|
+
Requires-Dist: pillow>=10
|
|
25
|
+
Requires-Dist: ppf-datamatrix>=0.2
|
|
26
|
+
Requires-Dist: python-barcode>=0.15
|
|
27
|
+
Requires-Dist: pyyaml>=6
|
|
28
|
+
Requires-Dist: segno>=1.6
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: openpyxl>=3.1; extra == 'dev'
|
|
31
|
+
Requires-Dist: pypdfium2>=5.14; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
33
|
+
Requires-Dist: resvg-py>=0.5; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
35
|
+
Provides-Extra: pdf
|
|
36
|
+
Requires-Dist: pypdfium2>=5.14; extra == 'pdf'
|
|
37
|
+
Provides-Extra: svg
|
|
38
|
+
Requires-Dist: resvg-py>=0.5; extra == 'svg'
|
|
39
|
+
Provides-Extra: xlsx
|
|
40
|
+
Requires-Dist: openpyxl>=3.1; extra == 'xlsx'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# bobina
|
|
44
|
+
|
|
45
|
+
Print Markdown on thermal roll printers — receipts, lists, labels, QR codes, payment codes (Pix, UPI, SEPA…), photos —
|
|
46
|
+
with calibrated defaults, a preview you can trust, and **nudges instead of blocks**.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bobina print "# MARKET\n- [ ] milk\n- [ ] bread"
|
|
50
|
+
bobina print list.bob.md
|
|
51
|
+
bobina template new receipt sale.bob.md && bobina print sale.bob.md
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Every command that prints shows a **confirmation screen** first (the dry run): a terminal preview
|
|
55
|
+
on the real 32-column grid, paper length, counters it will use, and nudges with the evidence
|
|
56
|
+
behind them. `p` prints; `--yes` skips it. **Without a terminal (scripts, agents, ssh) add `--yes`**:
|
|
57
|
+
otherwise nobody can answer and the print stops with `E107` (exit 7). `-n` shows the screen and prints nothing.
|
|
58
|
+
Agents: start at [llms.txt](llms.txt) (or [llms-full.txt](llms-full.txt), everything in one file); a ready skill
|
|
59
|
+
is in [skills/bobina/SKILL.md](skills/bobina/SKILL.md).
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
┌─ preview · mtp-ii-58 · 58 mm ──┐
|
|
63
|
+
│ │
|
|
64
|
+
│M A R K E T │
|
|
65
|
+
│ │
|
|
66
|
+
│☐ milk │
|
|
67
|
+
│☐ bread │
|
|
68
|
+
└────────────────────────────────┘
|
|
69
|
+
~3.1 cm of paper · 1 native rows, 2 raster · 2.9 KB
|
|
70
|
+
[p] print [s] save as… [q] quit
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Why
|
|
74
|
+
|
|
75
|
+
Cheap 58/80 mm printers are good enough for a lot — if something takes care of their limits.
|
|
76
|
+
bobina was calibrated on a minimum-quality printer (Goldensky MTP-II): every default comes from
|
|
77
|
+
a printed and photographed test, so what works there works on better printers. You can ask for
|
|
78
|
+
anything; bobina only warns, and tells you why (`warning[Q101] … (evidence: N6)`).
|
|
79
|
+
|
|
80
|
+
- **One engine** for check, preview and print: the PNG preview is the exact dot pattern sent.
|
|
81
|
+
- **Native when the printer does it well, raster when it does not** — per line, decided by the
|
|
82
|
+
printer profile (glyphs the font lacks, italic, inverted bands, drawn checkboxes).
|
|
83
|
+
- **Readable documents**: plain Markdown plus Pandoc-style attributes. See [docs/FORMAT.md](docs/FORMAT.md).
|
|
84
|
+
- **Safe data**: values from CSV or `--set` are printed literally and can never inject Markdown,
|
|
85
|
+
template code or printer commands; templates run in a sandbox.
|
|
86
|
+
|
|
87
|
+
## Install
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
uv tool install bobina # or: pipx install bobina (from a checkout: ./install.sh)
|
|
91
|
+
bobina printer scan # find the Bluetooth address
|
|
92
|
+
bobina printer add mtp AA:BB:CC:DD:EE:FF
|
|
93
|
+
bobina status # paper, cover, errors
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Printer on another machine? `bobina printer add tower --command "ssh tower bobina send --yes -"`.
|
|
97
|
+
No printer at hand? `bobina printer add test --path out.bin` appends every job's bytes to `out.bin` (a file
|
|
98
|
+
printer: `status` and `paper` need Bluetooth; `bobina printer list` shows where each printer goes).
|
|
99
|
+
|
|
100
|
+
## The format in one screen
|
|
101
|
+
|
|
102
|
+
````markdown
|
|
103
|
+
---
|
|
104
|
+
locale: pt-BR
|
|
105
|
+
counters:
|
|
106
|
+
ticket: { start: 1, format: "A-{:03}" }
|
|
107
|
+
fonts:
|
|
108
|
+
body: native # the printer's own font (fast)
|
|
109
|
+
hand: Z003 # any alias you like
|
|
110
|
+
---
|
|
111
|
+
# TICKET {{ seq.ticket }} {.invert .center}
|
|
112
|
+
|
|
113
|
+
{{ now | date("full") }} <!-- quinta-feira, 5 de novembro de 2026 -->
|
|
114
|
+
|
|
115
|
+
- [ ] open task
|
|
116
|
+
- [x] done task (struck)
|
|
117
|
+
|
|
118
|
+
| Item | Qty | Price |
|
|
119
|
+
|---|--:|--:|
|
|
120
|
+
| Bread | 6 | {{ 4.8 | currency("BRL") }} |
|
|
121
|
+
|
|
122
|
+
```qr
|
|
123
|
+
https://example.com
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```pay {scheme=pix to="you@example.com" name="Shop" city="Rio" amount=10.00}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Signed, [Ana]{font=hand size=32px}
|
|
130
|
+
|
|
131
|
+
--- {.cut}
|
|
132
|
+
````
|
|
133
|
+
|
|
134
|
+
`bobina help format` has the rest: sizes (`small`…`huge`, physical mm), dividers, barcodes,
|
|
135
|
+
Data Matrix, PDF417, images (dither and gamma), rotation, upside-down text, templates and data.
|
|
136
|
+
|
|
137
|
+
## One label per row
|
|
138
|
+
|
|
139
|
+
A document with `{{ blanks }}` plus a table (CSV, TSV, JSON, Excel) prints one label per row; the
|
|
140
|
+
columns fill the blanks. Step by step: [docs/guides/batch-printing.md](docs/guides/batch-printing.md);
|
|
141
|
+
in the terminal: `bobina help data`.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
bobina check jar-label.bob.md --data pantry.csv # paper length and problems, nothing printed
|
|
145
|
+
bobina print jar-label.bob.md --data pantry.csv --where shelf=A --copies-from jars --yes
|
|
146
|
+
bobina print tag.bob.md --data stock.xlsx --sheet Estoque --yes # a sheet other than the first
|
|
147
|
+
bobina print shelf-tag.bob.md --data a=prices.xlsx --join b=catalog.json --on name --yes
|
|
148
|
+
bobina print note.bob.md --set produto=Feijão --set validade=2027-01-31 --yes # one value, no table
|
|
149
|
+
bobina preview jar-label.bob.md --data pantry.csv -o 'labels/{product}.png' # one PNG per label
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
A `{{ name }}` that is not a column prints empty and is reported (`M130`, with the columns the data has).
|
|
153
|
+
|
|
154
|
+
## Commands
|
|
155
|
+
|
|
156
|
+
| Command | Does |
|
|
157
|
+
|---|---|
|
|
158
|
+
| `print [FILE \| "TEXT" \| -]` | render → confirm → print. `--data rows.csv` (above), `--set KEY=VALUE`, `--copies N` (identical), `--repeat N` (new counter values each), `-o job.bin` (save, do not print) |
|
|
159
|
+
| `print --last` / `--again` | reprint the last job: identical bytes / re-rendered (new `now` and counters); with `-o FILE` save instead |
|
|
160
|
+
| `check FILE` | problems, paper length, native/raster rows, nothing printed (`--data`, `--json` → `{"stats", "problems"}`, `--strict`) |
|
|
161
|
+
| `preview FILE \| "TEXT"` | exact PNG (`-o out.png`); `--data rows.csv -o 'dir/{column}.png'` one per label; `--realistic` simulates ink spread |
|
|
162
|
+
| `view FILE` | preview in the terminal |
|
|
163
|
+
| `edit [FILE] [--template NAME]` | write in your editor, confirm, print |
|
|
164
|
+
| `template list \| show \| new` | receipt, label, todo, card, note, and a shop gallery: [guides/templates.md](docs/guides/templates.md) |
|
|
165
|
+
| `seq list \| set \| reset` | persistent counters (`{{ seq.NAME }}`); `seq set ticket 100`: the next print uses 100 |
|
|
166
|
+
| `printer scan \| add \| list \| use \| remove` | printers live in `~/.config/bobina/config.toml` |
|
|
167
|
+
| `status`, `mode [esc\|tspl\|cpcl]` | printer state; command language |
|
|
168
|
+
| `calibrate compare PHOTO FILE` | how close the preview is to the paper |
|
|
169
|
+
| `calibrate tear` / `calibrate feed` | where the tear bar is; whether feeds after images need the safe mode |
|
|
170
|
+
| `paper [new 15m \| used 3m]` | paper left on the roll (the printer only reports "out") |
|
|
171
|
+
| `profile list \| show`, `fonts list`, `send FILE.bin` | profiles, tested fonts, raw bytes |
|
|
172
|
+
|
|
173
|
+
`bob` is the same binary. Short aliases exist for typing (`bobina help aliases`); docs always use
|
|
174
|
+
full names. Exit codes: 0 ok · 1 usage · 2 document · 3 strict nudges · 4 unreachable · 5 printer
|
|
175
|
+
not ready · 6 language · 7 confirmation needed/declined · 8 security · 9 a payment code left out (Q111).
|
|
176
|
+
|
|
177
|
+
## Repository
|
|
178
|
+
|
|
179
|
+
| Path | What |
|
|
180
|
+
|---|---|
|
|
181
|
+
| `src/bobina/` | the package (engine, layout, ESC/POS, preview, CLI) and built-in profiles, templates, fonts |
|
|
182
|
+
| `docs/` | [`FORMAT.md`](docs/FORMAT.md) (the `.bob.md` spec), [`CLI.md`](docs/CLI.md) (commands, options, problems, exit codes), [`SECURITY.md`](docs/SECURITY.md) (threat model), [`guides/batch-printing.md`](docs/guides/batch-printing.md), [`TESTED.md`](docs/TESTED.md) (what was and was not tested on paper) |
|
|
183
|
+
| `examples/` | documents and data used by the guides (`examples/batch/`) |
|
|
184
|
+
| `spec/conformance/` | test tickets with expected bitmaps — a `bob/1` implementation renders them identically |
|
|
185
|
+
| `tests/` | unit, security and golden tests (no printer needed) |
|
|
186
|
+
| `docs/printers/` | measured capabilities of each profiled printer (`mtp-ii-58.md`) |
|
|
187
|
+
| `contrib/` | Bluetooth boot service for Linux (`install.sh` sets it up) |
|
|
188
|
+
| `tools/` | maintenance scripts: public export and personal-data scan |
|
|
189
|
+
|
|
190
|
+
## Status
|
|
191
|
+
|
|
192
|
+
Pre-release (v0.x). MIT licensed (bundled fonts keep their own licenses: `src/bobina/fonts/LICENSES.md`). Baseline profile: `mtp-ii-58` (measured); 32 more imported from escpos-printer-db, not measured on paper (`bobina profile list`; calibrate before trusting them). Realistic preview is an estimate until
|
|
193
|
+
the profile is calibrated from photos (`preview.calibrated = false`).
|
|
194
|
+
|
|
195
|
+
## Hardware notes (MTP-II)
|
|
196
|
+
|
|
197
|
+
| Symptom | Likely cause |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `printer unreachable` | printer off or asleep: press POWER until the yellow LED is steady |
|
|
200
|
+
| Blank output | roll upside down (the side that darkens with a fingernail faces the head) |
|
|
201
|
+
| Turns off when printing | battery: charge it; first use: remove the film on the battery contacts |
|
|
202
|
+
| Prints garbage | another app sent a different command language (TSPL/CPCL) as text; the MTP-II itself only speaks ESC/POS |
|
|
203
|
+
| Self-test | printer off → hold FEED, press POWER, release at the beep |
|
|
204
|
+
| The last line stops at the tear bar, or a `--marks tear` line is off | `bobina calibrate tear` (a ruler: tear it and read the mm), then `bobina calibrate feed` (does the printer obey a paper feed right after an image?) |
|