ppdreport accepts a PPD file path (plain or gzipped) as well as a queue

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kfattic
2026-09-16 21:04:02 -05:00
parent a54ec0f4be
commit 7a98cb3bd3
3 changed files with 30 additions and 4 deletions

View File

@@ -176,3 +176,25 @@ public struct PPD {
options.first { $0.keyword == keyword }
}
}
extension PPD {
/// Reads a PPD file, plain or gzip-compressed (vendors ship `.ppd.gz` / `.gz` under
/// /Library/Printers/PPDs). Text that isn't valid UTF-8 is read as ISO Latin-1, the PPD default.
public static func text(contentsOfFile path: String) throws -> String {
guard var data = FileManager.default.contents(atPath: path) else {
throw CupsAdminError.failed("can’t read \(path)")
}
if data.starts(with: [0x1F, 0x8B]) {
let result = try CupsTools.run("/usr/bin/gzip", ["-dc", path])
guard result.succeeded else { throw CupsAdminError.failed("can’t decompress \(path): \(result.message)") }
data = result.standardOutput
}
return String(data: data, encoding: .utf8) ?? String(data: data, encoding: .isoLatin1) ?? ""
}
/// True when a `ppdreport` argument names a PPD file rather than a queue. Queue names can't contain "/".
public static func looksLikeFile(_ argument: String) -> Bool {
argument.contains("/") || [".ppd", ".gz", ".ppd.gz"].contains { argument.lowercased().hasSuffix($0) }
}
}

View File

@@ -8,8 +8,7 @@ enum PPDReportCommand {
let source: String
var attributes: IPPGroup?
if let file {
guard let data = FileManager.default.contents(atPath: file) else { throw CupsAdminError.failed("can’t read \(file)") }
text = String(decoding: data, as: UTF8.self)
text = try PPD.text(contentsOfFile: file)
source = file
} else if let queue {
guard try await client.queueExists(queue) else { throw CupsAdminError.failed("no such queue \(queue)") }

View File

@@ -42,7 +42,7 @@ let usage = """
quick list quick actions (task-named queue defaults)
quick <action> <queue> [code | --clear]
apply one via lpadmin, then verify
ppdreport <queue> | --file <path.ppd>
ppdreport <queue | path.ppd[.gz]>
every PPD group and option: keyword, label, type, default, choices
help show this help
"""
@@ -130,7 +130,12 @@ func runCommand(_ args: [String]) async throws -> String {
}
try rejectUnknownFlags()
guard (file == nil && rest.count == 1) || (file != nil && rest.isEmpty) else {
throw CupsAdminError.usage("ppdreport needs one queue name or --file <path.ppd>")
throw CupsAdminError.usage("ppdreport needs one queue name or PPD file path")
}
// A path (anything with "/", or ending in .ppd / .gz) is a PPD file; queue names can't contain "/".
if file == nil, let argument = rest.first, PPD.looksLikeFile(argument) {
file = argument
rest.removeAll()
}
return try await PPDReportCommand.run(client: client, queue: rest.first, file: file)