Refactored the Pandoc process setup and improved the executable path handling.

This commit is contained in:
Thorsten Sommer 2025-05-29 15:50:51 +02:00
parent a31b3fff2d
commit 7dcb9416a2
Signed by: tsommer
GPG Key ID: 371BBA77A02C0108

View File

@ -23,6 +23,29 @@ public static partial class Pandoc
private static readonly Version MINIMUM_REQUIRED_VERSION = new (3, 7); private static readonly Version MINIMUM_REQUIRED_VERSION = new (3, 7);
private static readonly Version FALLBACK_VERSION = new (3, 7, 0, 2); private static readonly Version FALLBACK_VERSION = new (3, 7, 0, 2);
/// <summary>
/// Prepares a ProcessStartInfo for running pandoc with the given parameters.
/// </summary>
/// <remarks>
/// Any local installation of pandoc will be preferred over the system-wide installation.
/// </remarks>
/// <param name="rustService">The global rust service to access file system and data dir.</param>
/// <param name="inputFile">The input file to convert.</param>
/// <param name="outputFile">The output file to write the converted content to.</param>
/// <param name="inputFormat">The format of the input file (e.g., markdown, html, etc.).</param>
/// <param name="outputFormat">The format of the output file (e.g., pdf, docx, etc.).</param>
/// <param name="additionalArgs">Additional arguments to pass to the pandoc command (optional).</param>
/// <returns>The ProcessStartInfo object configured to run pandoc with the specified parameters.</returns>
public static async Task<ProcessStartInfo> PreparePandocProcess(RustService rustService, string inputFile, string outputFile, string inputFormat, string outputFormat, string? additionalArgs = null) => new()
{
FileName = await PandocExecutablePath(rustService),
Arguments = $"{inputFile} -f {inputFormat} -t {outputFormat} {additionalArgs ?? string.Empty} -o {outputFile}",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
/// <summary> /// <summary>
/// Checks if pandoc is available on the system and can be started as a process or is present in AI Studio's data dir. /// Checks if pandoc is available on the system and can be started as a process or is present in AI Studio's data dir.
/// </summary> /// </summary>
@ -31,20 +54,11 @@ public static partial class Pandoc
/// <returns>True, if pandoc is available and the minimum required version is met, else false.</returns> /// <returns>True, if pandoc is available and the minimum required version is met, else false.</returns>
public static async Task<bool> CheckAvailabilityAsync(RustService rustService, bool showMessages = true) public static async Task<bool> CheckAvailabilityAsync(RustService rustService, bool showMessages = true)
{ {
var installDir = await GetPandocDataFolder(rustService);
if (HasPandoc(installDir))
return true;
try try
{ {
var startInfo = new ProcessStartInfo var startInfo = await PreparePandocProcess(rustService, string.Empty, string.Empty, string.Empty, string.Empty);
{ startInfo.Arguments = "--version";
FileName = PandocExecutableName,
Arguments = "--version",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(startInfo); using var process = Process.Start(startInfo);
if (process == null) if (process == null)
{ {
@ -103,27 +117,6 @@ public static partial class Pandoc
} }
} }
private static bool HasPandoc(string pandocDirectory)
{
try
{
var subdirectories = Directory.GetDirectories(pandocDirectory);
foreach (var subdirectory in subdirectories)
{
var pandocPath = Path.Combine(subdirectory, PandocExecutableName);
if (File.Exists(pandocPath))
return true;
}
return false;
}
catch (Exception ex)
{
LOG.LogInformation("Pandoc is not installed in the data directory and might have thrown and error: {0}", ex.Message);
return false;
}
}
/// <summary> /// <summary>
/// Automatically decompresses the latest pandoc archive into AiStudio's data directory /// Automatically decompresses the latest pandoc archive into AiStudio's data directory
/// </summary> /// </summary>
@ -302,6 +295,45 @@ public static partial class Pandoc
/// </summary> /// </summary>
private static string PandocExecutableName => CPU_ARCHITECTURE is RID.WIN_ARM64 or RID.WIN_X64 ? "pandoc.exe" : "pandoc"; private static string PandocExecutableName => CPU_ARCHITECTURE is RID.WIN_ARM64 or RID.WIN_X64 ? "pandoc.exe" : "pandoc";
/// <summary>
/// Returns the path to the pandoc executable.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="rustService">Global rust service to access file system and data dir.</param>
/// <returns>Path to the pandoc executable.</returns>
private static async Task<string> 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 pandocPath;
}
}
catch
{
// ignored
}
//
// When no local installation was found, we assume that the pandoc executable is in the system PATH.
//
return PandocExecutableName;
}
private static async Task<string> GetPandocDataFolder(RustService rustService) => Path.Join(await rustService.GetDataDirectory(), "pandoc"); private static async Task<string> GetPandocDataFolder(RustService rustService) => Path.Join(await rustService.GetDataDirectory(), "pandoc");
[GeneratedRegex(@"pandoc(?:\.exe)?\s*([0-9]+\.[0-9]+(?:\.[0-9]+)?(?:\.[0-9]+)?)")] [GeneratedRegex(@"pandoc(?:\.exe)?\s*([0-9]+\.[0-9]+(?:\.[0-9]+)?(?:\.[0-9]+)?)")]