@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.
- package/CHANGELOG.md +24 -0
- package/README.md +72 -10
- package/adapters/python/LICENSE +21 -0
- package/adapters/python/README.md +29 -0
- package/adapters/python/cli_style/__init__.py +73 -0
- package/adapters/python/cli_style/_wrappers.py +503 -0
- package/adapters/python/cli_style/core.py +136 -0
- package/adapters/python/pyproject.toml +32 -0
- package/adapters/swift/Package.swift +16 -0
- package/adapters/swift/Sources/CliStyle/CliStyle.swift +223 -0
- package/adapters/swift/Sources/CliStyle/Wrappers.swift +582 -0
- package/package.json +4 -2
- package/scripts/build-binary.sh +17 -3
- package/scripts/generate-adapters.js +485 -0
- package/scripts/package-binary.sh +6 -1
- package/src/catalogue/renderer-catalogue.js +958 -52
- package/src/cli/adapter-path-command.js +3 -3
- package/src/cli/render-command.js +6 -0
- package/src/gallery/render-gallery.js +40 -0
- package/src/index.js +3 -0
- package/src/patterns/audit-finding.js +4 -4
- package/src/patterns/command-result.js +2 -2
- package/src/patterns/diagnostic-report.js +6 -3
- package/src/patterns/helpers.js +11 -4
- package/src/patterns/next-step-block.js +2 -4
- package/src/patterns/task-summary.js +4 -4
- package/src/primitives/bullet-list.js +150 -0
- package/src/theme/bullets.js +41 -0
- package/adapters/python/cli_style.py +0 -428
- package/adapters/swift/CliStyle.swift +0 -482
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
#if canImport(Darwin)
|
|
3
|
+
import Darwin
|
|
4
|
+
#elseif canImport(Glibc)
|
|
5
|
+
import Glibc
|
|
6
|
+
#endif
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Errors raised while the cli-style adapter resolves or runs the renderer.
|
|
10
|
+
*/
|
|
11
|
+
public enum CliStyleError: Error {
|
|
12
|
+
case notFound(String)
|
|
13
|
+
case renderFailed(String)
|
|
14
|
+
case invalidInput(String)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for controlling cli-style render behaviour.
|
|
19
|
+
*/
|
|
20
|
+
public struct CliStyleOptions {
|
|
21
|
+
// Binary path or command name.
|
|
22
|
+
public var binary: String = "cli-style"
|
|
23
|
+
|
|
24
|
+
// Profile name such as human, agent, or json.
|
|
25
|
+
public var profile: String? = nil
|
|
26
|
+
|
|
27
|
+
// Terminal width in characters.
|
|
28
|
+
public var width: Int? = nil
|
|
29
|
+
|
|
30
|
+
// Force plain text output.
|
|
31
|
+
public var isPlain: Bool = false
|
|
32
|
+
|
|
33
|
+
// Disable colour output.
|
|
34
|
+
public var isNoColour: Bool = false
|
|
35
|
+
|
|
36
|
+
// Disable Unicode symbols.
|
|
37
|
+
public var isNoUnicode: Bool = false
|
|
38
|
+
|
|
39
|
+
// Additional arguments passed through to the binary.
|
|
40
|
+
public var extraArgs: [String] = []
|
|
41
|
+
|
|
42
|
+
// Throw when the cli-style binary cannot be found, instead of returning a plain-text rendering of the data.
|
|
43
|
+
public var throwsWhenMissing: Bool = false
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Create render options. Every argument has a default, so callers only pass what they change.
|
|
47
|
+
*/
|
|
48
|
+
public init(
|
|
49
|
+
binary: String = "cli-style",
|
|
50
|
+
profile: String? = nil,
|
|
51
|
+
width: Int? = nil,
|
|
52
|
+
isPlain: Bool = false,
|
|
53
|
+
isNoColour: Bool = false,
|
|
54
|
+
isNoUnicode: Bool = false,
|
|
55
|
+
extraArgs: [String] = [],
|
|
56
|
+
throwsWhenMissing: Bool = false
|
|
57
|
+
) {
|
|
58
|
+
self.binary = binary
|
|
59
|
+
self.profile = profile
|
|
60
|
+
self.width = width
|
|
61
|
+
self.isPlain = isPlain
|
|
62
|
+
self.isNoColour = isNoColour
|
|
63
|
+
self.isNoUnicode = isNoUnicode
|
|
64
|
+
self.extraArgs = extraArgs
|
|
65
|
+
self.throwsWhenMissing = throwsWhenMissing
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Render caller data through the shared cli-style binary.
|
|
71
|
+
*/
|
|
72
|
+
public enum CliStyle {
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
Render a dictionary through `cli-style render`.
|
|
76
|
+
|
|
77
|
+
When the binary cannot be found, return a plain-text rendering of the data instead, unless `options.throwsWhenMissing` is set.
|
|
78
|
+
|
|
79
|
+
- parameter renderer: Stable renderer name accepted by `cli-style render`.
|
|
80
|
+
- parameter data: JSON-serialisable renderer input.
|
|
81
|
+
- parameter options: Render options including binary path and output flags.
|
|
82
|
+
- returns: Rendered string output from cli-style.
|
|
83
|
+
- throws: `CliStyleError` if the binary is missing and `options.throwsWhenMissing` is set, input is invalid, or rendering fails.
|
|
84
|
+
*/
|
|
85
|
+
public static func render(
|
|
86
|
+
_ renderer: String,
|
|
87
|
+
data: [String: Any],
|
|
88
|
+
options: CliStyleOptions = .init()
|
|
89
|
+
) throws -> String {
|
|
90
|
+
if renderer.isEmpty {
|
|
91
|
+
throw CliStyleError.invalidInput("renderer must be a non-empty string")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let resolvedBinary: String
|
|
95
|
+
|
|
96
|
+
do {
|
|
97
|
+
resolvedBinary = try resolveBinary(options.binary)
|
|
98
|
+
} catch CliStyleError.notFound(let message) {
|
|
99
|
+
if options.throwsWhenMissing {
|
|
100
|
+
throw CliStyleError.notFound(message)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Swift dictionaries have no order, so sort the keys to keep the output the same between runs.
|
|
104
|
+
return data
|
|
105
|
+
.sorted { $0.key < $1.key }
|
|
106
|
+
.map { "\($0.key): \(String(describing: $0.value))" }
|
|
107
|
+
.joined(separator: "\n")
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let jsonData: Data
|
|
111
|
+
|
|
112
|
+
do {
|
|
113
|
+
jsonData = try JSONSerialization.data(withJSONObject: data)
|
|
114
|
+
} catch {
|
|
115
|
+
throw CliStyleError.invalidInput("data is not JSON-serialisable")
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
var arguments = ["render", renderer]
|
|
119
|
+
|
|
120
|
+
if let profile = options.profile {
|
|
121
|
+
arguments += ["--profile", profile]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if let width = options.width {
|
|
125
|
+
arguments += ["--width", String(width)]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if options.isPlain {
|
|
129
|
+
arguments.append("--plain")
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if options.isNoColour {
|
|
133
|
+
arguments.append("--no-colour")
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if options.isNoUnicode {
|
|
137
|
+
arguments.append("--no-unicode")
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
arguments += options.extraArgs
|
|
141
|
+
|
|
142
|
+
let process = Process()
|
|
143
|
+
process.executableURL = URL(fileURLWithPath: resolvedBinary)
|
|
144
|
+
process.arguments = arguments
|
|
145
|
+
process.environment = resolveProcessEnvironment()
|
|
146
|
+
|
|
147
|
+
let stdinPipe = Pipe()
|
|
148
|
+
let stdoutPipe = Pipe()
|
|
149
|
+
let stderrPipe = Pipe()
|
|
150
|
+
process.standardInput = stdinPipe
|
|
151
|
+
process.standardOutput = stdoutPipe
|
|
152
|
+
process.standardError = stderrPipe
|
|
153
|
+
|
|
154
|
+
do {
|
|
155
|
+
try process.run()
|
|
156
|
+
} catch {
|
|
157
|
+
throw CliStyleError.renderFailed("failed to launch cli-style: \(error.localizedDescription)")
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
stdinPipe.fileHandleForWriting.write(jsonData)
|
|
161
|
+
stdinPipe.fileHandleForWriting.closeFile()
|
|
162
|
+
|
|
163
|
+
process.waitUntilExit()
|
|
164
|
+
|
|
165
|
+
let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile()
|
|
166
|
+
let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile()
|
|
167
|
+
|
|
168
|
+
if process.terminationStatus != 0 {
|
|
169
|
+
let stderrText = String(data: stderrData, encoding: .utf8)?
|
|
170
|
+
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
171
|
+
let message = stderrText.isEmpty
|
|
172
|
+
? "cli-style render failed with exit code \(process.terminationStatus)"
|
|
173
|
+
: stderrText
|
|
174
|
+
|
|
175
|
+
throw CliStyleError.renderFailed(message)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let stdoutText = String(data: stdoutData, encoding: .utf8) ?? ""
|
|
179
|
+
return stdoutText.trimmingCharacters(in: .newlines)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Resolve the child environment for a captured render.
|
|
184
|
+
*/
|
|
185
|
+
private static func resolveProcessEnvironment() -> [String: String] {
|
|
186
|
+
var environment = ProcessInfo.processInfo.environment
|
|
187
|
+
|
|
188
|
+
if isatty(STDOUT_FILENO) == 1 &&
|
|
189
|
+
environment["FORCE_COLOR"] == nil &&
|
|
190
|
+
environment["NO_COLOR"] == nil {
|
|
191
|
+
environment["FORCE_COLOR"] = "1"
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return environment
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
Resolve a command name or executable path for subprocess use.
|
|
199
|
+
|
|
200
|
+
- parameter binary: Binary path or command name.
|
|
201
|
+
- returns: Resolved executable path.
|
|
202
|
+
- throws: `CliStyleError.notFound` if the binary cannot be found.
|
|
203
|
+
*/
|
|
204
|
+
public static func resolveBinary(_ binary: String) throws -> String {
|
|
205
|
+
if binary.contains("/") {
|
|
206
|
+
if FileManager.default.isExecutableFile(atPath: binary) {
|
|
207
|
+
return binary
|
|
208
|
+
}
|
|
209
|
+
throw CliStyleError.notFound("cli-style binary not found: \(binary)")
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let path = ProcessInfo.processInfo.environment["PATH"] ?? ""
|
|
213
|
+
|
|
214
|
+
for directory in path.split(separator: ":") {
|
|
215
|
+
let candidate = "\(directory)/\(binary)"
|
|
216
|
+
if FileManager.default.isExecutableFile(atPath: candidate) {
|
|
217
|
+
return candidate
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
throw CliStyleError.notFound("cli-style binary not found: \(binary)")
|
|
222
|
+
}
|
|
223
|
+
}
|