Sane C++ Libraries
C++ Platform Abstraction Libraries
File System

🟩 File System operations { exists | copy | delete } for { files | directories }

FileSystem executed executing operations on files and directories.
Path is able to parse and manipulate windows and posix paths.

Features

SC::Path Represents a posix or windows file system path.
SC::Path::join Joins multiple StringView with a Separator into an output String.
SC::Path::parseNameExtension Splits a StringView of type "name.ext" into "name" and "ext".
SC::Path::parse Splits a Posix or Windows path into a ParsedView.
SC::Path::dirname Returns the directory name of a path.
SC::Path::basename Returns the base name of a path.
SC::Path::isAbsolute Checks if a path is absolute.
SC::Path::normalize Resolves all .. to output a normalized path String.
SC::Path::relativeFromTo Get relative path that appended to source resolves to destination.
SC::Path::append Append to an existing path a series of StringView with a separator.
SC::Path::endsWithSeparator Check if the path ends with a Windows or Posix separator.
SC::Path::removeStartingSeparator Return a path without its (potential) starting separator.
SC::FileSystem Execute fs operations { exists | copy | delete } for { files | directories }.
Copy Files
SC::FileSystem::copyFile Copy a single file.
Delete Files
SC::FileSystem::removeFile Remove a single file.
SC::FileSystem::removeFileIfExists Remove a single file, giving no error if it doesn't exist.
Copy Directories
SC::FileSystem::copyDirectory Copy a single directory.
Delete Directories
SC::FileSystem::removeEmptyDirectory Removes an empty directory.
SC::FileSystem::removeEmptyDirectoryRecursive Removes an empty directory that only contains other empty directories (but no files)
SC::FileSystem::removeDirectoryRecursive Remove single directory with its entire content (like posix rm -rf)
Create Directories
SC::FileSystem::makeDirectory Creates a new directory that does not already exist.
SC::FileSystem::makeDirectoryIfNotExists Creates a new directory, if it doesn't already exists at the given path.
SC::FileSystem::makeDirectoryRecursive Create a new directory, creating also intermediate non existing directories (like posix mkdir -p)
Check Existence
SC::FileSystem::exists Check if a file or directory exists at a given path.
SC::FileSystem::existsAndIsFile Check if a file exists at given path.
SC::FileSystem::existsAndIsDirectory Check if a directory exists at given path.
Read / Change modification time
SC::FileSystem::getFileTime Reads a FileTime structure for a given file.
SC::FileSystem::setLastModifiedTime Change last modified time of a given file.
Miscellaneous Classes
SC::FileSystemDirectories Reports location of system directories (executable / application root)

Status

🟩 Usable
The library contains commonly used function but it's missing some notable ones like stat. SC::FileSystem::getFileTime and SC::FileSystem::setLastModifiedTime will probably be refactored in a future dedicated class for handling stat based operations.

Description

SC::Path class allows parsing and manipulating windows and posix paths.

SC::FileSystem allows all typical file operations ( exists | copy | delete | make files or directory). Some less used functions are SC::FileSystem::getFileTime and SC::FileSystem::setLastModifiedTime . The library doesn't allow reading or writing seeking inside a file, as that is domain of the File library. SC::FileSystem::init needs an absolute path to a directory and makes it a the base directory. All paths passed later on as arguments to all methods can be either absolute paths or relative. If they are relative, they will be interpreted as relative to the base directory and NOT current directory of the process. The class wants explicitly to make sure its behavior doesn't implicitly depend on current directory of process (unless it's passed explicitly to SC::FileSystem::init of course).

Path

Path::isAbsolute

Checks if a path is absolute. For example:

Path::isAbsolute("/dirname/basename", Path::AsPosix) == true; // Posix Absolute
Path::isAbsolute("./dirname/basename", Path::AsPosix) == false; // Posix Relative
Path::isAbsolute("C:\\dirname\\basename", Path::AsWindows) == true; // Windows with Drive
Path::isAbsolute("\\\\server\\dir", Path::AsWindows) == true; // Windows with Network
Path::isAbsolute("\\\\?\\C:\\server\\dir", Path::AsWindows) == true; // Windows with Long
Path::isAbsolute("..\\dirname\\basename", Path::AsWindows) == false; // Windows relative
Parameters
[in]inputThe StringView with path to be parsed. Trailing separators are ignored.
[in]typeSpecify to parse as Windows or Posix path
Returns
true if input is absolute

Path::dirname

Returns the directory name of a path. Trailing separators are ignored.

For example:

Path::dirname("/dirname/basename", Path::AsPosix) == "/dirname";
Path::dirname("/dirname/basename//", Path::AsPosix) == "/dirname";
Path::dirname("C:\\dirname\\basename", Path::AsWindows) == "C:\\dirname";
Path::dirname("\\dirname\\basename\\\\", Path::AsWindows) == "\\dirname";
Parameters
[in]inputThe StringView with path to be parsed. Trailing separators are ignored.
[in]typeSpecify to parse as Windows or Posix path
repeathow many directory levels should be removed dirname("/1/2/3/4", repeat=1) == "/1/2"
Returns
Substring of input holding the directory name

Path::basename

Returns the base name of a path. Trailing separators are ignored.

For example:

Path::basename("/a/basename", Path::AsPosix) == "basename";
Path::basename("/a/basename//", Path::AsPosix) == "basename";
Parameters
[in]inputThe StringView with path to be parsed. Trailing separators are ignored.
[in]typeSpecify to parse as Windows or Posix path
Returns
Substring of input holding the base name

Path::parseNameExtension

Splits a StringView of type "name.ext" into "name" and "ext".

Parameters
[in]inputAn input path coded as UTF8 sequence (ex. "name.ext")
[out]nameOutput string holding name ("name" in "name.ext")
[out]extensionOutput string holding extension ("ext" in "name.ext")
Returns
false if both name and extension will be empty after trying to parse them

Example:

SC_TEST_EXPECT(Path::parseNameExtension("name.ext", name, ext));
SC_TEST_EXPECT(name == "name");
SC_TEST_EXPECT(ext == "ext");
SC_TEST_EXPECT(!Path::parseNameExtension("", name, ext));
SC_TEST_EXPECT(name.isEmpty());
SC_TEST_EXPECT(ext.isEmpty());
SC_TEST_EXPECT(!Path::parseNameExtension(".", name, ext));
SC_TEST_EXPECT(name.isEmpty());
SC_TEST_EXPECT(ext.isEmpty());
SC_TEST_EXPECT(Path::parseNameExtension(".ext", name, ext));
SC_TEST_EXPECT(name.isEmpty());
SC_TEST_EXPECT(ext == "ext");
SC_TEST_EXPECT(Path::parseNameExtension("name.", name, ext));
SC_TEST_EXPECT(name == "name");
SC_TEST_EXPECT(ext.isEmpty());
SC_TEST_EXPECT(Path::parseNameExtension("name.name.ext", name, ext));
SC_TEST_EXPECT(name == "name.name");
SC_TEST_EXPECT(ext == "ext");
SC_TEST_EXPECT(Path::parseNameExtension("name..", name, ext));
SC_TEST_EXPECT(name == "name.");
SC_TEST_EXPECT(ext.isEmpty());
#define SC_TEST_EXPECT(e)
Records a test expectation (eventually aborting or breaking o n failed test)
Definition: Testing.h:113

Path::normalize

Resolves all .. to output a normalized path String. For example:

Path::normalize("/Users/SC/../Documents/", cmp, &path, Path::AsPosix);
SC_RELEASE_ASSERT(path == "/Users/Documents");
Parameters
viewThe path to be normalized (but it should not be a view() of the output String)
componentsThe parsed components that once joined will provide the normalized string
[out]output(Optional) pointer to String that will receive the normalized Path (if nullptr)
[in]typeSpecify to parse as Windows or Posix path
Returns
true if the Path was successfully parsed and normalized

Path::relativeFromTo

Get relative path that appended to source resolves to destination. For example:

Path::relativeFromTo("/a/b/1/2/3", "/a/b/d/e", path, Path::AsPosix, Path::AsPosix);
SC_TEST_ASSERT(path == "../../../d/e");
Parameters
[in]sourceThe source Path
[in]destinationThe destination Path
[out]outputThe output relative path computed that transforms source into destination
[in]typeSpecify to parse as Windows or Posix path
[in]outputTypeSpecify if the output relative path should be formatted as a Posix or Windows path
Returns
true if source and destination paths can be properly parsed as absolute paths

FileSystem

copyFile

Copy a single file.

Parameters
sourceSource file path
destinationDestination file path
copyFlagsCopy flags (overwrite, use clone api etc.)
Returns
Valid Result if copy succeeded

Example:

FileSystem fs;
// Make all operations relative to applicationRootDirectory
SC_TEST_EXPECT(fs.init(report.applicationRootDirectory));
// Create a File names 'sourceFile.txt'
StringView contentSource = "this is some content";
SC_TEST_EXPECT(not fs.exists("sourceFile.txt"));
SC_TEST_EXPECT(fs.writeString("sourceFile.txt", contentSource));
// Check that 'sourceFile.txt' exist, but not 'destinationFile.txt'
SC_TEST_EXPECT(fs.existsAndIsFile("sourceFile.txt"));
SC_TEST_EXPECT(not fs.exists("destinationFile.txt"));
// Ask to copy sourceFile.txt to destinationFile.txt (eventually overwriting, but without cloning)
SC_TEST_EXPECT(fs.copyFile("sourceFile.txt", "destinationFile.txt",
FileSystem::CopyFlags().setOverwrite(true).setUseCloneIfSupported(false)));
// Now read the destinationFile.txt content and check if it's the same as source
String content;
SC_TEST_EXPECT(fs.read("destinationFile.txt", content, StringEncoding::Ascii));
SC_TEST_EXPECT(content.view() == contentSource);
// Copy again sourceFile.txt to destinationFile.txt but using clone this time
SC_TEST_EXPECT(fs.copyFile("sourceFile.txt", "destinationFile.txt",
FileSystem::CopyFlags().setOverwrite(true).setUseCloneIfSupported(true)));
// Check again if file exists and its content
SC_TEST_EXPECT(fs.existsAndIsFile("destinationFile.txt"));
SC_TEST_EXPECT(fs.read("destinationFile.txt", content, StringEncoding::Ascii));
SC_TEST_EXPECT(content.view() == contentSource);
// Remove all files created by the test
SC_TEST_EXPECT(fs.removeFiles({"sourceFile.txt", "destinationFile.txt"}));
SC_TEST_EXPECT(not fs.exists("sourceFile.txt"));
SC_TEST_EXPECT(not fs.exists("destinationFile.txt"));

copyDirectory

Copy a single directory.

Parameters
sourceSource directory path
destinationDestination directory path
copyFlagsCopy flags (overwrite, use clone api etc.)
Returns
Valid Result if copy succeeded

Example:

FileSystem fs;
// Make all operations relative to applicationRootDirectory
SC_TEST_EXPECT(fs.init(report.applicationRootDirectory));
// Create a nested directory structure with some files too
SC_TEST_EXPECT(fs.makeDirectory("copyDirectory"));
SC_TEST_EXPECT(fs.write("copyDirectory/testFile.txt", "asdf"));
SC_TEST_EXPECT(fs.existsAndIsFile("copyDirectory/testFile.txt"));
SC_TEST_EXPECT(fs.makeDirectory("copyDirectory/subdirectory"));
SC_TEST_EXPECT(fs.write("copyDirectory/subdirectory/testFile.txt", "asdf"));
// Copy the directory (recursively)
SC_TEST_EXPECT(fs.copyDirectory("copyDirectory", "COPY_copyDirectory"));
// Check that file exists in the new copied directory
SC_TEST_EXPECT(fs.existsAndIsFile("COPY_copyDirectory/testFile.txt"));
SC_TEST_EXPECT(fs.existsAndIsFile("COPY_copyDirectory/subdirectory/testFile.txt"));
// Copying again fails (because we're not overwriting)
SC_TEST_EXPECT(not fs.copyDirectory("copyDirectory", "COPY_copyDirectory"));
// Try copying again but now we ask to overwrite destination
SC_TEST_EXPECT(fs.copyDirectory("copyDirectory", "COPY_copyDirectory", FileSystem::CopyFlags().setOverwrite(true)));
// Remove all files created by the test
SC_TEST_EXPECT(fs.removeFile("copyDirectory/testFile.txt"));
SC_TEST_EXPECT(fs.removeFile("copyDirectory/subdirectory/testFile.txt"));
SC_TEST_EXPECT(fs.removeEmptyDirectory("copyDirectory/subdirectory"));
SC_TEST_EXPECT(fs.removeEmptyDirectory("copyDirectory"));
SC_TEST_EXPECT(fs.removeFile("COPY_copyDirectory/testFile.txt"));
SC_TEST_EXPECT(fs.removeFile("COPY_copyDirectory/subdirectory/testFile.txt"));
SC_TEST_EXPECT(fs.removeEmptyDirectory("COPY_copyDirectory/subdirectory"));
SC_TEST_EXPECT(fs.removeEmptyDirectory("COPY_copyDirectory"));

removeDirectoryRecursive

Remove single directory with its entire content (like posix rm -rf)

Parameters
directoryDirectory to remove
Returns
Valid Result if directory contents has been successfully deleted

Example:

FileSystem fs;
// Make all operations relative to applicationRootDirectory
SC_TEST_EXPECT(fs.init(report.applicationRootDirectory));
// Create a nested directory structure with some files too
SC_TEST_EXPECT(fs.makeDirectory("removeDirectoryTest"));
SC_TEST_EXPECT(fs.write("removeDirectoryTest/testFile.txt", "asdf"));
SC_TEST_EXPECT(fs.makeDirectory("removeDirectoryTest/another"));
SC_TEST_EXPECT(fs.write("removeDirectoryTest/another/yeah.txt", "asdf"));
// Remove the entire tree of directories
SC_TEST_EXPECT(fs.removeDirectoryRecursive("removeDirectoryTest"));
// Check that all files and directories have been removed
SC_TEST_EXPECT(not fs.existsAndIsFile("removeDirectoryTest/testFile.txt"));
SC_TEST_EXPECT(not fs.existsAndIsFile("removeDirectoryTest/another/yeah.txt"));
SC_TEST_EXPECT(not fs.existsAndIsDirectory("removeDirectoryTest/another"));
SC_TEST_EXPECT(not fs.existsAndIsDirectory("removeDirectoryTest"));

makeDirectoryRecursive

Create a new directory, creating also intermediate non existing directories (like posix mkdir -p)

Parameters
directoryPath where to create such directory
Returns
Invalid Result in case of I/O or access error
FileSystem fs;
// Make all operations relative to applicationRootDirectory
SC_TEST_EXPECT(fs.init(report.applicationRootDirectory));
// Create a directory with 2 levels of nesting
SC_TEST_EXPECT(fs.makeDirectoryRecursive("Test3/Subdir"));
// Check that both levels have been created
SC_TEST_EXPECT(fs.existsAndIsDirectory("Test3"));
SC_TEST_EXPECT(fs.existsAndIsDirectory("Test3/Subdir"));
// Remove both levels of directory
SC_TEST_EXPECT(fs.removeEmptyDirectoryRecursive("Test3/Subdir"));
// Check that directory has been removed
SC_TEST_EXPECT(not fs.existsAndIsDirectory("Test3"));

existsAndIsFile

Check if a file exists at given path.

Parameters
fileFile path to check
Returns
true if a file exists at the given path

Example:

FileSystem fs;
// Make all operations relative to applicationRootDirectory
SC_TEST_EXPECT(fs.init(report.applicationRootDirectory));
// Create a File names 'sourceFile.txt'
StringView contentSource = "this is some content";
SC_TEST_EXPECT(not fs.exists("sourceFile.txt"));
SC_TEST_EXPECT(fs.writeString("sourceFile.txt", contentSource));
// Check that 'sourceFile.txt' exist, but not 'destinationFile.txt'
SC_TEST_EXPECT(fs.existsAndIsFile("sourceFile.txt"));
SC_TEST_EXPECT(not fs.exists("destinationFile.txt"));
// Ask to copy sourceFile.txt to destinationFile.txt (eventually overwriting, but without cloning)
SC_TEST_EXPECT(fs.copyFile("sourceFile.txt", "destinationFile.txt",
FileSystem::CopyFlags().setOverwrite(true).setUseCloneIfSupported(false)));
// Now read the destinationFile.txt content and check if it's the same as source
String content;
SC_TEST_EXPECT(fs.read("destinationFile.txt", content, StringEncoding::Ascii));
SC_TEST_EXPECT(content.view() == contentSource);
// Copy again sourceFile.txt to destinationFile.txt but using clone this time
SC_TEST_EXPECT(fs.copyFile("sourceFile.txt", "destinationFile.txt",
FileSystem::CopyFlags().setOverwrite(true).setUseCloneIfSupported(true)));
// Check again if file exists and its content
SC_TEST_EXPECT(fs.existsAndIsFile("destinationFile.txt"));
SC_TEST_EXPECT(fs.read("destinationFile.txt", content, StringEncoding::Ascii));
SC_TEST_EXPECT(content.view() == contentSource);
// Remove all files created by the test
SC_TEST_EXPECT(fs.removeFiles({"sourceFile.txt", "destinationFile.txt"}));
SC_TEST_EXPECT(not fs.exists("sourceFile.txt"));
SC_TEST_EXPECT(not fs.exists("destinationFile.txt"));

existsAndIsDirectory

Check if a directory exists at given path.

Parameters
directoryDirectory path to check
Returns
true if a directory exists at the given path

write

Writes a block of memory to a file.

Parameters
filePath to the file that is meant to be written
dataBlock of memory to write
Returns
Valid Result if the memory was successfully written

Example:

FileSystem fs;
// Make all operations relative to applicationRootDirectory
SC_TEST_EXPECT(fs.init(report.applicationRootDirectory));
StringView content = "ASDF content";
// Check that file doesn't exists before write-ing it and then check that it exist
SC_TEST_EXPECT(not fs.exists("file.txt"));
SC_TEST_EXPECT(fs.writeString("file.txt", content));
SC_TEST_EXPECT(fs.existsAndIsFile("file.txt"));
// Read the file and check its content
String newString;
SC_TEST_EXPECT(fs.read("file.txt", newString, StringEncoding::Ascii));
SC_TEST_EXPECT(newString.view() == content);
// Remove all files created by the test
SC_TEST_EXPECT(fs.removeFile("file.txt"));
SC_TEST_EXPECT(not fs.exists("file.txt"));

read

Reads contents of a file into a SC::Vector buffer.

Parameters
filePath to the file to read
[out]dataDestination buffer that will receive data
Returns
Valid Result if file was fully read successfully
See also
write (for an usage example)

Roadmap

🟦 Complete Features:

  • stat
  • fstat
  • rename
  • chmod
  • chown
  • fsync
  • link (hardlink)
  • symlink
  • sendfile

💡 Unplanned Features: There are many fs operations tat can be added

  • fchmod
  • fchown
  • lchown
  • access
  • ftruncate
  • lstat
  • statfs
  • fdatasync
  • ftruncate
  • readlink