mirror of
https://github.com/kfattic/cupsadmin.git
synced 2026-11-13 19:06:43 -05:00
"Current User ID" / "New User ID" on Xerox, "Current User Code" on Ricoh; the CLI validation error uses the same label. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
86 lines
4.8 KiB
Swift
86 lines
4.8 KiB
Swift
import Foundation
|
|
import CupsKit
|
|
|
|
enum QuickCommand {
|
|
static func run(client: CupsClient, args: [String], clear: Bool) async throws -> String {
|
|
guard let name = args.first else {
|
|
printTable(headers: ["ACTION", "VALUE", "WHAT IT SETS"], rows: QuickAction.all.map {
|
|
[$0.id, $0.input == .userCode ? "<code> [account] | --clear" : "", $0.summary]
|
|
})
|
|
print("\nDriver profiles (first match wins, generic last): "
|
|
+ DriverProfiles.builtIn.map(\.id).joined(separator: ", "))
|
|
print("See what a queue's driver offers: cupsadmin ppdreport <queue>")
|
|
return "OK: \(QuickAction.all.count) quick actions, \(DriverProfiles.builtIn.count) driver profiles"
|
|
}
|
|
guard let action = QuickAction.named(name) else {
|
|
throw CupsAdminError.usage("unknown quick action \(name) (one of: \(QuickAction.all.map(\.id).joined(separator: ", ")))")
|
|
}
|
|
guard args.count >= 2 else { throw CupsAdminError.usage("quick \(name) needs a queue name") }
|
|
let queue = args[1]
|
|
let value: String?
|
|
var secondValue: String?
|
|
switch action.input {
|
|
case .none:
|
|
guard args.count == 2, !clear else { throw CupsAdminError.usage("quick \(name) takes only a queue name") }
|
|
value = nil
|
|
case .userCode:
|
|
if clear {
|
|
guard args.count == 2 else { throw CupsAdminError.usage("use a code or --clear, not both") }
|
|
value = nil
|
|
} else {
|
|
guard args.count == 3 || args.count == 4 else {
|
|
throw CupsAdminError.usage("quick \(name) needs a code (and, for Xerox, an optional account ID) or --clear")
|
|
}
|
|
value = args[2]
|
|
secondValue = args.count == 4 ? args[3] : nil
|
|
}
|
|
}
|
|
guard try await client.queueExists(queue) else { throw CupsAdminError.failed("no such queue \(queue)") }
|
|
|
|
if action.isDefaultPrinter {
|
|
let before = try await client.getDefaultPrinter()
|
|
let status = try runTool(CupsTools.lpadminPath, ["-d", queue])
|
|
guard status == 0 else { throw CupsAdminError.failed("lpadmin exited \(status); default not changed") }
|
|
let after = try await client.getDefaultPrinter()
|
|
print()
|
|
printTable(headers: ["SETTING", "BEFORE", "AFTER", "RESULT"],
|
|
rows: [["server default", before ?? "none", after ?? "none", after == queue ? "ok" : "NOT APPLIED (wanted \(queue))"]])
|
|
guard after == queue else { throw CupsAdminError.failed("lpadmin exited 0 but the server default is \(after ?? "none")") }
|
|
return "OK: \(queue) is the server default printer"
|
|
}
|
|
|
|
let before = try await OptionState.load(client: client, queue: queue)
|
|
let context = DriverContext(ppd: before.ppd, attributes: before.snapshot.attributes)
|
|
if let value, let error = action.validationError(value, context: context) {
|
|
throw CupsAdminError.usage("\(action.inputLabel(context)) \(value): \(error)")
|
|
}
|
|
if let secondValue, let error = action.secondValidationError(secondValue, context: context) {
|
|
throw CupsAdminError.usage("second value \(secondValue): \(error)")
|
|
}
|
|
let resolved: ResolvedQuickAction
|
|
switch action.resolve(context, value: value, secondValue: secondValue, clearing: clear) {
|
|
case .success(let r): resolved = r
|
|
case .failure(let unavailable): throw CupsAdminError.failed("\(action.id) on \(queue): \(unavailable.reason)")
|
|
}
|
|
print("driver: \(context.driverName) · profile: \(resolved.profile.id)")
|
|
|
|
let (executable, arguments) = OptionApplier.command(queue: queue, changes: resolved.changes)
|
|
let status = try runTool(executable, arguments)
|
|
guard status == 0 else { throw CupsAdminError.failed("lpadmin exited \(status); nothing verified") }
|
|
let after = try await OptionState.load(client: client, queue: queue)
|
|
let checks = resolved.changes.map { OptionApplier.verify($0, state: after) }
|
|
|
|
print()
|
|
printTable(headers: ["SETTING", "BEFORE", "AFTER", "RESULT"], rows: zip(resolved.changes, checks).map { change, check in
|
|
[change.key, before.queueValue(change.key) ?? "-", after.queueValue(change.key) ?? "-",
|
|
check.applied ? "ok" : "NOT APPLIED (wanted \(change.value))"]
|
|
})
|
|
let failed = checks.filter { !$0.applied }
|
|
guard failed.isEmpty else {
|
|
throw CupsAdminError.failed("lpadmin exited 0 but \(failed.count) setting(s) not applied: "
|
|
+ failed.map(\.key).joined(separator: ", "))
|
|
}
|
|
return "OK: \(queue) \(action.id) applied (\(resolved.profile.id)), \(resolved.changes.count) setting(s) verified"
|
|
}
|
|
}
|