From f67a9a2de76645ff030413cecb56d87d3fba77e7 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Tue, 24 Oct 2023 11:31:08 +0200 Subject: [PATCH] Improve CLI --- Config/config_example.json | 10 ++++----- Sources/CapTrain.swift | 37 +++++++++++++++++++-------------- Sources/ConfigurationFile.swift | 8 +++---- Sources/TrainingError.swift | 6 +++--- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/Config/config_example.json b/Config/config_example.json index af6a6fe..fdce908 100644 --- a/Config/config_example.json +++ b/Config/config_example.json @@ -1,6 +1,6 @@ { - "contentDirectory": "../Public", - "trainingIterations": 20, - "serverPath": "https://mydomain.com/caps", - "authenticationToken": "mysecretkey", -} \ No newline at end of file + "folder": "../Public", + "iterations": 20, + "server": "https://mydomain.com/caps", + "authentication": "mysecretkey", +} diff --git a/Sources/CapTrain.swift b/Sources/CapTrain.swift index 83ea092..97340b1 100644 --- a/Sources/CapTrain.swift +++ b/Sources/CapTrain.swift @@ -4,49 +4,54 @@ import Foundation @main struct CapTrain: AsyncParsableCommand { + private static let defaultIterations = 10 + @Flag(name: .shortAndLong, help: "Resume the previous training session (default: false)") var resume: Bool = false - @Argument(help: "The path to the configuration file") - var configPath: String? + @Option(name: .shortAndLong, help: "The path to the configuration file. The file must be a json object containing command line arguments. Command line options take precedence over configuration file options") + var configuration: String? - @Option(name: .shortAndLong, help: "The number of iterations to train") + @Option(name: .shortAndLong, help: "The number of iterations to train (default: 10)") var iterations: Int? - @Option(name: .shortAndLong, help: "The url of the caps server") - var serverPath: String? + @Option(name: .shortAndLong, help: "The url of the caps server to retrieve images and upload the classifier") + var server: String? @Option(name: .shortAndLong, help: "The authentication token for the server") var authentication: String? - @Option(name: .shortAndLong, help: "The folder where the content (images, classifier, thumbnails) is stored") + @Option(name: .shortAndLong, help: "The path to the folder where the content (images, classifier, thumbnails) is stored") var folder: String? func run() async throws { let configurationFile = try configurationFile() - guard let contentFolder = folder ?? configurationFile?.contentFolder, - let trainingIterations = iterations ?? configurationFile?.trainingIterations, - let serverPath = serverPath ?? configurationFile?.serverPath, - let authenticationToken = authentication ?? configurationFile?.authenticationToken - else { - throw TrainingError.missingArguments + let iterations = iterations ?? configurationFile?.iterations ?? CapTrain.defaultIterations + guard let contentFolder = folder ?? configurationFile?.folder else { + throw TrainingError.missingArguments("folder") + } + guard let serverPath = server ?? configurationFile?.server else { + throw TrainingError.missingArguments("server") + } + guard let authentication = authentication ?? configurationFile?.authentication else { + throw TrainingError.missingArguments("authentication") } let configuration = Configuration( contentFolder: contentFolder, - trainingIterations: trainingIterations, + trainingIterations: iterations, serverPath: serverPath, - authenticationToken: authenticationToken) + authenticationToken: authentication) let creator = try ClassifierCreator(configuration: configuration, resume: resume) try await creator.run() } private func configurationFile() throws -> ConfigurationFile? { - guard let configPath else { + guard let configuration else { return nil } - let configurationFileUrl = URL(fileURLWithPath: configPath) + let configurationFileUrl = URL(fileURLWithPath: configuration) return try ConfigurationFile(at: configurationFileUrl) } } diff --git a/Sources/ConfigurationFile.swift b/Sources/ConfigurationFile.swift index 333aa8e..503abdf 100644 --- a/Sources/ConfigurationFile.swift +++ b/Sources/ConfigurationFile.swift @@ -2,13 +2,13 @@ import Foundation struct ConfigurationFile { - let contentFolder: String? + let folder: String? - let trainingIterations: Int? + let iterations: Int? - let serverPath: String? + let server: String? - let authenticationToken: String? + let authentication: String? } extension ConfigurationFile: Decodable { diff --git a/Sources/TrainingError.swift b/Sources/TrainingError.swift index 5efaea0..6aadcdf 100644 --- a/Sources/TrainingError.swift +++ b/Sources/TrainingError.swift @@ -4,7 +4,7 @@ enum TrainingError: Error { case invalidGetResponseData(Int) case invalidResponse(URL, Int) - case missingArguments + case missingArguments(String) case configurationFileMissing(URL) case configurationFileUnreadable(URL, Error) @@ -54,8 +54,8 @@ extension TrainingError: CustomStringConvertible { var description: String { switch self { - case .missingArguments: - return "Missing arguments" + case .missingArguments(let argument): + return "Missing argument '\(argument)'" case .configurationFileMissing(let url): return "No configuration at \(url.absoluteURL.path)" case .configurationFileUnreadable(let url, let error):