This commit is contained in:
9
Sources/PandocClient/Constants.swift
Normal file
9
Sources/PandocClient/Constants.swift
Normal file
@@ -0,0 +1,9 @@
|
||||
extension PandocClient {
|
||||
|
||||
/// Represents constant string values needed internally.
|
||||
enum Constants {
|
||||
static let pandocCommand = "pandoc"
|
||||
static let defaultOutputFileName = "Report"
|
||||
static let defaultPdfEngine = "xelatex"
|
||||
}
|
||||
}
|
||||
245
Sources/PandocClient/PandocClient+Run.swift
Normal file
245
Sources/PandocClient/PandocClient+Run.swift
Normal file
@@ -0,0 +1,245 @@
|
||||
import CommandClient
|
||||
import ConfigurationClient
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import PlaybookClient
|
||||
|
||||
extension PandocClient.RunOptions {
|
||||
|
||||
func run(
|
||||
_ fileType: PandocClient.FileType,
|
||||
_ environment: [String: String]
|
||||
) 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 ?? environment["PWD"]
|
||||
|
||||
guard let projectDirectory else {
|
||||
throw ProjectDirectoryNotSpecified()
|
||||
}
|
||||
|
||||
if shouldBuildProject {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension PandocClient.FileType {
|
||||
var fileExtension: String {
|
||||
switch self {
|
||||
case .html: return "html"
|
||||
case .latex: return "tex"
|
||||
case .pdf: return "pdf"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@_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 = ".\(outputFileType.fileExtension)"
|
||||
|
||||
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 {}
|
||||
141
Sources/PandocClient/PandocClient.swift
Normal file
141
Sources/PandocClient/PandocClient.swift
Normal file
@@ -0,0 +1,141 @@
|
||||
import CommandClient
|
||||
import ConfigurationClient
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
import Foundation
|
||||
|
||||
public extension DependencyValues {
|
||||
|
||||
/// Represents interactions with the `pandoc` command line application.
|
||||
///
|
||||
/// The `pandoc` command line application is used to generate the final output
|
||||
/// documents from a home performance assessment project.
|
||||
///
|
||||
var pandocClient: PandocClient {
|
||||
get { self[PandocClient.self] }
|
||||
set { self[PandocClient.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
@DependencyClient
|
||||
public struct PandocClient: Sendable {
|
||||
|
||||
/// Run a pandoc command.
|
||||
public var run: Run
|
||||
|
||||
/// Represents the pandoc commands that we can run.
|
||||
@DependencyClient
|
||||
public struct Run: Sendable {
|
||||
|
||||
/// Generate a latex file from the given options, returning the path the
|
||||
/// generated file was written to.
|
||||
public var generateLatex: @Sendable (RunOptions) async throws -> String
|
||||
|
||||
/// Generate an html file from the given options, returning the path the
|
||||
/// generated file was written to.
|
||||
public var generateHtml: @Sendable (RunOptions) async throws -> String
|
||||
|
||||
/// Generate a pdf file from the given options, returning the path the
|
||||
/// generated file was written to.
|
||||
public var generatePdf: @Sendable (RunOptions, String?) async throws -> String
|
||||
|
||||
/// Generate a pdf file from the given options, returning the path the
|
||||
/// generated file was written to.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - options: The shared run options.
|
||||
/// - pdfEngine: The pdf-engine to use to generate the pdf file.
|
||||
public func generatePdf(
|
||||
_ options: RunOptions,
|
||||
pdfEngine: String? = nil
|
||||
) async throws -> String {
|
||||
try await generatePdf(options, pdfEngine)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Represents the shared options used to run a `pandoc` command.
|
||||
///
|
||||
///
|
||||
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 shouldBuildProject: Bool
|
||||
|
||||
/// Create the shared run options.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - buildDirectory: Specify the build directory of the project.
|
||||
/// - extraOptions: Extra arguments / options passed to the `pandoc` command.
|
||||
/// - file: Files used to build the final output.
|
||||
/// - loggingOptions: The logging options used when running the command.
|
||||
/// - includeInHeader: Files to include in the header of the final output document.
|
||||
/// - outputDirectory: Optional output directory for the output file, defaults to current working directory.
|
||||
/// - projectDirectory: Optional project directory, defaults to current working directory.
|
||||
/// - outputFileName: Override the output file name.
|
||||
/// - quiet: Don't log output from the command.
|
||||
/// - shell: Optional shell to use when calling the pandoc command.
|
||||
/// - shouldBuildProject: Build the project prior to generating the output file.
|
||||
public init(
|
||||
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 shouldBuildProject: 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.shouldBuildProject = shouldBuildProject
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Represents the file types that we can generate output files for.
|
||||
@_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 func live(environment: [String: String]) -> PandocClient {
|
||||
.init(
|
||||
run: Run(
|
||||
generateLatex: { try await $0.run(.latex, environment) },
|
||||
generateHtml: { try await $0.run(.html, environment) },
|
||||
generatePdf: { try await $0.run(.pdf(engine: $1), environment) }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
public static var liveValue: PandocClient {
|
||||
.live(environment: ProcessInfo.processInfo.environment)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user