using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
namespace Undoc;
internal static class NativeMethods
{
private const string LibraryName = "undoc";
static NativeMethods()
{
NativeLibrary.SetDllImportResolver(typeof(NativeMethods).Assembly, ResolveDllImport);
}
private static IntPtr ResolveDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
if (libraryName != LibraryName)
return IntPtr.Zero;
foreach (var candidatePath in GetCandidatePaths(assembly))
{
if (NativeLibrary.TryLoad(candidatePath, out var handle))
return handle;
}
string[] namesToTry;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
namesToTry = new[] { "undoc_native", "undoc" };
}
else
{
namesToTry = new[] { "undoc" };
}
foreach (var name in namesToTry)
{
if (NativeLibrary.TryLoad(name, assembly, searchPath, out var handle))
return handle;
}
return IntPtr.Zero;
}
private static string[] GetCandidatePaths(Assembly assembly)
{
var assemblyDir = Path.GetDirectoryName(assembly.Location);
return GetCandidatePaths(
assemblyDir,
AppContext.BaseDirectory,
GetRuntimeIdentifier(),
GetLibraryFileNames())
.Where(File.Exists)
.ToArray();
}
internal static string[] BuildCandidatePaths(
string? baseDir,
string? assemblyDir,
string? runtimeId,
IEnumerable<string> fileNames)
{
var candidates = new List<string>();
foreach (var probeRoot in GetProbeRoots(assemblyDir, baseDir, runtimeId))
{
foreach (var fileName in fileNames)
{
candidates.Add(Path.Combine(probeRoot, fileName));
}
}
return candidates.Distinct().ToArray();
}
internal static string[] GetCandidatePaths(
string? assemblyDir,
string? baseDir,
string? runtimeId,
IReadOnlyList<string> fileNames)
{
return BuildCandidatePaths(baseDir, assemblyDir, runtimeId, fileNames)
.Where(File.Exists)
.ToArray();
}
private static IEnumerable<string> GetProbeRoots(
string? assemblyDir,
string? baseDir,
string? runtimeId)
{
if (!string.IsNullOrEmpty(baseDir) && !string.IsNullOrEmpty(runtimeId))
{
yield return Path.Combine(baseDir, "runtimes", runtimeId, "native");
}
if (!string.IsNullOrEmpty(assemblyDir) && !string.IsNullOrEmpty(runtimeId))
{
yield return Path.Combine(assemblyDir, "runtimes", runtimeId, "native");
}
if (!string.IsNullOrEmpty(baseDir))
{
yield return baseDir;
}
if (!string.IsNullOrEmpty(assemblyDir))
{
yield return assemblyDir;
}
}
internal static string? GetRuntimeIdentifierForCurrentPlatform() => GetRuntimeIdentifier();
internal static string[] GetLibraryFileNamesForCurrentPlatform() => GetLibraryFileNames();
private static string[] GetLibraryFileNames()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return new[] { "undoc_native.dll", "undoc.dll" };
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return new[] { "libundoc.dylib" };
return new[] { "libundoc.so" };
}
internal static string? GetRuntimeIdentifier()
{
return RuntimeInformation.OSArchitecture switch
{
Architecture.X64 when RuntimeInformation.IsOSPlatform(OSPlatform.Windows) => "win-x64",
Architecture.X64 when RuntimeInformation.IsOSPlatform(OSPlatform.OSX) => "osx-x64",
Architecture.Arm64 when RuntimeInformation.IsOSPlatform(OSPlatform.OSX) => "osx-arm64",
Architecture.X64 when RuntimeInformation.IsOSPlatform(OSPlatform.Linux) =>
IsMuslLinux() ? "linux-musl-x64" : "linux-x64",
_ => null,
};
}
private static bool IsMuslLinux()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return false;
try
{
if (File.Exists("/etc/os-release"))
{
var osRelease = File.ReadAllText("/etc/os-release");
if (osRelease.Contains("alpine", StringComparison.OrdinalIgnoreCase))
return true;
}
}
catch (IOException)
{
}
catch (UnauthorizedAccessException)
{
}
try
{
using var process = Process.Start(new ProcessStartInfo
{
FileName = "ldd",
ArgumentList = { "--version" },
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
});
if (process is null)
return false;
var output = process.StandardOutput.ReadToEnd() + process.StandardError.ReadToEnd();
process.WaitForExit(5000);
return output.Contains("musl", StringComparison.OrdinalIgnoreCase);
}
catch (InvalidOperationException)
{
return false;
}
catch (Win32Exception)
{
return false;
}
}
public const uint UNDOC_FLAG_FRONTMATTER = 1;
public const uint UNDOC_FLAG_ESCAPE_SPECIAL = 2;
public const uint UNDOC_FLAG_PARAGRAPH_SPACING = 4;
public const int UNDOC_JSON_PRETTY = 0;
public const int UNDOC_JSON_COMPACT = 1;
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr undoc_version();
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr undoc_last_error();
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern IntPtr undoc_parse_file([MarshalAs(UnmanagedType.LPUTF8Str)] string path);
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr undoc_parse_bytes(IntPtr data, UIntPtr len);
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern void undoc_free_document(IntPtr doc);
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr undoc_to_markdown(IntPtr doc, uint flags);
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr undoc_to_text(IntPtr doc);
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr undoc_to_json(IntPtr doc, int format);
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr undoc_plain_text(IntPtr doc);
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern int undoc_section_count(IntPtr doc);
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern int undoc_resource_count(IntPtr doc);
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr undoc_get_title(IntPtr doc);
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr undoc_get_author(IntPtr doc);
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern void undoc_free_string(IntPtr str);
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr undoc_get_resource_ids(IntPtr doc);
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern IntPtr undoc_get_resource_info(
IntPtr doc,
[MarshalAs(UnmanagedType.LPUTF8Str)] string resourceId);
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern IntPtr undoc_get_resource_data(
IntPtr doc,
[MarshalAs(UnmanagedType.LPUTF8Str)] string resourceId,
out UIntPtr outLen);
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern void undoc_free_bytes(IntPtr data, UIntPtr len);
}