Improve CLI

This commit is contained in:
Christoph Hagen 2023-10-24 11:31:08 +02:00
parent 6aa1026478
commit f67a9a2de7
4 changed files with 33 additions and 28 deletions

View File

@ -1,6 +1,6 @@
{ {
"contentDirectory": "../Public", "folder": "../Public",
"trainingIterations": 20, "iterations": 20,
"serverPath": "https://mydomain.com/caps", "server": "https://mydomain.com/caps",
"authenticationToken": "mysecretkey", "authentication": "mysecretkey",
} }

View File

@ -4,49 +4,54 @@ import Foundation
@main @main
struct CapTrain: AsyncParsableCommand { struct CapTrain: AsyncParsableCommand {
private static let defaultIterations = 10
@Flag(name: .shortAndLong, help: "Resume the previous training session (default: false)") @Flag(name: .shortAndLong, help: "Resume the previous training session (default: false)")
var resume: Bool = false var resume: Bool = false
@Argument(help: "The path to the configuration file") @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 configPath: String? 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? var iterations: Int?
@Option(name: .shortAndLong, help: "The url of the caps server") @Option(name: .shortAndLong, help: "The url of the caps server to retrieve images and upload the classifier")
var serverPath: String? var server: String?
@Option(name: .shortAndLong, help: "The authentication token for the server") @Option(name: .shortAndLong, help: "The authentication token for the server")
var authentication: String? 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? var folder: String?
func run() async throws { func run() async throws {
let configurationFile = try configurationFile() let configurationFile = try configurationFile()
guard let contentFolder = folder ?? configurationFile?.contentFolder, let iterations = iterations ?? configurationFile?.iterations ?? CapTrain.defaultIterations
let trainingIterations = iterations ?? configurationFile?.trainingIterations, guard let contentFolder = folder ?? configurationFile?.folder else {
let serverPath = serverPath ?? configurationFile?.serverPath, throw TrainingError.missingArguments("folder")
let authenticationToken = authentication ?? configurationFile?.authenticationToken }
else { guard let serverPath = server ?? configurationFile?.server else {
throw TrainingError.missingArguments throw TrainingError.missingArguments("server")
}
guard let authentication = authentication ?? configurationFile?.authentication else {
throw TrainingError.missingArguments("authentication")
} }
let configuration = Configuration( let configuration = Configuration(
contentFolder: contentFolder, contentFolder: contentFolder,
trainingIterations: trainingIterations, trainingIterations: iterations,
serverPath: serverPath, serverPath: serverPath,
authenticationToken: authenticationToken) authenticationToken: authentication)
let creator = try ClassifierCreator(configuration: configuration, resume: resume) let creator = try ClassifierCreator(configuration: configuration, resume: resume)
try await creator.run() try await creator.run()
} }
private func configurationFile() throws -> ConfigurationFile? { private func configurationFile() throws -> ConfigurationFile? {
guard let configPath else { guard let configuration else {
return nil return nil
} }
let configurationFileUrl = URL(fileURLWithPath: configPath) let configurationFileUrl = URL(fileURLWithPath: configuration)
return try ConfigurationFile(at: configurationFileUrl) return try ConfigurationFile(at: configurationFileUrl)
} }
} }

View File

@ -2,13 +2,13 @@ import Foundation
struct ConfigurationFile { 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 { extension ConfigurationFile: Decodable {

View File

@ -4,7 +4,7 @@ enum TrainingError: Error {
case invalidGetResponseData(Int) case invalidGetResponseData(Int)
case invalidResponse(URL, Int) case invalidResponse(URL, Int)
case missingArguments case missingArguments(String)
case configurationFileMissing(URL) case configurationFileMissing(URL)
case configurationFileUnreadable(URL, Error) case configurationFileUnreadable(URL, Error)
@ -54,8 +54,8 @@ extension TrainingError: CustomStringConvertible {
var description: String { var description: String {
switch self { switch self {
case .missingArguments: case .missingArguments(let argument):
return "Missing arguments" return "Missing argument '\(argument)'"
case .configurationFileMissing(let url): case .configurationFileMissing(let url):
return "No configuration at \(url.absoluteURL.path)" return "No configuration at \(url.absoluteURL.path)"
case .configurationFileUnreadable(let url, let error): case .configurationFileUnreadable(let url, let error):