/// Create an NPath by changing the extension of this one.
/// </summary>
/// <param name="extension">The new extension to use. Starting it with a "." character is optional. If you pass an empty string, the resulting path will have the extension stripped entirely, including the dot character.</param>
/// <returns>A new NPath which is the existing path but with the new extension at the end.</returns>
/// Provides a quoted version of the path as a string, with the requested path separator type.
/// </summary>
/// <param name="slashMode">The path separator to use. See the <see cref="SlashMode">SlashMode</see> enum for an explanation of the values. Defaults to <c>SlashMode.Forward</c>.</param>
/// <returns>The path, with the requested path separator type, in quotes.</returns>
/// Checks if this NPath represents the same path as another object.
/// </summary>
/// <param name="obj">The object to compare to.</param>
/// <returns>True if this NPath represents the same path as the other object; false if it does not, if the other object is not an NPath, or is null.</returns>
publicoverrideboolEquals(objectobj)
{
returnEquals(objasNPath);
}
/// <summary>
/// Checks if this NPath is equal to another NPath.
/// </summary>
/// <param name="p">The path to compare to.</param>
/// <returns>True if this NPath represents the same path as the other NPath; false otherwise.</returns>
/// <remarks>Note that the comparison requires that the paths are the same, not just that the targets are the same; "foo/bar" and "foo/baz/../bar" refer to the same target but will not be treated as equal by this comparison. However, this comparison will ignore case differences when the current operating system does not use case-sensitive filesystems.</remarks>
/// <param name="a">The first NPath to compare.</param>
/// <param name="b">The second NPath to compare.</param>
/// <returns>True if the NPaths are not equal, false otherwise.</returns>
publicstaticbooloperator!=(NPatha,NPathb)
{
return!(a==b);
}
/// <summary>
/// Tests whether this NPath has one of the provided extensions, or if no extensions are provided, whether it has any extension at all.
/// </summary>
/// <param name="extensions">The possible extensions to test for.</param>
/// <returns>True if this NPath has one of the provided extensions; or, if no extensions are provided, true if this NPath has an extension. False otherwise.</returns>
/// <remarks>The extension "*" is special, and will return true for all paths if specified.</remarks>
/// Whether this path is rooted or not (begins with a slash character or drive specifier).
/// </summary>
publicboolIsRoot
{
get
{
if(_path=="/")
returntrue;
if(_path.Length==3&&_path[1]==':'&&_path[2]=='/')
returntrue;
if(IsUNC&&_path.Length==_path.IndexOf('/')+1)
returntrue;
returnfalse;
}
}
/// <summary>
/// Whether this path starts with an UNC path prefix "\\" or not.
/// </summary>
publicboolIsUNC=>IsUNCPath(_path);
#endregioninspection
#regiondirectoryenumeration
/// <summary>
/// Find all files within this path that match the given filter.
/// </summary>
/// <param name="filter">The filter to match against the names of files. Wildcards can be included.</param>
/// <param name="recurse">If true, search recursively inside subdirectories of this path; if false, search only for files that are immediate children of this path. Defaults to false.</param>
/// <returns>An array of files that were found.</returns>
/// <param name="recurse">If true, search recursively inside subdirectories of this path; if false, search only for files that are immediate children of this path. Defaults to false.</param>
/// <returns>An array of files that were found.</returns>
publicNPath[]Files(boolrecurse=false)
{
returnFiles("*",recurse);
}
/// <summary>
/// Find all files within this path that have one of the provided extensions.
/// </summary>
/// <param name="extensions">The extensions to search for.</param>
/// <param name="recurse">If true, search recursively inside subdirectories of this path; if false, search only for files that are immediate children of this path. Defaults to false.</param>
/// <returns>An array of files that were found.</returns>
/// Find all files or directories within this path that match the given filter.
/// </summary>
/// <param name="filter">The filter to match against the names of files and directories. Wildcards can be included.</param>
/// <param name="recurse">If true, search recursively inside subdirectories of this path; if false, search only for files and directories that are immediate children of this path. Defaults to false.</param>
/// <returns>An array of files and directories that were found.</returns>
/// Find all files and directories within this path.
/// </summary>
/// <param name="recurse">If true, search recursively inside subdirectories of this path; if false, search only for files and directories that are immediate children of this path. Defaults to false.</param>
/// <returns>An array of files and directories that were found.</returns>
publicNPath[]Contents(boolrecurse=false)
{
returnContents("*",recurse);
}
/// <summary>
/// Find all directories within this path that match the given filter.
/// </summary>
/// <param name="filter">The filter to match against the names of directories. Wildcards can be included.</param>
/// <param name="recurse">If true, search recursively inside subdirectories of this path; if false, search only for directories that are immediate children of this path. Defaults to false.</param>
/// <returns>An array of directories that were found.</returns>
/// <param name="recurse">If true, search recursively inside subdirectories of this path; if false, search only for directories that are immediate children of this path. Defaults to false.</param>
/// <returns>An array of directories that were found.</returns>
publicNPath[]Directories(boolrecurse=false)
{
returnDirectories("*",recurse);
}
#endregion
#regionfilesystemwritingoperations
/// <summary>
/// Create an empty file at this path.
/// </summary>
/// <returns>This NPath, for chaining further operations.</returns>
/// <remarks>If a file already exists at this path, it will be overwritten.</remarks>
publicNPathCreateFile()
{
ThrowIfRelative();
ThrowIfRoot();
EnsureParentDirectoryExists();
File.WriteAllBytes(_path,newbyte[0]);
returnthis;
}
/// <summary>
/// Append the given path fragment to this path, and create an empty file there.
/// </summary>
/// <param name="file">The path fragment to append.</param>
/// <returns>The path to the created file, for chaining further operations.</returns>
/// <remarks>If a file already exists at that path, it will be overwritten.</remarks>
publicNPathCreateFile(NPathfile)
{
if(!file.IsRelative)
thrownewArgumentException(
"You cannot call CreateFile() on an existing path with a non relative argument");
returnCombine(file).CreateFile();
}
/// <summary>
/// Create this path as a directory if it does not already exist.
/// </summary>
/// <returns>This NPath, for chaining further operations.</returns>
/// <remark>This is identical to <see cref="EnsureDirectoryExists(NPath)"/>, except that EnsureDirectoryExists triggers "Stat" callbacks and this doesn't.</remark>
publicNPathCreateDirectory()
{
ThrowIfRelative();
if(IsRoot)
thrownewNotSupportedException(
"CreateDirectory is not supported on a root level directory because it would be dangerous:"+
ToString());
Directory.CreateDirectory(_path);
returnthis;
}
/// <summary>
/// Append the given path fragment to this path, and create it as a directory if it does not already exist.
/// </summary>
/// <param name="directory">The path fragment to append.</param>
/// <returns>The path to the created directory, for chaining further operations.</returns>
publicNPathCreateDirectory(NPathdirectory)
{
if(!directory.IsRelative)
thrownewArgumentException("Cannot call CreateDirectory with an absolute argument");
returnCombine(directory).CreateDirectory();
}
/// <summary>
/// Create this path as a symbolic link to another file or directory.
/// </summary>
/// <param name="targetPath">The path that this path should be a symbolic link to. Can be relative or absolute.</param>
/// <param name="targetIsFile">Specifies whether this link is to a file or to a directory (required on Windows). Defaults to file.</param>
/// <returns>The path to the created symbolic link, for chaining further operations.</returns>
thrownewIOException($"Failed to create stat path {this} (error code {errorCode})",errorCode);
}
returnPosixNative.S_ISLNK(stat.st_mode);
}
}
/// <summary>
/// Copy this NPath to the given destination.
/// </summary>
/// <param name="dest">The path to copy to.</param>
/// <returns>The path to the copied result, for chaining further operations.</returns>
publicNPathCopy(NPathdest)
{
returnCopy(dest,p=>true);
}
/// <summary>
/// Copy this NPath to the given destination, applying a filter function to decide which files are copied.
/// </summary>
/// <param name="dest">The path to copy to.</param>
/// <param name="fileFilter">The filter function. Each candidate file is passed to this function; if the function returns true, the file will be copied, otherwise it will not.</param>
/// Move the file or directory targetted by this NPath to a new location.
/// </summary>
/// <param name="dest">The destination for the move.</param>
/// <returns>An NPath representing the newly moved file or directory.</returns>
publicNPathMove(NPathdest)
{
ThrowIfRelative();
if(IsRoot)
thrownewNotSupportedException(
"Move is not supported on a root level directory because it would be dangerous:"+ToString());
if(dest.IsRelative)
returnMove(Parent.Combine(dest));
if(dest.DirectoryExists())
returnMove(dest.Combine(FileName));
if(FileExists())
{
dest.EnsureParentDirectoryExists();
File.Move(_path,dest._path);
returndest;
}
if(DirectoryExists())
{
Directory.Move(_path,dest._path);
returndest;
}
thrownewArgumentException("Move() called on a path that doesn't exist: "+ToString());
}
#endregion
#regionspecialpaths
/// <summary>
/// The current directory in use by the process.
/// </summary>
/// <remarks>Note that every read from this property will result in an operating system query, unless <see cref="WithFrozenCurrentDirectory">WithFrozenCurrentDirectory</see> is used.</remarks>
/// Temporarily change the current directory for the process.
/// </summary>
/// <param name="directory">The new directory to set as the current directory.</param>
/// <returns>A token representing the change in current directory. When this is disposed, the current directory will be returned to its previous value. The usual usage pattern is to capture the token with a <c>using</c> statement, such that it is automatically disposed of when the <c>using</c> block exits.</returns>
/// Create the parent directory of this NPath if it does not already exist.
/// </summary>
/// <returns>This NPath, for chaining further operations.</returns>
publicNPathEnsureParentDirectoryExists()
{
Parent.EnsureDirectoryExists();
returnthis;
}
/// <summary>
/// Throw an exception if this path does not exist as a file.
/// </summary>
/// <returns>This path, in order to chain further operations.</returns>
/// <exception cref="System.IO.FileNotFoundException">The path does not exist, or is not a file.</exception>
publicNPathFileMustExist()
{
if(!FileExists())
thrownewFileNotFoundException("File was expected to exist : "+ToString());
returnthis;
}
/// <summary>
/// Throw an exception if this directory does not exist.
/// </summary>
/// <returns>This path, in order to chain further operations.</returns>
/// <exception cref="System.IO.FileNotFoundException">The path does not exist, or is not a directory.</exception>
publicNPathDirectoryMustExist()
{
if(!DirectoryExists())
thrownewDirectoryNotFoundException("Expected directory to exist : "+ToString());
returnthis;
}
/// <summary>
/// Check if this path is a child of the given path hierarchy root (i.e. is a file or directory that is inside the given hierachy root directory or one of its descendent directories).
/// </summary>
/// <param name="potentialBasePath">The path hierarchy root to check.</param>
/// <returns>True if this path is a child of the given root path, false otherwise.</returns>
/// Check if this path is a child of the given path hierarchy root (i.e. is a file or directory that is inside the given hierachy root directory or one of its descendent directories), or is equal to it.
/// </summary>
/// <param name="potentialBasePath">The path hierarchy root to check.</param>
/// <returns>True if this path is equal to or is a child of the given root path, false otherwise.</returns>
/// Return each parent directory of this path, starting with the immediate parent, then that directory's parent, and so on, until the root of the path is reached.
/// </summary>
publicIEnumerable<NPath>RecursiveParents
{
get
{
varcandidate=this;
while(true)
{
if(candidate.IsRoot||candidate._path==".")
yieldbreak;
candidate=candidate.Parent;
yieldreturncandidate;
}
}
}
/// <summary>
/// Search all parent directories of this path for one that contains a file or directory with the given name.
/// </summary>
/// <param name="needle">The name of the file or directory to search for.</param>
/// <returns>The path to the parent directory that contains the file or directory, or null if none of the parents contained a file or directory with the requested name.</returns>
/// Open this path as a text file, write the given string to it, then close the file.
/// </summary>
/// <param name="contents">The string to write to the text file.</param>
/// <returns>The path to this file, for use in chaining further operations.</returns>
publicNPathWriteAllText(stringcontents)
{
ThrowIfRelative();
EnsureParentDirectoryExists();
File.WriteAllText(_path,contents);
returnthis;
}
/// <summary>
/// Open this file as a text file, and replace the contents with the provided string, if they do not already match. Then close the file.
/// </summary>
/// <param name="contents">The string to replace the file's contents with.</param>
/// <returns>The path to this file, for use in chaining further operations.</returns>
/// <remarks>Note that if the contents of the file already match the provided string, the file is not modified - this includes not modifying the file's "last written" timestamp.</remarks>
publicNPathReplaceAllText(stringcontents)
{
ThrowIfRelative();
if(FileExists()&&ReadAllText()==contents)
returnthis;
WriteAllText(contents);
returnthis;
}
/// <summary>
/// Opens a text file, reads all the text in the file into a single string, then closes the file.
/// </summary>
/// <returns>The contents of the text file, as a single string.</returns>
publicstringReadAllText()
{
ThrowIfRelative();
ReadContentsCallback.Invoke(this);
returnFile.ReadAllText(_path);
}
/// <summary>
/// Opens a text file, writes all entries of a string array as separate lines into the file, then closes the file.
/// </summary>
/// <param name="contents">The entries to write into the file as separate lines.</param>
/// <returns>The path to this file.</returns>
publicNPathWriteAllLines(string[]contents)
{
ThrowIfRelative();
EnsureParentDirectoryExists();
File.WriteAllLines(_path,contents);
returnthis;
}
/// <summary>
/// Opens a text file, reads all lines of the file into a string array, and then closes the file.
/// </summary>
/// <returns>A string array containing all lines of the file.</returns>
publicstring[]ReadAllLines()
{
ThrowIfRelative();
ReadContentsCallback.Invoke(this);
returnFile.ReadAllLines(_path);
}
/// <summary>
/// Copy all files in this NPath to the given destination directory.
/// </summary>
/// <param name="destination">The directory to copy the files to.</param>
/// <param name="recurse">If true, files inside subdirectories of this NPath will also be copied. If false, only immediate child files of this NPath will be copied.</param>
/// <param name="fileFilter">An optional predicate function that can be used to filter files. It is passed each source file path in turn, and if it returns true, the file is copied; otherwise, the file is not copied.</param>
/// <returns>The paths to all the newly copied files.</returns>
/// <remarks>Note that the directory structure of the files relative to this NPath will be preserved within the target directory.</remarks>
/// Move all files in this NPath to the given destination directory.
/// </summary>
/// <param name="destination">The directory to move the files to.</param>
/// <param name="recurse">If true, files inside subdirectories of this NPath will also be moved. If false, only immediate child files of this NPath will be moved.</param>
/// <param name="fileFilter">An optional predicate function that can be used to filter files. It is passed each source file path in turn, and if it returns true, the file is moved; otherwise, the file is not moved.</param>
/// <returns>The paths to all the newly moved files.</returns>
/// <remarks>Note that the directory structure of the files relative to this NPath will be preserved within the target directory.</remarks>
/// Register a callback to be invoked when any globbing (selection of files/directories using filesystem enumeration) is performed on the current thread.
/// </summary>
/// <param name="callback">The callback to invoke.</param>
/// <returns>A token representing the registered callback. This should be disposed of when the callback is no longer required. The usual usage pattern is to capture the token with a <c>using</c> statement, such that it is automatically disposed of when the <c>using</c> block exits.</returns>
/// Register a callback to be invoked when any reading of file contents (e.g. <c>ReadAllText</c>) is performed on the current thread.
/// </summary>
/// <param name="callback">The callback to invoke.</param>
/// <returns>A token representing the registered callback. This should be disposed of when the callback is no longer required. The usual usage pattern is to capture the token with a <c>using</c> statement, such that it is automatically disposed of when the <c>using</c> block exits.</returns>
/// Register a callback to be invoked when any checking of file existence (e.g. <c>FileExists</c>) is performed on the current thread.
/// </summary>
/// <param name="callback">The callback to invoke.</param>
/// <returns>A token representing the registered callback. This should be disposed of when the callback is no longer required. The usual usage pattern is to capture the token with a <c>using</c> statement, such that it is automatically disposed of when the <c>using</c> block exits.</returns>
/// Temporarily assume that the current directory is a given value, instead of querying it from the environment when needed, in order to improve performance.
/// </summary>
/// <param name="frozenCurrentDirectory">The current directory to assume.</param>
/// <returns>A token representing the registered callback. This should be disposed of when the assumption is no longer required. The usual usage pattern is to capture the token with a <c>using</c> statement, such that it is automatically disposed of when the <c>using</c> block exits.</returns>
$"{nameof(WithFrozenCurrentDirectory)} called, while there was already a frozen current directory set: {_frozenCurrentDirectory}");
_frozenCurrentDirectory=frozenCurrentDirectory;
returnnewWithFrozenDirectoryHelper();
}
}
/// <summary>
/// Describes an individual attempt to 'glob' filesystem entries (multiply-select filesystem entries using filesystem enumeration).
/// </summary>
///
#ifNICEIO_PUBLIC
publicstructGlobRequest
#else
internalstructGlobRequest
#endif
{
/// <summary>
/// The path in which globbing was performed.
/// </summary>
publicNPathPath;
/// <summary>
/// Was the attempt flagged as recursive, meaning that the results should include filesystem entries in subdirectories of <c>Path</c>?
/// </summary>
publicboolRecurse;
/// <summary>
/// The filters used to select filesystem entries. As long as an entry matches any one filter, it is included.
/// </summary>
publicstring[]Filters;
}
/// <summary>
/// NPath-related extension methods for other common types.
/// </summary>
#ifNICEIO_PUBLIC
publicstaticclassExtensions
#else
internalstaticclassExtensions
#endif
{
/// <summary>
/// Copy these NPaths into the given directory.
/// </summary>
/// <param name="self">An enumerable sequence of NPaths.</param>
/// <param name="dest">The path to the target directory.</param>
/// <returns>The paths to the newly copied files.</returns>
/// <remarks>All path information in the source paths is ignored, other than the final file name; the resulting copied files and directories will all be immediate children of the target directory.</remarks>
/// <param name="self">An enumerable sequence of NPaths.</param>
/// <param name="dest">The path to the target directory.</param>
/// <returns>The paths to the newly moved files.</returns>
/// <remarks>All path information in the source paths is ignored, other than the final file name; the resulting moved files and directories will all be immediate children of the target directory.</remarks>