import ArgumentParser import CliClient import Dependencies import Foundation import Logging struct CreateCommand: AsyncParsableCommand { static let commandName = "create" static let configuration = CommandConfiguration.playbook( commandName: commandName, abstract: "Create a home performance assesment project.", examples: ( label: "Create Assesment", example: "\(commandName) /new/assement/path" ) ) @OptionGroup var globals: GlobalOptions @Option( name: .shortAndLong, help: "The template repository to use." ) var repo: String? @Option( name: .shortAndLong, help: "The repo branch or version to use." ) var branch: String? @Option( name: .shortAndLong, help: "Path to local template directory to use.", completion: .directory ) var templateDir: String? @Flag( name: .shortAndLong, help: "Force using a local template directory." ) var localTemplateDir = false @Argument( help: "Path to the project directory.", completion: .directory ) var projectDir: String @Argument( help: "Extra arguments passed to the playbook." ) var extraArgs: [String] = [] mutating func run() async throws { try await _run() } private func _run() async throws { try await withSetupLogger(commandName: Self.commandName, globals: globals) { @Dependency(\.cliClient) var cliClient @Dependency(\.logger) var logger let encoder = cliClient.encoder() let configuration = try cliClient.loadConfiguration() logger.debug("Configuration: \(configuration)") let jsonData = try parseOptions( command: self, configuration: configuration, logger: logger, encoder: encoder ) guard let jsonString = String(data: jsonData, encoding: .utf8) else { throw CreateError.encodingError } logger.debug("JSON string: \(jsonString)") try await runPlaybook( commandName: Self.commandName, globals: self.globals, configuration: configuration, extraArgs: extraArgs, "--tags", "setup-project", "--extra-vars", "project_dir=\(self.projectDir)", "--extra-vars", "'\(jsonString)'" ) } } } private func parseOptions( command: CreateCommand, configuration: Configuration, logger: Logger, encoder: JSONEncoder ) throws -> Data { let templateDir = command.templateDir ?? configuration.templateDir let templateRepo = command.repo ?? configuration.templateRepo let version = (command.branch ?? configuration.templateRepoVersion) ?? "main" logger.debug(""" (\(command.localTemplateDir), \(String(describing: templateDir)), \(String(describing: templateRepo))) """) switch (command.localTemplateDir, templateDir, templateRepo) { case (true, .none, _): // User supplied they wanted to use a local template directory, but we could not find // the path set from command line or in configuration. throw CreateError.templateDirNotFound case let (false, _, .some(repo)): // User did not supply they wanted to use a local template directory, and we found a repo url that was // either set by the command line or found in the configuration. logger.debug("Using repo.") return try encoder.encode(TemplateRepo(repo: repo, version: version)) case let (true, .some(templateDir), _): // User supplied they wanted to use a local template directory, and we found the template directory // either set by the command line or in the configuration. logger.debug("Using template directory.") return try encoder.encode(TemplateDirJson(path: templateDir)) case let (false, .some(templateDir), _): // User supplied they did not wanted to use a local template directory, and we found the template directory // either set by the command line or in the configuration, and no repo was found / handled previously. logger.debug("Using template directory.") return try encoder.encode(TemplateDirJson(path: templateDir)) case (_, .none, .none): // We could not find a repo or template directory. throw CreateError.templateDirOrRepoNotSpecified } } private struct TemplateDirJson: Encodable { let template: Template init(path: String) { self.template = .init(path: path) } struct Template: Encodable { let path: String } } private struct TemplateRepo: Encodable { let template: Template init(repo: String, version: String?) { self.template = .init(repo: repo, version: version ?? "main") } struct Template: Encodable { let repo: String let version: String } } enum CreateError: Error { case encodingError case templateDirNotFound case templateDirOrRepoNotSpecified }