Files
wehub-resource-sync 869b84f27c
Build / build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:29:30 +08:00

178 lines
5.8 KiB
Swift

import Foundation
struct CLI {
private static let knownCommands: Set<String> = [
"scan", "disk-info", "list",
"help", "--help", "-h",
"version", "--version", "-v",
]
static func isKnownCommand(_ arg: String) -> Bool {
knownCommands.contains(arg)
}
static func run() -> Never {
let args = Array(CommandLine.arguments.dropFirst())
guard let command = args.first else {
printUsage()
exit(0)
}
switch command {
case "scan":
handleScan(args: Array(args.dropFirst()))
case "disk-info":
handleDiskInfo()
case "list":
handleList()
case "help", "--help", "-h":
printUsage()
case "version", "--version", "-v":
printVersion()
default:
printError("Unknown command: \(command)")
printUsage()
exit(1)
}
exit(0)
}
// MARK: - Commands
private static func handleScan(args: [String]) {
let json = args.contains("--json")
let categoryFilter = extractValue(for: "--category", in: args)
let engine = ScanEngine()
let categories: [CleaningCategory]
if let filter = categoryFilter {
guard let cat = CleaningCategory.scannable.first(where: {
$0.rawValue.lowercased().replacingOccurrences(of: " ", with: "") ==
filter.lowercased().replacingOccurrences(of: " ", with: "")
}) else {
printError("Unknown category: \(filter)")
print("Available: \(CleaningCategory.scannable.map(\.rawValue).joined(separator: ", "))")
exit(1)
}
categories = [cat]
} else {
categories = CleaningCategory.scannable
}
var allResults: [(String, Int, Int64)] = []
let group = DispatchGroup()
let queue = DispatchQueue(label: "cli.scan")
for category in categories {
group.enter()
Task {
let result = await engine.scanCategory(category)
queue.sync {
allResults.append((category.rawValue, result.itemCount, result.totalSize))
}
group.leave()
}
}
group.wait()
if json {
printJSON(allResults)
} else {
printTable(allResults)
}
}
private static func handleDiskInfo() {
let engine = ScanEngine()
var info = DiskInfo()
let group = DispatchGroup()
group.enter()
Task {
info = await engine.getDiskInfo()
group.leave()
}
group.wait()
print("Disk Usage:")
print(" Total: \(info.formattedTotal)")
print(" Used: \(info.formattedUsed)")
print(" Free: \(info.formattedFree)")
if info.purgeableSpace > 0 {
print(" Purgeable: \(info.formattedPurgeable)")
}
}
private static func handleList() {
let apps = AppInfoFetcher.shared.fetchInstalledApps()
print("Installed Apps (\(apps.count)):")
for app in apps {
let size = ByteCountFormatter.string(fromByteCount: app.size, countStyle: .file)
print(" \(app.appName.padding(toLength: 35, withPad: " ", startingAt: 0)) \(size.padding(toLength: 12, withPad: " ", startingAt: 0)) \(app.bundleIdentifier)")
}
}
// MARK: - Output
private static func printTable(_ results: [(String, Int, Int64)]) {
var totalSize: Int64 = 0
var totalItems = 0
print("Category Items Size")
print("---------------------- ----- --------")
for (name, count, size) in results {
let sizeStr = ByteCountFormatter.string(fromByteCount: size, countStyle: .file)
print("\(name.padding(toLength: 22, withPad: " ", startingAt: 0)) \(String(count).padding(toLength: 5, withPad: " ", startingAt: 0)) \(sizeStr)")
totalSize += size
totalItems += count
}
print("---------------------- ----- --------")
let totalStr = ByteCountFormatter.string(fromByteCount: totalSize, countStyle: .file)
print("Total \(String(totalItems).padding(toLength: 5, withPad: " ", startingAt: 0)) \(totalStr)")
}
private static func printJSON(_ results: [(String, Int, Int64)]) {
var entries: [String] = []
for (name, count, size) in results {
entries.append(" {\"category\": \"\(name)\", \"items\": \(count), \"bytes\": \(size)}")
}
print("[\n\(entries.joined(separator: ",\n"))\n]")
}
private static func printUsage() {
print("""
PureMac CLI
Usage: puremac <command> [options]
Commands:
scan Scan all categories
scan --category <name> Scan a specific category
scan --json Output as JSON
disk-info Show disk usage
list List installed apps
version Show version
help Show this help
Categories:
\(CleaningCategory.scannable.map(\.rawValue).joined(separator: ", "))
""")
}
private static func printVersion() {
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "2.0.0"
print("PureMac \(version)")
}
private static func printError(_ message: String) {
FileHandle.standardError.write(Data("Error: \(message)\n".utf8))
}
private static func extractValue(for flag: String, in args: [String]) -> String? {
guard let index = args.firstIndex(of: flag), index + 1 < args.count else { return nil }
return args[index + 1]
}
}