feat: Updates tests for html generation, adds some more documentation strings.
All checks were successful
CI / Run Tests (pull_request) Successful in 4m25s
Build docker images / docker (pull_request) Successful in 7m31s

This commit is contained in:
2025-11-17 10:19:11 -05:00
parent 045f48348d
commit 357eecabf9
2 changed files with 94 additions and 56 deletions

View File

@@ -6,14 +6,19 @@ import PlaybookClient
extension PandocClient.RunOptions { extension PandocClient.RunOptions {
/// Runs a pandoc conversion on the project generating the given file type.
///
/// - Parameters:
/// - fileType: The file type to convert to.
/// - environment: The environment variables.
///
/// - Returns: File path to the converted output file.
func run( func run(
_ fileType: PandocClient.FileType, _ fileType: PandocClient.FileType,
_ environment: [String: String] _ environment: [String: String]
) async throws -> String { ) async throws -> String {
@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 ensuredOptions = try await self.ensuredOptions(fileType)
let projectDirectory = self.projectDirectory ?? environment["PWD"] let projectDirectory = self.projectDirectory ?? environment["PWD"]
@@ -22,13 +27,12 @@ extension PandocClient.RunOptions {
throw ProjectDirectoryNotSpecified() throw ProjectDirectoryNotSpecified()
} }
try await buildProject(fileType, projectDirectory, ensuredOptions) try await buildProject(projectDirectory, ensuredOptions)
let outputDirectory = self.outputDirectory ?? projectDirectory let outputDirectory = self.outputDirectory ?? projectDirectory
let outputPath = "\(outputDirectory)/\(ensuredOptions.ensuredExtensionFileName)" let outputPath = "\(outputDirectory)/\(ensuredOptions.ensuredFilename)"
let arguments = ensuredOptions.makeArguments( let arguments = ensuredOptions.makeArguments(
fileType: fileType,
outputPath: outputPath, outputPath: outputPath,
projectDirectory: projectDirectory projectDirectory: projectDirectory
) )
@@ -36,16 +40,13 @@ extension PandocClient.RunOptions {
logger.debug("Pandoc arguments: \(arguments)") logger.debug("Pandoc arguments: \(arguments)")
return try await runCommand(arguments, outputPath) return try await runCommand(arguments, outputPath)
// return (arguments, outputPath)
// }
} }
/// Runs a shell command with the given arguments, returning the passed in output path /// Runs a shell command with the given arguments, returning the passed in output path
/// so the command can be chained, if needed. /// so the command can be chained, if needed.
/// ///
@_spi(Internal)
@discardableResult @discardableResult
public func runCommand( func runCommand(
_ arguments: [String], _ arguments: [String],
_ outputPath: String _ outputPath: String
) async throws -> String { ) async throws -> String {
@@ -60,9 +61,7 @@ extension PandocClient.RunOptions {
/// Build the project if necessary, before running the shell command that builds the final /// Build the project if necessary, before running the shell command that builds the final
/// output file(s). /// output file(s).
/// ///
@_spi(Internal) func buildProject(
public func buildProject(
_ fileType: PandocClient.FileType,
_ projectDirectory: String, _ projectDirectory: String,
_ ensuredOptions: EnsuredPandocOptions _ ensuredOptions: EnsuredPandocOptions
) async throws { ) async throws {
@@ -87,11 +86,10 @@ extension PandocClient.RunOptions {
// Build latex file pre-html, so that we can properly convert the latex document // Build latex file pre-html, so that we can properly convert the latex document
// into an html document. // into an html document.
if fileType == .html { if ensuredOptions.outputFileType == .html {
logger.debug("Building latex, pre-html conversion...") logger.debug("Building latex, pre-html conversion...")
let outputPath = "\(ensuredOptions.buildDirectory)/Report.tex" let outputPath = "\(ensuredOptions.buildDirectory)/\(EnsuredPandocOptions.latexFilename)"
let arguments = ensuredOptions.preHtmlLatexOptions.makeArguments( let arguments = ensuredOptions.preHtmlLatexOptions.makeArguments(
fileType: .latex,
outputPath: outputPath, outputPath: outputPath,
projectDirectory: projectDirectory projectDirectory: projectDirectory
) )
@@ -99,6 +97,11 @@ extension PandocClient.RunOptions {
} }
} }
/// Generates the ensured/parsed options for a pandoc conversion.
///
/// - Parameter fileType: The file type we're converting to.
///
/// - Returns: The ensured options.
func ensuredOptions( func ensuredOptions(
_ fileType: PandocClient.FileType _ fileType: PandocClient.FileType
) async throws -> EnsuredPandocOptions { ) async throws -> EnsuredPandocOptions {
@@ -117,6 +120,7 @@ extension PandocClient.RunOptions {
} }
extension PandocClient.FileType { extension PandocClient.FileType {
/// Represents the appropriate file extension for a file type.
var fileExtension: String { var fileExtension: String {
switch self { switch self {
case .html: return "html" case .html: return "html"
@@ -126,6 +130,10 @@ extension PandocClient.FileType {
} }
} }
/// Represents pandoc options that get parsed based on the given run options, configuration, etc.
///
/// This set's potentially optional values into real values that are required for pandoc to run
/// properly and convert files for the given file type conversion.
@_spi(Internal) @_spi(Internal)
public struct EnsuredPandocOptions: Equatable, Sendable { public struct EnsuredPandocOptions: Equatable, Sendable {
public static let latexFilename = "Report.tex" public static let latexFilename = "Report.tex"
@@ -138,7 +146,9 @@ public struct EnsuredPandocOptions: Equatable, Sendable {
public let outputFileType: PandocClient.FileType public let outputFileType: PandocClient.FileType
public let pdfEngine: String? public let pdfEngine: String?
public var ensuredExtensionFileName: String { /// Ensures the output filename includes the file extension, so that pandoc
/// can properly convert the files.
public var ensuredFilename: String {
let extensionString = ".\(outputFileType.fileExtension)" let extensionString = ".\(outputFileType.fileExtension)"
if !outputFileName.hasSuffix(extensionString) { if !outputFileName.hasSuffix(extensionString) {
@@ -147,7 +157,9 @@ public struct EnsuredPandocOptions: Equatable, Sendable {
return outputFileName return outputFileName
} }
public var preHtmlLatexOptions: Self { /// Generates the options required for building the latex file that is needed
/// to convert the project to an html output file.
var preHtmlLatexOptions: Self {
.init( .init(
buildDirectory: buildDirectory, buildDirectory: buildDirectory,
extraOptions: extraOptions, extraOptions: extraOptions,
@@ -159,14 +171,16 @@ public struct EnsuredPandocOptions: Equatable, Sendable {
) )
} }
/// Generate arguments for the pandoc shell command based on the parsed options
/// for a given conversion.
///
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]
if fileType != .html { if outputFileType != .html {
arguments += includeInHeader.map { arguments += includeInHeader.map {
"--include-in-header=\(projectDirectory)/\(buildDirectory)/\($0)" "--include-in-header=\(projectDirectory)/\(buildDirectory)/\($0)"
} }
@@ -182,7 +196,7 @@ public struct EnsuredPandocOptions: Equatable, Sendable {
arguments.append(contentsOf: extraOptions) arguments.append(contentsOf: extraOptions)
} }
if fileType != .html { if outputFileType != .html {
arguments += files.map { arguments += files.map {
"\(projectDirectory)/\(buildDirectory)/\($0)" "\(projectDirectory)/\(buildDirectory)/\($0)"
} }

View File

@@ -1,8 +1,8 @@
@_spi(Internal) import ConfigurationClient @_spi(Internal) import ConfigurationClient
@_spi(Internal) import PandocClient @_spi(Internal) import PandocClient
import PlaybookClient import PlaybookClient
import Testing
import TestSupport import TestSupport
import Testing
@Suite("PandocClientTests") @Suite("PandocClientTests")
struct PandocClientTests: TestCase { struct PandocClientTests: TestCase {
@@ -13,12 +13,12 @@ struct PandocClientTests: TestCase {
static let expectedIncludeInHeaders = [ static let expectedIncludeInHeaders = [
"--include-in-header=/project/.build/head.tex", "--include-in-header=/project/.build/head.tex",
"--include-in-header=/project/.build/footer.tex" "--include-in-header=/project/.build/footer.tex",
] ]
static let expectedFiles = [ static let expectedFiles = [
"/project/.build/Report.md", "/project/.build/Report.md",
"/project/.build/Definitions.md" "/project/.build/Definitions.md",
] ]
static var sharedRunOptions: PandocClient.RunOptions { static var sharedRunOptions: PandocClient.RunOptions {
@@ -49,7 +49,8 @@ struct PandocClientTests: TestCase {
#expect(output == "\(Self.outputDirectory)/\(Self.defaultFileName).tex") #expect(output == "\(Self.outputDirectory)/\(Self.defaultFileName).tex")
} assert: { output in } assert: { output in
let expected = ["pandoc"] let expected =
["pandoc"]
+ Self.expectedIncludeInHeaders + Self.expectedIncludeInHeaders
+ ["--output=\(Self.outputDirectory)/\(Self.defaultFileName).tex"] + ["--output=\(Self.outputDirectory)/\(Self.defaultFileName).tex"]
+ Self.expectedFiles + Self.expectedFiles
@@ -71,10 +72,11 @@ struct PandocClientTests: TestCase {
#expect(output == "\(Self.outputDirectory)/\(Self.defaultFileName).html") #expect(output == "\(Self.outputDirectory)/\(Self.defaultFileName).html")
} assert: { output in } assert: { output in
let expected = ["pandoc"] let expected = [
+ Self.expectedIncludeInHeaders "pandoc",
+ ["--output=\(Self.outputDirectory)/\(Self.defaultFileName).html"] "--output=\(Self.outputDirectory)/\(Self.defaultFileName).html",
+ Self.expectedFiles "\(Self.projectDirectory)/.build/Report.tex",
]
#expect(output.arguments == expected) #expect(output.arguments == expected)
} }
@@ -83,7 +85,7 @@ struct PandocClientTests: TestCase {
@Test( @Test(
arguments: [ arguments: [
nil, nil,
"lualatex" "lualatex",
] ]
) )
func generatePdf(pdfEngine: String?) async throws { func generatePdf(pdfEngine: String?) async throws {
@@ -94,11 +96,13 @@ struct PandocClientTests: TestCase {
} run: { } run: {
@Dependency(\.pandocClient) var pandocClient @Dependency(\.pandocClient) var pandocClient
let output = try await pandocClient.run.generatePdf(Self.sharedRunOptions, pdfEngine: pdfEngine) let output = try await pandocClient.run.generatePdf(
Self.sharedRunOptions, pdfEngine: pdfEngine)
#expect(output == "\(Self.outputDirectory)/\(Self.defaultFileName).pdf") #expect(output == "\(Self.outputDirectory)/\(Self.defaultFileName).pdf")
} assert: { output in } assert: { output in
let expected = ["pandoc"] let expected =
["pandoc"]
+ Self.expectedIncludeInHeaders + Self.expectedIncludeInHeaders
+ ["--pdf-engine=\(pdfEngine ?? "xelatex")"] + ["--pdf-engine=\(pdfEngine ?? "xelatex")"]
+ ["--output=\(Self.outputDirectory)/\(Self.defaultFileName).pdf"] + ["--output=\(Self.outputDirectory)/\(Self.defaultFileName).pdf"]
@@ -147,10 +151,18 @@ struct TestPdfEngine: Sendable {
static let testCases: [Self] = [ static let testCases: [Self] = [
.init(fileType: .html, expectedEngine: nil, configuration: .init(), defaults: .default), .init(fileType: .html, expectedEngine: nil, configuration: .init(), defaults: .default),
.init(fileType: .latex, 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(
.init(fileType: .pdf(engine: nil), expectedEngine: "xelatex", configuration: .init(), defaults: .default), fileType: .pdf(engine: "lualatex"), expectedEngine: "lualatex", configuration: .init(),
.init(fileType: .pdf(engine: nil), expectedEngine: "xelatex", configuration: .init(), defaults: .init()), defaults: .default),
.init(fileType: .pdf(engine: nil), expectedEngine: "xelatex", configuration: .init(generate: .default), defaults: .init()) .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()),
] ]
} }
@@ -174,7 +186,9 @@ struct TestParseFiles: Sendable {
} }
var parsedFiles: [String] { var parsedFiles: [String] {
let runOptions = self.runOptions ?? PandocClient.RunOptions( let runOptions =
self.runOptions
?? PandocClient.RunOptions(
loggingOptions: .init(commandName: "parseFiles", logLevel: .debug), loggingOptions: .init(commandName: "parseFiles", logLevel: .debug),
projectDirectory: nil, projectDirectory: nil,
quiet: true, quiet: true,
@@ -186,7 +200,9 @@ struct TestParseFiles: Sendable {
static let testCases: [Self] = [ static let testCases: [Self] = [
.init(expectedFiles: ["Report.md", "Definitions.md"]), .init(expectedFiles: ["Report.md", "Definitions.md"]),
.init(expectedFiles: ["Report.md", "Definitions.md"], configuration: .init(generate: .default), defaults: .init()), .init(
expectedFiles: ["Report.md", "Definitions.md"], configuration: .init(generate: .default),
defaults: .init()),
.init(expectedFiles: [], defaults: .init()), .init(expectedFiles: [], defaults: .init()),
.init( .init(
expectedFiles: ["custom.md"], expectedFiles: ["custom.md"],
@@ -199,7 +215,7 @@ struct TestParseFiles: Sendable {
quiet: true, quiet: true,
shouldBuild: false shouldBuild: false
) )
) ),
] ]
} }
@@ -223,7 +239,9 @@ struct TestParseIncludeInHeaderFiles: Sendable {
} }
var parsedFiles: [String] { var parsedFiles: [String] {
let runOptions = self.runOptions ?? PandocClient.RunOptions( let runOptions =
self.runOptions
?? PandocClient.RunOptions(
loggingOptions: .init(commandName: "parseFiles", logLevel: .debug) loggingOptions: .init(commandName: "parseFiles", logLevel: .debug)
) )
@@ -232,7 +250,9 @@ struct TestParseIncludeInHeaderFiles: Sendable {
static let testCases: [Self] = [ static let testCases: [Self] = [
.init(expectedHeaderFiles: ["head.tex", "footer.tex"]), .init(expectedHeaderFiles: ["head.tex", "footer.tex"]),
.init(expectedHeaderFiles: ["head.tex", "footer.tex"], configuration: .init(generate: .default), defaults: .init()), .init(
expectedHeaderFiles: ["head.tex", "footer.tex"], configuration: .init(generate: .default),
defaults: .init()),
.init(expectedHeaderFiles: [], defaults: .init()), .init(expectedHeaderFiles: [], defaults: .init()),
.init( .init(
expectedHeaderFiles: ["custom.tex"], expectedHeaderFiles: ["custom.tex"],
@@ -242,7 +262,7 @@ struct TestParseIncludeInHeaderFiles: Sendable {
loggingOptions: .init(commandName: "parseFiles", logLevel: .debug), loggingOptions: .init(commandName: "parseFiles", logLevel: .debug),
includeInHeader: ["custom.tex"] includeInHeader: ["custom.tex"]
) )
) ),
] ]
} }
@@ -266,7 +286,9 @@ struct TestParseOutputFileName: Sendable {
} }
var parsedFileName: String { var parsedFileName: String {
let runOptions = self.runOptions ?? PandocClient.RunOptions( let runOptions =
self.runOptions
?? PandocClient.RunOptions(
loggingOptions: .init(commandName: "parseFiles", logLevel: .debug) loggingOptions: .init(commandName: "parseFiles", logLevel: .debug)
) )
@@ -285,7 +307,7 @@ struct TestParseOutputFileName: Sendable {
loggingOptions: .init(commandName: "parseFiles", logLevel: .debug), loggingOptions: .init(commandName: "parseFiles", logLevel: .debug),
outputFileName: "custom" outputFileName: "custom"
) )
) ),
] ]
} }
@@ -309,7 +331,9 @@ struct TestParseBuildDirectory: Sendable {
} }
var parsedBuildDirectory: String { var parsedBuildDirectory: String {
let runOptions = self.runOptions ?? PandocClient.RunOptions( let runOptions =
self.runOptions
?? PandocClient.RunOptions(
loggingOptions: .init(commandName: "parseFiles", logLevel: .debug) loggingOptions: .init(commandName: "parseFiles", logLevel: .debug)
) )
@@ -328,6 +352,6 @@ struct TestParseBuildDirectory: Sendable {
buildDirectory: "custom", buildDirectory: "custom",
loggingOptions: .init(commandName: "parseFiles", logLevel: .debug) loggingOptions: .init(commandName: "parseFiles", logLevel: .debug)
) )
) ),
] ]
} }