feat(paths): support the XDG Base Directory specification via the ?state path token - #36
Open
line0 wants to merge 7 commits into
Open
feat(paths): support the XDG Base Directory specification via the ?state path token#36line0 wants to merge 7 commits into
line0 wants to merge 7 commits into
Conversation
…oken Aegisub's XDG support (arch1t3cht/Aegisub#160) adds a ?state path token and moves its own logs to ?state/log. Point DepCtrl's log directory there and its cache at ?local/cache, the XDG cache home, leaving config on ?user. The new path-ops module probes each token once and substitutes a fallback for one the running build can't resolve, so ?state degrades to ?user on an Aegisub that predates it and a path setting naming ?state works on every version. Decoding for logs, caches, package configs and every FileOps path now routes through it. The headless shim gains ?state (DEPCTRL_STATE_DIR), defaulting to ?user as Aegisub does on Windows and macOS. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
FileOps requires Logger, so nothing at or below Logger in the dependency graph can reach its path helpers; Logger assembles log file paths by hand for that reason. PathOps sits below Logger and now holds the handling that never touches disk: joinPath, pathSegments, validateFullPath, getTempDir, __getPathRoot, the long-path detection and the path limit constants. FileOps re-exports each under its existing name, so external callers are unaffected. getNamespacedPath stays behind, since domain.validateNamespace reaches Logger through Enum and would close the cycle. The path tests move to the PathOps suite, where the limit overrides have to target the fields the path code actually reads. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ocations Identification, not location, decides what goes: a log file matching the name Logger gives DepCtrl's own, and a cache index whose contents decode as one plus the snapshot it names. Aegisub's logs shared the old log directory before it gained ?state, and a cache directory holding no decodable index was never ours to begin with, so both survive untouched. A directory is removed only when the sweep emptied it. Both locations are skipped when they resolve to the configured one, which is every platform where Aegisub maps ?state onto ?user, and covers a user who set the old path deliberately. No migration flag is needed: the sweep removes its own trigger, so it costs one directory probe once the location is gone, and a file it cannot delete is retried on the next run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MS1 already lists postfix conditionals and loop modifiers among the idioms to prefer, but says nothing about when a block is the wrong shape for a body that is one statement. MS5 states the test — it has to fit the line and stay readable — and shows the `when` filter collapsing a guarded accumulation that would otherwise carry an inner `if`. PathOps.isTokenSupported had the memo lookup's early return in a block; it folds into a postfix `unless`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nd defaults Resolving an index entry's snapshot — joining it onto the cache directory and confirming the file is still there — was written out in both getFile and get, so the rule for where a snapshot lives had two sites. A private __snapshotPath holds it once; get keeps its own index read, so nothing pays for a second decode. The lock namespace spelled out l0.DependencyControl, which constants already carries, and the lock holder name repeated the class name a third time. Both derive now, so renaming the class can't leave a stale identity behind (ID1). The FileCacheOptions annotations restated the 3600 and 50 that defaultMaxAge and defaultMaxFiles define twelve lines below, with nothing keeping the two in step; they point at the fields instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
Test results — macos-latest ✅1084 · ubuntu-latest ✅1084 · windows-latest ✅1081Summary
🎉 No failed tests in this run. Github Test Reporter by CTRF 💚 🔄 This comment has been updated |
…ve callers over validateFullPath split a "device" off the front of the directory, leaving `dir` relative to it: `/usr/local/bin` came back as `/usr` plus `/local`, and `C:\Users\foo\bar.txt` as `C:\` plus `Users\foo`. Neither is a directory a caller can use, which is why the one consumer of the pair, FileOps.mkdir, concatenated them straight back together. It now reports the whole directory, which frees the second return to be an error and nothing else — it previously carried the device on success, and getAttributes already misread it as an error under the name `dev`. The name went with the shape. "Validate" described only the failure half, while every call site wants the canonical absolute path it produces, so the PathOps member is resolveFullPath. FileOps.validateFullPath keeps the pre-0.9 four values for callers written against it, deprecated, splitting the root back off through PathOps._getPathRoot; the same split backs the pre-0.7 `attributes` shim. FileOpsAttributesInfo drops `dev` outright and its `dir` is absolute: it shipped in 0.7.0 nine days ago and nothing outside FileOps read it. Internal callers move off the deprecated re-exports at the same time, since deprecating a function and then calling it from every module advertises the opposite of what the deprecation says. Test doubles follow their subjects, and their module-name constants gained the word boundary they were missing — FILEOPS only passed the spell check because cspell's AWS dictionary carries it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The repo spell-checks against cspell's en-US dictionary, which rejected the two British forms in these comments. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
line0
force-pushed
the
xdg-base-dir-spec-support
branch
from
August 2, 2026 13:52
69faebc to
6668919
Compare
This was referenced Aug 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Companion to arch1t3cht/Aegisub#160, which adds a
?statepath token and moves Aegisub's own logs, autosaves and recovered files there. This points DependencyControl's paths at the matching XDG locations, while degrading cleanly on every Aegisub that predates the token.Where files go
paths.log?user/log?state/logpaths.cache?user/cache?local/cachepaths.config?user/configInstalled scripts stay under
?user/automation/…, since the PR leavesPath/Automation/*pointing there.Only Windows and XDG-mode Linux see a real relocation — elsewhere
?stateand?localresolve to the same directory as?user.Backwards compatibility
An Aegisub without
?statereturns the token verbatim fromdecode_path, which would leave a literal?state/logdirectory next to the working directory.PathOpsprobes each token once and substitutes a fallback (?state→?user) when the running build can't resolve it, so a path setting naming?stateworks on every version and no config migration is needed.Files an earlier release left in the old locations are swept on startup. Identification is by content, not location: log files matching the name
Loggergives DepCtrl's own, and cache entries in a directory holding an index that decodes. Aegisub's own logs shared the old log directory and are untouched, as is any file the sweep can't identify. Directories are only removed when the sweep emptied them, and the sweep is self-extinguishing — one directory probe once the location is gone.Along the way
DependencyControl.PathOps— path composition, validation and token resolution, with no filesystem access.joinPath,pathSegments,getTempDir,validateFullPathand the path constants moved out ofFileOps, which keeps deprecated re-exports. The split was forced by the dependency graph:FileOpsrequiresLogger, so nothing at or belowLoggercould reach its path helpers — which is whyLoggerwas hand-building log paths with a raw/.getNamespacedPathmoved toDomain, joininggetAutomationDirandgetTestDiras the third namespace-to-path mapping.validateFullPath→PathOps.resolveFullPath, now returning the absolute directory instead of a device plus a fragment relative to it. The olddirwas unusable alone on both platforms, which is why its only consumer concatenated the pair straight back together.Breaking
FileOps.getAttributesdropsdevfrom its info table and itsdiris now absolute. The type shipped in 0.7.0 nine days ago and nothing outsideFileOpsread it.FileOps.validateFullPathkeeps its old four-value shape, deprecated.Both packages bump to 0.9.0.
Testing
Full suite green (4 packages). New coverage: 8 tests for token fallback, 5 for cache-artifact recognition, 6 for the legacy sweep, plus assertions pinning the legacy shape the deprecated shim still has to produce. Feed validates against schema v0.4.0; types and docs generate with 0 warnings.
Deferred
UNC paths are rejected by
resolveFullPath—^[A-Za-z]:[/\]never matches\server\share, and the separator-collapsing gsub mangles the leading\first. Internal-only fix, no API impact, so it's left for its own PR; worth deciding there whether feed-supplied install paths should be allowed to be UNC.🤖 Generated with Claude Code