import CommandClient import ConfigurationClient import Dependencies import Foundation import PlaybookClient extension PandocClient.RunOptions { func run(_ fileType: PandocClient.FileType) 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 ?? ProcessInfo.processInfo.environment["PWD"] guard let projectDirectory else { throw ProjectDirectoryNotSpecified() } if shouldBuild { 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 ) } } @_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: String switch outputFileType { case .html: extensionString = ".html" case .latex: extensionString = ".tex" case .pdf: extensionString = ".pdf" } 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 {}