Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1260c66
(#224) LocalPath: implement PathRoot property
ForNeVeR Sep 22, 2026
b081c20
(#224) AbsolutePath: make PathRoot a property
ForNeVeR Sep 22, 2026
ca42d52
(#224) LocalPath: implement the new path combination algorithm
ForNeVeR Sep 23, 2026
332fde9
CI: update the XML doc verification to not clear experimental warning…
ForNeVeR Sep 25, 2026
49df5ff
Docs: add a reference to C++ standard as inspiration
ForNeVeR Sep 25, 2026
ed11a48
(#224) LocalPath: correct parent for drive-current-directory-relative…
ForNeVeR Sep 25, 2026
8d6e748
(#224) RelativeTo: cover with tests, introduce checks for interesting…
ForNeVeR Sep 26, 2026
888e385
(#224) PathEx: port the version from .NET
ForNeVeR Sep 26, 2026
8c02b39
(#224) Polyfills: put into correct namespace and rename
ForNeVeR Sep 26, 2026
9cb594d
Legal: update a copyright year in the docs
ForNeVeR Sep 26, 2026
6947b53
Tests: disable parallel execution for tests changing the CWD
ForNeVeR Sep 27, 2026
bb073f6
(#224) Docs: update the changelog
ForNeVeR Sep 27, 2026
e011f71
PathStrings: fix the case with `C:\base\..` getting normalized to `C:`
ForNeVeR Sep 27, 2026
02e29f4
(#224) Mark the UNC path problems with TODO[#24]
ForNeVeR Sep 27, 2026
d0a57ed
Temporary: explain the current directory resolution
ForNeVeR Sep 27, 2026
39286ac
PathStrings: mark the unresolved .. after the root with TODO[#95]
ForNeVeR Sep 27, 2026
1d05527
(#224) Docs: document the uninitialized path structures
ForNeVeR Sep 27, 2026
e76e158
(#224) Polyfills: move PathPolyfills into the Polyfills namespace
ForNeVeR Sep 27, 2026
f3b3ea3
(#224) PathPolyfills: mark the stream syntax problem with TODO[#235]
ForNeVeR Sep 27, 2026
d9cfc85
(#224) AbsolutePath: delegate PathRoot to LocalPath
ForNeVeR Sep 27, 2026
91e2d3b
(#224) Share the Windows platform check
ForNeVeR Sep 27, 2026
2ebaa71
(#224) LocalPath: fix the operator / documentation
ForNeVeR Sep 27, 2026
4837507
(#224) AbsolutePath: resolve paths relative to another drive in opera…
ForNeVeR Sep 27, 2026
1bb9f52
(#224) LocalPath: resolve to the current directory via AbsolutePath's…
ForNeVeR Sep 27, 2026
308b098
(#224) LocalPath: don't claim a case-sensitivity difference from C++
ForNeVeR Sep 27, 2026
467c08e
(#224) AbsolutePathTests: clean up a test with unclear semantics
ForNeVeR Sep 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:
- name: Verify encoding
shell: pwsh
run: scripts/Test-Encoding.ps1
nowarn-empty:
xml-doc:
runs-on: ubuntu-26.04
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
Expand All @@ -94,7 +94,5 @@ jobs:
with:
key: ${{ runner.os }}.nuget.${{ hashFiles('**/*.*proj', '**/*.props') }}
path: ${{ env.NUGET_PACKAGES }}
- name: Check out the sources
uses: actions/checkout@v7
- name: Verify with NoWarn as empty
run: dotnet build /p:NoWarn='' --no-incremental
- name: Verify XML documentation
run: dotnet build -p:CheckXmlDoc=true --no-incremental
19 changes: 16 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,30 @@ Changelog
=========
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), with one exception: the behavior of the members and types marked as `[System.Diagnostics.CodeAnalysis.ExperimentalAttribute]` may change without a major version bump (see [Versioning](README.md#versioning)).

## [Unreleased]
### Added
- `LocalPath.PathRoot`, returning the root of an absolute path, or the drive root of a drive-relative path (e.g. `C:foo`, which only exists on Windows), or `null` when the root can't be determined.
- `AbsolutePath.PathRoot` property.
- [#224](https://github.com/ForNeVeR/TruePath/issues/224): `PathKind` enumeration and `LocalPath.Kind` property, telling an absolute path from a relative one, and on Windows also from a path rooted without a drive letter (`\Windows`) and from a path relative to the current directory of a drive (`C:Windows`). These are **experimental** (diagnostic `TRUEPATH001`): more kinds may be added later, e.g. for UNC paths.

### Changed
- **Breaking:** on Windows, `LocalPath.IsAbsolute` now only returns `true` for fully qualified paths, such as `C:\Windows`. Paths rooted without a drive letter (`\Windows`), and paths relative to the current directory of a drive (`C:Windows`, `C:`) are not absolute anymore, and `AbsolutePath` now rejects them. Previously, this was following the `Path.IsPathRooted` API.
- **Breaking:** `LocalPath.IsPrefixOf` and `StartsWith` now consider paths of different `PathKind` unrelated: an absolute path is never a prefix of a relative one, nor the other way round, and on Windows `\Windows`, `C:Windows` and `Windows` are all unrelated to each other. Previously, the result depended on a string comparison.
- **Breaking:** on Windows, the `/` operator of `LocalPath` and `AbsolutePath` no longer delegates to `Path.Combine`, and instead follows the algorithm of C++ `std::filesystem::path::operator/` ([fs.path.append](https://eel.is/c++draft/fs.path.append)) with case-insensitive drive letters. In particular, `C:\base / \x` is now `C:\x` (was `\x`), `C:\base / C:x` is now `C:\base\x` (was `C:x`), and `C: / x` is now `C:x` (was `C:\x`). On Unix, the behavior is unchanged.
- On Windows, `AbsolutePath / LocalPath` now resolves a path relative to the current directory of another drive against the current directory of that drive, so the result is always absolute: e.g. `C:\base / D:x` is `D:\x` if the current directory of drive `D:` is its root. Previously, the result was `D:x`, which is not absolute.
- On Windows, `LocalPath.ResolveToCurrentDirectory` now resolves `\x` against the drive of the current directory, and `D:x` against the current directory of drive `D:`.
- **Breaking:** `LocalPath.StartsWith` and `AbsolutePath.StartsWith` now compare whole path segments instead of raw strings, which makes them exact inverses of `IsPrefixOf` as originally intended in [#43](https://github.com/ForNeVeR/TruePath/issues/43). For example, `new LocalPath("/foo1").StartsWith(new LocalPath("/foo"))` is now `false`, where it used to be `true`.
- `LocalPath.IsPrefixOf` now returns `false` whenever the two paths differ in absoluteness: an absolute path is never a prefix of a relative one, nor the other way round. Previously, the result depended on an incidental string comparison.
- `LocalPath.IsPrefixOf` now treats an empty path — the normalized form of `""`, `"."` and `"a/.."`, and the parent of any single-segment relative path — as the current directory, so it is a prefix of every relative path that does not begin with a `..` reference.
- `LocalPath.IsPrefixOf` now treats an empty path — the normalized form of `""`, `"."` and `"a/.."`, and the parent of any single-segment relative path — as the current directory, so it is a prefix of every path of kind `PathKind.Relative` that does not begin with a `..` reference.
- The platform-default path comparers (`LocalPath.PlatformDefaultComparer`, `AbsolutePath.PlatformDefaultComparer`) are now case-insensitive on iOS and tvOS as well, matching the .NET runtime.

### Fixed
- [#225](https://github.com/ForNeVeR/TruePath/issues/225): Make path prefix checks use the same platform-default case sensitivity as path equality.
- `LocalPath.IsPrefixOf` and `StartsWith` now compare path strings ordinally. Previously they used the current culture, which ignores collation-ignorable characters, so a path could be reported as a prefix of an unrelated one.
- Path normalization no longer drops the root separator when a rooted path ends with `..`: `C:\base\..` is now normalized to `C:\` (was `C:`, a path relative to the current directory of drive `C:`), and `/base/..` to `/` (was an empty path, i.e. the current directory). This affects `PathStrings.Normalize`, `LocalPath` and `AbsolutePath`.
- `LocalPath.RelativeTo` no longer throws an exception when either path is empty (i.e. designates the current directory).
- On .NET Standard 2.0 (e.g., .NET Framework), `LocalPath.RelativeTo` and `AbsolutePath.RelativeTo` now use a port of the .NET runtime's `Path.GetRelativePath`, and return the same results as on .NET 8+ (except for the stream syntax, see [#235](https://github.com/ForNeVeR/TruePath/issues/235)). Previously, the result was wrong for a destination equal to or above the base path, and for names containing `%XX` sequences.

## [1.12.0] - 2026-03-14
### Changed
Expand Down
7 changes: 4 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
SPDX-FileCopyrightText: 2024-2025 TruePath contributors <https://github.com/ForNeVeR/TruePath>
SPDX-FileCopyrightText: 2024-2026 TruePath contributors <https://github.com/ForNeVeR/TruePath>

SPDX-License-Identifier: MIT
-->
Expand All @@ -8,7 +8,7 @@ SPDX-License-Identifier: MIT
<PropertyGroup Label="Packaging">
<Version>1.12.0</Version>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Copyright>2024-2026 TruePath contributors &lt;https://github.com/ForNeVeR/TruePath&gt;</Copyright>
<Copyright>2024-2026 TruePath contributors &lt;https://github.com/ForNeVeR/TruePath&gt;; .NET Foundation and Contributors</Copyright>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
Expand All @@ -20,7 +20,8 @@ SPDX-License-Identifier: MIT
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>CS0419;CS1570;CS1571;CS1572;CS1573;CS1574;CS1580;CS1581;CS1584;CS1587;CS1589;CS1590;CS1591;CS1592;CS1598;CS1710;CS1711;CS1712;$(NoWarn)</NoWarn>
<!-- XML documentation warnings are suppressed for local development; CI verifies them with -p:CheckXmlDoc=true. -->
<NoWarn Condition="'$(CheckXmlDoc)' != 'true'">CS0419;CS1570;CS1571;CS1572;CS1573;CS1574;CS1580;CS1581;CS1584;CS1587;CS1589;CS1590;CS1591;CS1592;CS1598;CS1710;CS1711;CS1712;$(NoWarn)</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
Expand Down
1 change: 1 addition & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
MIT License

Copyright (c) 2024-2026 TruePath contributors <https://github.com/ForNeVeR/TruePath>
Copyright (c) .NET Foundation and Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
SPDX-FileCopyrightText: 2024-2025 Friedrich von Never <friedrich@fornever.me>
SPDX-FileCopyrightText: 2024-2026 Friedrich von Never <friedrich@fornever.me>

SPDX-License-Identifier: MIT
-->
Expand All @@ -16,7 +16,9 @@ This library aims to fill this gap by providing a set of types that represent pa

Also, the methods in the library provide some qualities that are missing from the `System.IO.Path`: say, we aim to provide several ways of path normalization and comparison, the ones that will and will not perform disk IO to resolve paths on case-insensitive file systems.

The library is inspired by the path libraries used in other ecosystems: in particular, Java's [java.nio.file.Path][java.path] and [Kotlin's extensions][kotlin.path].
The library is inspired by the path libraries used in other ecosystems, in no particular order:
- Java's [java.nio.file.Path][java.path] and [Kotlin's extensions][kotlin.path] for general API shape;
- [\[fs.path.append\]][cpp.fs.path.append] from the C++ standard for path concatenation algorithms.

Read more on [the documentation site][docs].

Expand All @@ -36,6 +38,10 @@ TruePath provides two NuGet packages:

A third-party package, [**TruePath.TestableIO.System.IO**][nuget.true-path.testableio.system-io], adds [TestableIO.System.IO.Abstractions][testable-io.system.io.abstractions] integration on top of TruePath.

Versioning
----------
This project follows [Semantic Versioning][semver], with one exception: the behavior of the types and members marked as `[System.Diagnostics.CodeAnalysis.ExperimentalAttribute]` may change without a major version bump. Using them causes the compiler to report diagnostic `TRUEPATH001`; suppress it (e.g. by adding `<NoWarn>$(NoWarn);TRUEPATH001</NoWarn>` into your project file, or with `#pragma warning disable TRUEPATH001`) to acknowledge that.

Documentation
-------------
- [Project Documentation Site][docs]
Expand All @@ -50,6 +56,7 @@ The project is distributed under the terms of [the MIT license][docs.license].
The license indication in the project's sources is compliant with the [REUSE specification v3.3][reuse.spec].

[andivionian-status-classifier]: https://andivionian.fornever.me/v1/#status-ventis-
[cpp.fs.path.append]: https://eel.is/c++draft/fs.path.append
[discussions]: https://github.com/ForNeVeR/TruePath/discussions
[docs.changelog]: CHANGELOG.md
[docs.contributing]: CONTRIBUTING.md
Expand All @@ -69,5 +76,6 @@ The license indication in the project's sources is compliant with the [REUSE spe
[nuget.true-path.testableio.system-io]: https://www.nuget.org/packages/TruePath.TestableIO.System.IO/
[nuget.true-path]: https://www.nuget.org/packages/TruePath
[reuse.spec]: https://reuse.software/spec-3.3/
[semver]: https://semver.org/spec/v2.0.0.html
[status-ventis]: https://img.shields.io/badge/status-ventis-yellow.svg
[testable-io.system.io.abstractions]: https://github.com/TestableIO/System.IO.Abstractions
Loading
Loading