feat: Removes cli-client
This commit is contained in:
@@ -7,12 +7,12 @@ let package = Package(
|
||||
platforms: [.macOS(.v14)],
|
||||
products: [
|
||||
.executable(name: "hpa", targets: ["hpa"]),
|
||||
.library(name: "CliClient", targets: ["CliClient"]),
|
||||
.library(name: "CodersClient", targets: ["CodersClient"]),
|
||||
.library(name: "CommandClient", targets: ["CommandClient"]),
|
||||
.library(name: "ConfigurationClient", targets: ["ConfigurationClient"]),
|
||||
.library(name: "FileClient", targets: ["FileClient"]),
|
||||
.library(name: "PlaybookClient", targets: ["PlaybookClient"]),
|
||||
.library(name: "PandocClient", targets: ["PandocClient"]),
|
||||
.library(name: "VaultClient", targets: ["VaultClient"])
|
||||
],
|
||||
dependencies: [
|
||||
@@ -27,9 +27,9 @@ let package = Package(
|
||||
.executableTarget(
|
||||
name: "hpa",
|
||||
dependencies: [
|
||||
"CliClient",
|
||||
"ConfigurationClient",
|
||||
"FileClient",
|
||||
"PandocClient",
|
||||
"PlaybookClient",
|
||||
"VaultClient",
|
||||
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||
@@ -38,25 +38,6 @@ let package = Package(
|
||||
.product(name: "ShellClient", package: "swift-shell-client")
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "CliClient",
|
||||
dependencies: [
|
||||
"CommandClient",
|
||||
"CodersClient",
|
||||
"ConfigurationClient",
|
||||
"PlaybookClient",
|
||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||
.product(name: "ShellClient", package: "swift-shell-client")
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "CliClientTests",
|
||||
dependencies: [
|
||||
"CliClient",
|
||||
"TestSupport"
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "CodersClient",
|
||||
dependencies: [
|
||||
@@ -110,6 +91,20 @@ let package = Package(
|
||||
.product(name: "DependenciesMacros", package: "swift-dependencies")
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "PandocClient",
|
||||
dependencies: [
|
||||
"CommandClient",
|
||||
"ConfigurationClient",
|
||||
"PlaybookClient",
|
||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||
.product(name: "DependenciesMacros", package: "swift-dependencies")
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "PandocClientTests",
|
||||
dependencies: ["PandocClient", "TestSupport"]
|
||||
),
|
||||
.target(
|
||||
name: "PlaybookClient",
|
||||
dependencies: [
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
import ConfigurationClient
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
import FileClient
|
||||
import Foundation
|
||||
import PlaybookClient
|
||||
import ShellClient
|
||||
|
||||
public extension CliClient {
|
||||
|
||||
func runCommand(
|
||||
quiet: Bool,
|
||||
shell: ShellCommand.Shell,
|
||||
_ args: [String]
|
||||
) async throws {
|
||||
try await runCommand(.init(arguments: args, quiet: quiet, shell: shell))
|
||||
}
|
||||
|
||||
func runCommand(
|
||||
quiet: Bool,
|
||||
shell: ShellCommand.Shell,
|
||||
_ args: String...
|
||||
) async throws {
|
||||
try await runCommand(quiet: quiet, shell: shell, args)
|
||||
}
|
||||
|
||||
func installDependencies(
|
||||
quiet: Bool = false,
|
||||
shell: String? = nil,
|
||||
extraArgs: [String]? = nil
|
||||
) async throws {
|
||||
@Dependency(\.playbookClient) var playbookClient
|
||||
@Dependency(\.configurationClient) var configurationClient
|
||||
|
||||
var arguments = [
|
||||
"brew", "install"
|
||||
] + Constants.brewPackages
|
||||
|
||||
if let extraArgs {
|
||||
arguments.append(contentsOf: extraArgs)
|
||||
}
|
||||
|
||||
try await runCommand(
|
||||
quiet: quiet,
|
||||
shell: shell.orDefault,
|
||||
arguments
|
||||
)
|
||||
|
||||
let configuration = try await configurationClient.findAndLoad()
|
||||
try await playbookClient.repository.install(configuration)
|
||||
}
|
||||
|
||||
func runPlaybookCommand(
|
||||
_ options: PlaybookOptions,
|
||||
logging loggingOptions: LoggingOptions
|
||||
) async throws {
|
||||
try await withLogger(loggingOptions) {
|
||||
@Dependency(\.configurationClient) var configurationClient
|
||||
@Dependency(\.logger) var logger
|
||||
@Dependency(\.playbookClient) var playbookClient
|
||||
|
||||
let configuration = try await configurationClient.ensuredConfiguration(options.configuration)
|
||||
logger.trace("Configuration: \(configuration)")
|
||||
|
||||
let playbookDirectory = try await playbookClient.repository.directory(configuration)
|
||||
let playbookPath = "\(playbookDirectory)/\(Constants.playbookFileName)"
|
||||
logger.trace("Playbook path: \(playbookPath)")
|
||||
|
||||
let inventoryPath = ensuredInventoryPath(
|
||||
options.inventoryFilePath,
|
||||
configuration: configuration,
|
||||
playbookDirectory: playbookDirectory
|
||||
)
|
||||
logger.trace("Inventory path: \(inventoryPath)")
|
||||
|
||||
var arguments = [
|
||||
Constants.playbookCommand, playbookPath,
|
||||
"--inventory", inventoryPath
|
||||
] + options.arguments
|
||||
|
||||
if let defaultArgs = configuration.args {
|
||||
arguments.append(contentsOf: defaultArgs)
|
||||
}
|
||||
|
||||
if configuration.useVaultArgs, let vaultArgs = configuration.vault.args {
|
||||
arguments.append(contentsOf: vaultArgs)
|
||||
}
|
||||
|
||||
logger.trace("Running playbook command with arguments: \(arguments)")
|
||||
|
||||
try await runCommand(
|
||||
quiet: options.quiet,
|
||||
shell: options.shell.orDefault,
|
||||
arguments
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func runVaultCommand(
|
||||
_ options: VaultOptions,
|
||||
logging loggingOptions: LoggingOptions
|
||||
) async throws {
|
||||
try await withLogger(loggingOptions) {
|
||||
@Dependency(\.configurationClient) var configurationClient
|
||||
@Dependency(\.fileClient) var fileClient
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
let configuration = try await configurationClient.ensuredConfiguration(options.configuration)
|
||||
logger.trace("Configuration: \(configuration)")
|
||||
|
||||
let vaultFilePath = try await fileClient.ensuredVaultFilePath(options.vaultFilePath)
|
||||
logger.trace("Vault file: \(vaultFilePath)")
|
||||
|
||||
var arguments = [
|
||||
Constants.vaultCommand
|
||||
] + options.arguments
|
||||
|
||||
if let defaultArgs = configuration.vault.args {
|
||||
arguments.append(contentsOf: defaultArgs)
|
||||
}
|
||||
|
||||
if arguments.contains("encrypt"),
|
||||
!arguments.contains("--encrypt-vault-id"),
|
||||
let id = configuration.vault.encryptId
|
||||
{
|
||||
arguments.append(contentsOf: ["--encrypt-vault-id", id])
|
||||
}
|
||||
|
||||
arguments.append(vaultFilePath)
|
||||
|
||||
logger.trace("Running vault command with arguments: \(arguments)")
|
||||
|
||||
try await runCommand(
|
||||
quiet: options.quiet,
|
||||
shell: options.shell.orDefault,
|
||||
arguments
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@_spi(Internal)
|
||||
public extension ConfigurationClient {
|
||||
func ensuredConfiguration(_ optionalConfig: Configuration?) async throws -> Configuration {
|
||||
guard let config = optionalConfig else {
|
||||
return try await findAndLoad()
|
||||
}
|
||||
return config
|
||||
}
|
||||
}
|
||||
|
||||
@_spi(Internal)
|
||||
public extension Optional where Wrapped == String {
|
||||
var orDefault: ShellCommand.Shell {
|
||||
guard let shell = self else { return .zsh(useDashC: true) }
|
||||
return .custom(path: shell, useDashC: true)
|
||||
}
|
||||
}
|
||||
|
||||
@_spi(Internal)
|
||||
public func ensuredInventoryPath(
|
||||
_ optionalInventoryPath: String?,
|
||||
configuration: Configuration,
|
||||
playbookDirectory: String
|
||||
) -> String {
|
||||
guard let path = optionalInventoryPath else {
|
||||
guard let path = configuration.playbook?.inventory else {
|
||||
return "\(playbookDirectory)/\(Constants.inventoryFileName)"
|
||||
}
|
||||
return path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
@_spi(Internal)
|
||||
public extension FileClient {
|
||||
|
||||
func ensuredVaultFilePath(_ optionalPath: String?) async throws -> String {
|
||||
guard let path = optionalPath else {
|
||||
guard let url = try await findVaultFileInCurrentDirectory() else {
|
||||
throw CliClientError.vaultFileNotFound
|
||||
}
|
||||
return url.cleanFilePath
|
||||
}
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
extension ShellCommand.Shell: @retroactive @unchecked Sendable {}
|
||||
@@ -1,223 +0,0 @@
|
||||
import ConfigurationClient
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import ShellClient
|
||||
|
||||
// TODO: Need to parse / ensure that header includes and files are in the build directory, not sure
|
||||
// exactly how to handle if they're not, but it seems reasonable to potentially allow files that are
|
||||
// outside of the build directory to be included.
|
||||
|
||||
public extension CliClient {
|
||||
|
||||
@discardableResult
|
||||
func runPandocCommand(
|
||||
_ options: PandocOptions,
|
||||
logging loggingOptions: LoggingOptions
|
||||
) async throws -> String {
|
||||
try await withLogger(loggingOptions) {
|
||||
@Dependency(\.configurationClient) var configurationClient
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
let configuration = try await configurationClient.findAndLoad()
|
||||
logger.trace("Configuration: \(configuration)")
|
||||
|
||||
let ensuredOptions = try await ensurePandocOptions(
|
||||
configuration: configuration,
|
||||
options: options
|
||||
)
|
||||
|
||||
let projectDirectory = options.projectDirectory ?? ProcessInfo.processInfo.environment["PWD"]
|
||||
guard let projectDirectory else {
|
||||
throw CliClientError.generate(.projectDirectoryNotSpecified)
|
||||
}
|
||||
|
||||
let outputDirectory = options.outputDirectory ?? projectDirectory
|
||||
let outputPath = "\(outputDirectory)/\(ensuredOptions.ensuredExtensionFileName)"
|
||||
|
||||
var arguments = [
|
||||
"pandoc"
|
||||
]
|
||||
arguments += ensuredOptions.includeInHeader.map {
|
||||
"--include-in-header=\(projectDirectory)/\(ensuredOptions.buildDirectory)/\($0)"
|
||||
}
|
||||
|
||||
if let pdfEngine = ensuredOptions.pdfEngine {
|
||||
arguments.append("--pdf-engine=\(pdfEngine)")
|
||||
}
|
||||
|
||||
arguments.append("--output=\(outputPath)")
|
||||
arguments += ensuredOptions.files.map {
|
||||
"\(projectDirectory)/\(ensuredOptions.buildDirectory)/\($0)"
|
||||
}
|
||||
|
||||
if options.shouldBuild {
|
||||
logger.trace("Building project...")
|
||||
try await runPlaybookCommand(
|
||||
.init(
|
||||
arguments: [
|
||||
"--tags", "build-project",
|
||||
"--extra-vars", "project_dir=\(projectDirectory)"
|
||||
],
|
||||
configuration: configuration,
|
||||
quiet: options.quiet,
|
||||
shell: options.shell
|
||||
),
|
||||
logging: loggingOptions
|
||||
)
|
||||
}
|
||||
|
||||
logger.trace("Running pandoc with arguments: \(arguments)")
|
||||
|
||||
try await runCommand(
|
||||
quiet: options.quiet,
|
||||
shell: options.shell.orDefault,
|
||||
arguments
|
||||
)
|
||||
|
||||
return outputPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@_spi(Internal)
|
||||
public struct EnsuredPandocOptions: Equatable, Sendable {
|
||||
public let buildDirectory: String
|
||||
public let files: [String]
|
||||
public let includeInHeader: [String]
|
||||
public let outputFileName: String
|
||||
public let outputFileType: CliClient.PandocOptions.FileType
|
||||
public let pdfEngine: String?
|
||||
|
||||
public var ensuredExtensionFileName: String {
|
||||
let extensionString: String
|
||||
|
||||
switch outputFileType {
|
||||
case .html:
|
||||
extensionString = ".html"
|
||||
case .latex:
|
||||
extensionString = ".tex"
|
||||
case .pdf:
|
||||
extensionString = ".pdf"
|
||||
}
|
||||
|
||||
if !outputFileName.hasSuffix(extensionString) {
|
||||
return outputFileName + extensionString
|
||||
}
|
||||
return outputFileName
|
||||
}
|
||||
}
|
||||
|
||||
@_spi(Internal)
|
||||
public func ensurePandocOptions(
|
||||
configuration: Configuration,
|
||||
options: CliClient.PandocOptions
|
||||
) async throws -> EnsuredPandocOptions {
|
||||
let defaults = Configuration.Generate.default
|
||||
let pdfEngine = parsePdfEngine(configuration.generate, defaults, options)
|
||||
|
||||
return .init(
|
||||
buildDirectory: parseBuildDirectory(configuration.generate, defaults, options),
|
||||
files: parseFiles(configuration.generate, defaults, options),
|
||||
includeInHeader: parseIncludeInHeader(configuration.generate, defaults, options),
|
||||
outputFileName: parseOutputFileName(configuration.generate, defaults, options),
|
||||
outputFileType: options.outputFileType,
|
||||
pdfEngine: pdfEngine
|
||||
)
|
||||
}
|
||||
|
||||
private func parsePdfEngine(
|
||||
_ configuration: Configuration.Generate?,
|
||||
_ defaults: Configuration.Generate,
|
||||
_ options: CliClient.PandocOptions
|
||||
) -> String? {
|
||||
switch options.outputFileType {
|
||||
case .html, .latex:
|
||||
return nil
|
||||
case let .pdf(engine: engine):
|
||||
if let engine {
|
||||
return engine
|
||||
} else if let engine = configuration?.pdfEngine {
|
||||
return engine
|
||||
} else if let engine = defaults.pdfEngine {
|
||||
return engine
|
||||
} else {
|
||||
return "xelatex"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func parseFiles(
|
||||
_ configuration: Configuration.Generate?,
|
||||
_ defaults: Configuration.Generate,
|
||||
_ options: CliClient.PandocOptions
|
||||
) -> [String] {
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
if let files = options.files {
|
||||
return files
|
||||
} else if let files = configuration?.files {
|
||||
return files
|
||||
} else if let files = defaults.files {
|
||||
return files
|
||||
} else {
|
||||
logger.warning("Files not specified, this could lead to errors.")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
private func parseIncludeInHeader(
|
||||
_ configuration: Configuration.Generate?,
|
||||
_ defaults: Configuration.Generate,
|
||||
_ options: CliClient.PandocOptions
|
||||
) -> [String] {
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
if let files = options.includeInHeader {
|
||||
return files
|
||||
} else if let files = configuration?.includeInHeader {
|
||||
return files
|
||||
} else if let files = defaults.includeInHeader {
|
||||
return files
|
||||
} else {
|
||||
logger.warning("Include in header files not specified, this could lead to errors.")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
private func parseOutputFileName(
|
||||
_ configuration: Configuration.Generate?,
|
||||
_ defaults: Configuration.Generate,
|
||||
_ options: CliClient.PandocOptions
|
||||
) -> String {
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
if let output = options.outputFileName {
|
||||
return output
|
||||
} else if let output = configuration?.outputFileName {
|
||||
return output
|
||||
} else if let output = defaults.outputFileName {
|
||||
return output
|
||||
} else {
|
||||
logger.warning("Output file name not specified, this could lead to errors.")
|
||||
return "Report"
|
||||
}
|
||||
}
|
||||
|
||||
private func parseBuildDirectory(
|
||||
_ configuration: Configuration.Generate?,
|
||||
_ defaults: Configuration.Generate,
|
||||
_ options: CliClient.PandocOptions
|
||||
) -> String {
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
if let output = options.buildDirectory {
|
||||
return output
|
||||
} else if let output = configuration?.buildDirectory {
|
||||
return output
|
||||
} else if let output = defaults.buildDirectory {
|
||||
return output
|
||||
} else {
|
||||
logger.warning("Output file name not specified, this could lead to errors.")
|
||||
return ".build"
|
||||
}
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
import ConfigurationClient
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import PlaybookClient
|
||||
|
||||
extension CliClient.RunPlaybook {
|
||||
|
||||
static func makeCommonArguments(
|
||||
configuration: Configuration,
|
||||
inventoryFilePath: String?
|
||||
) async throws -> [String] {
|
||||
@Dependency(\.logger) var logger
|
||||
@Dependency(\.playbookClient) var playbookClient
|
||||
|
||||
let playbookDirectory = try await playbookClient.repository.directory(configuration)
|
||||
let playbookPath = "\(playbookDirectory)/\(Constants.playbookFileName)"
|
||||
logger.trace("Playbook path: \(playbookPath)")
|
||||
|
||||
let inventoryPath = ensuredInventoryPath(
|
||||
inventoryFilePath,
|
||||
configuration: configuration,
|
||||
playbookDirectory: playbookDirectory
|
||||
)
|
||||
logger.trace("Inventory path: \(inventoryPath)")
|
||||
|
||||
var arguments = [
|
||||
Constants.playbookCommand, playbookPath,
|
||||
"--inventory", inventoryPath
|
||||
]
|
||||
|
||||
if let defaultArgs = configuration.args {
|
||||
arguments.append(contentsOf: defaultArgs)
|
||||
}
|
||||
|
||||
if configuration.useVaultArgs, let vaultArgs = configuration.vault.args {
|
||||
arguments.append(contentsOf: vaultArgs)
|
||||
}
|
||||
|
||||
logger.trace("Common arguments: \(inventoryPath)")
|
||||
return arguments
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension CliClient.RunPlaybook.BuildOptions {
|
||||
private func applyArguments(
|
||||
to arguments: inout [String],
|
||||
configuration: Configuration
|
||||
) throws {
|
||||
let projectDirectory = projectDirectory
|
||||
?? ProcessInfo.processInfo.environment["PWD"]
|
||||
|
||||
guard let projectDirectory else {
|
||||
throw CliClientError.projectDirectoryNotFound
|
||||
}
|
||||
|
||||
arguments.append(contentsOf: [
|
||||
"--tags", "build-project",
|
||||
"--extra-vars", "project_dir=\(projectDirectory)"
|
||||
])
|
||||
|
||||
if let extraOptions = extraOptions {
|
||||
arguments.append(contentsOf: extraOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CliClient.RunPlaybook.CreateOptions {
|
||||
private func applyArguments(
|
||||
to arguments: inout [String],
|
||||
configuration: Configuration
|
||||
) throws {
|
||||
let json = try createJSONData(configuration: configuration)
|
||||
|
||||
arguments.append(contentsOf: [
|
||||
"--tags", "setup-project",
|
||||
"--extra-vars", "project_dir=\(projectDirectory)",
|
||||
"--extra-vars", "'\(json)'"
|
||||
])
|
||||
|
||||
if let extraOptions {
|
||||
arguments.append(contentsOf: extraOptions)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension CliClient.PlaybookOptions.Route {
|
||||
|
||||
private func parseInventoryPath(
|
||||
_ configuration: Configuration,
|
||||
_ playbookDirectory: String
|
||||
) -> String {
|
||||
let inventoryFilePath: String?
|
||||
|
||||
switch self {
|
||||
case let .build(options):
|
||||
inventoryFilePath = options.inventoryFilePath
|
||||
case let .create(options):
|
||||
inventoryFilePath = options.inventoryFilePath
|
||||
}
|
||||
|
||||
return ensuredInventoryPath(
|
||||
inventoryFilePath,
|
||||
configuration: configuration,
|
||||
playbookDirectory: playbookDirectory
|
||||
)
|
||||
}
|
||||
|
||||
func makeArguments(configuration: Configuration) async throws -> [String] {
|
||||
@Dependency(\.logger) var logger
|
||||
@Dependency(\.playbookClient) var playbookClient
|
||||
|
||||
let playbookDirectory = try await playbookClient.repository.directory(configuration)
|
||||
let playbookPath = "\(playbookDirectory)/\(Constants.playbookFileName)"
|
||||
logger.trace("Playbook path: \(playbookPath)")
|
||||
|
||||
let inventoryPath = parseInventoryPath(configuration, playbookDirectory)
|
||||
logger.trace("Inventory path: \(inventoryPath)")
|
||||
|
||||
var arguments = [
|
||||
Constants.playbookCommand, playbookPath,
|
||||
"--inventory", inventoryPath
|
||||
]
|
||||
|
||||
if let defaultArgs = configuration.args {
|
||||
arguments.append(contentsOf: defaultArgs)
|
||||
}
|
||||
|
||||
if configuration.useVaultArgs, let vaultArgs = configuration.vault.args {
|
||||
arguments.append(contentsOf: vaultArgs)
|
||||
}
|
||||
|
||||
// try applyArguments(to: &arguments, configuration: configuration)
|
||||
|
||||
return arguments
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: We're not using the `Coders` client because we generally do not
|
||||
// want the output to be `prettyPrinted` or anything, unless we're running
|
||||
// tests, so we use a supplied json encoder.
|
||||
|
||||
extension CliClient.RunPlaybook.CreateOptions {
|
||||
|
||||
func createJSONData(
|
||||
configuration: Configuration,
|
||||
encoder: JSONEncoder = .init()
|
||||
) throws -> Data {
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
let templateDir = template.directory ?? configuration.template.directory
|
||||
let templateRepo = template.url ?? configuration.template.url
|
||||
let version = template.version ?? configuration.template.version
|
||||
|
||||
logger.debug("""
|
||||
(\(useLocalTemplateDirectory), \(String(describing: templateDir)), \(String(describing: templateRepo)))
|
||||
""")
|
||||
|
||||
switch (useLocalTemplateDirectory, 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 CliClientError.templateDirectoryNotFound
|
||||
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 CliClientError.templateDirectoryOrRepoNotSpecified
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: .init(url: repo, version: version ?? "main"))
|
||||
}
|
||||
|
||||
struct Template: Encodable {
|
||||
let repo: Repo
|
||||
}
|
||||
|
||||
struct Repo: Encodable {
|
||||
let url: String
|
||||
let version: String
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
public enum CliClientError: Error {
|
||||
case brewfileNotFound
|
||||
case encodingError
|
||||
case playbookDirectoryNotFound
|
||||
case projectDirectoryNotFound
|
||||
case generate(GenerateError)
|
||||
case templateDirectoryNotFound
|
||||
case templateDirectoryOrRepoNotSpecified
|
||||
case vaultFileNotFound
|
||||
|
||||
public enum GenerateError: Sendable {
|
||||
case projectDirectoryNotSpecified
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
enum Constants {
|
||||
static let executableName = "hpa"
|
||||
static let playbookBundleDirectoryName = "ansible-hpa-playbook"
|
||||
static let playbookCommand = "ansible-playbook"
|
||||
static let playbookFileName = "main.yml"
|
||||
static let inventoryFileName = "inventory.ini"
|
||||
static let vaultCommand = "ansible-vault"
|
||||
static let brewPackages = [
|
||||
"ansible", "imagemagick", "pandoc", "texLive"
|
||||
]
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import ConfigurationClient
|
||||
import Dependencies
|
||||
import Foundation
|
||||
|
||||
// NOTE: We're not using the `Coders` client because we generally do not
|
||||
// want the output to be `prettyPrinted` or anything, unless we're running
|
||||
// tests, so we use a supplied json encoder.
|
||||
|
||||
// TODO: Remove.
|
||||
func createJSONData(
|
||||
_ options: CliClient.GenerateJsonOptions,
|
||||
logging loggingOptions: CliClient.LoggingOptions,
|
||||
encoder: JSONEncoder = .init()
|
||||
) async throws -> Data {
|
||||
try await CliClient.withLogger(loggingOptions) {
|
||||
@Dependency(\.logger) var logger
|
||||
@Dependency(\.configurationClient) var configurationClient
|
||||
|
||||
let configuration = try await configurationClient.findAndLoad()
|
||||
|
||||
let templateDir = options.templateDirectory ?? configuration.template.directory
|
||||
let templateRepo = options.templateRepo ?? configuration.template.url
|
||||
let version = options.version ?? configuration.template.version
|
||||
|
||||
logger.debug("""
|
||||
(\(options.useLocalTemplateDirectory), \(String(describing: templateDir)), \(String(describing: templateRepo)))
|
||||
""")
|
||||
|
||||
switch (options.useLocalTemplateDirectory, 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 CliClientError.templateDirectoryNotFound
|
||||
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 CliClientError.templateDirectoryOrRepoNotSpecified
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: .init(url: repo, version: version ?? "main"))
|
||||
}
|
||||
|
||||
struct Template: Encodable {
|
||||
let repo: Repo
|
||||
}
|
||||
|
||||
struct Repo: Encodable {
|
||||
let url: String
|
||||
let version: String
|
||||
}
|
||||
}
|
||||
@@ -1,329 +0,0 @@
|
||||
import ConfigurationClient
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
import Foundation
|
||||
import ShellClient
|
||||
|
||||
public extension DependencyValues {
|
||||
var cliClient: CliClient {
|
||||
get { self[CliClient.self] }
|
||||
set { self[CliClient.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
@DependencyClient
|
||||
public struct CliClient: Sendable {
|
||||
public var runCommand: @Sendable (RunCommandOptions) async throws -> Void
|
||||
public var generateJSON: @Sendable (GenerateJsonOptions, LoggingOptions, JSONEncoder) async throws -> String
|
||||
|
||||
public func generateJSON(
|
||||
_ options: GenerateJsonOptions,
|
||||
logging loggingOptions: LoggingOptions,
|
||||
encoder jsonEncoder: JSONEncoder = .init()
|
||||
) async throws -> String {
|
||||
try await generateJSON(options, loggingOptions, jsonEncoder)
|
||||
}
|
||||
}
|
||||
|
||||
public extension CliClient {
|
||||
|
||||
@DependencyClient
|
||||
struct RunPlaybook: Sendable {
|
||||
public var buildProject: @Sendable (RunOptions, BuildOptions) async throws -> Void
|
||||
public var createProject: @Sendable (RunOptions, CreateOptions) async throws -> String
|
||||
|
||||
public struct RunOptions: Equatable, Sendable {
|
||||
public let loggingOptions: CliClient.LoggingOptions
|
||||
public let quiet: Bool
|
||||
public let shell: String?
|
||||
}
|
||||
|
||||
public struct BuildOptions: Equatable, Sendable {
|
||||
public let extraOptions: [String]?
|
||||
public let inventoryFilePath: String?
|
||||
public let projectDirectory: String?
|
||||
|
||||
public init(
|
||||
extraOptions: [String]?,
|
||||
inventoryFilePath: String?,
|
||||
projectDirectory: String
|
||||
) {
|
||||
self.extraOptions = extraOptions
|
||||
self.inventoryFilePath = inventoryFilePath
|
||||
self.projectDirectory = projectDirectory
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct CreateOptions: Equatable, Sendable {
|
||||
public let extraOptions: [String]?
|
||||
public let inventoryFilePath: String?
|
||||
public let projectDirectory: String
|
||||
public let template: Configuration.Template
|
||||
public let useLocalTemplateDirectory: Bool
|
||||
|
||||
public init(
|
||||
extraOptions: [String]?,
|
||||
inventoryFilePath: String?,
|
||||
projectDirectory: String,
|
||||
template: Configuration.Template,
|
||||
useLocalTemplateDirectory: Bool
|
||||
) {
|
||||
self.extraOptions = extraOptions
|
||||
self.inventoryFilePath = inventoryFilePath
|
||||
self.projectDirectory = projectDirectory
|
||||
self.template = template
|
||||
self.useLocalTemplateDirectory = useLocalTemplateDirectory
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension CliClient {
|
||||
|
||||
struct PandocOptions: Equatable, Sendable {
|
||||
|
||||
let buildDirectory: String?
|
||||
let files: [String]?
|
||||
let includeInHeader: [String]?
|
||||
let outputDirectory: String?
|
||||
let outputFileName: String?
|
||||
let outputFileType: FileType
|
||||
let projectDirectory: String?
|
||||
let quiet: Bool
|
||||
let shell: String?
|
||||
let shouldBuild: Bool
|
||||
|
||||
public init(
|
||||
buildDirectory: String? = nil,
|
||||
files: [String]? = nil,
|
||||
includeInHeader: [String]? = nil,
|
||||
outputDirectory: String? = nil,
|
||||
outputFileName: String? = nil,
|
||||
outputFileType: FileType,
|
||||
projectDirectory: String?,
|
||||
quiet: Bool,
|
||||
shell: String? = nil,
|
||||
shouldBuild: Bool
|
||||
) {
|
||||
self.buildDirectory = buildDirectory
|
||||
self.files = files
|
||||
self.includeInHeader = includeInHeader
|
||||
self.outputDirectory = outputDirectory
|
||||
self.outputFileName = outputFileName
|
||||
self.outputFileType = outputFileType
|
||||
self.projectDirectory = projectDirectory
|
||||
self.quiet = quiet
|
||||
self.shell = shell
|
||||
self.shouldBuild = shouldBuild
|
||||
}
|
||||
|
||||
// swiftlint:disable nesting
|
||||
public enum FileType: Equatable, Sendable {
|
||||
case html
|
||||
case latex
|
||||
case pdf(engine: String?)
|
||||
}
|
||||
// swiftlint:enable nesting
|
||||
}
|
||||
|
||||
struct GenerateJsonOptions: Equatable, Sendable {
|
||||
let templateDirectory: String?
|
||||
let templateRepo: String?
|
||||
let version: String?
|
||||
let useLocalTemplateDirectory: Bool
|
||||
|
||||
public init(
|
||||
templateDirectory: String?,
|
||||
templateRepo: String?,
|
||||
version: String?,
|
||||
useLocalTemplateDirectory: Bool
|
||||
) {
|
||||
self.templateDirectory = templateDirectory
|
||||
self.templateRepo = templateRepo
|
||||
self.version = version
|
||||
self.useLocalTemplateDirectory = useLocalTemplateDirectory
|
||||
}
|
||||
}
|
||||
|
||||
struct LoggingOptions: Equatable, Sendable {
|
||||
let commandName: String
|
||||
let logLevel: Logger.Level
|
||||
|
||||
public init(commandName: String, logLevel: Logger.Level) {
|
||||
self.commandName = commandName
|
||||
self.logLevel = logLevel
|
||||
}
|
||||
}
|
||||
|
||||
struct PlaybookOptions: Sendable, Equatable {
|
||||
let arguments: [String]
|
||||
let configuration: Configuration?
|
||||
let inventoryFilePath: String?
|
||||
let playbookDirectory: String?
|
||||
let quiet: Bool
|
||||
let shell: String?
|
||||
|
||||
public init(
|
||||
arguments: [String],
|
||||
configuration: Configuration? = nil,
|
||||
inventoryFilePath: String? = nil,
|
||||
playbookDirectory: String? = nil,
|
||||
quiet: Bool,
|
||||
shell: String? = nil
|
||||
) {
|
||||
self.arguments = arguments
|
||||
self.configuration = configuration
|
||||
self.inventoryFilePath = inventoryFilePath
|
||||
self.playbookDirectory = playbookDirectory
|
||||
self.quiet = quiet
|
||||
self.shell = shell
|
||||
}
|
||||
|
||||
public enum Route {
|
||||
case build(BuildOption)
|
||||
case create(CreateOption)
|
||||
|
||||
public struct BuildOption: Equatable, Sendable {
|
||||
public let extraOptions: [String]?
|
||||
public let inventoryFilePath: String?
|
||||
public let projectDirectory: String?
|
||||
|
||||
public init(
|
||||
extraOptions: [String]?,
|
||||
inventoryFilePath: String?,
|
||||
projectDirectory: String
|
||||
) {
|
||||
self.extraOptions = extraOptions
|
||||
self.inventoryFilePath = inventoryFilePath
|
||||
self.projectDirectory = projectDirectory
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct CreateOption: Equatable, Sendable {
|
||||
public let extraOptions: [String]?
|
||||
public let inventoryFilePath: String?
|
||||
public let projectDirectory: String
|
||||
public let template: Configuration.Template
|
||||
public let useLocalTemplateDirectory: Bool
|
||||
|
||||
public init(
|
||||
extraOptions: [String]?,
|
||||
inventoryFilePath: String?,
|
||||
projectDirectory: String,
|
||||
template: Configuration.Template,
|
||||
useLocalTemplateDirectory: Bool
|
||||
) {
|
||||
self.extraOptions = extraOptions
|
||||
self.inventoryFilePath = inventoryFilePath
|
||||
self.projectDirectory = projectDirectory
|
||||
self.template = template
|
||||
self.useLocalTemplateDirectory = useLocalTemplateDirectory
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RunCommandOptions: Sendable, Equatable {
|
||||
public let arguments: [String]
|
||||
public let quiet: Bool
|
||||
public let shell: ShellCommand.Shell
|
||||
|
||||
public init(
|
||||
arguments: [String],
|
||||
quiet: Bool,
|
||||
shell: ShellCommand.Shell
|
||||
) {
|
||||
self.arguments = arguments
|
||||
self.quiet = quiet
|
||||
self.shell = shell
|
||||
}
|
||||
}
|
||||
|
||||
struct VaultOptions: Equatable, Sendable {
|
||||
let arguments: [String]
|
||||
let configuration: Configuration?
|
||||
let quiet: Bool
|
||||
let shell: String?
|
||||
let vaultFilePath: String?
|
||||
|
||||
public init(
|
||||
arguments: [String],
|
||||
configuration: Configuration? = nil,
|
||||
quiet: Bool,
|
||||
shell: String?,
|
||||
vaultFilePath: String? = nil
|
||||
) {
|
||||
self.arguments = arguments
|
||||
self.configuration = configuration
|
||||
self.quiet = quiet
|
||||
self.shell = shell
|
||||
self.vaultFilePath = vaultFilePath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CliClient: DependencyKey {
|
||||
|
||||
public static func live(
|
||||
env: [String: String]
|
||||
) -> Self {
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
return .init { options in
|
||||
@Dependency(\.asyncShellClient) var shellClient
|
||||
if !options.quiet {
|
||||
try await shellClient.foreground(.init(
|
||||
shell: options.shell,
|
||||
environment: ProcessInfo.processInfo.environment,
|
||||
in: nil,
|
||||
options.arguments
|
||||
))
|
||||
} else {
|
||||
try await shellClient.background(.init(
|
||||
shell: options.shell,
|
||||
environment: ProcessInfo.processInfo.environment,
|
||||
in: nil,
|
||||
options.arguments
|
||||
))
|
||||
}
|
||||
} generateJSON: { options, loggingOptions, encoder in
|
||||
let data = try await createJSONData(options, logging: loggingOptions, encoder: encoder)
|
||||
guard let string = String(data: data, encoding: .utf8) else {
|
||||
throw CliClientError.encodingError
|
||||
}
|
||||
return string
|
||||
}
|
||||
}
|
||||
|
||||
public static var liveValue: CliClient {
|
||||
.live(env: ProcessInfo.processInfo.environment)
|
||||
}
|
||||
|
||||
public static let testValue: CliClient = Self()
|
||||
|
||||
public static func capturing(_ client: CapturingClient) -> Self {
|
||||
.init { options in
|
||||
await client.set(options)
|
||||
} generateJSON: {
|
||||
try await Self().generateJSON($0, $1, $2)
|
||||
}
|
||||
}
|
||||
|
||||
public actor CapturingClient: Sendable {
|
||||
public private(set) var quiet: Bool?
|
||||
public private(set) var shell: ShellCommand.Shell?
|
||||
public private(set) var arguments: [String]?
|
||||
|
||||
public init() {}
|
||||
|
||||
public func set(
|
||||
_ options: RunCommandOptions
|
||||
) {
|
||||
quiet = options.quiet
|
||||
shell = options.shell
|
||||
arguments = options.arguments
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import Dependencies
|
||||
import Logging
|
||||
import ShellClient
|
||||
|
||||
// TODO: Remove.
|
||||
public extension CliClient {
|
||||
|
||||
@discardableResult
|
||||
func withLogger<T>(
|
||||
_ options: LoggingOptions,
|
||||
operation: @Sendable @escaping () async throws -> T
|
||||
) async rethrows -> T {
|
||||
try await Self.withLogger(options, operation: operation)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
static func withLogger<T>(
|
||||
_ options: LoggingOptions,
|
||||
operation: @Sendable @escaping () async throws -> T
|
||||
) async rethrows -> T {
|
||||
try await withDependencies {
|
||||
$0.logger = .init(label: "\(Constants.executableName)")
|
||||
$0.logger.logLevel = options.logLevel
|
||||
$0.logger[metadataKey: "command"] = "\(options.commandName.blue)"
|
||||
} operation: {
|
||||
try await operation()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ public extension DependencyValues {
|
||||
@DependencyClient
|
||||
public struct ConfigurationClient: Sendable {
|
||||
public var find: @Sendable () async throws -> File
|
||||
var generate: @Sendable (File, Bool) async throws -> Void
|
||||
public var generate: @Sendable (GenerateOptions) async throws -> String
|
||||
public var load: @Sendable (File?) async throws -> Configuration
|
||||
var write: @Sendable (File, Configuration, Bool) async throws -> Void
|
||||
|
||||
@@ -24,13 +24,6 @@ public struct ConfigurationClient: Sendable {
|
||||
return try await load(file)
|
||||
}
|
||||
|
||||
public func generate(
|
||||
at file: File,
|
||||
force: Bool = false
|
||||
) async throws {
|
||||
try await generate(file, force)
|
||||
}
|
||||
|
||||
public func write(
|
||||
_ configuration: Configuration,
|
||||
to file: File,
|
||||
@@ -38,6 +31,28 @@ public struct ConfigurationClient: Sendable {
|
||||
) async throws {
|
||||
try await write(file, configuration, force)
|
||||
}
|
||||
|
||||
public struct GenerateOptions: Equatable, Sendable {
|
||||
|
||||
public let force: Bool
|
||||
public let json: Bool
|
||||
public let path: Path?
|
||||
|
||||
public init(
|
||||
force: Bool = false,
|
||||
json: Bool = false,
|
||||
path: Path? = nil
|
||||
) {
|
||||
self.force = force
|
||||
self.json = json
|
||||
self.path = path
|
||||
}
|
||||
|
||||
public enum Path: Equatable, Sendable {
|
||||
case file(File)
|
||||
case directory(String)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ConfigurationClient: DependencyKey {
|
||||
@@ -47,8 +62,8 @@ extension ConfigurationClient: DependencyKey {
|
||||
let liveClient = LiveConfigurationClient(environment: environment)
|
||||
return .init {
|
||||
try await liveClient.find()
|
||||
} generate: { file, force in
|
||||
try await liveClient.generate(at: file, force: force)
|
||||
} generate: {
|
||||
try await liveClient.generate($0)
|
||||
} load: { file in
|
||||
try await liveClient.load(file: file)
|
||||
} write: { file, configuration, force in
|
||||
@@ -126,10 +141,24 @@ struct LiveConfigurationClient {
|
||||
throw ConfigurationError.configurationNotFound
|
||||
}
|
||||
|
||||
func generate(at file: File, force: Bool) async throws {
|
||||
func generate(_ options: ConfigurationClient.GenerateOptions) async throws -> String {
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
logger.debug("Begin generating configuration: \(file.path), force: \(force)")
|
||||
let file: File
|
||||
|
||||
if let path = options.path {
|
||||
switch path {
|
||||
case let .file(requestedFile):
|
||||
file = requestedFile
|
||||
case let .directory(directory):
|
||||
file = .init("\(directory)/\(HPAKey.defaultFileNameWithoutExtension).\(options.json ? "json" : "toml")")!
|
||||
}
|
||||
} else {
|
||||
let configDir = "\(environment.xdgConfigHome)/\(HPAKey.configDirName)"
|
||||
file = .init("\(configDir)/\(HPAKey.defaultFileName)")!
|
||||
}
|
||||
|
||||
logger.debug("Begin generating configuration: \(file.path), force: \(options.force)")
|
||||
|
||||
let expandedPath = file.path.replacingOccurrences(
|
||||
of: "~",
|
||||
@@ -138,7 +167,7 @@ struct LiveConfigurationClient {
|
||||
|
||||
let fileUrl = URL(filePath: expandedPath)
|
||||
|
||||
if !force {
|
||||
if !options.force {
|
||||
guard !fileManager.fileExists(fileUrl) else {
|
||||
throw ConfigurationError.fileExists(path: file.path)
|
||||
}
|
||||
@@ -166,8 +195,10 @@ struct LiveConfigurationClient {
|
||||
} else {
|
||||
// Json does not allow comments, so we write the mock configuration
|
||||
// to the file path.
|
||||
try await write(.mock, to: File(fileUrl)!, force: force)
|
||||
try await write(.mock, to: File(fileUrl)!, force: options.force)
|
||||
}
|
||||
|
||||
return fileUrl.cleanFilePath
|
||||
}
|
||||
|
||||
func load(file: File?) async throws -> Configuration {
|
||||
|
||||
@@ -11,6 +11,7 @@ public enum HPAKey {
|
||||
public static let resourceFileName = "hpa"
|
||||
public static let resourceFileExtension = "toml"
|
||||
public static let defaultFileName = "config.toml"
|
||||
public static let defaultFileNameWithoutExtension = "config.toml"
|
||||
}
|
||||
|
||||
extension [String: String] {
|
||||
|
||||
7
Sources/PandocClient/Constants.swift
Normal file
7
Sources/PandocClient/Constants.swift
Normal file
@@ -0,0 +1,7 @@
|
||||
extension PandocClient {
|
||||
enum Constants {
|
||||
static let pandocCommand = "pandoc"
|
||||
static let defaultOutputFileName = "Report"
|
||||
static let defaultPdfEngine = "xelatex"
|
||||
}
|
||||
}
|
||||
241
Sources/PandocClient/PandocClient+Run.swift
Normal file
241
Sources/PandocClient/PandocClient+Run.swift
Normal file
@@ -0,0 +1,241 @@
|
||||
import CommandClient
|
||||
import ConfigurationClient
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import PlaybookClient
|
||||
|
||||
extension PandocClient.RunOptions {
|
||||
|
||||
func run(_ fileType: PandocClient.FileType) async throws -> String {
|
||||
@Dependency(\.commandClient) var commandClient
|
||||
@Dependency(\.logger) var logger
|
||||
@Dependency(\.playbookClient) var playbookClient
|
||||
|
||||
return try await commandClient.run(logging: loggingOptions, quiet: quiet, shell: shell) {
|
||||
let ensuredOptions = try await self.ensuredOptions(fileType)
|
||||
|
||||
let projectDirectory = self.projectDirectory ?? ProcessInfo.processInfo.environment["PWD"]
|
||||
|
||||
guard let projectDirectory else {
|
||||
throw ProjectDirectoryNotSpecified()
|
||||
}
|
||||
|
||||
if shouldBuild {
|
||||
logger.debug("Building project...")
|
||||
try await playbookClient.run.buildProject(.init(
|
||||
projectDirectory: projectDirectory,
|
||||
shared: .init(
|
||||
extraOptions: nil,
|
||||
inventoryFilePath: nil,
|
||||
loggingOptions: loggingOptions,
|
||||
quiet: quiet,
|
||||
shell: shell
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
let outputDirectory = self.outputDirectory ?? projectDirectory
|
||||
let outputPath = "\(outputDirectory)/\(ensuredOptions.ensuredExtensionFileName)"
|
||||
|
||||
let arguments = ensuredOptions.makeArguments(
|
||||
outputPath: outputPath,
|
||||
projectDirectory: projectDirectory
|
||||
)
|
||||
|
||||
logger.debug("Pandoc arguments: \(arguments)")
|
||||
|
||||
return (arguments, outputPath)
|
||||
}
|
||||
}
|
||||
|
||||
func ensuredOptions(
|
||||
_ fileType: PandocClient.FileType
|
||||
) async throws -> EnsuredPandocOptions {
|
||||
@Dependency(\.configurationClient) var configurationClient
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
let configuration = try await configurationClient.findAndLoad()
|
||||
logger.debug("Configuration: \(configuration)")
|
||||
|
||||
return try await ensurePandocOptions(
|
||||
configuration: configuration,
|
||||
fileType: fileType,
|
||||
options: self
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@_spi(Internal)
|
||||
public struct EnsuredPandocOptions: Equatable, Sendable {
|
||||
public let buildDirectory: String
|
||||
public let extraOptions: [String]?
|
||||
public let files: [String]
|
||||
public let includeInHeader: [String]
|
||||
public let outputFileName: String
|
||||
public let outputFileType: PandocClient.FileType
|
||||
public let pdfEngine: String?
|
||||
|
||||
public var ensuredExtensionFileName: String {
|
||||
let extensionString: String
|
||||
|
||||
switch outputFileType {
|
||||
case .html:
|
||||
extensionString = ".html"
|
||||
case .latex:
|
||||
extensionString = ".tex"
|
||||
case .pdf:
|
||||
extensionString = ".pdf"
|
||||
}
|
||||
|
||||
if !outputFileName.hasSuffix(extensionString) {
|
||||
return outputFileName + extensionString
|
||||
}
|
||||
return outputFileName
|
||||
}
|
||||
|
||||
func makeArguments(
|
||||
outputPath: String,
|
||||
projectDirectory: String
|
||||
) -> [String] {
|
||||
var arguments = [PandocClient.Constants.pandocCommand]
|
||||
|
||||
arguments += includeInHeader.map {
|
||||
"--include-in-header=\(projectDirectory)/\(buildDirectory)/\($0)"
|
||||
}
|
||||
|
||||
if let pdfEngine {
|
||||
arguments.append("--pdf-engine=\(pdfEngine)")
|
||||
}
|
||||
|
||||
arguments.append("--output=\(outputPath)")
|
||||
|
||||
if let extraOptions {
|
||||
arguments.append(contentsOf: extraOptions)
|
||||
}
|
||||
|
||||
arguments += files.map {
|
||||
"\(projectDirectory)/\(buildDirectory)/\($0)"
|
||||
}
|
||||
|
||||
return arguments
|
||||
}
|
||||
}
|
||||
|
||||
@_spi(Internal)
|
||||
public func ensurePandocOptions(
|
||||
configuration: Configuration,
|
||||
fileType: PandocClient.FileType,
|
||||
options: PandocClient.RunOptions
|
||||
) async throws -> EnsuredPandocOptions {
|
||||
let defaults = Configuration.Generate.default
|
||||
|
||||
return .init(
|
||||
buildDirectory: options.parseBuildDirectory(configuration.generate, defaults),
|
||||
extraOptions: options.extraOptions,
|
||||
files: options.parseFiles(configuration.generate, defaults),
|
||||
includeInHeader: options.parseIncludeInHeader(configuration.generate, defaults),
|
||||
outputFileName: options.parseOutputFileName(configuration.generate, defaults),
|
||||
outputFileType: fileType,
|
||||
pdfEngine: fileType.parsePdfEngine(configuration.generate, defaults)
|
||||
)
|
||||
}
|
||||
|
||||
@_spi(Internal)
|
||||
public extension PandocClient.FileType {
|
||||
func parsePdfEngine(
|
||||
_ configuration: Configuration.Generate?,
|
||||
_ defaults: Configuration.Generate
|
||||
) -> String? {
|
||||
switch self {
|
||||
case .html, .latex:
|
||||
return nil
|
||||
case let .pdf(engine: engine):
|
||||
if let engine {
|
||||
return engine
|
||||
} else if let engine = configuration?.pdfEngine {
|
||||
return engine
|
||||
} else if let engine = defaults.pdfEngine {
|
||||
return engine
|
||||
} else {
|
||||
return PandocClient.Constants.defaultPdfEngine
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@_spi(Internal)
|
||||
public extension PandocClient.RunOptions {
|
||||
func parseFiles(
|
||||
_ configuration: Configuration.Generate?,
|
||||
_ defaults: Configuration.Generate
|
||||
) -> [String] {
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
if let files = files {
|
||||
return files
|
||||
} else if let files = configuration?.files {
|
||||
return files
|
||||
} else if let files = defaults.files {
|
||||
return files
|
||||
} else {
|
||||
logger.warning("Files not specified, this could lead to errors.")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
func parseIncludeInHeader(
|
||||
_ configuration: Configuration.Generate?,
|
||||
_ defaults: Configuration.Generate
|
||||
) -> [String] {
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
if let files = includeInHeader {
|
||||
return files
|
||||
} else if let files = configuration?.includeInHeader {
|
||||
return files
|
||||
} else if let files = defaults.includeInHeader {
|
||||
return files
|
||||
} else {
|
||||
logger.warning("Include in header files not specified, this could lead to errors.")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
func parseOutputFileName(
|
||||
_ configuration: Configuration.Generate?,
|
||||
_ defaults: Configuration.Generate
|
||||
) -> String {
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
if let output = outputFileName {
|
||||
return output
|
||||
} else if let output = configuration?.outputFileName {
|
||||
return output
|
||||
} else if let output = defaults.outputFileName {
|
||||
return output
|
||||
} else {
|
||||
logger.warning("Output file name not specified, this could lead to errors.")
|
||||
return PandocClient.Constants.defaultOutputFileName
|
||||
}
|
||||
}
|
||||
|
||||
func parseBuildDirectory(
|
||||
_ configuration: Configuration.Generate?,
|
||||
_ defaults: Configuration.Generate
|
||||
) -> String {
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
if let output = buildDirectory {
|
||||
return output
|
||||
} else if let output = configuration?.buildDirectory {
|
||||
return output
|
||||
} else if let output = defaults.buildDirectory {
|
||||
return output
|
||||
} else {
|
||||
logger.warning("Output file name not specified, this could lead to errors.")
|
||||
return ".build"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ProjectDirectoryNotSpecified: Error {}
|
||||
95
Sources/PandocClient/PandocClient.swift
Normal file
95
Sources/PandocClient/PandocClient.swift
Normal file
@@ -0,0 +1,95 @@
|
||||
import CommandClient
|
||||
import ConfigurationClient
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
|
||||
public extension DependencyValues {
|
||||
var pandocClient: PandocClient {
|
||||
get { self[PandocClient.self] }
|
||||
set { self[PandocClient.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
@DependencyClient
|
||||
public struct PandocClient: Sendable {
|
||||
|
||||
public var run: Run
|
||||
|
||||
@DependencyClient
|
||||
public struct Run: Sendable {
|
||||
public var generateLatex: @Sendable (RunOptions) async throws -> String
|
||||
public var generateHtml: @Sendable (RunOptions) async throws -> String
|
||||
public var generatePdf: @Sendable (RunOptions, String?) async throws -> String
|
||||
|
||||
public func generatePdf(
|
||||
_ options: RunOptions,
|
||||
pdfEngine: String? = nil
|
||||
) async throws -> String {
|
||||
try await generatePdf(options, pdfEngine)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct RunOptions: Equatable, Sendable {
|
||||
|
||||
let buildDirectory: String?
|
||||
let extraOptions: [String]?
|
||||
let files: [String]?
|
||||
let loggingOptions: LoggingOptions
|
||||
let includeInHeader: [String]?
|
||||
let outputDirectory: String?
|
||||
let outputFileName: String?
|
||||
let projectDirectory: String?
|
||||
let quiet: Bool
|
||||
let shell: String?
|
||||
let shouldBuild: Bool
|
||||
|
||||
public init(
|
||||
buildDirectory: String? = nil,
|
||||
extraOptions: [String]? = nil,
|
||||
files: [String]? = nil,
|
||||
loggingOptions: LoggingOptions,
|
||||
includeInHeader: [String]? = nil,
|
||||
outputDirectory: String? = nil,
|
||||
projectDirectory: String? = nil,
|
||||
outputFileName: String? = nil,
|
||||
quiet: Bool = false,
|
||||
shell: String? = nil,
|
||||
shouldBuild: Bool = true
|
||||
) {
|
||||
self.buildDirectory = buildDirectory
|
||||
self.extraOptions = extraOptions
|
||||
self.files = files
|
||||
self.loggingOptions = loggingOptions
|
||||
self.includeInHeader = includeInHeader
|
||||
self.outputDirectory = outputDirectory
|
||||
self.outputFileName = outputFileName
|
||||
self.projectDirectory = projectDirectory
|
||||
self.quiet = quiet
|
||||
self.shell = shell
|
||||
self.shouldBuild = shouldBuild
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@_spi(Internal)
|
||||
public enum FileType: Equatable, Sendable {
|
||||
case html
|
||||
case latex
|
||||
case pdf(engine: String?)
|
||||
}
|
||||
}
|
||||
|
||||
extension PandocClient: DependencyKey {
|
||||
public static let testValue: PandocClient = Self(run: Run())
|
||||
|
||||
public static var liveValue: PandocClient {
|
||||
.init(
|
||||
run: Run(
|
||||
generateLatex: { try await $0.run(.latex) },
|
||||
generateHtml: { try await $0.run(.html) },
|
||||
generatePdf: { try await $0.run(.pdf(engine: $1)) }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import PlaybookClient
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import ConfigurationClient
|
||||
import Dependencies
|
||||
import Foundation
|
||||
@@ -71,14 +70,3 @@ struct CreateCommand: AsyncParsableCommand {
|
||||
print(projectDir)
|
||||
}
|
||||
}
|
||||
|
||||
private extension CreateCommand {
|
||||
var generateJsonOptions: CliClient.GenerateJsonOptions {
|
||||
.init(
|
||||
templateDirectory: templateDir,
|
||||
templateRepo: repo,
|
||||
version: branch,
|
||||
useLocalTemplateDirectory: localTemplateDir
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import Dependencies
|
||||
import PandocClient
|
||||
|
||||
// TODO: Need to add a step to build prior to generating file.
|
||||
struct GenerateHtmlCommand: AsyncParsableCommand {
|
||||
@@ -13,12 +13,12 @@ struct GenerateHtmlCommand: AsyncParsableCommand {
|
||||
@OptionGroup var globals: GenerateOptions
|
||||
|
||||
mutating func run() async throws {
|
||||
@Dependency(\.cliClient) var cliClient
|
||||
@Dependency(\.pandocClient) var pandocClient
|
||||
|
||||
try await cliClient.runPandocCommand(
|
||||
globals.pandocOptions(.html),
|
||||
logging: globals.loggingOptions(commandName: Self.commandName)
|
||||
let output = try await pandocClient.run.generateHtml(
|
||||
globals.pandocRunOptions(commandName: Self.commandName)
|
||||
)
|
||||
print(output)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import Dependencies
|
||||
|
||||
// TODO: Need to add a step to build prior to generating file.
|
||||
@@ -13,11 +12,11 @@ struct GenerateLatexCommand: AsyncParsableCommand {
|
||||
@OptionGroup var globals: GenerateOptions
|
||||
|
||||
mutating func run() async throws {
|
||||
@Dependency(\.cliClient) var cliClient
|
||||
@Dependency(\.pandocClient) var pandocClient
|
||||
|
||||
try await cliClient.runPandocCommand(
|
||||
globals.pandocOptions(.latex),
|
||||
logging: globals.loggingOptions(commandName: Self.commandName)
|
||||
let output = try await pandocClient.run.generateLatex(
|
||||
globals.pandocRunOptions(commandName: Self.commandName)
|
||||
)
|
||||
print(output)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import CommandClient
|
||||
import PandocClient
|
||||
|
||||
@dynamicMemberLookup
|
||||
struct GenerateOptions: ParsableArguments {
|
||||
|
||||
@OptionGroup var basic: BasicGlobalOptions
|
||||
|
||||
@Option(
|
||||
name: .shortAndLong,
|
||||
help: "Custom build directory path.",
|
||||
completion: .directory
|
||||
)
|
||||
var buildDirectory: String?
|
||||
|
||||
@Option(
|
||||
name: [.short, .customLong("file")],
|
||||
help: "Files used to generate the output, can be specified multiple times.",
|
||||
@@ -65,20 +73,20 @@ struct GenerateOptions: ParsableArguments {
|
||||
|
||||
extension GenerateOptions {
|
||||
|
||||
func loggingOptions(commandName: String) -> CliClient.LoggingOptions {
|
||||
func loggingOptions(commandName: String) -> LoggingOptions {
|
||||
basic.loggingOptions(commandName: commandName)
|
||||
}
|
||||
|
||||
func pandocOptions(
|
||||
_ fileType: CliClient.PandocOptions.FileType
|
||||
) -> CliClient.PandocOptions {
|
||||
func pandocRunOptions(commandName: String) -> PandocClient.RunOptions {
|
||||
.init(
|
||||
buildDirectory: buildDirectory,
|
||||
extraOptions: extraOptions.count > 0 ? extraOptions : nil,
|
||||
files: files.count > 0 ? files : nil,
|
||||
loggingOptions: .init(commandName: commandName, logLevel: .init(globals: basic, quietOnlyPlaybook: false)),
|
||||
includeInHeader: includeInHeader.count > 0 ? includeInHeader : nil,
|
||||
outputDirectory: outputDirectory,
|
||||
outputFileName: outputFileName,
|
||||
outputFileType: fileType,
|
||||
projectDirectory: projectDirectory,
|
||||
outputFileName: outputFileName,
|
||||
quiet: basic.quiet,
|
||||
shell: basic.shell,
|
||||
shouldBuild: !noBuild
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import Dependencies
|
||||
import PandocClient
|
||||
|
||||
// TODO: Need to add a step to build prior to generating file.
|
||||
|
||||
@@ -20,11 +20,11 @@ struct GeneratePdfCommand: AsyncParsableCommand {
|
||||
@OptionGroup var globals: GenerateOptions
|
||||
|
||||
mutating func run() async throws {
|
||||
@Dependency(\.cliClient) var cliClient
|
||||
@Dependency(\.pandocClient) var pandocClient
|
||||
|
||||
let output = try await cliClient.runPandocCommand(
|
||||
globals.pandocOptions(.pdf(engine: pdfEngine)),
|
||||
logging: globals.loggingOptions(commandName: Self.commandName)
|
||||
let output = try await pandocClient.run.generatePdf(
|
||||
globals.pandocRunOptions(commandName: Self.commandName),
|
||||
pdfEngine: pdfEngine
|
||||
)
|
||||
|
||||
print(output)
|
||||
|
||||
@@ -3,6 +3,9 @@ import Rainbow
|
||||
// Constant string values.
|
||||
enum Constants {
|
||||
static let appName = "hpa"
|
||||
static let brewPackages = [
|
||||
"ansible", "imagemagick", "pandoc", "texLive"
|
||||
]
|
||||
static let playbookFileName = "main.yml"
|
||||
static let inventoryFileName = "inventory.ini"
|
||||
static let importantExtraArgsNote = """
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import CommandClient
|
||||
import ConfigurationClient
|
||||
import PlaybookClient
|
||||
|
||||
@@ -59,10 +59,8 @@ struct GlobalOptions: ParsableArguments {
|
||||
|
||||
}
|
||||
|
||||
// TODO: Update these to use CommandClient.LoggingOptions
|
||||
|
||||
extension GlobalOptions {
|
||||
func loggingOptions(commandName: String) -> CliClient.LoggingOptions {
|
||||
func loggingOptions(commandName: String) -> LoggingOptions {
|
||||
.init(
|
||||
commandName: commandName,
|
||||
logLevel: .init(globals: basic, quietOnlyPlaybook: quietOnlyPlaybook)
|
||||
@@ -71,7 +69,7 @@ extension GlobalOptions {
|
||||
}
|
||||
|
||||
extension BasicGlobalOptions {
|
||||
func loggingOptions(commandName: String) -> CliClient.LoggingOptions {
|
||||
func loggingOptions(commandName: String) -> LoggingOptions {
|
||||
.init(
|
||||
commandName: commandName,
|
||||
logLevel: .init(globals: self, quietOnlyPlaybook: false)
|
||||
@@ -79,24 +77,6 @@ extension BasicGlobalOptions {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
extension GlobalOptions {
|
||||
|
||||
func playbookOptions(
|
||||
arguments: [String],
|
||||
configuration: Configuration?
|
||||
) -> CliClient.PlaybookOptions {
|
||||
.init(
|
||||
arguments: arguments,
|
||||
configuration: configuration,
|
||||
inventoryFilePath: inventoryPath,
|
||||
playbookDirectory: playbookDirectory,
|
||||
quiet: quietOnlyPlaybook ? true : basic.quiet,
|
||||
shell: basic.shell
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension GlobalOptions {
|
||||
func sharedPlaybookRunOptions(
|
||||
commandName: String,
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import CliClient
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
// 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
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import CliDoc
|
||||
import CommandClient
|
||||
import ConfigurationClient
|
||||
import Dependencies
|
||||
|
||||
@@ -16,14 +16,14 @@ struct GenerateConfigurationCommand: AsyncParsableCommand {
|
||||
Note {
|
||||
"""
|
||||
If a directory is not supplied then a configuration file will be created
|
||||
at \("'~/.config/hpa-playbook/config'".yellow).
|
||||
at \("'~/.config/hpa/config.toml'".yellow).
|
||||
"""
|
||||
}
|
||||
VStack {
|
||||
"EXAMPLE:".yellow.bold
|
||||
"Create a directory and generate the configuration".green
|
||||
ShellCommand("mkdir -p ~/.config/hpa-playbook")
|
||||
ShellCommand("hpa generate-config --path ~/.config/hpa-playbook")
|
||||
ShellCommand("mkdir -p ~/.config/hpa")
|
||||
ShellCommand("hpa generate-config --path ~/.config/hpa")
|
||||
}
|
||||
}
|
||||
.separator(.newLine(count: 2))
|
||||
@@ -39,7 +39,7 @@ struct GenerateConfigurationCommand: AsyncParsableCommand {
|
||||
|
||||
@Flag(
|
||||
name: .shortAndLong,
|
||||
help: "Generate a json file, instead of default env style"
|
||||
help: "Generate a json file, instead of the default toml style"
|
||||
)
|
||||
var json: Bool = false
|
||||
|
||||
@@ -56,26 +56,18 @@ struct GenerateConfigurationCommand: AsyncParsableCommand {
|
||||
}
|
||||
|
||||
private func _run() async throws {
|
||||
@Dependency(\.cliClient) var cliClient
|
||||
@Dependency(\.configurationClient) var configurationClient
|
||||
|
||||
try await cliClient.withLogger(globals.loggingOptions(commandName: Self.commandName)) {
|
||||
try await globals.loggingOptions(commandName: Self.commandName).withLogger {
|
||||
@Dependency(\.logger) var logger
|
||||
|
||||
let actualPath: File
|
||||
let output = try await configurationClient.generate(.init(
|
||||
force: force,
|
||||
json: json,
|
||||
path: path != nil ? .directory(path!) : nil
|
||||
))
|
||||
|
||||
if let path, let file = File("\(path)/config.\(json ? "json" : "toml")") {
|
||||
actualPath = file
|
||||
} else {
|
||||
actualPath = .default
|
||||
}
|
||||
|
||||
logger.debug("Generating config at path: \(actualPath.path)")
|
||||
|
||||
try await configurationClient.generate(
|
||||
at: actualPath,
|
||||
force: force
|
||||
)
|
||||
print(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import Dependencies
|
||||
import PlaybookClient
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import CliDoc
|
||||
import CommandClient
|
||||
import Dependencies
|
||||
|
||||
struct InstallDependenciesCommand: AsyncParsableCommand {
|
||||
@@ -33,11 +33,20 @@ struct InstallDependenciesCommand: AsyncParsableCommand {
|
||||
var extraOptions: [String] = []
|
||||
|
||||
mutating func run() async throws {
|
||||
@Dependency(\.cliClient) var cliClient
|
||||
try await cliClient.installDependencies(
|
||||
@Dependency(\.commandClient) var commandClient
|
||||
@Dependency(\.playbookClient) var playbookClient
|
||||
|
||||
let arguments = [
|
||||
"brew", "install"
|
||||
] + Constants.brewPackages
|
||||
+ extraOptions
|
||||
|
||||
try await commandClient.run(
|
||||
quiet: globals.quiet,
|
||||
shell: globals.shell,
|
||||
extraArgs: extraOptions
|
||||
arguments
|
||||
)
|
||||
|
||||
try await playbookClient.repository.install()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import Dependencies
|
||||
import VaultClient
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import Dependencies
|
||||
import VaultClient
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import ArgumentParser
|
||||
import CliClient
|
||||
import CommandClient
|
||||
import VaultClient
|
||||
|
||||
// Holds the common options for vault commands, as they all share the
|
||||
@@ -32,12 +32,6 @@ struct VaultOptions: ParsableArguments {
|
||||
|
||||
}
|
||||
|
||||
extension VaultOptions {
|
||||
func loggingOptions(commandName: String) -> CliClient.LoggingOptions {
|
||||
globals.loggingOptions(commandName: commandName)
|
||||
}
|
||||
}
|
||||
|
||||
extension VaultOptions {
|
||||
|
||||
func runOptions(
|
||||
|
||||
@@ -1,333 +0,0 @@
|
||||
@_spi(Internal) import CliClient
|
||||
import ConfigurationClient
|
||||
import Dependencies
|
||||
import FileClient
|
||||
import Foundation
|
||||
import PlaybookClient
|
||||
import ShellClient
|
||||
import Testing
|
||||
import TestSupport
|
||||
|
||||
@Suite("CliClientTests")
|
||||
struct CliClientTests: TestCase {
|
||||
|
||||
static let loggingOptions: CliClient.LoggingOptions = {
|
||||
let levelString = ProcessInfo.processInfo.environment["LOGGING_LEVEL"] ?? "debug"
|
||||
let logLevel = Logger.Level(rawValue: levelString) ?? .debug
|
||||
return .init(commandName: "CliClientTests", logLevel: logLevel)
|
||||
}()
|
||||
|
||||
@Test
|
||||
func capturingClient() async throws {
|
||||
let captured = CliClient.CapturingClient()
|
||||
let client = CliClient.capturing(captured)
|
||||
try await client.runCommand(quiet: false, shell: .zsh(), "foo", "bar")
|
||||
|
||||
let quiet = await captured.quiet!
|
||||
#expect(quiet == false)
|
||||
|
||||
let shell = await captured.shell
|
||||
#expect(shell == .zsh())
|
||||
|
||||
let arguments = await captured.arguments!
|
||||
#expect(arguments == ["foo", "bar"])
|
||||
}
|
||||
|
||||
@Test(arguments: ["encrypt", "decrypt"])
|
||||
func runVault(argument: String) async throws {
|
||||
let captured = CliClient.CapturingClient()
|
||||
try await withMockConfiguration(captured, key: "runVault") {
|
||||
$0.fileClient.findVaultFileInCurrentDirectory = { URL(filePath: "vault.yml") }
|
||||
} operation: {
|
||||
@Dependency(\.cliClient) var cliClient
|
||||
let configuration = Configuration.mock
|
||||
|
||||
try await cliClient.runVaultCommand(
|
||||
.init(arguments: [argument], quiet: false, shell: nil),
|
||||
logging: Self.loggingOptions
|
||||
)
|
||||
|
||||
let shell = await captured.shell
|
||||
#expect(shell == .zsh(useDashC: true))
|
||||
|
||||
let vaultPath = URL(filePath: #file)
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.appending(path: "vault.yml")
|
||||
|
||||
var encryptArgs: [String] = []
|
||||
if argument == "encrypt", let id = configuration.vault.encryptId {
|
||||
encryptArgs = ["--encrypt-vault-id", id]
|
||||
}
|
||||
|
||||
let expectedArguments = [
|
||||
"ansible-vault", argument
|
||||
] + configuration.vault.args!
|
||||
+ encryptArgs
|
||||
+ [vaultPath.cleanFilePath]
|
||||
|
||||
let arguments = await captured.arguments
|
||||
#expect(arguments == expectedArguments)
|
||||
}
|
||||
}
|
||||
|
||||
@Test(arguments: [
|
||||
Configuration(
|
||||
args: ["--tags", "debug"],
|
||||
useVaultArgs: true,
|
||||
playbook: .init(directory: "playbook", inventory: nil),
|
||||
vault: .mock
|
||||
)
|
||||
])
|
||||
func runPlaybook(configuration: Configuration) async throws {
|
||||
let captured = CliClient.CapturingClient()
|
||||
try await withMockConfiguration(captured, configuration: configuration, key: "runPlaybook") {
|
||||
@Dependency(\.cliClient) var cliClient
|
||||
|
||||
try await cliClient.runPlaybookCommand(
|
||||
.init(
|
||||
arguments: [],
|
||||
quiet: false,
|
||||
shell: nil
|
||||
),
|
||||
logging: Self.loggingOptions
|
||||
)
|
||||
|
||||
let expectedArguments = [
|
||||
"ansible-playbook", "playbook/main.yml",
|
||||
"--inventory", "playbook/inventory.ini",
|
||||
"--tags", "debug",
|
||||
"--vault-id=myId@$SCRIPTS/vault-gopass-client"
|
||||
]
|
||||
|
||||
let arguments = await captured.arguments
|
||||
#expect(arguments == expectedArguments)
|
||||
}
|
||||
}
|
||||
|
||||
// @Test
|
||||
// func ensuredPlaybookDirectory() throws {
|
||||
// let configuration = Configuration.mock
|
||||
// let playbookDir = try configuration.ensuredPlaybookDirectory("playbook")
|
||||
// #expect(playbookDir == "playbook")
|
||||
//
|
||||
// do {
|
||||
// _ = try configuration.ensuredPlaybookDirectory(nil)
|
||||
// #expect(Bool(false))
|
||||
// } catch {
|
||||
// #expect(Bool(true))
|
||||
// }
|
||||
// }
|
||||
|
||||
@Test
|
||||
func shellOrDefault() {
|
||||
var shell: String? = "/bin/bash"
|
||||
#expect(shell.orDefault == .custom(path: "/bin/bash", useDashC: true))
|
||||
|
||||
shell = nil
|
||||
#expect(shell.orDefault == .zsh(useDashC: true))
|
||||
}
|
||||
|
||||
@Test
|
||||
func testEnsuredInventoryPath() {
|
||||
let configuration = Configuration(playbook: .init(inventory: "inventory.ini"))
|
||||
let playbookDir = "playbook"
|
||||
let inventoryPath = "inventory.ini"
|
||||
|
||||
var output = ensuredInventoryPath(
|
||||
inventoryPath,
|
||||
configuration: configuration,
|
||||
playbookDirectory: playbookDir
|
||||
)
|
||||
|
||||
#expect(output == "inventory.ini")
|
||||
|
||||
output = ensuredInventoryPath(
|
||||
nil,
|
||||
configuration: configuration,
|
||||
playbookDirectory: playbookDir
|
||||
)
|
||||
#expect(output == "inventory.ini")
|
||||
}
|
||||
|
||||
@Test
|
||||
func vaultFilePath() async throws {
|
||||
var fileClient = FileClient.testValue
|
||||
fileClient.findVaultFileInCurrentDirectory = { URL(string: "vault.yml") }
|
||||
|
||||
var output = try await fileClient.ensuredVaultFilePath("vault.yml")
|
||||
#expect(output == "vault.yml")
|
||||
|
||||
output = try await fileClient.ensuredVaultFilePath(nil)
|
||||
|
||||
fileClient.findVaultFileInCurrentDirectory = { nil }
|
||||
do {
|
||||
_ = try await fileClient.ensuredVaultFilePath(nil)
|
||||
#expect(Bool(false))
|
||||
} catch {
|
||||
#expect(Bool(true))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(
|
||||
arguments: [
|
||||
GenerateJsonTestOption(
|
||||
options: .init(
|
||||
templateDirectory: nil,
|
||||
templateRepo: nil,
|
||||
version: nil,
|
||||
useLocalTemplateDirectory: true
|
||||
),
|
||||
configuration: nil,
|
||||
expectation: .failing
|
||||
),
|
||||
GenerateJsonTestOption(
|
||||
options: .init(
|
||||
templateDirectory: nil,
|
||||
templateRepo: nil,
|
||||
version: nil,
|
||||
useLocalTemplateDirectory: false
|
||||
),
|
||||
configuration: nil,
|
||||
expectation: .failing
|
||||
),
|
||||
GenerateJsonTestOption(
|
||||
options: .init(
|
||||
templateDirectory: "template",
|
||||
templateRepo: nil,
|
||||
version: nil,
|
||||
useLocalTemplateDirectory: true
|
||||
),
|
||||
configuration: nil,
|
||||
expectation: .success("""
|
||||
{
|
||||
"template" : {
|
||||
"path" : "template"
|
||||
}
|
||||
}
|
||||
""")
|
||||
),
|
||||
GenerateJsonTestOption(
|
||||
options: .init(
|
||||
templateDirectory: nil,
|
||||
templateRepo: nil,
|
||||
version: nil,
|
||||
useLocalTemplateDirectory: false
|
||||
),
|
||||
configuration: .init(template: .init(directory: "template")),
|
||||
expectation: .success("""
|
||||
{
|
||||
"template" : {
|
||||
"path" : "template"
|
||||
}
|
||||
}
|
||||
""")
|
||||
),
|
||||
GenerateJsonTestOption(
|
||||
options: .init(
|
||||
templateDirectory: nil,
|
||||
templateRepo: "https://git.example.com/template.git",
|
||||
version: nil,
|
||||
useLocalTemplateDirectory: false
|
||||
),
|
||||
configuration: nil,
|
||||
expectation: .success("""
|
||||
{
|
||||
"template" : {
|
||||
"repo" : {
|
||||
"url" : "https://git.example.com/template.git",
|
||||
"version" : "main"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
),
|
||||
GenerateJsonTestOption(
|
||||
options: .init(
|
||||
templateDirectory: nil,
|
||||
templateRepo: nil,
|
||||
version: nil,
|
||||
useLocalTemplateDirectory: false
|
||||
),
|
||||
configuration: .init(template: .init(url: "https://git.example.com/template.git", version: "v0.1.0")),
|
||||
expectation: .success("""
|
||||
{
|
||||
"template" : {
|
||||
"repo" : {
|
||||
"url" : "https://git.example.com/template.git",
|
||||
"version" : "v0.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
)
|
||||
]
|
||||
)
|
||||
func generateJson(input: GenerateJsonTestOption) async {
|
||||
await withTestLogger(key: "generateJson") {
|
||||
$0.configurationClient = .mock(input.configuration ?? .init())
|
||||
$0.cliClient = .liveValue
|
||||
} operation: {
|
||||
@Dependency(\.cliClient) var cliClient
|
||||
|
||||
let json = try? await cliClient.generateJSON(
|
||||
input.options,
|
||||
logging: Self.loggingOptions,
|
||||
encoder: jsonEncoder
|
||||
)
|
||||
|
||||
switch input.expectation {
|
||||
case let .success(expected):
|
||||
#expect(json == expected)
|
||||
case .failing:
|
||||
#expect(json == nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func withMockConfiguration(
|
||||
_ capturing: CliClient.CapturingClient,
|
||||
configuration: Configuration = .mock,
|
||||
key: String,
|
||||
logLevel: Logger.Level = .trace,
|
||||
depednencies setupDependencies: @escaping (inout DependencyValues) -> Void = { _ in },
|
||||
operation: @Sendable @escaping () async throws -> Void
|
||||
) async rethrows {
|
||||
try await withTestLogger(key: key, logLevel: logLevel) {
|
||||
$0.configurationClient = .mock(configuration)
|
||||
$0.cliClient = .capturing(capturing)
|
||||
$0.playbookClient = .liveValue
|
||||
setupDependencies(&$0)
|
||||
} operation: {
|
||||
try await operation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GenerateJsonTestOption: Sendable {
|
||||
let options: CliClient.GenerateJsonOptions
|
||||
let configuration: Configuration?
|
||||
let expectation: GenerateJsonExpectation
|
||||
}
|
||||
|
||||
enum GenerateJsonExpectation: Sendable {
|
||||
case failing
|
||||
case success(String)
|
||||
}
|
||||
|
||||
extension ConfigurationClient {
|
||||
static func mock(_ configuration: Configuration) -> Self {
|
||||
var mock = Self.testValue
|
||||
mock.find = { throw TestError() }
|
||||
mock.load = { _ in configuration }
|
||||
return mock
|
||||
}
|
||||
}
|
||||
|
||||
struct TestError: Error {}
|
||||
|
||||
let jsonEncoder: JSONEncoder = {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = [.prettyPrinted, .withoutEscapingSlashes, .sortedKeys]
|
||||
return encoder
|
||||
}()
|
||||
@@ -26,13 +26,22 @@ struct ConfigurationClientTests: TestCase {
|
||||
|
||||
try await withTemporaryDirectory { tempDir in
|
||||
let tempFile = tempDir.appending(path: fileName)
|
||||
try await configuration.generate(at: File(tempFile)!, force: false)
|
||||
let output = try await configuration.generate(.init(
|
||||
force: false,
|
||||
json: fileName.hasSuffix("json"),
|
||||
path: .file(File(tempFile)!)
|
||||
))
|
||||
#expect(FileManager.default.fileExists(atPath: tempFile.cleanFilePath))
|
||||
#expect(fileClient.fileExists(tempFile))
|
||||
#expect(output == tempFile.cleanFilePath)
|
||||
|
||||
// Ensure that we do not overwrite files if they exist.
|
||||
do {
|
||||
try await configuration.generate(at: File(tempFile)!, force: false)
|
||||
_ = try await configuration.generate(.init(
|
||||
force: false,
|
||||
json: fileName.hasSuffix("json"),
|
||||
path: .file(File(tempFile)!)
|
||||
))
|
||||
#expect(Bool(false))
|
||||
} catch {
|
||||
#expect(Bool(true))
|
||||
@@ -150,7 +159,11 @@ func withGeneratedConfigFile(
|
||||
) async rethrows {
|
||||
try await withTemporaryDirectory { tempDir in
|
||||
let file = File(tempDir.appending(path: fileName))!
|
||||
try await client.generate(at: file)
|
||||
_ = try await client.generate(.init(
|
||||
force: true,
|
||||
json: fileName.hasSuffix("json"),
|
||||
path: .file(file)
|
||||
))
|
||||
try await operation(file)
|
||||
}
|
||||
}
|
||||
@@ -167,7 +180,11 @@ func withGeneratedXDGConfigFile(
|
||||
withIntermediateDirectories: false
|
||||
)
|
||||
let file = File(xdgDir.appending(path: fileName))!
|
||||
try await client.generate(at: file)
|
||||
_ = try await client.generate(.init(
|
||||
force: true,
|
||||
json: fileName.hasSuffix("json"),
|
||||
path: .file(file)
|
||||
))
|
||||
try await operation(file, tempDir)
|
||||
}
|
||||
}
|
||||
|
||||
333
Tests/PandocClientTests/PandocClientTests.swift
Normal file
333
Tests/PandocClientTests/PandocClientTests.swift
Normal file
@@ -0,0 +1,333 @@
|
||||
@_spi(Internal) import ConfigurationClient
|
||||
@_spi(Internal) import PandocClient
|
||||
import PlaybookClient
|
||||
import Testing
|
||||
import TestSupport
|
||||
|
||||
@Suite("PandocClientTests")
|
||||
struct PandocClientTests: TestCase {
|
||||
|
||||
static let outputDirectory = "/output"
|
||||
static let projectDirectory = "/project"
|
||||
static let defaultFileName = "Report"
|
||||
|
||||
static let expectedIncludeInHeaders = [
|
||||
"--include-in-header=/project/.build/head.tex",
|
||||
"--include-in-header=/project/.build/footer.tex"
|
||||
]
|
||||
|
||||
static let expectedFiles = [
|
||||
"/project/.build/Report.md",
|
||||
"/project/.build/Definitions.md"
|
||||
]
|
||||
|
||||
static var sharedRunOptions: PandocClient.RunOptions {
|
||||
.init(
|
||||
buildDirectory: nil,
|
||||
files: nil,
|
||||
loggingOptions: loggingOptions,
|
||||
includeInHeader: nil,
|
||||
outputDirectory: outputDirectory,
|
||||
projectDirectory: projectDirectory,
|
||||
outputFileName: nil,
|
||||
quiet: false,
|
||||
shell: nil,
|
||||
shouldBuild: true
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
func generateLatex() async throws {
|
||||
try await withCapturingCommandClient("generateLatex") {
|
||||
$0.configurationClient = .mock()
|
||||
$0.playbookClient.run.buildProject = { _ in }
|
||||
$0.pandocClient = .liveValue
|
||||
} run: {
|
||||
@Dependency(\.pandocClient) var pandocClient
|
||||
|
||||
let output = try await pandocClient.run.generateLatex(Self.sharedRunOptions)
|
||||
#expect(output == "\(Self.outputDirectory)/\(Self.defaultFileName).tex")
|
||||
|
||||
} assert: { output in
|
||||
let expected = ["pandoc"]
|
||||
+ Self.expectedIncludeInHeaders
|
||||
+ ["--output=\(Self.outputDirectory)/\(Self.defaultFileName).tex"]
|
||||
+ Self.expectedFiles
|
||||
|
||||
#expect(output.arguments == expected)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func generateHtml() async throws {
|
||||
try await withCapturingCommandClient("generateHtml") {
|
||||
$0.configurationClient = .mock()
|
||||
$0.playbookClient.run.buildProject = { _ in }
|
||||
$0.pandocClient = .liveValue
|
||||
} run: {
|
||||
@Dependency(\.pandocClient) var pandocClient
|
||||
|
||||
let output = try await pandocClient.run.generateHtml(Self.sharedRunOptions)
|
||||
#expect(output == "\(Self.outputDirectory)/\(Self.defaultFileName).html")
|
||||
|
||||
} assert: { output in
|
||||
let expected = ["pandoc"]
|
||||
+ Self.expectedIncludeInHeaders
|
||||
+ ["--output=\(Self.outputDirectory)/\(Self.defaultFileName).html"]
|
||||
+ Self.expectedFiles
|
||||
|
||||
#expect(output.arguments == expected)
|
||||
}
|
||||
}
|
||||
|
||||
@Test(
|
||||
arguments: [
|
||||
nil,
|
||||
"lualatex"
|
||||
]
|
||||
)
|
||||
func generatePdf(pdfEngine: String?) async throws {
|
||||
try await withCapturingCommandClient("generatePdf") {
|
||||
$0.configurationClient = .mock()
|
||||
$0.playbookClient.run.buildProject = { _ in }
|
||||
$0.pandocClient = .liveValue
|
||||
} run: {
|
||||
@Dependency(\.pandocClient) var pandocClient
|
||||
|
||||
let output = try await pandocClient.run.generatePdf(Self.sharedRunOptions, pdfEngine: pdfEngine)
|
||||
#expect(output == "\(Self.outputDirectory)/\(Self.defaultFileName).pdf")
|
||||
|
||||
} assert: { output in
|
||||
let expected = ["pandoc"]
|
||||
+ Self.expectedIncludeInHeaders
|
||||
+ ["--pdf-engine=\(pdfEngine ?? "xelatex")"]
|
||||
+ ["--output=\(Self.outputDirectory)/\(Self.defaultFileName).pdf"]
|
||||
+ Self.expectedFiles
|
||||
|
||||
#expect(output.arguments == expected)
|
||||
}
|
||||
}
|
||||
|
||||
@Test(arguments: TestPdfEngine.testCases)
|
||||
func parsePdfEngine(input: TestPdfEngine) {
|
||||
#expect(input.engine == input.expectedEngine)
|
||||
}
|
||||
|
||||
@Test(arguments: TestParseFiles.testCases)
|
||||
func parseFiles(input: TestParseFiles) {
|
||||
#expect(input.parsedFiles == input.expectedFiles)
|
||||
}
|
||||
|
||||
@Test(arguments: TestParseIncludeInHeaderFiles.testCases)
|
||||
func parseInclueInHeaderFiles(input: TestParseIncludeInHeaderFiles) {
|
||||
#expect(input.parsedFiles == input.expectedHeaderFiles)
|
||||
}
|
||||
|
||||
@Test(arguments: TestParseOutputFileName.testCases)
|
||||
func parseOutputFileName(input: TestParseOutputFileName) {
|
||||
#expect(input.parsedFileName == input.expected)
|
||||
}
|
||||
|
||||
@Test(arguments: TestParseBuildDirectory.testCases)
|
||||
func parseBuildDirectory(input: TestParseBuildDirectory) {
|
||||
#expect(input.parsedBuildDirectory == input.expected)
|
||||
}
|
||||
}
|
||||
|
||||
struct TestPdfEngine: Sendable {
|
||||
let fileType: PandocClient.FileType
|
||||
let expectedEngine: String?
|
||||
let configuration: Configuration
|
||||
let defaults: Configuration.Generate
|
||||
|
||||
var engine: String? {
|
||||
fileType.parsePdfEngine(configuration.generate, defaults)
|
||||
}
|
||||
|
||||
static let testCases: [Self] = [
|
||||
.init(fileType: .html, expectedEngine: nil, configuration: .init(), defaults: .default),
|
||||
.init(fileType: .latex, expectedEngine: nil, configuration: .init(), defaults: .default),
|
||||
.init(fileType: .pdf(engine: "lualatex"), expectedEngine: "lualatex", configuration: .init(), defaults: .default),
|
||||
.init(fileType: .pdf(engine: nil), expectedEngine: "xelatex", configuration: .init(), defaults: .default),
|
||||
.init(fileType: .pdf(engine: nil), expectedEngine: "xelatex", configuration: .init(), defaults: .init()),
|
||||
.init(fileType: .pdf(engine: nil), expectedEngine: "xelatex", configuration: .init(generate: .default), defaults: .init())
|
||||
]
|
||||
}
|
||||
|
||||
struct TestParseFiles: Sendable {
|
||||
|
||||
let expectedFiles: [String]
|
||||
let configuration: Configuration
|
||||
let defaults: Configuration.Generate
|
||||
let runOptions: PandocClient.RunOptions?
|
||||
|
||||
init(
|
||||
expectedFiles: [String],
|
||||
configuration: Configuration = .init(),
|
||||
defaults: Configuration.Generate = .default,
|
||||
runOptions: PandocClient.RunOptions? = nil
|
||||
) {
|
||||
self.expectedFiles = expectedFiles
|
||||
self.configuration = configuration
|
||||
self.defaults = defaults
|
||||
self.runOptions = runOptions
|
||||
}
|
||||
|
||||
var parsedFiles: [String] {
|
||||
let runOptions = self.runOptions ?? PandocClient.RunOptions(
|
||||
loggingOptions: .init(commandName: "parseFiles", logLevel: .debug),
|
||||
projectDirectory: nil,
|
||||
quiet: true,
|
||||
shouldBuild: false
|
||||
)
|
||||
|
||||
return runOptions.parseFiles(configuration.generate, defaults)
|
||||
}
|
||||
|
||||
static let testCases: [Self] = [
|
||||
.init(expectedFiles: ["Report.md", "Definitions.md"]),
|
||||
.init(expectedFiles: ["Report.md", "Definitions.md"], configuration: .init(generate: .default), defaults: .init()),
|
||||
.init(expectedFiles: [], defaults: .init()),
|
||||
.init(
|
||||
expectedFiles: ["custom.md"],
|
||||
configuration: .init(),
|
||||
defaults: .init(),
|
||||
runOptions: .init(
|
||||
files: ["custom.md"],
|
||||
loggingOptions: .init(commandName: "parseFiles", logLevel: .debug),
|
||||
projectDirectory: nil,
|
||||
quiet: true,
|
||||
shouldBuild: false
|
||||
)
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
struct TestParseIncludeInHeaderFiles: Sendable {
|
||||
|
||||
let expectedHeaderFiles: [String]
|
||||
let configuration: Configuration
|
||||
let defaults: Configuration.Generate
|
||||
let runOptions: PandocClient.RunOptions?
|
||||
|
||||
init(
|
||||
expectedHeaderFiles: [String],
|
||||
configuration: Configuration = .init(),
|
||||
defaults: Configuration.Generate = .default,
|
||||
runOptions: PandocClient.RunOptions? = nil
|
||||
) {
|
||||
self.expectedHeaderFiles = expectedHeaderFiles
|
||||
self.configuration = configuration
|
||||
self.defaults = defaults
|
||||
self.runOptions = runOptions
|
||||
}
|
||||
|
||||
var parsedFiles: [String] {
|
||||
let runOptions = self.runOptions ?? PandocClient.RunOptions(
|
||||
loggingOptions: .init(commandName: "parseFiles", logLevel: .debug)
|
||||
)
|
||||
|
||||
return runOptions.parseIncludeInHeader(configuration.generate, defaults)
|
||||
}
|
||||
|
||||
static let testCases: [Self] = [
|
||||
.init(expectedHeaderFiles: ["head.tex", "footer.tex"]),
|
||||
.init(expectedHeaderFiles: ["head.tex", "footer.tex"], configuration: .init(generate: .default), defaults: .init()),
|
||||
.init(expectedHeaderFiles: [], defaults: .init()),
|
||||
.init(
|
||||
expectedHeaderFiles: ["custom.tex"],
|
||||
configuration: .init(),
|
||||
defaults: .init(),
|
||||
runOptions: .init(
|
||||
loggingOptions: .init(commandName: "parseFiles", logLevel: .debug),
|
||||
includeInHeader: ["custom.tex"]
|
||||
)
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
struct TestParseOutputFileName: Sendable {
|
||||
|
||||
let expected: String
|
||||
let configuration: Configuration
|
||||
let defaults: Configuration.Generate
|
||||
let runOptions: PandocClient.RunOptions?
|
||||
|
||||
init(
|
||||
expected: String,
|
||||
configuration: Configuration = .init(),
|
||||
defaults: Configuration.Generate = .default,
|
||||
runOptions: PandocClient.RunOptions? = nil
|
||||
) {
|
||||
self.expected = expected
|
||||
self.configuration = configuration
|
||||
self.defaults = defaults
|
||||
self.runOptions = runOptions
|
||||
}
|
||||
|
||||
var parsedFileName: String {
|
||||
let runOptions = self.runOptions ?? PandocClient.RunOptions(
|
||||
loggingOptions: .init(commandName: "parseFiles", logLevel: .debug)
|
||||
)
|
||||
|
||||
return runOptions.parseOutputFileName(configuration.generate, defaults)
|
||||
}
|
||||
|
||||
static let testCases: [Self] = [
|
||||
.init(expected: "Report"),
|
||||
.init(expected: "Report", configuration: .init(generate: .default), defaults: .init()),
|
||||
.init(expected: "Report", defaults: .init()),
|
||||
.init(
|
||||
expected: "custom",
|
||||
configuration: .init(),
|
||||
defaults: .init(),
|
||||
runOptions: .init(
|
||||
loggingOptions: .init(commandName: "parseFiles", logLevel: .debug),
|
||||
outputFileName: "custom"
|
||||
)
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
struct TestParseBuildDirectory: Sendable {
|
||||
|
||||
let expected: String
|
||||
let configuration: Configuration
|
||||
let defaults: Configuration.Generate
|
||||
let runOptions: PandocClient.RunOptions?
|
||||
|
||||
init(
|
||||
expected: String = ".build",
|
||||
configuration: Configuration = .init(),
|
||||
defaults: Configuration.Generate = .default,
|
||||
runOptions: PandocClient.RunOptions? = nil
|
||||
) {
|
||||
self.expected = expected
|
||||
self.configuration = configuration
|
||||
self.defaults = defaults
|
||||
self.runOptions = runOptions
|
||||
}
|
||||
|
||||
var parsedBuildDirectory: String {
|
||||
let runOptions = self.runOptions ?? PandocClient.RunOptions(
|
||||
loggingOptions: .init(commandName: "parseFiles", logLevel: .debug)
|
||||
)
|
||||
|
||||
return runOptions.parseBuildDirectory(configuration.generate, defaults)
|
||||
}
|
||||
|
||||
static let testCases: [Self] = [
|
||||
.init(),
|
||||
.init(configuration: .init(generate: .default), defaults: .init()),
|
||||
.init(defaults: .init()),
|
||||
.init(
|
||||
expected: "custom",
|
||||
configuration: .init(),
|
||||
defaults: .init(),
|
||||
runOptions: .init(
|
||||
buildDirectory: "custom",
|
||||
loggingOptions: .init(commandName: "parseFiles", logLevel: .debug)
|
||||
)
|
||||
)
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user