@lewishowles/cli-style 0.13.0 → 0.15.0

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.
@@ -0,0 +1,503 @@
1
+ # Generated by scripts/generate-adapters.js. Do not edit directly.
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from .core import render
8
+
9
+ def agent_transcript(
10
+ entries: list[dict[str, Any]] | None = None,
11
+ title: str | None = None,
12
+ **kwargs: Any,
13
+ ) -> str:
14
+ """Return agent-transcript output; extra keyword arguments go to render()."""
15
+ data = {
16
+ "entries": [] if entries is None else entries,
17
+ }
18
+
19
+ if title is not None:
20
+ data["title"] = title
21
+
22
+ return render("agent-transcript", data, **kwargs)
23
+
24
+
25
+ def audit_finding(
26
+ result: str,
27
+ finding: str,
28
+ location: str = "",
29
+ recommendation: str = "",
30
+ evidence: str = "",
31
+ reference: str = "",
32
+ **kwargs: Any,
33
+ ) -> str:
34
+ """Return audit-finding output; extra keyword arguments go to render()."""
35
+ data = {
36
+ "result": result,
37
+ "finding": finding,
38
+ "location": location,
39
+ "recommendation": recommendation,
40
+ "evidence": [evidence] if evidence else [],
41
+ "references": [reference] if reference else [],
42
+ }
43
+
44
+ return render("audit-finding", data, **kwargs)
45
+
46
+
47
+ def bar_chart(
48
+ rows: list[dict[str, Any]] | None = None,
49
+ bar_width: int | float | None = None,
50
+ **kwargs: Any,
51
+ ) -> str:
52
+ """Return bar-chart output; extra keyword arguments go to render()."""
53
+ data = {
54
+ "rows": [] if rows is None else rows,
55
+ }
56
+
57
+ if bar_width is not None:
58
+ data["barWidth"] = bar_width
59
+
60
+ return render("bar-chart", data, **kwargs)
61
+
62
+
63
+ def bullet_list(
64
+ items: list[dict[str, Any]],
65
+ **kwargs: Any,
66
+ ) -> str:
67
+ """Return bullet-list output; extra keyword arguments go to render()."""
68
+ data = {
69
+ "items": items,
70
+ }
71
+
72
+ return render("bullet-list", data, **kwargs)
73
+
74
+
75
+ def chip(
76
+ label: str,
77
+ tone: str = "neutral",
78
+ **kwargs: Any,
79
+ ) -> str:
80
+ """Return chip output; extra keyword arguments go to render()."""
81
+ data = {
82
+ "label": label,
83
+ "tone": tone,
84
+ }
85
+
86
+ return render("chip", data, **kwargs)
87
+
88
+
89
+ def command_result(
90
+ result: str,
91
+ summary: str,
92
+ command: str = "",
93
+ exit_code: int | float | None = None,
94
+ duration: str = "",
95
+ detail: str = "",
96
+ **kwargs: Any,
97
+ ) -> str:
98
+ """Return command-result output; extra keyword arguments go to render()."""
99
+ data = {
100
+ "result": result,
101
+ "summary": summary,
102
+ "command": command,
103
+ "duration": duration,
104
+ "details": [detail] if detail else [],
105
+ }
106
+
107
+ if exit_code is not None:
108
+ data["exitCode"] = exit_code
109
+
110
+ return render("command-result", data, **kwargs)
111
+
112
+
113
+ def compact_data_table(
114
+ columns: list[dict[str, Any]],
115
+ rows: list[dict[str, Any]],
116
+ summary: str = "",
117
+ title: str | None = None,
118
+ **kwargs: Any,
119
+ ) -> str:
120
+ """Return compact-data-table output; extra keyword arguments go to render()."""
121
+ data = {
122
+ "columns": columns,
123
+ "rows": rows,
124
+ "summary": summary,
125
+ }
126
+
127
+ if title is not None:
128
+ data["title"] = title
129
+
130
+ return render("compact-data-table", data, **kwargs)
131
+
132
+
133
+ def confirmation_result(
134
+ state: str,
135
+ action: str,
136
+ item: str = "",
137
+ detail: str = "",
138
+ **kwargs: Any,
139
+ ) -> str:
140
+ """Return confirmation-result output; extra keyword arguments go to render()."""
141
+ data = {
142
+ "state": state,
143
+ "action": action,
144
+ "item": item,
145
+ "detail": detail,
146
+ }
147
+
148
+ return render("confirmation-result", data, **kwargs)
149
+
150
+
151
+ def diagnostic_report(
152
+ title: str | None = None,
153
+ checks: list[dict[str, Any]] | None = None,
154
+ findings: list[dict[str, Any]] | None = None,
155
+ skipped_checks: list[dict[str, Any]] | None = None,
156
+ next_actions: list[str] | None = None,
157
+ **kwargs: Any,
158
+ ) -> str:
159
+ """Return diagnostic-report output; extra keyword arguments go to render()."""
160
+ data = {
161
+ "checks": [] if checks is None else checks,
162
+ "findings": [] if findings is None else findings,
163
+ "skippedChecks": [] if skipped_checks is None else skipped_checks,
164
+ "nextActions": [] if next_actions is None else next_actions,
165
+ }
166
+
167
+ if title is not None:
168
+ data["title"] = title
169
+
170
+ return render("diagnostic-report", data, **kwargs)
171
+
172
+
173
+ def diff_block(
174
+ lines: list[dict[str, Any]] | None = None,
175
+ path: str = "",
176
+ title: str | None = None,
177
+ **kwargs: Any,
178
+ ) -> str:
179
+ """Return diff-block output; extra keyword arguments go to render()."""
180
+ data = {
181
+ "path": path,
182
+ }
183
+
184
+ if lines is not None:
185
+ data["lines"] = lines
186
+ if title is not None:
187
+ data["title"] = title
188
+
189
+ return render("diff-block", data, **kwargs)
190
+
191
+
192
+ def divider(
193
+ label: str = "",
194
+ divider_width: int | float | None = None,
195
+ divider_colour: str | None = None,
196
+ label_colour: str | None = None,
197
+ **kwargs: Any,
198
+ ) -> str:
199
+ """Return divider output; extra keyword arguments go to render()."""
200
+ data = {
201
+ "label": label,
202
+ }
203
+
204
+ if divider_width is not None:
205
+ data["dividerWidth"] = divider_width
206
+ if divider_colour is not None:
207
+ data["dividerColour"] = divider_colour
208
+ if label_colour is not None:
209
+ data["labelColour"] = label_colour
210
+
211
+ return render("divider", data, **kwargs)
212
+
213
+
214
+ def empty_state(
215
+ title: str = "No results",
216
+ detail: str = "",
217
+ **kwargs: Any,
218
+ ) -> str:
219
+ """Return empty-state output; extra keyword arguments go to render()."""
220
+ data = {
221
+ "title": title,
222
+ "detail": detail,
223
+ }
224
+
225
+ return render("empty-state", data, **kwargs)
226
+
227
+
228
+ def error_block(
229
+ title: str = "Failed",
230
+ lines: list[str] | None = None,
231
+ **kwargs: Any,
232
+ ) -> str:
233
+ """Return error-block output; extra keyword arguments go to render()."""
234
+ data = {
235
+ "title": title,
236
+ "lines": [] if lines is None else lines,
237
+ }
238
+
239
+ return render("error-block", data, **kwargs)
240
+
241
+
242
+ def hint(
243
+ message: str,
244
+ **kwargs: Any,
245
+ ) -> str:
246
+ """Return hint output; extra keyword arguments go to render()."""
247
+ data = {
248
+ "message": message,
249
+ }
250
+
251
+ return render("hint", data, **kwargs)
252
+
253
+
254
+ def labelled_line(
255
+ label: str = "",
256
+ message: str = "",
257
+ icon: str = "",
258
+ tone: str = "info",
259
+ wrap: bool = True,
260
+ wrap_width: int | float | None = None,
261
+ **kwargs: Any,
262
+ ) -> str:
263
+ """Return labelled-line output; extra keyword arguments go to render()."""
264
+ data = {
265
+ "label": label,
266
+ "message": message,
267
+ "icon": icon,
268
+ "tone": tone,
269
+ "wrap": wrap,
270
+ }
271
+
272
+ if wrap_width is not None:
273
+ data["wrapWidth"] = wrap_width
274
+
275
+ return render("labelled-line", data, **kwargs)
276
+
277
+
278
+ def next_step_block(
279
+ next_step: str,
280
+ reason: str = "",
281
+ command: str = "",
282
+ alternative: str = "",
283
+ **kwargs: Any,
284
+ ) -> str:
285
+ """Return next-step-block output; extra keyword arguments go to render()."""
286
+ data = {
287
+ "next": next_step,
288
+ "reason": reason,
289
+ "commands": [command] if command else [],
290
+ "alternatives": [alternative] if alternative else [],
291
+ }
292
+
293
+ return render("next-step-block", data, **kwargs)
294
+
295
+
296
+ def panel(
297
+ lines: list[str] | None = None,
298
+ title: str | None = None,
299
+ tone: str = "info",
300
+ panel_width: int | float | None = None,
301
+ **kwargs: Any,
302
+ ) -> str:
303
+ """Return panel output; extra keyword arguments go to render()."""
304
+ data = {
305
+ "lines": [] if lines is None else lines,
306
+ "tone": tone,
307
+ }
308
+
309
+ if title is not None:
310
+ data["title"] = title
311
+ if panel_width is not None:
312
+ data["panelWidth"] = panel_width
313
+
314
+ return render("panel", data, **kwargs)
315
+
316
+
317
+ def progress_bar(
318
+ max: int | float = 100,
319
+ value: int | float = 0,
320
+ bar_width: int | float | None = None,
321
+ tone: str = "success",
322
+ **kwargs: Any,
323
+ ) -> str:
324
+ """Return progress-bar output; extra keyword arguments go to render()."""
325
+ data = {
326
+ "max": max,
327
+ "value": value,
328
+ "tone": tone,
329
+ }
330
+
331
+ if bar_width is not None:
332
+ data["barWidth"] = bar_width
333
+
334
+ return render("progress-bar", data, **kwargs)
335
+
336
+
337
+ def row(
338
+ label: str,
339
+ value: str,
340
+ result: str = "",
341
+ label_width: int | float | None = None,
342
+ label_colour: str | None = None,
343
+ value_colour: str | None = None,
344
+ separator: str | None = None,
345
+ **kwargs: Any,
346
+ ) -> str:
347
+ """Return row output; extra keyword arguments go to render()."""
348
+ data = {
349
+ "label": label,
350
+ "value": value,
351
+ "result": result,
352
+ }
353
+
354
+ if label_width is not None:
355
+ data["labelWidth"] = label_width
356
+ if label_colour is not None:
357
+ data["labelColour"] = label_colour
358
+ if value_colour is not None:
359
+ data["valueColour"] = value_colour
360
+ if separator is not None:
361
+ data["separator"] = separator
362
+
363
+ return render("row", data, **kwargs)
364
+
365
+
366
+ def row_group(
367
+ rows: list[dict[str, Any]],
368
+ label_width: int | float | None = None,
369
+ **kwargs: Any,
370
+ ) -> str:
371
+ """Return row-group output; extra keyword arguments go to render()."""
372
+ data = {
373
+ "rows": rows,
374
+ }
375
+
376
+ if label_width is not None:
377
+ data["labelWidth"] = label_width
378
+
379
+ return render("row-group", data, **kwargs)
380
+
381
+
382
+ def sparkline(
383
+ values: list[int | float] | None = None,
384
+ width: int | float | None = None,
385
+ label: str | None = None,
386
+ tone: str = "success",
387
+ **kwargs: Any,
388
+ ) -> str:
389
+ """Return sparkline output; extra keyword arguments go to render()."""
390
+ data = {
391
+ "values": [] if values is None else values,
392
+ "tone": tone,
393
+ }
394
+
395
+ if width is not None:
396
+ data["width"] = width
397
+ if label is not None:
398
+ data["label"] = label
399
+
400
+ return render("sparkline", data, **kwargs)
401
+
402
+
403
+ def span(
404
+ value: str,
405
+ tone: str = "info",
406
+ weight: str | None = None,
407
+ **kwargs: Any,
408
+ ) -> str:
409
+ """Return span output; extra keyword arguments go to render()."""
410
+ data = {
411
+ "value": value,
412
+ "tone": tone,
413
+ }
414
+
415
+ if weight is not None:
416
+ data["weight"] = weight
417
+
418
+ return render("span", data, **kwargs)
419
+
420
+
421
+ def status(
422
+ type: str,
423
+ label: str = "",
424
+ detail: str = "",
425
+ **kwargs: Any,
426
+ ) -> str:
427
+ """Return status output; extra keyword arguments go to render()."""
428
+ data = {
429
+ "type": type,
430
+ "label": label,
431
+ "detail": detail,
432
+ }
433
+
434
+ return render("status", data, **kwargs)
435
+
436
+
437
+ def step(
438
+ label: str = "",
439
+ state: str = "pending",
440
+ **kwargs: Any,
441
+ ) -> str:
442
+ """Return step output; extra keyword arguments go to render()."""
443
+ data = {
444
+ "label": label,
445
+ "state": state,
446
+ }
447
+
448
+ return render("step", data, **kwargs)
449
+
450
+
451
+ def step_progress(
452
+ current: int | float = 0,
453
+ steps: list[str] | None = None,
454
+ **kwargs: Any,
455
+ ) -> str:
456
+ """Return step-progress output; extra keyword arguments go to render()."""
457
+ data = {
458
+ "current": current,
459
+ "steps": [] if steps is None else steps,
460
+ }
461
+
462
+ return render("step-progress", data, **kwargs)
463
+
464
+
465
+ def table(
466
+ columns: list[dict[str, Any]],
467
+ rows: list[dict[str, Any]],
468
+ unicode: bool | None = None,
469
+ width: int | float | None = None,
470
+ **kwargs: Any,
471
+ ) -> str:
472
+ """Return table output; extra keyword arguments go to render()."""
473
+ data = {
474
+ "columns": columns,
475
+ "rows": rows,
476
+ }
477
+
478
+ if unicode is not None:
479
+ data["unicode"] = unicode
480
+ if width is not None:
481
+ data["width"] = width
482
+
483
+ return render("table", data, **kwargs)
484
+
485
+
486
+ def task_summary(
487
+ result: str,
488
+ task: str,
489
+ summary: str = "",
490
+ completed: str = "",
491
+ remaining: str = "",
492
+ **kwargs: Any,
493
+ ) -> str:
494
+ """Return task-summary output; extra keyword arguments go to render()."""
495
+ data = {
496
+ "result": result,
497
+ "task": task,
498
+ "summary": summary,
499
+ "completed": [completed] if completed else [],
500
+ "remaining": [remaining] if remaining else [],
501
+ }
502
+
503
+ return render("task-summary", data, **kwargs)
@@ -0,0 +1,136 @@
1
+ """Run the shared cli-style binary from Python."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import shutil
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Any, Sequence
12
+
13
+
14
+ class CliStyleError(RuntimeError):
15
+ """Base error for cli-style adapter failures."""
16
+
17
+
18
+ class CliStyleNotFoundError(CliStyleError):
19
+ """Raised when the cli-style binary cannot be found."""
20
+
21
+
22
+ class CliStyleRenderError(CliStyleError):
23
+ """Raised when cli-style rejects a render request."""
24
+
25
+
26
+ def resolve_subprocess_environment() -> dict[str, str]:
27
+ """Resolve the child environment while preserving caller controls."""
28
+ environment = os.environ.copy()
29
+
30
+ if (
31
+ sys.stdout.isatty()
32
+ and "FORCE_COLOR" not in environment
33
+ and "NO_COLOR" not in environment
34
+ ):
35
+ environment["FORCE_COLOR"] = "1"
36
+
37
+ return environment
38
+
39
+
40
+ def render(
41
+ renderer: str,
42
+ data: dict[str, Any],
43
+ *,
44
+ binary: str = "cli-style",
45
+ profile: str | None = None,
46
+ width: int | None = None,
47
+ plain: bool = False,
48
+ no_colour: bool = False,
49
+ no_unicode: bool = False,
50
+ extra_args: Sequence[str] | None = None,
51
+ raise_on_missing: bool = False,
52
+ ) -> str:
53
+ """Render JSON-serialisable data through ``cli-style render``.
54
+
55
+ When the binary cannot be found, return a plain-text rendering of the data
56
+ instead, unless ``raise_on_missing`` is set.
57
+
58
+ :param renderer: Stable renderer name accepted by the cli-style binary.
59
+ :param data: JSON-serialisable renderer input.
60
+ :param binary: Binary path or command name.
61
+ :param profile: Optional output profile.
62
+ :param width: Optional terminal width.
63
+ :param plain: Disable terminal colour and Unicode output.
64
+ :param no_colour: Disable terminal colour output.
65
+ :param no_unicode: Disable Unicode output.
66
+ :param extra_args: Additional flags passed to the binary.
67
+ :param raise_on_missing: Raise ``CliStyleNotFoundError`` when the binary
68
+ cannot be found, instead of returning plain text.
69
+ """
70
+ if not isinstance(renderer, str) or renderer == "":
71
+ raise ValueError("renderer must be a non-empty string")
72
+
73
+ if not isinstance(data, dict):
74
+ raise TypeError("data must be a dict")
75
+
76
+ try:
77
+ resolved_binary = resolve_binary(binary)
78
+ except CliStyleNotFoundError:
79
+ if raise_on_missing:
80
+ raise
81
+
82
+ # Sort the keys so the lines match the Swift adapter, which has no key order to keep.
83
+ return "\n".join(f"{key}: {value}" for key, value in sorted(data.items(), key=lambda item: str(item[0])))
84
+
85
+ command = [resolved_binary, "render", renderer]
86
+
87
+ if profile is not None:
88
+ command.extend(["--profile", profile])
89
+
90
+ if width is not None:
91
+ command.extend(["--width", str(width)])
92
+
93
+ if plain:
94
+ command.append("--plain")
95
+
96
+ if no_colour:
97
+ command.append("--no-colour")
98
+
99
+ if no_unicode:
100
+ command.append("--no-unicode")
101
+
102
+ if extra_args is not None:
103
+ command.extend(extra_args)
104
+
105
+ result = subprocess.run(
106
+ command,
107
+ capture_output=True,
108
+ check=False,
109
+ env=resolve_subprocess_environment(),
110
+ input=json.dumps(data),
111
+ text=True,
112
+ )
113
+
114
+ if result.returncode != 0:
115
+ message = result.stderr.strip() or f"cli-style render failed with exit code {result.returncode}"
116
+ raise CliStyleRenderError(message)
117
+
118
+ return result.stdout.rstrip("\n")
119
+
120
+
121
+ def resolve_binary(binary: str) -> str:
122
+ """Resolve a cli-style command name or executable path."""
123
+ if "/" in binary:
124
+ path = Path(binary)
125
+
126
+ if path.is_file() and os.access(path, os.X_OK):
127
+ return str(path)
128
+
129
+ raise CliStyleNotFoundError(f"cli-style binary not found: {binary}")
130
+
131
+ resolved = shutil.which(binary)
132
+
133
+ if resolved is None:
134
+ raise CliStyleNotFoundError(f"cli-style binary not found: {binary}")
135
+
136
+ return resolved
@@ -0,0 +1,32 @@
1
+ [project]
2
+ name = "lewishowles-cli-style"
3
+ version = "0.15.0"
4
+ description = "Python adapter for the shared cli-style binary."
5
+ authors = [{ name = "Lewis Howles" }]
6
+ license = { text = "MIT" }
7
+ license-files = ["LICENSE"]
8
+ readme = "README.md"
9
+ requires-python = ">=3.9"
10
+ dependencies = []
11
+ classifiers = [
12
+ "License :: OSI Approved :: MIT License",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3 :: Only",
15
+ "Programming Language :: Python :: 3.9",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ "Topic :: Software Development :: Libraries :: Python Modules",
21
+ ]
22
+
23
+ [project.urls]
24
+ Repository = "https://github.com/lewishowles/cli-style"
25
+ Issues = "https://github.com/lewishowles/cli-style/issues"
26
+
27
+ [build-system]
28
+ requires = ["hatchling"]
29
+ build-backend = "hatchling.build"
30
+
31
+ [tool.hatch.build.targets.wheel]
32
+ packages = ["cli_style"]
@@ -0,0 +1,16 @@
1
+ // swift-tools-version: 5.9
2
+
3
+ import PackageDescription
4
+
5
+ let package = Package(
6
+ name: "CliStyle",
7
+ products: [
8
+ .library(
9
+ name: "CliStyle",
10
+ targets: ["CliStyle"]
11
+ ),
12
+ ],
13
+ targets: [
14
+ .target(name: "CliStyle"),
15
+ ]
16
+ )