feat: Fixes generation of html document, it now builds a latex document in the build directory that it then converts to html. Need to update tests.

This commit is contained in:
2025-11-17 09:38:23 -05:00
parent 8ee4e436aa
commit 045f48348d
3 changed files with 131 additions and 59 deletions

View File

@@ -4,10 +4,10 @@ import DependenciesMacros
import Foundation import Foundation
import ShellClient import ShellClient
public extension DependencyValues { extension DependencyValues {
/// Runs shell commands. /// Runs shell commands.
var commandClient: CommandClient { public var commandClient: CommandClient {
get { self[CommandClient.self] } get { self[CommandClient.self] }
set { self[CommandClient.self] = newValue } set { self[CommandClient.self] = newValue }
} }
@@ -67,12 +67,13 @@ public struct CommandClient: Sendable {
in workingDirectory: String? = nil, in workingDirectory: String? = nil,
_ arguments: [String] _ arguments: [String]
) async throws { ) async throws {
try await runCommand(.init( try await runCommand(
arguments: arguments, .init(
quiet: quiet, arguments: arguments,
shell: shell, quiet: quiet,
workingDirectory: workingDirectory shell: shell,
)) workingDirectory: workingDirectory
))
} }
/// Runs a shell command. /// Runs a shell command.
@@ -161,19 +162,21 @@ extension CommandClient: DependencyKey {
.init { options in .init { options in
@Dependency(\.asyncShellClient) var shellClient @Dependency(\.asyncShellClient) var shellClient
if !options.quiet { if !options.quiet {
try await shellClient.foreground(.init( try await shellClient.foreground(
shell: .init(options.shell), .init(
environment: environment, shell: .init(options.shell),
in: options.workingDirectory, environment: environment,
options.arguments in: options.workingDirectory,
)) options.arguments
))
} else { } else {
try await shellClient.background(.init( try await shellClient.background(
shell: .init(options.shell), .init(
environment: environment, shell: .init(options.shell),
in: options.workingDirectory, environment: environment,
options.arguments in: options.workingDirectory,
)) options.arguments
))
} }
} }
} }
@@ -184,12 +187,12 @@ extension CommandClient: DependencyKey {
} }
@_spi(Internal) @_spi(Internal)
public extension CommandClient { extension CommandClient {
/// Create a command client that can capture the arguments / options. /// Create a command client that can capture the arguments / options.
/// ///
/// This is used for testing. /// This is used for testing.
static func capturing(_ client: CapturingClient) -> Self { public static func capturing(_ client: CapturingClient) -> Self {
.init { options in .init { options in
await client.set(options) await client.set(options)
} }
@@ -198,7 +201,7 @@ public extension CommandClient {
/// Captures the arguments / options passed into the command client's run commands. /// Captures the arguments / options passed into the command client's run commands.
/// ///
@dynamicMemberLookup @dynamicMemberLookup
actor CapturingClient: Sendable { public actor CapturingClient: Sendable {
public private(set) var options: RunCommandOptions? public private(set) var options: RunCommandOptions?
public init() {} public init() {}

View File

@@ -12,20 +12,67 @@ extension PandocClient.RunOptions {
) async throws -> String { ) async throws -> String {
@Dependency(\.commandClient) var commandClient @Dependency(\.commandClient) var commandClient
@Dependency(\.logger) var logger @Dependency(\.logger) var logger
// 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()
}
try await buildProject(fileType, projectDirectory, ensuredOptions)
let outputDirectory = self.outputDirectory ?? projectDirectory
let outputPath = "\(outputDirectory)/\(ensuredOptions.ensuredExtensionFileName)"
let arguments = ensuredOptions.makeArguments(
fileType: fileType,
outputPath: outputPath,
projectDirectory: projectDirectory
)
logger.debug("Pandoc arguments: \(arguments)")
return try await runCommand(arguments, outputPath)
// return (arguments, outputPath)
// }
}
/// Runs a shell command with the given arguments, returning the passed in output path
/// so the command can be chained, if needed.
///
@_spi(Internal)
@discardableResult
public func runCommand(
_ arguments: [String],
_ outputPath: String
) async throws -> String {
@Dependency(\.commandClient) var commandClient
@Dependency(\.logger) var logger
logger.debug("Running shell command with arguments: \(arguments)")
return try await commandClient.run(logging: loggingOptions, quiet: quiet, shell: shell) {
(arguments, outputPath)
}
}
/// Build the project if necessary, before running the shell command that builds the final
/// output file(s).
///
@_spi(Internal)
public func buildProject(
_ fileType: PandocClient.FileType,
_ projectDirectory: String,
_ ensuredOptions: EnsuredPandocOptions
) async throws {
@Dependency(\.logger) var logger
@Dependency(\.playbookClient) var playbookClient @Dependency(\.playbookClient) var playbookClient
return try await commandClient.run(logging: loggingOptions, quiet: quiet, shell: shell) { if shouldBuildProject {
let ensuredOptions = try await self.ensuredOptions(fileType) logger.debug("Building project...")
try await playbookClient.run.buildProject(
let projectDirectory = self.projectDirectory ?? environment["PWD"] .init(
guard let projectDirectory else {
throw ProjectDirectoryNotSpecified()
}
if shouldBuildProject {
logger.debug("Building project...")
try await playbookClient.run.buildProject(.init(
projectDirectory: projectDirectory, projectDirectory: projectDirectory,
shared: .init( shared: .init(
extraOptions: nil, extraOptions: nil,
@@ -34,20 +81,21 @@ extension PandocClient.RunOptions {
quiet: quiet, quiet: quiet,
shell: shell shell: shell
) )
)) )
} )
}
let outputDirectory = self.outputDirectory ?? projectDirectory // Build latex file pre-html, so that we can properly convert the latex document
let outputPath = "\(outputDirectory)/\(ensuredOptions.ensuredExtensionFileName)" // into an html document.
if fileType == .html {
let arguments = ensuredOptions.makeArguments( logger.debug("Building latex, pre-html conversion...")
let outputPath = "\(ensuredOptions.buildDirectory)/Report.tex"
let arguments = ensuredOptions.preHtmlLatexOptions.makeArguments(
fileType: .latex,
outputPath: outputPath, outputPath: outputPath,
projectDirectory: projectDirectory projectDirectory: projectDirectory
) )
try await runCommand(arguments, outputPath)
logger.debug("Pandoc arguments: \(arguments)")
return (arguments, outputPath)
} }
} }
@@ -80,6 +128,8 @@ extension PandocClient.FileType {
@_spi(Internal) @_spi(Internal)
public struct EnsuredPandocOptions: Equatable, Sendable { public struct EnsuredPandocOptions: Equatable, Sendable {
public static let latexFilename = "Report.tex"
public let buildDirectory: String public let buildDirectory: String
public let extraOptions: [String]? public let extraOptions: [String]?
public let files: [String] public let files: [String]
@@ -97,14 +147,29 @@ public struct EnsuredPandocOptions: Equatable, Sendable {
return outputFileName return outputFileName
} }
public var preHtmlLatexOptions: Self {
.init(
buildDirectory: buildDirectory,
extraOptions: extraOptions,
files: files,
includeInHeader: includeInHeader,
outputFileName: Self.latexFilename,
outputFileType: .latex,
pdfEngine: nil
)
}
func makeArguments( func makeArguments(
fileType: PandocClient.FileType,
outputPath: String, outputPath: String,
projectDirectory: String projectDirectory: String
) -> [String] { ) -> [String] {
var arguments = [PandocClient.Constants.pandocCommand] var arguments = [PandocClient.Constants.pandocCommand]
arguments += includeInHeader.map { if fileType != .html {
"--include-in-header=\(projectDirectory)/\(buildDirectory)/\($0)" arguments += includeInHeader.map {
"--include-in-header=\(projectDirectory)/\(buildDirectory)/\($0)"
}
} }
if let pdfEngine { if let pdfEngine {
@@ -117,8 +182,12 @@ public struct EnsuredPandocOptions: Equatable, Sendable {
arguments.append(contentsOf: extraOptions) arguments.append(contentsOf: extraOptions)
} }
arguments += files.map { if fileType != .html {
"\(projectDirectory)/\(buildDirectory)/\($0)" arguments += files.map {
"\(projectDirectory)/\(buildDirectory)/\($0)"
}
} else {
arguments.append("\(projectDirectory)/\(buildDirectory)/\(Self.latexFilename)")
} }
return arguments return arguments
@@ -145,15 +214,15 @@ public func ensurePandocOptions(
} }
@_spi(Internal) @_spi(Internal)
public extension PandocClient.FileType { extension PandocClient.FileType {
func parsePdfEngine( public func parsePdfEngine(
_ configuration: Configuration.Generate?, _ configuration: Configuration.Generate?,
_ defaults: Configuration.Generate _ defaults: Configuration.Generate
) -> String? { ) -> String? {
switch self { switch self {
case .html, .latex: case .html, .latex:
return nil return nil
case let .pdf(engine: engine): case .pdf(let engine):
if let engine { if let engine {
return engine return engine
} else if let engine = configuration?.pdfEngine { } else if let engine = configuration?.pdfEngine {
@@ -168,8 +237,8 @@ public extension PandocClient.FileType {
} }
@_spi(Internal) @_spi(Internal)
public extension PandocClient.RunOptions { extension PandocClient.RunOptions {
func parseFiles( public func parseFiles(
_ configuration: Configuration.Generate?, _ configuration: Configuration.Generate?,
_ defaults: Configuration.Generate _ defaults: Configuration.Generate
) -> [String] { ) -> [String] {
@@ -187,7 +256,7 @@ public extension PandocClient.RunOptions {
} }
} }
func parseIncludeInHeader( public func parseIncludeInHeader(
_ configuration: Configuration.Generate?, _ configuration: Configuration.Generate?,
_ defaults: Configuration.Generate _ defaults: Configuration.Generate
) -> [String] { ) -> [String] {
@@ -205,7 +274,7 @@ public extension PandocClient.RunOptions {
} }
} }
func parseOutputFileName( public func parseOutputFileName(
_ configuration: Configuration.Generate?, _ configuration: Configuration.Generate?,
_ defaults: Configuration.Generate _ defaults: Configuration.Generate
) -> String { ) -> String {
@@ -223,7 +292,7 @@ public extension PandocClient.RunOptions {
} }
} }
func parseBuildDirectory( public func parseBuildDirectory(
_ configuration: Configuration.Generate?, _ configuration: Configuration.Generate?,
_ defaults: Configuration.Generate _ defaults: Configuration.Generate
) -> String { ) -> String {

View File

@@ -4,14 +4,14 @@ import Dependencies
import DependenciesMacros import DependenciesMacros
import Foundation import Foundation
public extension DependencyValues { extension DependencyValues {
/// Represents interactions with the `pandoc` command line application. /// Represents interactions with the `pandoc` command line application.
/// ///
/// The `pandoc` command line application is used to generate the final output /// The `pandoc` command line application is used to generate the final output
/// documents from a home performance assessment project. /// documents from a home performance assessment project.
/// ///
var pandocClient: PandocClient { public var pandocClient: PandocClient {
get { self[PandocClient.self] } get { self[PandocClient.self] }
set { self[PandocClient.self] = newValue } set { self[PandocClient.self] = newValue }
} }