public void all_kind_of_root(string p, NormalizedPathRootKind o, string path) { var n = new NormalizedPath(p); n.RootKind.Should().Be(o); n.Path.Should().Be(path); }
NormalizedPath(string[]?parts, string path, NormalizedPathRootKind o) { Debug.Assert(path != null); Debug.Assert(parts != null || o != NormalizedPathRootKind.RootedByFirstPart, "parts == null ==> option != RootedByFirstPart"); _parts = parts; _path = path; _option = o; }
static string BuildNonEmptyPath(string[] parts, NormalizedPathRootKind o) { var path = parts.Concatenate(DirectorySeparatorString); switch (o) { case NormalizedPathRootKind.RootedBySeparator: return(DirectorySeparatorChar + path); case NormalizedPathRootKind.RootedByDoubleSeparator: return(DoubleDirectorySeparatorString + path); default: return(path); } }
NormalizedPath(NormalizedPathRootKind o) { if (o == NormalizedPathRootKind.RootedByFirstPart) { o = NormalizedPathRootKind.None; } _parts = null; _path = o == NormalizedPathRootKind.RootedBySeparator ? DirectorySeparatorString : (o == NormalizedPathRootKind.RootedByDoubleSeparator ? DoubleDirectorySeparatorString : String.Empty); _option = o; }
public void changing_RootKind(string p, NormalizedPathRootKind newKind, string result) { if (result == "ArgumentException") { new NormalizedPath(p).Invoking(sut => sut.With(newKind)) .Should().Throw <ArgumentException>(); } else { var r = new NormalizedPath(p).With(newKind); r.RootKind.Should().Be(newKind); r.Should().Be(new NormalizedPath(result)); } }
/// <summary> /// Explicitly builds a new <see cref="NormalizedPath"/> struct from a string (that can be null or empty). /// </summary> /// <param name="path">The path as a string (can be null or empty).</param> public NormalizedPath(string path) { _parts = path?.Split(_separators, StringSplitOptions.RemoveEmptyEntries); if (_parts == null || _parts.Length == 0) { _parts = null; _path = String.Empty; _option = NormalizedPathRootKind.None; if (path != null && path.Length > 0) { if (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar) { if (path.Length > 1 && (path[1] == DirectorySeparatorChar || path[1] == AltDirectorySeparatorChar)) { _path = DoubleDirectorySeparatorString; _option = NormalizedPathRootKind.RootedByDoubleSeparator; } else { _path = DirectorySeparatorString; _option = NormalizedPathRootKind.RootedBySeparator; } } } } else { Debug.Assert(path != null); var c = path[0]; if (c == DirectorySeparatorChar || c == AltDirectorySeparatorChar) { _path = DirectorySeparatorChar + _parts.Concatenate(DirectorySeparatorString); if (path.Length > 1) { c = path[1]; if (c == DirectorySeparatorChar || c == AltDirectorySeparatorChar) { _path = DirectorySeparatorChar + _path; _option = NormalizedPathRootKind.RootedByDoubleSeparator; } else { _option = NormalizedPathRootKind.RootedBySeparator; } } else { _option = NormalizedPathRootKind.None; } } else if (c == '~') { _option = NormalizedPathRootKind.RootedByFirstPart; _path = _parts.Concatenate(DirectorySeparatorString); } else { _option = NormalizedPathRootKind.None; _path = _parts.Concatenate(DirectorySeparatorString); var first = _parts[0]; if (first.Length > 0 && first[first.Length - 1] == ':') { _option = NormalizedPathRootKind.RootedByFirstPart; } } } Debug.Assert(_parts != null || _option != NormalizedPathRootKind.RootedByFirstPart, "parts == null ==> option != RootedByFirstPart"); }
/// <summary> /// Returns a path where '.' and '..' parts are resolved under a root part. /// When <paramref name="throwOnAboveRoot"/> is true (the default), any '..' that would /// lead to a path above the root throws an <see cref="InvalidOperationException"/>. /// When false, the root acts as an absorbing element. /// </summary> /// <param name="rootPartsCount"> /// By default, the resolution can reach the empty root. /// By specifying a positive number, any prefix length can be locked. /// Dotted parts in this locked prefix will be ignored and left as-is in the result. /// </param> /// <param name="throwOnAboveRoot"> /// By default any attempt to resolve above the root will throw an <see cref="InvalidOperationException"/>. /// By specifying false, the root acts as an absorbing element. /// </param> /// <returns>The resolved normalized path.</returns> public NormalizedPath ResolveDots(int rootPartsCount = 0, bool throwOnAboveRoot = true) { int len = _parts != null ? _parts.Length : 0; if (rootPartsCount > len) { throw new ArgumentOutOfRangeException(nameof(rootPartsCount)); } if (rootPartsCount == 0 && _option == NormalizedPathRootKind.RootedByFirstPart) { rootPartsCount = 1; } if (rootPartsCount == len) { return(this); } Debug.Assert(!IsEmptyPath); string[] newParts = null; int current = 0; NormalizedPathRootKind o = _option; for (int i = rootPartsCount; i < len; ++i) { string curPart = _parts[i]; bool isDot = curPart == "."; bool isDotDot = !isDot && curPart == ".."; if (isDot || isDotDot) { if (newParts == null) { newParts = new string[_parts.Length]; current = i; if (isDotDot) { --current; } if (current < rootPartsCount) { if (throwOnAboveRoot) { ThrowAboveRootException(_parts, rootPartsCount, i); } current = rootPartsCount; } Array.Copy(_parts, 0, newParts, 0, current); } else if (isDotDot) { if (current == rootPartsCount) { if (throwOnAboveRoot) { ThrowAboveRootException(_parts, rootPartsCount, i); } } else { --current; } } } else if (newParts != null) { newParts[current++] = curPart; } } if (newParts == null) { return(this); } if (current == 0) { return(new NormalizedPath(_option)); } Array.Resize(ref newParts, current); return(new NormalizedPath(newParts, BuildNonEmptyPath(newParts, o), o)); }
/// <summary> /// Sets the <see cref="RootKind"/> by returning this or a new <see cref="NormalizedPath"/>. /// The only forbidden case is to set the <see cref="NormalizedPathRootKind.RootedByFirstPart"/> /// when <see cref="HasParts"/> is false: this throws an <see cref="ArgumentException"/>. /// </summary> /// <param name="kind">The <see cref="NormalizedPathRootKind"/> to set.</param> /// <returns>This or a new path.</returns> public NormalizedPath With(NormalizedPathRootKind kind) { if (kind == _option) { return(this); } if (_parts == null) { switch (kind) { case NormalizedPathRootKind.None: { Debug.Assert(_option == NormalizedPathRootKind.RootedBySeparator || _option == NormalizedPathRootKind.RootedByDoubleSeparator); return(new NormalizedPath()); } case NormalizedPathRootKind.RootedByFirstPart: { throw new ArgumentException("Invalid RootedByFirstPart on path without any parts."); } case NormalizedPathRootKind.RootedBySeparator: { return(new NormalizedPath(kind)); } case NormalizedPathRootKind.RootedByDoubleSeparator: { return(new NormalizedPath(kind)); } default: throw new NotSupportedException(); } } if (_option == NormalizedPathRootKind.None || _option == NormalizedPathRootKind.RootedByFirstPart) { switch (kind) { case NormalizedPathRootKind.None: case NormalizedPathRootKind.RootedByFirstPart: return(new NormalizedPath(_parts, _path, kind)); case NormalizedPathRootKind.RootedBySeparator: return(new NormalizedPath(_parts, DirectorySeparatorChar + _path, kind)); case NormalizedPathRootKind.RootedByDoubleSeparator: return(new NormalizedPath(_parts, DoubleDirectorySeparatorString + _path, kind)); default: throw new NotSupportedException(); } } Debug.Assert(_option == NormalizedPathRootKind.RootedBySeparator || _option == NormalizedPathRootKind.RootedByDoubleSeparator); switch (kind) { case NormalizedPathRootKind.None: case NormalizedPathRootKind.RootedByFirstPart: return(new NormalizedPath(_parts, _path.Substring(_option == NormalizedPathRootKind.RootedBySeparator ? 1 : 2), kind)); case NormalizedPathRootKind.RootedBySeparator: return(new NormalizedPath(_parts, _path.Substring(1), kind)); case NormalizedPathRootKind.RootedByDoubleSeparator: return(new NormalizedPath(_parts, DirectorySeparatorChar + _path, kind)); default: throw new NotSupportedException(); } }