diff options
| -rw-r--r-- | firefly_cli/output.py | 37 | ||||
| -rw-r--r-- | tests/unit/test_output.py | 31 |
2 files changed, 68 insertions, 0 deletions
diff --git a/firefly_cli/output.py b/firefly_cli/output.py new file mode 100644 index 0000000..8da2b1f --- /dev/null +++ b/firefly_cli/output.py @@ -0,0 +1,37 @@ +# Copyright (C) 2026 Danilo M. <danix@danix.xyz> GPL-2.0-only +import json +import sys + +def unwrap(resp): + """Flatten Firefly's JSON:API envelope to plain id+attributes objects.""" + def flat(item): + out = {"id": item.get("id")} + out.update(item.get("attributes", {})) + return out + data = resp.get("data", resp) + if isinstance(data, list): + return [flat(i) for i in data] + if isinstance(data, dict) and "attributes" in data: + return flat(data) + return data + +def emit(data, human=False, stream=None): + stream = stream or sys.stdout + if not human: + json.dump(data, stream, indent=2, default=str) + stream.write("\n") + return + rows = data if isinstance(data, list) else [data] + if not rows: + stream.write("(no results)\n") + return + cols = list(rows[0].keys()) + widths = {c: max(len(c), *(len(str(r.get(c, ""))) for r in rows)) for c in cols} + stream.write(" ".join(c.ljust(widths[c]) for c in cols) + "\n") + for r in rows: + stream.write(" ".join(str(r.get(c, "")).ljust(widths[c]) for c in cols) + "\n") + +def emit_error(message, stream=None): + stream = stream or sys.stderr + json.dump({"error": message}, stream) + stream.write("\n") diff --git a/tests/unit/test_output.py b/tests/unit/test_output.py new file mode 100644 index 0000000..c71a424 --- /dev/null +++ b/tests/unit/test_output.py @@ -0,0 +1,31 @@ +import io, json, unittest +from contextlib import redirect_stdout +from firefly_cli.output import unwrap, emit + +class TestOutput(unittest.TestCase): + def test_unwrap_list_returns_clean_objects(self): + resp = {"data": [ + {"id": "1", "type": "accounts", "attributes": {"name": "Checking"}}, + {"id": "2", "type": "accounts", "attributes": {"name": "Savings"}}, + ]} + self.assertEqual(unwrap(resp), + [{"id": "1", "name": "Checking"}, {"id": "2", "name": "Savings"}]) + + def test_unwrap_single_object(self): + resp = {"data": {"id": "5", "type": "accounts", + "attributes": {"name": "Wallet"}}} + self.assertEqual(unwrap(resp), {"id": "5", "name": "Wallet"}) + + def test_emit_json_default(self): + buf = io.StringIO() + with redirect_stdout(buf): + emit([{"id": "1", "name": "x"}], human=False) + self.assertEqual(json.loads(buf.getvalue()), [{"id": "1", "name": "x"}]) + + def test_emit_human_table_contains_values(self): + buf = io.StringIO() + with redirect_stdout(buf): + emit([{"id": "1", "name": "Checking"}], human=True) + out = buf.getvalue() + self.assertIn("Checking", out) + self.assertIn("id", out) |
