From 357eecabf91ee2fc1c83a9148810ecf9c7c504d0 Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Mon, 17 Nov 2025 10:19:11 -0500 Subject: [PATCH] feat: Updates tests for html generation, adds some more documentation strings. --- Sources/PandocClient/PandocClient+Run.swift | 54 +++++++---- .../PandocClientTests/PandocClientTests.swift | 96 ++++++++++++------- 2 files changed, 94 insertions(+), 56 deletions(-) diff --git a/Sources/PandocClient/PandocClient+Run.swift b/Sources/PandocClient/PandocClient+Run.swift index f76e511..09eb6d2 100644 --- a/Sources/PandocClient/PandocClient+Run.swift +++ b/Sources/PandocClient/PandocClient+Run.swift @@ -6,14 +6,19 @@ import PlaybookClient 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( _ fileType: PandocClient.FileType, _ environment: [String: String] ) async throws -> String { - @Dependency(\.commandClient) var commandClient @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"] @@ -22,13 +27,12 @@ extension PandocClient.RunOptions { throw ProjectDirectoryNotSpecified() } - try await buildProject(fileType, projectDirectory, ensuredOptions) + try await buildProject(projectDirectory, ensuredOptions) let outputDirectory = self.outputDirectory ?? projectDirectory - let outputPath = "\(outputDirectory)/\(ensuredOptions.ensuredExtensionFileName)" + let outputPath = "\(outputDirectory)/\(ensuredOptions.ensuredFilename)" let arguments = ensuredOptions.makeArguments( - fileType: fileType, outputPath: outputPath, projectDirectory: projectDirectory ) @@ -36,16 +40,13 @@ extension PandocClient.RunOptions { 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( + func runCommand( _ arguments: [String], _ outputPath: 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 /// output file(s). /// - @_spi(Internal) - public func buildProject( - _ fileType: PandocClient.FileType, + func buildProject( _ projectDirectory: String, _ ensuredOptions: EnsuredPandocOptions ) async throws { @@ -87,11 +86,10 @@ extension PandocClient.RunOptions { // Build latex file pre-html, so that we can properly convert the latex document // into an html document. - if fileType == .html { + if ensuredOptions.outputFileType == .html { logger.debug("Building latex, pre-html conversion...") - let outputPath = "\(ensuredOptions.buildDirectory)/Report.tex" + let outputPath = "\(ensuredOptions.buildDirectory)/\(EnsuredPandocOptions.latexFilename)" let arguments = ensuredOptions.preHtmlLatexOptions.makeArguments( - fileType: .latex, outputPath: outputPath, 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( _ fileType: PandocClient.FileType ) async throws -> EnsuredPandocOptions { @@ -117,6 +120,7 @@ extension PandocClient.RunOptions { } extension PandocClient.FileType { + /// Represents the appropriate file extension for a file type. var fileExtension: String { switch self { 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) public struct EnsuredPandocOptions: Equatable, Sendable { public static let latexFilename = "Report.tex" @@ -138,7 +146,9 @@ public struct EnsuredPandocOptions: Equatable, Sendable { public let outputFileType: PandocClient.FileType 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)" if !outputFileName.hasSuffix(extensionString) { @@ -147,7 +157,9 @@ public struct EnsuredPandocOptions: Equatable, Sendable { 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( buildDirectory: buildDirectory, 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( - fileType: PandocClient.FileType, outputPath: String, projectDirectory: String ) -> [String] { var arguments = [PandocClient.Constants.pandocCommand] - if fileType != .html { + if outputFileType != .html { arguments += includeInHeader.map { "--include-in-header=\(projectDirectory)/\(buildDirectory)/\($0)" } @@ -182,7 +196,7 @@ public struct EnsuredPandocOptions: Equatable, Sendable { arguments.append(contentsOf: extraOptions) } - if fileType != .html { + if outputFileType != .html { arguments += files.map { "\(projectDirectory)/\(buildDirectory)/\($0)" } diff --git a/Tests/PandocClientTests/PandocClientTests.swift b/Tests/PandocClientTests/PandocClientTests.swift index 9537c28..ef4549f 100644 --- a/Tests/PandocClientTests/PandocClientTests.swift +++ b/Tests/PandocClientTests/PandocClientTests.swift @@ -1,8 +1,8 @@ @_spi(Internal) import ConfigurationClient @_spi(Internal) import PandocClient import PlaybookClient -import Testing import TestSupport +import Testing @Suite("PandocClientTests") struct PandocClientTests: TestCase { @@ -13,12 +13,12 @@ struct PandocClientTests: TestCase { static let expectedIncludeInHeaders = [ "--include-in-header=/project/.build/head.tex", - "--include-in-header=/project/.build/footer.tex" + "--include-in-header=/project/.build/footer.tex", ] static let expectedFiles = [ "/project/.build/Report.md", - "/project/.build/Definitions.md" + "/project/.build/Definitions.md", ] static var sharedRunOptions: PandocClient.RunOptions { @@ -49,7 +49,8 @@ struct PandocClientTests: TestCase { #expect(output == "\(Self.outputDirectory)/\(Self.defaultFileName).tex") } assert: { output in - let expected = ["pandoc"] + let expected = + ["pandoc"] + Self.expectedIncludeInHeaders + ["--output=\(Self.outputDirectory)/\(Self.defaultFileName).tex"] + Self.expectedFiles @@ -71,10 +72,11 @@ struct PandocClientTests: TestCase { #expect(output == "\(Self.outputDirectory)/\(Self.defaultFileName).html") } assert: { output in - let expected = ["pandoc"] - + Self.expectedIncludeInHeaders - + ["--output=\(Self.outputDirectory)/\(Self.defaultFileName).html"] - + Self.expectedFiles + let expected = [ + "pandoc", + "--output=\(Self.outputDirectory)/\(Self.defaultFileName).html", + "\(Self.projectDirectory)/.build/Report.tex", + ] #expect(output.arguments == expected) } @@ -83,7 +85,7 @@ struct PandocClientTests: TestCase { @Test( arguments: [ nil, - "lualatex" + "lualatex", ] ) func generatePdf(pdfEngine: String?) async throws { @@ -94,11 +96,13 @@ struct PandocClientTests: TestCase { } run: { @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") } assert: { output in - let expected = ["pandoc"] + let expected = + ["pandoc"] + Self.expectedIncludeInHeaders + ["--pdf-engine=\(pdfEngine ?? "xelatex")"] + ["--output=\(Self.outputDirectory)/\(Self.defaultFileName).pdf"] @@ -147,10 +151,18 @@ struct TestPdfEngine: Sendable { 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()) + .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()), ] } @@ -174,19 +186,23 @@ struct TestParseFiles: Sendable { } var parsedFiles: [String] { - let runOptions = self.runOptions ?? PandocClient.RunOptions( - loggingOptions: .init(commandName: "parseFiles", logLevel: .debug), - projectDirectory: nil, - quiet: true, - shouldBuild: false - ) + 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: ["Report.md", "Definitions.md"], configuration: .init(generate: .default), + defaults: .init()), .init(expectedFiles: [], defaults: .init()), .init( expectedFiles: ["custom.md"], @@ -199,7 +215,7 @@ struct TestParseFiles: Sendable { quiet: true, shouldBuild: false ) - ) + ), ] } @@ -223,16 +239,20 @@ struct TestParseIncludeInHeaderFiles: Sendable { } var parsedFiles: [String] { - let runOptions = self.runOptions ?? PandocClient.RunOptions( - loggingOptions: .init(commandName: "parseFiles", logLevel: .debug) - ) + 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: ["head.tex", "footer.tex"], configuration: .init(generate: .default), + defaults: .init()), .init(expectedHeaderFiles: [], defaults: .init()), .init( expectedHeaderFiles: ["custom.tex"], @@ -242,7 +262,7 @@ struct TestParseIncludeInHeaderFiles: Sendable { loggingOptions: .init(commandName: "parseFiles", logLevel: .debug), includeInHeader: ["custom.tex"] ) - ) + ), ] } @@ -266,9 +286,11 @@ struct TestParseOutputFileName: Sendable { } var parsedFileName: String { - let runOptions = self.runOptions ?? PandocClient.RunOptions( - loggingOptions: .init(commandName: "parseFiles", logLevel: .debug) - ) + let runOptions = + self.runOptions + ?? PandocClient.RunOptions( + loggingOptions: .init(commandName: "parseFiles", logLevel: .debug) + ) return runOptions.parseOutputFileName(configuration.generate, defaults) } @@ -285,7 +307,7 @@ struct TestParseOutputFileName: Sendable { loggingOptions: .init(commandName: "parseFiles", logLevel: .debug), outputFileName: "custom" ) - ) + ), ] } @@ -309,9 +331,11 @@ struct TestParseBuildDirectory: Sendable { } var parsedBuildDirectory: String { - let runOptions = self.runOptions ?? PandocClient.RunOptions( - loggingOptions: .init(commandName: "parseFiles", logLevel: .debug) - ) + let runOptions = + self.runOptions + ?? PandocClient.RunOptions( + loggingOptions: .init(commandName: "parseFiles", logLevel: .debug) + ) return runOptions.parseBuildDirectory(configuration.generate, defaults) } @@ -328,6 +352,6 @@ struct TestParseBuildDirectory: Sendable { buildDirectory: "custom", loggingOptions: .init(commandName: "parseFiles", logLevel: .debug) ) - ) + ), ] }