Files
swift-hpa/Sources/CliClient/CliClient+PandocCommands.swift

224 lines
6.3 KiB
Swift

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"
}
}