diff --git a/.packageguard/cache.bin b/.packageguard/cache.bin index 80d7d29..3b16981 100644 Binary files a/.packageguard/cache.bin and b/.packageguard/cache.bin differ diff --git a/Build/_build.csproj b/Build/_build.csproj index 43acdf6..3f03ec4 100644 --- a/Build/_build.csproj +++ b/Build/_build.csproj @@ -16,7 +16,7 @@ - + diff --git a/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.net47.verified.txt b/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.net47.verified.txt index d1db159..e785eeb 100644 --- a/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.net47.verified.txt +++ b/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.net47.verified.txt @@ -1 +1,8 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Pathy.Specs")] +namespace Pathy +{ + public static class ChainablePathExtensions + { + public static Pathy.ChainablePath[] GlobFiles(this Pathy.ChainablePath path, string globPattern) { } + } +} \ No newline at end of file diff --git a/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.net8.0.verified.txt b/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.net8.0.verified.txt index d1db159..e785eeb 100644 --- a/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.net8.0.verified.txt +++ b/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.net8.0.verified.txt @@ -1 +1,8 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Pathy.Specs")] +namespace Pathy +{ + public static class ChainablePathExtensions + { + public static Pathy.ChainablePath[] GlobFiles(this Pathy.ChainablePath path, string globPattern) { } + } +} \ No newline at end of file diff --git a/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.netstandard2.0.verified.txt b/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.netstandard2.0.verified.txt index d1db159..e785eeb 100644 --- a/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.netstandard2.0.verified.txt +++ b/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.netstandard2.0.verified.txt @@ -1 +1,8 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Pathy.Specs")] +namespace Pathy +{ + public static class ChainablePathExtensions + { + public static Pathy.ChainablePath[] GlobFiles(this Pathy.ChainablePath path, string globPattern) { } + } +} \ No newline at end of file diff --git a/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.netstandard2.1.verified.txt b/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.netstandard2.1.verified.txt index d1db159..e785eeb 100644 --- a/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.netstandard2.1.verified.txt +++ b/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.netstandard2.1.verified.txt @@ -1 +1,8 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Pathy.Specs")] +namespace Pathy +{ + public static class ChainablePathExtensions + { + public static Pathy.ChainablePath[] GlobFiles(this Pathy.ChainablePath path, string globPattern) { } + } +} \ No newline at end of file diff --git a/Pathy.ApiVerificationTests/Pathy.ApiVerificationTests.csproj b/Pathy.ApiVerificationTests/Pathy.ApiVerificationTests.csproj index 0a5a52d..2a08a4f 100644 --- a/Pathy.ApiVerificationTests/Pathy.ApiVerificationTests.csproj +++ b/Pathy.ApiVerificationTests/Pathy.ApiVerificationTests.csproj @@ -13,7 +13,7 @@ - + diff --git a/Pathy.Globbing/Pathy.Globbing.csproj b/Pathy.Globbing/Pathy.Globbing.csproj index e92e6d7..7486775 100644 --- a/Pathy.Globbing/Pathy.Globbing.csproj +++ b/Pathy.Globbing/Pathy.Globbing.csproj @@ -7,7 +7,7 @@ disable .nuspec version=$(Version) - MYPACKAGE_COMPILE + PATHY_PUBLIC 1591;1573 False $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb diff --git a/Pathy.Globbing/PathyGlobbing.cs b/Pathy.Globbing/PathyGlobbing.cs index 2ef14e0..74a13f5 100644 --- a/Pathy.Globbing/PathyGlobbing.cs +++ b/Pathy.Globbing/PathyGlobbing.cs @@ -1,6 +1,7 @@ // using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Extensions.FileSystemGlobbing; using Microsoft.Extensions.FileSystemGlobbing.Abstractions; @@ -10,32 +11,48 @@ #nullable disable // ReSharper disable once CheckNamespace -namespace Pathy; - +namespace Pathy +{ #if PATHY_PUBLIC -public static class ChainablePathExtensions + public static class ChainablePathExtensions #else -internal static class ChainablePathExtensions + [global::Microsoft.CodeAnalysis.Embedded] + internal static class ChainablePathExtensions #endif + { + /// + /// Matches files in the specified directory or subdirectories according to the provided glob pattern + /// and returns them as an array of objects. + /// + /// + /// See also + /// + /// The base directory path to start the glob search from. + /// The glob pattern used to match file paths, e.g. **/*.md or dir/**/* + public static ChainablePath[] GlobFiles(this ChainablePath path, string globPattern) + { + Matcher matcher = new(StringComparison.OrdinalIgnoreCase); + matcher.AddInclude(globPattern); + + return matcher + .Execute(new DirectoryInfoWrapper(path.ToDirectoryInfo())) + .Files + .Select(file => ChainablePath.From(path / file.Path)) + .ToArray(); + } + } +} + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +namespace Microsoft.CodeAnalysis { /// - /// Matches files in the specified directory or subdirectories according to the provided glob pattern - /// and returns them as an array of objects. + /// A special attribute recognized by Roslyn, that marks a type as "embedded", meaning it won't ever be visible from other assemblies. /// - /// - /// See also - /// - /// The base directory path to start the glob search from. - /// The glob pattern used to match file paths, e.g. **/*.md or dir/**/* - public static ChainablePath[] GlobFiles(this ChainablePath path, string globPattern) + [AttributeUsage(AttributeTargets.All)] + [ExcludeFromCodeCoverage] + internal sealed class EmbeddedAttribute : Attribute { - Matcher matcher = new(StringComparison.OrdinalIgnoreCase); - matcher.AddInclude(globPattern); - - return matcher - .Execute(new DirectoryInfoWrapper(path.ToDirectoryInfo())) - .Files - .Select(file => ChainablePath.From(path / file.Path)) - .ToArray(); } } diff --git a/Pathy.Specs/ChainablePathSpecs.cs b/Pathy.Specs/ChainablePathSpecs.cs index e92c703..94fa5f3 100644 --- a/Pathy.Specs/ChainablePathSpecs.cs +++ b/Pathy.Specs/ChainablePathSpecs.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using System.Reflection; using FluentAssertions; using Xunit; diff --git a/Pathy.Specs/Pathy.Specs.csproj b/Pathy.Specs/Pathy.Specs.csproj index a9b22a8..e837617 100644 --- a/Pathy.Specs/Pathy.Specs.csproj +++ b/Pathy.Specs/Pathy.Specs.csproj @@ -18,7 +18,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Pathy/ChainablePath.cs b/Pathy/ChainablePath.cs index 90d9ecc..430cc1d 100644 --- a/Pathy/ChainablePath.cs +++ b/Pathy/ChainablePath.cs @@ -1,6 +1,7 @@ // using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -8,347 +9,348 @@ #pragma warning disable -namespace Pathy; - -/// -/// Represents an absolute or relative path to a directory or file that simplifies operations like combining paths using -/// the / operator -/// +namespace Pathy +{ + /// + /// Represents an absolute or relative path to a directory or file that simplifies operations like combining paths using + /// the / operator + /// #if PATHY_PUBLIC -public readonly record struct ChainablePath + public readonly record struct ChainablePath #else -internal readonly record struct ChainablePath + [global::Microsoft.CodeAnalysis.Embedded] + internal readonly record struct ChainablePath #endif -{ - private readonly string path = string.Empty; - - private ChainablePath(string path) { - this.path = path; - } + private readonly string path = string.Empty; - /// - /// Represents an empty instance. - /// - public static ChainablePath Empty { get; } = new(string.Empty); + private ChainablePath(string path) + { + this.path = path; + } - /// - /// Represents a not pointing to any file or directory. - /// - /// - /// Implements the Null Pattern and should be used as a replacement of - /// - public static ChainablePath Null { get; } = new(string.Empty); + /// + /// Represents an empty instance. + /// + public static ChainablePath Empty { get; } = new(string.Empty); + + /// + /// Represents a not pointing to any file or directory. + /// + /// + /// Implements the Null Pattern and should be used as a replacement of + /// + public static ChainablePath Null { get; } = new(string.Empty); + + /// + /// Gets a default, empty instance. + /// + public static ChainablePath New => new(string.Empty); + + /// + /// Creates a new instance of representing the specified path. + /// + /// + /// The string representation of the path. This can be relative or absolute. + /// + /// + /// A object representing the normalized path. + /// + /// + /// Thrown if the is null, empty, or invalid. + /// + public static ChainablePath From(string path) + { + if (path is null or { Length: 0 }) + { + throw new ArgumentException("Path cannot be null or empty", nameof(path)); + } - /// - /// Gets a default, empty instance. - /// - public static ChainablePath New => new(string.Empty); + try + { + path = NormalizeSlashes(path); - /// - /// Creates a new instance of representing the specified path. - /// - /// - /// The string representation of the path. This can be relative or absolute. - /// - /// - /// A object representing the normalized path. - /// - /// - /// Thrown if the is null, empty, or invalid. - /// - public static ChainablePath From(string path) - { - if (path is null or { Length: 0 }) + if (path[path.Length - 1] == Path.VolumeSeparatorChar) + { + path += Path.DirectorySeparatorChar; + } + + if (Path.IsPathRooted(path)) + { + return new ChainablePath(Path.GetFullPath(path)); + } + + return new ChainablePath(path); + } + catch (NotSupportedException) + { + throw new ArgumentException($"Path {path} is not valid", nameof(path)); + } + } + + /// + /// Creates a new instance of representing the first existing path from the specified list of paths. + /// + /// + /// An array of string representations of paths to check for existence. Paths are checked in the order provided. + /// + /// + /// A object representing the first path that exists or if none exist. + /// + /// + /// Thrown if the array is null. + /// + /// + /// Thrown if the array is empty. + /// + public static ChainablePath FindFirst(params string[] paths) { - throw new ArgumentException("Path cannot be null or empty", nameof(path)); + if (paths == null) + { + throw new ArgumentNullException(nameof(paths)); + } + + return FindFirst(paths.Select(From).ToArray()); } - try + /// + /// Creates a new instance of representing the first existing path from the specified list of paths. + /// + /// + /// An array of instances to check for existence. Paths are checked in the order provided. + /// + /// + /// A object representing the first path that exists or if none exist. + /// + /// + /// Thrown if the array is null. + /// + /// + /// Thrown if the array is empty. + /// + public static ChainablePath FindFirst(params ChainablePath[] paths) { - path = NormalizeSlashes(path); + if (paths == null) + { + throw new ArgumentNullException(nameof(paths)); + } - if (path[path.Length - 1] == Path.VolumeSeparatorChar) + if (paths.Length == 0) { - path += Path.DirectorySeparatorChar; + throw new ArgumentException("At least one path must be provided", nameof(paths)); } - if (Path.IsPathRooted(path)) + foreach (var path in paths) { - return new ChainablePath(Path.GetFullPath(path)); + if (path.Exists) + { + return path; + } } - return new ChainablePath(path); + return Empty; } - catch (NotSupportedException) + + private static string NormalizeSlashes(string path) { - throw new ArgumentException($"Path {path} is not valid", nameof(path)); + return path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); } - } - /// - /// Creates a new instance of representing the first existing path from the specified list of paths. - /// - /// - /// An array of string representations of paths to check for existence. Paths are checked in the order provided. - /// - /// - /// A object representing the first path that exists or if none exist. - /// - /// - /// Thrown if the array is null. - /// - /// - /// Thrown if the array is empty. - /// - public static ChainablePath FindFirst(params string[] paths) - { - if (paths == null) + /// + /// Combines a instance with a string representing a sub-path while + /// handling the path separators suitable for the specific operating system. + /// + /// + /// + /// var combinedPath = ChainablePath.From("C:/BasePath") / "SubPath" / "File.txt"; + /// + /// + /// + /// You don't need to add any slashes before the sub-path. The operator will handle it for you. + /// + /// + /// The base to which the will be appended. + /// + /// + /// The string representation of the relative or additional path to combine with . + /// + /// + /// A new instance representing the combined path. + /// + /// + /// Thrown if either or is null. + /// + public static ChainablePath operator /(ChainablePath leftPath, string subPath) { - throw new ArgumentNullException(nameof(paths)); + return From(Path.Combine(leftPath.path, subPath)); } - return FindFirst(paths.Select(From).ToArray()); - } - - /// - /// Creates a new instance of representing the first existing path from the specified list of paths. - /// - /// - /// An array of instances to check for existence. Paths are checked in the order provided. - /// - /// - /// A object representing the first path that exists or if none exist. - /// - /// - /// Thrown if the array is null. - /// - /// - /// Thrown if the array is empty. - /// - public static ChainablePath FindFirst(params ChainablePath[] paths) - { - if (paths == null) + /// + /// Combines a instance with a string representing a sub-path while + /// handling the path separators suitable for the specific operating system. + /// + /// + /// + /// var combinedPath = ChainablePath.From("C:/BasePath") / "SubPath" / "File.txt"; + /// + /// + /// + /// You don't need to add any slashes before the sub-path. The operator will handle it for you. + /// + /// + /// The base to which the will be appended. + /// + /// + /// The string representation of the relative or additional path to combine with . + /// + /// + /// A new instance representing the combined path. + /// + /// + /// Thrown if either or is null. + /// + public static ChainablePath operator /(ChainablePath? leftPath, string subPath) { - throw new ArgumentNullException(nameof(paths)); + return From(Path.Combine(leftPath.GetValueOrDefault(New), subPath)); } - if (paths.Length == 0) + /// + /// Adds a raw string to the end of a instance. + /// + /// + /// The base object representing an initial path. + /// + /// + /// The additional string representing a relative path or file extension (with dot) to append to the . + /// + /// + /// A new instance representing the combined path of and . + /// + public static ChainablePath operator +(ChainablePath leftPath, string additionalPath) { - throw new ArgumentException("At least one path must be provided", nameof(paths)); + return From(leftPath.ToString() + additionalPath); } - foreach (var path in paths) + /// + /// Gets a value indicating whether the current instance is equal to . + /// + public bool IsNull => Equals(Null); + + /// + /// Gets the name of the file or directory represented by the current path, without the parent directory. + /// + public string Name => Path.GetFileName(path); + + /// + /// If the current path represents a file, gets the directory of that file. + /// Or, if the current path represents a directory, gets the parent directory. + /// Returns if the path represents the root of a file system. + /// + public ChainablePath Directory { - if (path.Exists) + get { - return path; + string directory = DirectoryName; + if (directory.Length > 0) + { + return From(directory); + } + + return Empty; } } - return Empty; - } - - private static string NormalizeSlashes(string path) - { - return path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); - } - - /// - /// Combines a instance with a string representing a sub-path while - /// handling the path separators suitable for the specific operating system. - /// - /// - /// - /// var combinedPath = ChainablePath.From("C:/BasePath") / "SubPath" / "File.txt"; - /// - /// - /// - /// You don't need to add any slashes before the sub-path. The operator will handle it for you. - /// - /// - /// The base to which the will be appended. - /// - /// - /// The string representation of the relative or additional path to combine with . - /// - /// - /// A new instance representing the combined path. - /// - /// - /// Thrown if either or is null. - /// - public static ChainablePath operator /(ChainablePath leftPath, string subPath) - { - return From(Path.Combine(leftPath.path, subPath)); - } - - /// - /// Combines a instance with a string representing a sub-path while - /// handling the path separators suitable for the specific operating system. - /// - /// - /// - /// var combinedPath = ChainablePath.From("C:/BasePath") / "SubPath" / "File.txt"; - /// - /// - /// - /// You don't need to add any slashes before the sub-path. The operator will handle it for you. - /// - /// - /// The base to which the will be appended. - /// - /// - /// The string representation of the relative or additional path to combine with . - /// - /// - /// A new instance representing the combined path. - /// - /// - /// Thrown if either or is null. - /// - public static ChainablePath operator /(ChainablePath? leftPath, string subPath) - { - return From(Path.Combine(leftPath.GetValueOrDefault(New), subPath)); - } - - /// - /// Adds a raw string to the end of a instance. - /// - /// - /// The base object representing an initial path. - /// - /// - /// The additional string representing a relative path or file extension (with dot) to append to the . - /// - /// - /// A new instance representing the combined path of and . - /// - public static ChainablePath operator +(ChainablePath leftPath, string additionalPath) - { - return From(leftPath.ToString() + additionalPath); - } - - /// - /// Gets a value indicating whether the current instance is equal to . - /// - public bool IsNull => Equals(Null); - - /// - /// Gets the name of the file or directory represented by the current path, without the parent directory. - /// - public string Name => Path.GetFileName(path); - - /// - /// If the current path represents a file, gets the directory of that file. - /// Or, if the current path represents a directory, gets the parent directory. - /// Returns if the path represents the root of a file system. - /// - public ChainablePath Directory - { - get + /// + /// If the current path represents a file, gets the directory of that file. + /// Or, if the current path represents a directory, gets the parent directory. + /// Returns if the path represents the root of a file system. + /// + public ChainablePath Parent => From(DirectoryName); + + /// + /// If the current path represents a file, gets the directory of that file. + /// Or, if the current path represents a directory, gets the parent directory. + /// Returns an empty string if the path represents the root of a file system. + /// + public string DirectoryName { - string directory = DirectoryName; - if (directory.Length > 0) + get { - return From(directory); + return Path.GetDirectoryName(path.TrimEnd(Path.DirectorySeparatorChar)) ?? ""; } - - return Empty; } - } - - /// - /// If the current path represents a file, gets the directory of that file. - /// Or, if the current path represents a directory, gets the parent directory. - /// Returns if the path represents the root of a file system. - /// - public ChainablePath Parent => From(DirectoryName); - - /// - /// If the current path represents a file, gets the directory of that file. - /// Or, if the current path represents a directory, gets the parent directory. - /// Returns an empty string if the path represents the root of a file system. - /// - public string DirectoryName - { - get - { - return Path.GetDirectoryName(path.TrimEnd(Path.DirectorySeparatorChar)) ?? ""; - } - } - - /// - /// Indicates whether the current path is rooted, meaning it begins with a root component such as - /// a drive letter (e.g., "C:\") or directory separator (e.g., "/"). - /// Returns true if the path is rooted; otherwise, false. - /// - public bool IsRooted => Path.IsPathRooted(path); - - /// - /// Returns true if the path represents an existing file or directory; otherwise, returns false. - /// - public bool Exists => FileExists || DirectoryExists; - - /// - /// Returns true if a file exists at the path; otherwise, false. - /// - public bool FileExists => File.Exists(path); - - /// - /// Returns true if the path exists and is a directory; otherwise, false. - /// - public bool DirectoryExists => System.IO.Directory.Exists(path); - /// - /// Gets the file extension of the current path, including the leading period (".") if an extension is present. - /// Returns an empty string if the path does not contain a file extension. - /// - public string Extension - { - get + /// + /// Indicates whether the current path is rooted, meaning it begins with a root component such as + /// a drive letter (e.g., "C:\") or directory separator (e.g., "/"). + /// Returns true if the path is rooted; otherwise, false. + /// + public bool IsRooted => Path.IsPathRooted(path); + + /// + /// Returns true if the path represents an existing file or directory; otherwise, returns false. + /// + public bool Exists => FileExists || DirectoryExists; + + /// + /// Returns true if a file exists at the path; otherwise, false. + /// + public bool FileExists => File.Exists(path); + + /// + /// Returns true if the path exists and is a directory; otherwise, false. + /// + public bool DirectoryExists => System.IO.Directory.Exists(path); + + /// + /// Gets the file extension of the current path, including the leading period (".") if an extension is present. + /// Returns an empty string if the path does not contain a file extension. + /// + public string Extension { - return Path.GetExtension(path); + get + { + return Path.GetExtension(path); + } } - } - /// - /// Gets the root directory of the current path. - /// - public ChainablePath Root => From(Path.GetPathRoot(path)); - - /// - /// Gets a value indicating whether the path represented by the current object points to an existing file. - /// - public bool IsFile => File.Exists(ToString()); - - /// - /// Gets a value indicating whether the current path represents an existing directory. - /// - public bool IsDirectory => System.IO.Directory.Exists(ToString()); - - /// - /// Gets a instance representing the current working directory of the application. - /// - public static ChainablePath Current => From(Environment.CurrentDirectory); - - /// - /// Gets a instance representing the system's temporary directory. - /// - public static ChainablePath Temp => From(Path.GetTempPath()); - - /// - /// Returns a new instance representing the path relative to the specified base path. - /// - /// - /// The base path to which the current path should be relativized. It must be an absolute path. - /// - /// - /// A instance representing the relative path from the base path to this path. - /// - /// - /// Thrown if is not absolute, or if this path is not absolute. - /// + /// + /// Gets the root directory of the current path. + /// + public ChainablePath Root => From(Path.GetPathRoot(path)); + + /// + /// Gets a value indicating whether the path represented by the current object points to an existing file. + /// + public bool IsFile => File.Exists(ToString()); + + /// + /// Gets a value indicating whether the current path represents an existing directory. + /// + public bool IsDirectory => System.IO.Directory.Exists(ToString()); + + /// + /// Gets a instance representing the current working directory of the application. + /// + public static ChainablePath Current => From(Environment.CurrentDirectory); + + /// + /// Gets a instance representing the system's temporary directory. + /// + public static ChainablePath Temp => From(Path.GetTempPath()); + + /// + /// Returns a new instance representing the path relative to the specified base path. + /// + /// + /// The base path to which the current path should be relativized. It must be an absolute path. + /// + /// + /// A instance representing the relative path from the base path to this path. + /// + /// + /// Thrown if is not absolute, or if this path is not absolute. + /// #if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER public ChainablePath AsRelativeTo(ChainablePath basePath) { @@ -356,159 +358,175 @@ public ChainablePath AsRelativeTo(ChainablePath basePath) } #endif - /// - /// Returns the string representation of the current instance. - /// - public override string ToString() - { - return path; - } - - /// - /// Allows casting a instance to a string so it can be used anywhere where a path as string is expected. - /// - public static implicit operator string(ChainablePath chainablePath) - { - return chainablePath.ToString(); - } - - /// - /// Allows casting a string to a instance. - /// - /// - /// Serves as a shortcut for . - /// - public static implicit operator ChainablePath(string path) - { - return From(path); - } + /// + /// Returns the string representation of the current instance. + /// + public override string ToString() + { + return path; + } - /// - /// Converts the current instance to a object. - /// - public DirectoryInfo ToDirectoryInfo() - { - return new DirectoryInfo(ToString()); - } + /// + /// Allows casting a instance to a string so it can be used anywhere where a path as string is expected. + /// + public static implicit operator string(ChainablePath chainablePath) + { + return chainablePath.ToString(); + } - /// - /// Converts the current instance to a object. - /// - public FileInfo ToFileInfo() - { - return new FileInfo(ToString()); - } + /// + /// Allows casting a string to a instance. + /// + /// + /// Serves as a shortcut for . + /// + public static implicit operator ChainablePath(string path) + { + return From(path); + } - /// - /// Determines if the current path has the specified file extension. - /// - /// - /// The file extension to check for, with or without the leading period (e.g., ".txt"). - /// - public bool HasExtension(string extension) - { - if (string.IsNullOrEmpty(extension)) + /// + /// Converts the current instance to a object. + /// + public DirectoryInfo ToDirectoryInfo() { - throw new ArgumentException("Extension cannot be null or empty", nameof(extension)); + return new DirectoryInfo(ToString()); } - // Ensure the extension starts with a dot - if (!extension.StartsWith(".", StringComparison.InvariantCulture)) + /// + /// Converts the current instance to a object. + /// + public FileInfo ToFileInfo() { - extension = '.' + extension; + return new FileInfo(ToString()); } - return string.Equals(Extension, extension, StringComparison.OrdinalIgnoreCase); - } + /// + /// Determines if the current path has the specified file extension. + /// + /// + /// The file extension to check for, with or without the leading period (e.g., ".txt"). + /// + public bool HasExtension(string extension) + { + if (string.IsNullOrEmpty(extension)) + { + throw new ArgumentException("Extension cannot be null or empty", nameof(extension)); + } - /// - /// Converts the current to an absolute path using the current working directory. - /// - public ChainablePath ToAbsolute() - { - return Path.GetFullPath(this); - } + // Ensure the extension starts with a dot + if (!extension.StartsWith(".", StringComparison.InvariantCulture)) + { + extension = '.' + extension; + } - /// - /// Converts the current instance to an absolute path, using the specified parent - /// path as the base. - /// - public object ToAbsolute(ChainablePath parentPath) - { - if (!parentPath.IsRooted) - { - throw new ArgumentException("Parent path must be an absolute path", nameof(parentPath)); + return string.Equals(Extension, extension, StringComparison.OrdinalIgnoreCase); } - return From(Path.Combine(parentPath.ToString(), this.ToString())); - } - - /// - /// Finds the first parent directory in the hierarchy that contains a file matching any of the provided wildcard patterns. - /// - /// One or more wildcard patterns to match against files in parent directories (e.g., "*.sln", "*.slnx"). - /// - /// A representing the first parent directory that contains a matching file, - /// or if no parent directory contains a matching file. - /// - /// Thrown if no wildcards are provided or if any wildcard is null or empty. - public ChainablePath FindParentWithFileMatching(params string[] wildcards) - { - if (wildcards == null || wildcards.Length == 0) + /// + /// Converts the current to an absolute path using the current working directory. + /// + public ChainablePath ToAbsolute() { - throw new ArgumentException("At least one wildcard pattern must be provided", nameof(wildcards)); + return Path.GetFullPath(this); } - foreach (string wildcard in wildcards) + /// + /// Converts the current instance to an absolute path, using the specified parent + /// path as the base. + /// + public object ToAbsolute(ChainablePath parentPath) { - if (string.IsNullOrWhiteSpace(wildcard)) + if (!parentPath.IsRooted) { - throw new ArgumentException("Wildcard patterns cannot be null or empty", nameof(wildcards)); + throw new ArgumentException("Parent path must be an absolute path", nameof(parentPath)); } - } - // Start from the directory containing this path, or the path itself if it's already a directory - ChainablePath currentDirectory = IsFile ? Directory : this; + return From(Path.Combine(parentPath.ToString(), ToString())); + } - while (currentDirectory != Root && currentDirectory != Empty && currentDirectory.DirectoryExists) + /// + /// Finds the first parent directory in the hierarchy that contains a file matching any of the provided wildcard patterns. + /// + /// One or more wildcard patterns to match against files in parent directories (e.g., "*.sln", "*.slnx"). + /// + /// A representing the first parent directory that contains a matching file, + /// or if no parent directory contains a matching file. + /// + /// Thrown if no wildcards are provided or if any wildcard is null or empty. + public ChainablePath FindParentWithFileMatching(params string[] wildcards) { + if (wildcards == null || wildcards.Length == 0) + { + throw new ArgumentException("At least one wildcard pattern must be provided", nameof(wildcards)); + } + foreach (string wildcard in wildcards) { - // Check if any files in the current directory match the wildcards - if (System.IO.Directory.GetFiles(currentDirectory, wildcard).Any()) + if (string.IsNullOrWhiteSpace(wildcard)) { - return currentDirectory; + throw new ArgumentException("Wildcard patterns cannot be null or empty", nameof(wildcards)); } } - // Move to the parent directory - currentDirectory = currentDirectory.Parent; - } + // Start from the directory containing this path, or the path itself if it's already a directory + ChainablePath currentDirectory = IsFile ? Directory : this; - return Empty; + while (currentDirectory != Root && currentDirectory != Empty && currentDirectory.DirectoryExists) + { + foreach (string wildcard in wildcards) + { + // Check if any files in the current directory match the wildcards + if (System.IO.Directory.GetFiles(currentDirectory, wildcard).Any()) + { + return currentDirectory; + } + } + + // Move to the parent directory + currentDirectory = currentDirectory.Parent; + } + + return Empty; + } } -} #if PATHY_PUBLIC -public static class StringExtensions + public static class StringExtensions #else -internal static class StringExtensions + [global::Microsoft.CodeAnalysis.Embedded] + internal static class StringExtensions #endif + { + /// + /// Converts the specified string representation of a path to a instance. + /// + /// + /// The string representation of the path to convert. + /// + /// + /// A instance representing the specified path. + /// + /// + /// Thrown if the is null, empty, or invalid. + /// + public static ChainablePath ToPath(this string path) + { + return ChainablePath.From(path); + } + } +} + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +namespace Microsoft.CodeAnalysis { /// - /// Converts the specified string representation of a path to a instance. + /// A special attribute recognized by Roslyn, that marks a type as "embedded", meaning it won't ever be visible from other assemblies. /// - /// - /// The string representation of the path to convert. - /// - /// - /// A instance representing the specified path. - /// - /// - /// Thrown if the is null, empty, or invalid. - /// - public static ChainablePath ToPath(this string path) + [AttributeUsage(AttributeTargets.All)] + [ExcludeFromCodeCoverage] + internal sealed class EmbeddedAttribute : Attribute { - return ChainablePath.From(path); } }