import CommandClient import ConfigurationClient import Dependencies import DependenciesMacros import FileClient import Foundation import ShellClient // TODO: Add update checks and pull for the playbook. public extension DependencyValues { /// Manages interactions with the playbook repository as well as `ansible-playbook` /// command line application. /// /// var playbookClient: PlaybookClient { get { self[PlaybookClient.self] } set { self[PlaybookClient.self] = newValue } } } @DependencyClient public struct PlaybookClient: Sendable { /// Manages interactions with the playbook repository. public var repository: Repository /// Run the `ansible-playbook` command line application. public var run: RunPlaybook } public extension PlaybookClient { /// Manages interactions with the `ansible-hpa-playbook` repository, which is /// used to build and generate home performance assessment projects. @DependencyClient struct Repository: Sendable { /// Install the repository based on the given configuration. If configuration is /// not supplied then the default location for the playbook repository is /// `~/.local/share/hpa/playbook` public var install: @Sendable (Configuration?) async throws -> Void /// Get the current directory of the playbook repository based on the given /// configuration. public var directory: @Sendable (Configuration?) async throws -> String /// Install the playbook in the default location. public func install() async throws { try await install(nil) } /// Get the current directory path of the playbook repository. public func directory() async throws -> String { try await directory(nil) } } } public extension PlaybookClient { /// Runs the `ansible-playbook` command line application with the `ansible-hpa-playbook`. /// /// This is used to build and create home performance projects. It can also generate a /// template for a user to customize to their use case. @DependencyClient struct RunPlaybook: Sendable { /// Build a home performance assesment project with the given options. public var buildProject: @Sendable (BuildOptions) async throws -> Void /// Create a new home performance assesment project with the given options. public var createProject: @Sendable (CreateOptions, JSONEncoder?) async throws -> Void /// Generate a user's template from the default home performance template. public var generateTemplate: @Sendable (GenerateTemplateOptions) async throws -> String /// Create a new home performance assesment project with the given options. /// /// - Parameters: /// - options: The options used to create the project. public func createProject(_ options: CreateOptions) async throws { try await createProject(options, nil) } /// Represents options that are shared for all the `ansible-playbook` commands. public struct SharedRunOptions: Equatable, Sendable { /// Extra arguments / options passed to the `ansible-playbook` command. let extraOptions: [String]? /// Specify the inventory file path. let inventoryFilePath: String? /// The logging options used when running the command. let loggingOptions: LoggingOptions /// Disables log output of the command. let quiet: Bool /// Optional shell to use when running the command. let shell: String? /// Create the shared options. /// /// - Parameters: /// - extraOptions: The extra arguments / options to pass to the command. /// - inventoryFilePath: Specify the inventory file path. /// - loggingOptions: The logging options used when running the command. /// - quiet: Disable log output from the command. /// - shell: Specify a shell used when running the command. public init( extraOptions: [String]? = nil, inventoryFilePath: String? = nil, loggingOptions: LoggingOptions, quiet: Bool = false, shell: String? = nil ) { self.extraOptions = extraOptions self.inventoryFilePath = inventoryFilePath self.loggingOptions = loggingOptions self.quiet = quiet self.shell = shell } } /// Options require when calling the build project command. @dynamicMemberLookup public struct BuildOptions: Equatable, Sendable { /// An optional project directory, if not supplied then we will use the current working directory. let projectDirectory: String? /// The shared run options. let shared: SharedRunOptions /// Create new build options. /// /// - Parameters: /// - projectDirectory: The optional project directory to build, if not supplied then we'll use the current working directory. /// - shared: The shared run options. public init( projectDirectory: String? = nil, shared: SharedRunOptions ) { self.projectDirectory = projectDirectory self.shared = shared } public subscript(dynamicMember keyPath: KeyPath) -> T { shared[keyPath: keyPath] } } /// Options required when creating a new home performance assessment project. @dynamicMemberLookup public struct CreateOptions: Equatable, Sendable { /// The directory to generate the new project in. let projectDirectory: String /// Shared run options. let shared: SharedRunOptions /// Custom template configuration to use. let template: Configuration.Template? /// Specify whether we should only use a local template directory to create the project from. let useLocalTemplateDirectory: Bool /// Create new create options. /// /// - Parameters: /// - projectDirectory: The directory to generate the project in. /// - shared: The shared run options. /// - template: Custom template configuration used when generating the project. /// - useLocalTemplateDirectory: Whether to use a local template directory, not a template repository. public init( projectDirectory: String, shared: SharedRunOptions, template: Configuration.Template? = nil, useLocalTemplateDirectory: Bool ) { self.projectDirectory = projectDirectory self.shared = shared self.template = template self.useLocalTemplateDirectory = useLocalTemplateDirectory } public subscript(dynamicMember keyPath: KeyPath) -> T { shared[keyPath: keyPath] } } /// Options required when generating a new template repository / directory. @dynamicMemberLookup public struct GenerateTemplateOptions: Equatable, Sendable { /// The shared run options. let shared: SharedRunOptions /// The path to generate the template in. let templateDirectory: String /// Specify the name of the directory for template variables. let templateVarsDirectory: String? /// Specify whether to use `ansible-vault` encrypted variables. let useVault: Bool /// Create new generate template options. /// /// - Parameters: /// - shared: The shared run options /// - templateDirectory: The path to generate the template in. /// - templateVarsDirectory: Specify the name of the directory for template variables. /// - useVault: Specify wheter to use `ansible-vault` encrypted variables. public init( shared: SharedRunOptions, templateDirectory: String, templateVarsDirectory: String? = nil, useVault: Bool = true ) { self.shared = shared self.templateDirectory = templateDirectory self.templateVarsDirectory = templateVarsDirectory self.useVault = useVault } public subscript(dynamicMember keyPath: KeyPath) -> T { shared[keyPath: keyPath] } } } } extension PlaybookClient.Repository: DependencyKey { public static var liveValue: Self { .init { try await installPlaybook(configuration: $0) } directory: { try await findDirectory(configuration: $0) } } } extension PlaybookClient.RunPlaybook: DependencyKey { public static var liveValue: PlaybookClient.RunPlaybook { .init( buildProject: { try await $0.run() }, createProject: { try await $0.run(encoder: $1) }, generateTemplate: { try await $0.run() } ) } } extension PlaybookClient: DependencyKey { public static let testValue: PlaybookClient = Self( repository: Repository(), run: RunPlaybook() ) public static var liveValue: PlaybookClient { .init( repository: .liveValue, run: .liveValue ) } }