Files
michele 58185c2215 Add ADG936BCPZ, OSD32MP157F-1G-BAA, TPS22965DSGR, AXP2101; add programmatic API and usage guide
- scripts/mikilab_lib.py / mikilab_cli.py: typed Python API + JSON CLI
  for add/update/remove/query, for other apps to integrate without
  shelling out to add_component.py
- lib_common.py/import_component.py/import_batch.py: regenerate
  sym-lib-table.global/fp-lib-table.global on every mutating call, not
  just the project-local tables
- scripts/find_missing_3d_models.py: lists components with no 3D model
  and search links to fill the gap
- docs/GUIDA_USO.md: practical Italian usage guide (companion to README.md)
- checkup fixes: removed orphan duplicate footprint
  footprints/other/CP_20_1_ADI.kicad_mod, and a dangling
  ${easyeda2kicad}/tmp 3D model reference in AXP2101's footprint that
  pointed at a file that never existed on disk
2026-08-30 14:50:08 +02:00

163 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""
mikilab_cli.py
===============
Stable JSON-over-stdout CLI for mikilab_lib.py, for apps that are not
Python (Swift, C++, ...) or that otherwise want to drive the MIKILAB
library out-of-process instead of importing it directly.
Every invocation prints exactly one JSON object to stdout and nothing
else (diagnostics, if any, go to stderr) -- safe to pipe straight into a
JSON parser. Exit code is 0 iff "ok" is true.
On success:
{"ok": true, "data": <command-specific object or array>}
On failure:
{"ok": false, "error": "<human-readable message>"}
Commands:
add --name NAME --symbol PATH [--footprint PATH] [--model PATH] [--category CAT]
update --name NAME --symbol PATH [--footprint PATH] [--model PATH] [--category CAT]
remove --name NAME
get --name NAME
list [--category CAT]
find --query TEXT [--category CAT]
Examples:
python3 scripts/mikilab_cli.py add --name TPS7A2018PDBVR \\
--symbol /path/TPS7A2018PDBVR.kicad_sym \\
--footprint /path/SOT95P280X145-5N.kicad_mod --category power
python3 scripts/mikilab_cli.py get --name TPS7A2018PDBVR
python3 scripts/mikilab_cli.py find --query tps22
From Swift: run as a subprocess (Process), read stdout, decode with
JSONDecoder. From C++: popen()/posix_spawn + any JSON library (e.g.
nlohmann::json). In both cases, check the process exit code as well as
"ok" -- a non-zero exit always means "ok": false was printed (or, if the
process crashed before printing anything, stdout will be empty).
"""
from __future__ import annotations
import argparse
import dataclasses
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import mikilab_lib as mikilab
def _emit(payload: dict) -> int:
print(json.dumps(payload, indent=2))
return 0 if payload.get("ok") else 1
def _ok(data) -> dict:
if dataclasses.is_dataclass(data):
data = dataclasses.asdict(data)
elif isinstance(data, list):
data = [dataclasses.asdict(x) if dataclasses.is_dataclass(x) else x for x in data]
return {"ok": True, "data": data}
def _err(message: str) -> dict:
return {"ok": False, "error": message}
def cmd_add(args) -> dict:
result = mikilab.add_component(
name=args.name, symbol=args.symbol, footprint=args.footprint,
model=args.model, category=args.category,
)
return _ok(result)
def cmd_update(args) -> dict:
result = mikilab.update_component(
name=args.name, symbol=args.symbol, footprint=args.footprint,
model=args.model, category=args.category,
)
return _ok(result)
def cmd_remove(args) -> dict:
result = mikilab.remove_component(args.name)
return _ok(result)
def cmd_get(args) -> dict:
result = mikilab.get_component(args.name)
if result is None:
return _err(f"no component named '{args.name}' found")
return _ok(result)
def cmd_list(args) -> dict:
return _ok(mikilab.list_components(category=args.category))
def cmd_find(args) -> dict:
return _ok(mikilab.find_components(args.query, category=args.category))
def main() -> int:
parser = argparse.ArgumentParser(description="JSON CLI over mikilab_lib.py")
sub = parser.add_subparsers(dest="command", required=True)
p_add = sub.add_parser("add", help="Add a new component")
p_add.add_argument("--name", required=True)
p_add.add_argument("--symbol", required=True)
p_add.add_argument("--footprint")
p_add.add_argument("--model")
p_add.add_argument("--category", choices=mikilab.lc.CATEGORIES)
p_add.set_defaults(func=cmd_add)
p_update = sub.add_parser("update", help="Replace an existing component in place (or create it)")
p_update.add_argument("--name", required=True)
p_update.add_argument("--symbol", required=True)
p_update.add_argument("--footprint")
p_update.add_argument("--model")
p_update.add_argument("--category", choices=mikilab.lc.CATEGORIES)
p_update.set_defaults(func=cmd_update)
p_remove = sub.add_parser("remove", help="Remove a component")
p_remove.add_argument("--name", required=True)
p_remove.set_defaults(func=cmd_remove)
p_get = sub.add_parser("get", help="Look up one component by exact name")
p_get.add_argument("--name", required=True)
p_get.set_defaults(func=cmd_get)
p_list = sub.add_parser("list", help="List components, optionally filtered by category")
p_list.add_argument("--category", choices=mikilab.lc.CATEGORIES)
p_list.set_defaults(func=cmd_list)
p_find = sub.add_parser("find", help="Case-insensitive substring search by name")
p_find.add_argument("--query", required=True)
p_find.add_argument("--category", choices=mikilab.lc.CATEGORIES)
p_find.set_defaults(func=cmd_find)
args = parser.parse_args()
try:
payload = args.func(args)
except mikilab.MikilabError as e:
payload = _err(str(e))
except Exception as e: # last-resort guard so callers always get valid JSON, never a traceback on stdout
payload = _err(f"unexpected error: {e}")
return _emit(payload)
if __name__ == "__main__":
sys.exit(main())