namespace ClaudeDo.Data;
public static class Paths
{
///
/// Expands a leading "~" or "%USERPROFILE%" and returns an absolute path.
/// Relative paths are resolved against (default: current directory).
///
public static string Expand(string path, string? baseDir = null)
{
if (string.IsNullOrWhiteSpace(path))
throw new ArgumentException("Path must not be empty.", nameof(path));
var expanded = System.Environment.ExpandEnvironmentVariables(path);
if (expanded.StartsWith("~", StringComparison.Ordinal))
{
var home = System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile);
expanded = home + expanded[1..];
}
if (!Path.IsPathRooted(expanded))
expanded = Path.GetFullPath(expanded, baseDir ?? System.Environment.CurrentDirectory);
return Path.GetFullPath(expanded);
}
///
/// Strips trailing directory separators off a path bound for a Windows command line. Windows
/// argv rules read \" as an escaped quote, so a quoted token ending in '\' never closes
/// ("C:\repo\" parses as C:\repo") and either the path itself or every argument
/// after it is corrupted. A bare root ("C:\", "/") is all separator and is returned untouched;
/// so is null/blank.
///
public static string? TrimTrailingSeparator(string? path)
{
if (string.IsNullOrWhiteSpace(path))
return path;
var trimmed = path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
return trimmed.Length == 0 || trimmed.EndsWith(':') ? path : trimmed;
}
/// ~/.todo-app — parent directory for db, logs, config, sandbox, worktrees.
public static string AppDataRoot() =>
Expand("~/.todo-app");
}