feat: Adding documentation comments.
All checks were successful
CI / Run Tests (push) Successful in 2m28s

This commit is contained in:
2024-12-17 13:32:05 -05:00
parent f8e89ed0fa
commit 54c07886ad
8 changed files with 245 additions and 68 deletions

View File

@@ -38,7 +38,7 @@ public struct ConfigurationClient: Sendable {
/// ## NOTE: This uses the `fileClient.write` under the hood, so if you need to control /// ## NOTE: This uses the `fileClient.write` under the hood, so if you need to control
/// what happens during tests, then you can customize the behavior of the fileClient. /// what happens during tests, then you can customize the behavior of the fileClient.
/// ///
var write: @Sendable (File, Configuration, Bool) async throws -> Void public var write: @Sendable (WriteOptions) async throws -> Void
/// Find the user's configuration and load it. /// Find the user's configuration and load it.
public func findAndLoad() async throws -> Configuration { public func findAndLoad() async throws -> Configuration {
@@ -62,13 +62,19 @@ public struct ConfigurationClient: Sendable {
to file: File, to file: File,
force: Bool = false force: Bool = false
) async throws { ) async throws {
try await write(file, configuration, force) try await write(.init(configuration, to: file, force: force))
} }
/// Represents the options to generate a configuration file for a user.
public struct GenerateOptions: Equatable, Sendable { public struct GenerateOptions: Equatable, Sendable {
/// Force generation, overwritting existing file if it exists.
public let force: Bool public let force: Bool
/// Generate a `json` file instead of the default `toml` file.
public let json: Bool public let json: Bool
/// The path to generate a file in.
public let path: Path? public let path: Path?
public init( public init(
@@ -81,11 +87,39 @@ public struct ConfigurationClient: Sendable {
self.path = path self.path = path
} }
/// Represents the path option for generating a configuration file for a user.
///
/// This can either be a full file path or a directory. If a directory is supplied
/// then we will use the default file name of 'config' and add the extension dependning
/// on if the caller wants a `json` or `toml` file.
public enum Path: Equatable, Sendable { public enum Path: Equatable, Sendable {
case file(File) case file(File)
case directory(String) case directory(String)
} }
} }
/// Represents the options required to write a configuration file.
public struct WriteOptions: Equatable, Sendable {
/// The configuration to wrtie to the file path.
public let configuration: Configuration
/// The file path to write the configuration to.
public let file: File
/// Force overwritting an existing file, if it exists.
public let force: Bool
public init(
_ configuration: Configuration,
to file: File,
force: Bool
) {
self.configuration = configuration
self.file = file
self.force = force
}
}
} }
extension ConfigurationClient: DependencyKey { extension ConfigurationClient: DependencyKey {
@@ -97,7 +131,7 @@ extension ConfigurationClient: DependencyKey {
find: { try await liveClient.find() }, find: { try await liveClient.find() },
generate: { try await liveClient.generate($0) }, generate: { try await liveClient.generate($0) },
load: { try await liveClient.load(file: $0) }, load: { try await liveClient.load(file: $0) },
write: { try await liveClient.write($1, to: $0, force: $2) } write: { try await liveClient.write($0) }
) )
} }
@@ -225,7 +259,7 @@ struct LiveConfigurationClient {
} else { } else {
// Json does not allow comments, so we write the mock configuration // Json does not allow comments, so we write the mock configuration
// to the file path. // to the file path.
try await write(.mock, to: File(fileUrl)!, force: options.force) try await write(.init(.mock, to: File(fileUrl)!, force: options.force))
} }
return fileUrl.cleanFilePath return fileUrl.cleanFilePath
@@ -247,10 +281,12 @@ struct LiveConfigurationClient {
} }
func write( func write(
_ configuration: Configuration, _ options: ConfigurationClient.WriteOptions
to file: File,
force: Bool
) async throws { ) async throws {
let configuration = options.configuration
let file = options.file
let force = options.force
let exists = fileManager.fileExists(file.url) let exists = fileManager.fileExists(file.url)
if !force, exists { if !force, exists {

View File

@@ -1,4 +1,6 @@
extension PandocClient { extension PandocClient {
/// Represents constant string values needed internally.
enum Constants { enum Constants {
static let pandocCommand = "pandoc" static let pandocCommand = "pandoc"
static let defaultOutputFileName = "Report" static let defaultOutputFileName = "Report"

View File

@@ -6,7 +6,10 @@ import PlaybookClient
extension PandocClient.RunOptions { extension PandocClient.RunOptions {
func run(_ fileType: PandocClient.FileType) async throws -> String { func run(
_ fileType: PandocClient.FileType,
_ environment: [String: String]
) async throws -> String {
@Dependency(\.commandClient) var commandClient @Dependency(\.commandClient) var commandClient
@Dependency(\.logger) var logger @Dependency(\.logger) var logger
@Dependency(\.playbookClient) var playbookClient @Dependency(\.playbookClient) var playbookClient
@@ -14,13 +17,13 @@ extension PandocClient.RunOptions {
return try await commandClient.run(logging: loggingOptions, quiet: quiet, shell: shell) { return try await commandClient.run(logging: loggingOptions, quiet: quiet, shell: shell) {
let ensuredOptions = try await self.ensuredOptions(fileType) let ensuredOptions = try await self.ensuredOptions(fileType)
let projectDirectory = self.projectDirectory ?? ProcessInfo.processInfo.environment["PWD"] let projectDirectory = self.projectDirectory ?? environment["PWD"]
guard let projectDirectory else { guard let projectDirectory else {
throw ProjectDirectoryNotSpecified() throw ProjectDirectoryNotSpecified()
} }
if shouldBuild { if shouldBuildProject {
logger.debug("Building project...") logger.debug("Building project...")
try await playbookClient.run.buildProject(.init( try await playbookClient.run.buildProject(.init(
projectDirectory: projectDirectory, projectDirectory: projectDirectory,
@@ -65,6 +68,16 @@ extension PandocClient.RunOptions {
} }
} }
extension PandocClient.FileType {
var fileExtension: String {
switch self {
case .html: return "html"
case .latex: return "tex"
case .pdf: return "pdf"
}
}
}
@_spi(Internal) @_spi(Internal)
public struct EnsuredPandocOptions: Equatable, Sendable { public struct EnsuredPandocOptions: Equatable, Sendable {
public let buildDirectory: String public let buildDirectory: String
@@ -76,16 +89,7 @@ public struct EnsuredPandocOptions: Equatable, Sendable {
public let pdfEngine: String? public let pdfEngine: String?
public var ensuredExtensionFileName: String { public var ensuredExtensionFileName: String {
let extensionString: String let extensionString = ".\(outputFileType.fileExtension)"
switch outputFileType {
case .html:
extensionString = ".html"
case .latex:
extensionString = ".tex"
case .pdf:
extensionString = ".pdf"
}
if !outputFileName.hasSuffix(extensionString) { if !outputFileName.hasSuffix(extensionString) {
return outputFileName + extensionString return outputFileName + extensionString

View File

@@ -2,8 +2,15 @@ import CommandClient
import ConfigurationClient import ConfigurationClient
import Dependencies import Dependencies
import DependenciesMacros import DependenciesMacros
import Foundation
public extension DependencyValues { public extension DependencyValues {
/// Represents interactions with the `pandoc` command line application.
///
/// The `pandoc` command line application is used to generate the final output
/// documents from a home performance assessment project.
///
var pandocClient: PandocClient { var pandocClient: PandocClient {
get { self[PandocClient.self] } get { self[PandocClient.self] }
set { self[PandocClient.self] = newValue } set { self[PandocClient.self] = newValue }
@@ -13,14 +20,31 @@ public extension DependencyValues {
@DependencyClient @DependencyClient
public struct PandocClient: Sendable { public struct PandocClient: Sendable {
/// Run a pandoc command.
public var run: Run public var run: Run
/// Represents the pandoc commands that we can run.
@DependencyClient @DependencyClient
public struct Run: Sendable { public struct Run: Sendable {
/// Generate a latex file from the given options, returning the path the
/// generated file was written to.
public var generateLatex: @Sendable (RunOptions) async throws -> String public var generateLatex: @Sendable (RunOptions) async throws -> String
/// Generate an html file from the given options, returning the path the
/// generated file was written to.
public var generateHtml: @Sendable (RunOptions) async throws -> String public var generateHtml: @Sendable (RunOptions) async throws -> String
/// Generate a pdf file from the given options, returning the path the
/// generated file was written to.
public var generatePdf: @Sendable (RunOptions, String?) async throws -> String public var generatePdf: @Sendable (RunOptions, String?) async throws -> String
/// Generate a pdf file from the given options, returning the path the
/// generated file was written to.
///
/// - Parameters:
/// - options: The shared run options.
/// - pdfEngine: The pdf-engine to use to generate the pdf file.
public func generatePdf( public func generatePdf(
_ options: RunOptions, _ options: RunOptions,
pdfEngine: String? = nil pdfEngine: String? = nil
@@ -30,6 +54,9 @@ public struct PandocClient: Sendable {
} }
/// Represents the shared options used to run a `pandoc` command.
///
///
public struct RunOptions: Equatable, Sendable { public struct RunOptions: Equatable, Sendable {
let buildDirectory: String? let buildDirectory: String?
@@ -42,8 +69,22 @@ public struct PandocClient: Sendable {
let projectDirectory: String? let projectDirectory: String?
let quiet: Bool let quiet: Bool
let shell: String? let shell: String?
let shouldBuild: Bool let shouldBuildProject: Bool
/// Create the shared run options.
///
/// - Parameters:
/// - buildDirectory: Specify the build directory of the project.
/// - extraOptions: Extra arguments / options passed to the `pandoc` command.
/// - file: Files used to build the final output.
/// - loggingOptions: The logging options used when running the command.
/// - includeInHeader: Files to include in the header of the final output document.
/// - outputDirectory: Optional output directory for the output file, defaults to current working directory.
/// - projectDirectory: Optional project directory, defaults to current working directory.
/// - outputFileName: Override the output file name.
/// - quiet: Don't log output from the command.
/// - shell: Optional shell to use when calling the pandoc command.
/// - shouldBuildProject: Build the project prior to generating the output file.
public init( public init(
buildDirectory: String? = nil, buildDirectory: String? = nil,
extraOptions: [String]? = nil, extraOptions: [String]? = nil,
@@ -55,7 +96,7 @@ public struct PandocClient: Sendable {
outputFileName: String? = nil, outputFileName: String? = nil,
quiet: Bool = false, quiet: Bool = false,
shell: String? = nil, shell: String? = nil,
shouldBuild: Bool = true shouldBuild shouldBuildProject: Bool = true
) { ) {
self.buildDirectory = buildDirectory self.buildDirectory = buildDirectory
self.extraOptions = extraOptions self.extraOptions = extraOptions
@@ -67,11 +108,12 @@ public struct PandocClient: Sendable {
self.projectDirectory = projectDirectory self.projectDirectory = projectDirectory
self.quiet = quiet self.quiet = quiet
self.shell = shell self.shell = shell
self.shouldBuild = shouldBuild self.shouldBuildProject = shouldBuildProject
} }
} }
/// Represents the file types that we can generate output files for.
@_spi(Internal) @_spi(Internal)
public enum FileType: Equatable, Sendable { public enum FileType: Equatable, Sendable {
case html case html
@@ -83,13 +125,17 @@ public struct PandocClient: Sendable {
extension PandocClient: DependencyKey { extension PandocClient: DependencyKey {
public static let testValue: PandocClient = Self(run: Run()) public static let testValue: PandocClient = Self(run: Run())
public static var liveValue: PandocClient { public static func live(environment: [String: String]) -> PandocClient {
.init( .init(
run: Run( run: Run(
generateLatex: { try await $0.run(.latex) }, generateLatex: { try await $0.run(.latex, environment) },
generateHtml: { try await $0.run(.html) }, generateHtml: { try await $0.run(.html, environment) },
generatePdf: { try await $0.run(.pdf(engine: $1)) } generatePdf: { try await $0.run(.pdf(engine: $1), environment) }
) )
) )
} }
public static var liveValue: PandocClient {
.live(environment: ProcessInfo.processInfo.environment)
}
} }

View File

@@ -1,4 +1,6 @@
// TODO: Use an actuall version tag for playbook repo. // TODO: Use an actual version tag for playbook repo.
// TODO: Use an externally public url for the playbook repo.
public extension PlaybookClient { public extension PlaybookClient {
@_spi(Internal) @_spi(Internal)
enum Constants { enum Constants {

View File

@@ -9,6 +9,11 @@ import ShellClient
// TODO: Add update checks and pull for the playbook. // TODO: Add update checks and pull for the playbook.
public extension DependencyValues { public extension DependencyValues {
/// Manages interactions with the playbook repository as well as `ansible-playbook`
/// command line application.
///
///
var playbookClient: PlaybookClient { var playbookClient: PlaybookClient {
get { self[PlaybookClient.self] } get { self[PlaybookClient.self] }
set { self[PlaybookClient.self] = newValue } set { self[PlaybookClient.self] = newValue }
@@ -17,21 +22,36 @@ public extension DependencyValues {
@DependencyClient @DependencyClient
public struct PlaybookClient: Sendable { public struct PlaybookClient: Sendable {
/// Manages interactions with the playbook repository.
public var repository: Repository public var repository: Repository
/// Run the `ansible-playbook` command line application.
public var run: RunPlaybook public var run: RunPlaybook
} }
public extension PlaybookClient { public extension PlaybookClient {
/// Manages interactions with the `ansible-hpa-playbook` repository, which is
/// used to build and generate home performance assessment projects.
@DependencyClient @DependencyClient
struct Repository: Sendable { 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 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 public var directory: @Sendable (Configuration?) async throws -> String
/// Install the playbook in the default location.
public func install() async throws { public func install() async throws {
try await install(nil) try await install(nil)
} }
/// Get the current directory path of the playbook repository.
public func directory() async throws -> String { public func directory() async throws -> String {
try await directory(nil) try await directory(nil)
} }
@@ -40,23 +60,56 @@ public extension PlaybookClient {
public extension PlaybookClient { 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 @DependencyClient
struct RunPlaybook: Sendable { struct RunPlaybook: Sendable {
/// Build a home performance assesment project with the given options.
public var buildProject: @Sendable (BuildOptions) async throws -> Void 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 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 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 { public func createProject(_ options: CreateOptions) async throws {
try await createProject(options, nil) try await createProject(options, nil)
} }
/// Represents options that are shared for all the `ansible-playbook` commands.
public struct SharedRunOptions: Equatable, Sendable { public struct SharedRunOptions: Equatable, Sendable {
public let extraOptions: [String]?
public let inventoryFilePath: String?
public let loggingOptions: LoggingOptions
public let quiet: Bool
public let shell: String?
/// 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( public init(
extraOptions: [String]? = nil, extraOptions: [String]? = nil,
inventoryFilePath: String? = nil, inventoryFilePath: String? = nil,
@@ -72,11 +125,21 @@ public extension PlaybookClient {
} }
} }
/// Options require when calling the build project command.
@dynamicMemberLookup @dynamicMemberLookup
public struct BuildOptions: Equatable, Sendable { public struct BuildOptions: Equatable, Sendable {
public let projectDirectory: String?
public let shared: SharedRunOptions
/// 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( public init(
projectDirectory: String? = nil, projectDirectory: String? = nil,
shared: SharedRunOptions shared: SharedRunOptions
@@ -90,13 +153,25 @@ public extension PlaybookClient {
} }
} }
/// Options required when creating a new home performance assessment project.
@dynamicMemberLookup @dynamicMemberLookup
public struct CreateOptions: Equatable, Sendable { public struct CreateOptions: Equatable, Sendable {
public let projectDirectory: String /// The directory to generate the new project in.
public let shared: SharedRunOptions let projectDirectory: String
public let template: Configuration.Template? /// Shared run options.
public let useLocalTemplateDirectory: Bool 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( public init(
projectDirectory: String, projectDirectory: String,
shared: SharedRunOptions, shared: SharedRunOptions,
@@ -114,13 +189,25 @@ public extension PlaybookClient {
} }
} }
/// Options required when generating a new template repository / directory.
@dynamicMemberLookup @dynamicMemberLookup
public struct GenerateTemplateOptions: Equatable, Sendable { public struct GenerateTemplateOptions: Equatable, Sendable {
public let shared: SharedRunOptions /// The shared run options.
public let templateDirectory: String let shared: SharedRunOptions
public let templateVarsDirectory: String? /// The path to generate the template in.
public let useVault: Bool 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( public init(
shared: SharedRunOptions, shared: SharedRunOptions,
templateDirectory: String, templateDirectory: String,

View File

@@ -4,9 +4,8 @@ import Dependencies
import DependenciesMacros import DependenciesMacros
import FileClient import FileClient
// TODO: Add edit / view routes, possibly create?
public extension DependencyValues { public extension DependencyValues {
/// Manages interactions with `ansible-vault` command line application.
var vaultClient: VaultClient { var vaultClient: VaultClient {
get { self[VaultClient.self] } get { self[VaultClient.self] }
set { self[VaultClient.self] = newValue } set { self[VaultClient.self] = newValue }
@@ -15,23 +14,41 @@ public extension DependencyValues {
@DependencyClient @DependencyClient
public struct VaultClient: Sendable { public struct VaultClient: Sendable {
/// Run an `ansible-vault` command.
public var run: Run public var run: Run
@DependencyClient @DependencyClient
public struct Run: Sendable { public struct Run: Sendable {
/// Decrypt an `ansible-vault` file.
public var decrypt: @Sendable (RunOptions) async throws -> String public var decrypt: @Sendable (RunOptions) async throws -> String
/// Encrypt an `ansible-vault` file.
public var encrypt: @Sendable (RunOptions) async throws -> String public var encrypt: @Sendable (RunOptions) async throws -> String
} }
public struct RunOptions: Equatable, Sendable { public struct RunOptions: Equatable, Sendable {
public let extraOptions: [String]? /// Extra arguments / options passed to the `ansible-vault` command.
public let loggingOptions: LoggingOptions let extraOptions: [String]?
public let outputFilePath: String? /// Logging options to use while running the command.
public let quiet: Bool let loggingOptions: LoggingOptions
public let shell: String? /// The optional output file path.
public let vaultFilePath: String? let outputFilePath: String?
/// Disable logging output.
let quiet: Bool
/// Optional shell used to call the command.
let shell: String?
/// The path to the vault file, if not supplied we will search the current directory for it.
let vaultFilePath: String?
/// Create new run options.
///
/// - Parameters:
/// - extraOptions: Extra arguments / options passed to the command.
/// - loggingOptions: The logging options used while running the command.
/// - outputFilePath: The optional output file path.
/// - quiet: Disable logging output of the command.
/// - shell: The shell used to call the command.
/// - vaultFilePath: The vault file, if not supplied we will search in the current working directory.
public init( public init(
extraOptions: [String]? = nil, extraOptions: [String]? = nil,
loggingOptions: LoggingOptions, loggingOptions: LoggingOptions,

View File

@@ -1,17 +0,0 @@
// import ConfigurationClient
//
// extension VaultOptions {
//
// func vaultOptions(
// arguments: [String],
// configuration: Configuration?
// ) -> CliClient.VaultOptions {
// .init(
// arguments: arguments,
// configuration: configuration,
// quiet: globals.quiet,
// shell: globals.shell,
// vaultFilePath: file
// )
// }
// }