From bb1ce9febd52989949a7f178d1aba28d3a6280aa Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 29 May 2025 16:29:47 +0200 Subject: [PATCH] Refactor Pandoc process setup using a dedicated builder class --- app/MindWork AI Studio/Tools/Pandoc.cs | 80 +---------- .../Tools/PandocProcessBuilder.cs | 135 ++++++++++++++++++ 2 files changed, 141 insertions(+), 74 deletions(-) create mode 100644 app/MindWork AI Studio/Tools/PandocProcessBuilder.cs diff --git a/app/MindWork AI Studio/Tools/Pandoc.cs b/app/MindWork AI Studio/Tools/Pandoc.cs index 6f4dc906..5a99a924 100644 --- a/app/MindWork AI Studio/Tools/Pandoc.cs +++ b/app/MindWork AI Studio/Tools/Pandoc.cs @@ -24,31 +24,10 @@ public static partial class Pandoc private static readonly Version FALLBACK_VERSION = new (3, 7, 0, 2); /// - /// Prepares a ProcessStartInfo for running pandoc with the given parameters. + /// Prepares a Pandoc process by using the Pandoc process builder. /// - /// - /// Any local installation of pandoc will be preferred over the system-wide installation. - /// - /// The global rust service to access file system and data dir. - /// The input file to convert. - /// The output file to write the converted content to. - /// The format of the input file (e.g., markdown, html, etc.). - /// The format of the output file (e.g., pdf, docx, etc.). - /// Additional arguments to pass to the pandoc command (optional). - /// The ProcessStartInfo object configured to run pandoc with the specified parameters. - public static async Task PreparePandocProcess(RustService rustService, string inputFile, string outputFile, string inputFormat, string outputFormat, string? additionalArgs = null) - { - var pandocExecutable = await PandocExecutablePath(rustService); - return new (new ProcessStartInfo - { - FileName = pandocExecutable.Executable, - Arguments = $"{inputFile} -f {inputFormat} -t {outputFormat} {additionalArgs ?? string.Empty} -o {outputFile}", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }, pandocExecutable.IsLocalInstallation); - } + /// The Pandoc process builder with default settings. + public static PandocProcessBuilder PreparePandocProcess() => PandocProcessBuilder.Create(); /// /// Checks if pandoc is available on the system and can be started as a process or is present in AI Studio's data dir. @@ -60,11 +39,8 @@ public static partial class Pandoc { try { - var preparedProcess = await PreparePandocProcess(rustService, string.Empty, string.Empty, string.Empty, string.Empty); - var startInfo = preparedProcess.StartInfo; - startInfo.Arguments = "--version"; - - using var process = Process.Start(startInfo); + var preparedProcess = await PreparePandocProcess().AddArgument("--version").BuildAsync(rustService); + using var process = Process.Start(preparedProcess.StartInfo); if (process == null) { if (showMessages) @@ -295,51 +271,7 @@ public static partial class Pandoc } } - /// - /// Reads the os platform to determine the used executable name. - /// - private static string PandocExecutableName => CPU_ARCHITECTURE is RID.WIN_ARM64 or RID.WIN_X64 ? "pandoc.exe" : "pandoc"; - - /// - /// Returns the path to the pandoc executable. - /// - /// - /// Any local installation of pandoc will be preferred over the system-wide installation. - /// When a local installation is found, its absolute path will be returned. In case no local - /// installation is found, the name of the pandoc executable will be returned. - /// - /// Global rust service to access file system and data dir. - /// Path to the pandoc executable. - private static async Task PandocExecutablePath(RustService rustService) - { - // - // First, we try to find the pandoc executable in the data directory. - // Any local installation should be preferred over the system-wide installation. - // - var localInstallationRootDirectory = await GetPandocDataFolder(rustService); - try - { - var executableName = PandocExecutableName; - var subdirectories = Directory.GetDirectories(localInstallationRootDirectory); - foreach (var subdirectory in subdirectories) - { - var pandocPath = Path.Combine(subdirectory, executableName); - if (File.Exists(pandocPath)) - return new(pandocPath, true); - } - } - catch - { - // ignored - } - - // - // When no local installation was found, we assume that the pandoc executable is in the system PATH. - // - return new(PandocExecutableName, false); - } - - private static async Task GetPandocDataFolder(RustService rustService) => Path.Join(await rustService.GetDataDirectory(), "pandoc"); + public static async Task GetPandocDataFolder(RustService rustService) => Path.Join(await rustService.GetDataDirectory(), "pandoc"); [GeneratedRegex(@"pandoc(?:\.exe)?\s*([0-9]+\.[0-9]+(?:\.[0-9]+)?(?:\.[0-9]+)?)")] private static partial Regex PandocCmdRegex(); diff --git a/app/MindWork AI Studio/Tools/PandocProcessBuilder.cs b/app/MindWork AI Studio/Tools/PandocProcessBuilder.cs new file mode 100644 index 00000000..b34fe8b7 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PandocProcessBuilder.cs @@ -0,0 +1,135 @@ +using System.Diagnostics; +using System.Reflection; +using System.Text; + +using AIStudio.Tools.Metadata; +using AIStudio.Tools.Services; + +using SharedTools; + +namespace AIStudio.Tools; + +public sealed class PandocProcessBuilder +{ + private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly(); + private static readonly MetaDataArchitectureAttribute META_DATA_ARCH = ASSEMBLY.GetCustomAttribute()!; + private static readonly RID CPU_ARCHITECTURE = META_DATA_ARCH.Architecture.ToRID(); + + private string? providedInputFile; + private string? providedOutputFile; + private string? providedInputFormat; + private string? providedOutputFormat; + + private readonly List additionalArguments = new(); + + private PandocProcessBuilder() + { + } + + public static PandocProcessBuilder Create() => new(); + + public PandocProcessBuilder WithInputFile(string inputFile) + { + this.providedInputFile = inputFile; + return this; + } + + public PandocProcessBuilder WithOutputFile(string outputFile) + { + this.providedOutputFile = outputFile; + return this; + } + + public PandocProcessBuilder WithInputFormat(string inputFormat) + { + this.providedInputFormat = inputFormat; + return this; + } + + public PandocProcessBuilder WithOutputFormat(string outputFormat) + { + this.providedOutputFormat = outputFormat; + return this; + } + + public PandocProcessBuilder AddArgument(string argument) + { + this.additionalArguments.Add(argument); + return this; + } + + public async Task BuildAsync(RustService rustService) + { + var sbArguments = new StringBuilder(); + + if(!string.IsNullOrWhiteSpace(this.providedInputFile)) + sbArguments.Append(this.providedInputFile); + + if(!string.IsNullOrWhiteSpace(this.providedInputFormat)) + sbArguments.Append($" -f {this.providedInputFormat}"); + + if(!string.IsNullOrWhiteSpace(this.providedOutputFormat)) + sbArguments.Append($" -t {this.providedOutputFormat}"); + + foreach (var additionalArgument in this.additionalArguments) + sbArguments.Append($" {additionalArgument}"); + + if(!string.IsNullOrWhiteSpace(this.providedOutputFile)) + sbArguments.Append($" -o {this.providedOutputFile}"); + + var pandocExecutable = await PandocExecutablePath(rustService); + return new (new ProcessStartInfo + { + FileName = pandocExecutable.Executable, + Arguments = sbArguments.ToString(), + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }, pandocExecutable.IsLocalInstallation); + } + + /// + /// Returns the path to the pandoc executable. + /// + /// + /// Any local installation of pandoc will be preferred over the system-wide installation. + /// When a local installation is found, its absolute path will be returned. In case no local + /// installation is found, the name of the pandoc executable will be returned. + /// + /// Global rust service to access file system and data dir. + /// Path to the pandoc executable. + private static async Task PandocExecutablePath(RustService rustService) + { + // + // First, we try to find the pandoc executable in the data directory. + // Any local installation should be preferred over the system-wide installation. + // + var localInstallationRootDirectory = await Pandoc.GetPandocDataFolder(rustService); + try + { + var executableName = PandocExecutableName; + var subdirectories = Directory.GetDirectories(localInstallationRootDirectory); + foreach (var subdirectory in subdirectories) + { + var pandocPath = Path.Combine(subdirectory, executableName); + if (File.Exists(pandocPath)) + return new(pandocPath, true); + } + } + catch + { + // ignored + } + + // + // When no local installation was found, we assume that the pandoc executable is in the system PATH. + // + return new(PandocExecutableName, false); + } + + /// + /// Reads the os platform to determine the used executable name. + /// + private static string PandocExecutableName => CPU_ARCHITECTURE is RID.WIN_ARM64 or RID.WIN_X64 ? "pandoc.exe" : "pandoc"; +} \ No newline at end of file