Platform Utilities

The platform module provides cross-platform utilities for interacting with the operating system.

environment

class environment

Utility class for safe environment variable access.

The environment class provides safe, cross-platform access to environment variables using std::optional to handle cases where variables may not exist. This approach avoids the security risks and undefined behavior associated with direct getenv() usage.

Key features:

  • Safe environment variable access with std::optional

  • Cross-platform compatibility (Unix, Windows)

  • No undefined behavior for missing variables

  • Thread-safe read operations

Security considerations:

  • Always check if a variable exists before using it

  • Be aware that environment variables are visible to all processes

  • Avoid storing sensitive information in environment variables

Performance characteristics:

  • O(1) typical case for environment variable lookup

  • Thread-safe for concurrent read access

  • No dynamic memory allocation for the API itself

// Basic usage
if (auto home = environment::value("HOME")) {
    std::cout << "Home directory: " << *home << std::endl;
} else {
    std::cout << "HOME environment variable not set" << std::endl;
}

// Providing defaults
std::string shell = environment::value("SHELL").value_or("/bin/sh");

// Common environment variables
auto path = environment::value("PATH");
auto user = environment::value("USER");
auto temp = environment::value("TMPDIR").value_or("/tmp");

Public Static Functions

static std::optional<std::string> value(const std::string &name)

Get the value of an environment variable.

Safely retrieves the value of the specified environment variable. Returns std::nullopt if the variable is not set or is empty.

Thread safety:

  • Safe for concurrent read access from multiple threads

  • Environment modifications during execution may not be visible

Platform notes:

  • On Unix-like systems, uses getenv() internally

  • On Windows, uses GetEnvironmentVariable() internally

  • Variable names are case-sensitive on Unix, case-insensitive on Windows

// Check if variable exists
if (auto value = environment::value("MY_VAR")) {
    std::cout << "MY_VAR = " << *value << std::endl;
}

// Use with default value
std::string config_dir = environment::value("CONFIG_DIR")
                          .value_or("/etc/myapp");
Parameters:

name – The name of the environment variable to retrieve

Returns:

std::optional containing the value if the variable exists, std::nullopt otherwise

The environment class provides access to environment variables:

#include <dross/environment.h>

// Get environment variable
if (auto home = dross::environment::get("HOME")) {
    std::cout << "Home directory: " << *home << std::endl;
}

// Set environment variable
dross::environment::set("MY_VAR", "my_value");

// Remove environment variable
dross::environment::unset("MY_VAR");

// Get all environment variables
auto all_vars = dross::environment::get_all();
for (const auto& [key, value] : all_vars) {
    std::cout << key << "=" << value << std::endl;
}

path

class path

Cross-platform filesystem path operations with error handling.

The path class provides a safe, cross-platform wrapper around filesystem operations using std::filesystem as the underlying implementation. It uses std::expected for operations that may fail and std::optional for operations that may not return a value.

Key features:

  • Cross-platform path handling (Windows, Unix-like systems)

  • Safe error handling with std::expected and std::optional

  • Path expansion and resolution

  • Directory creation with proper error reporting

  • Integration with std::filesystem

Error handling:

  • Uses std::expected<path, std::filesystem::filesystem_error> for fallible operations

  • Uses std::optional<path> for operations that may not return a value

  • No exceptions thrown directly (may propagate from std::filesystem)

Performance characteristics:

  • Thin wrapper over std::filesystem with minimal overhead

  • Path operations are typically O(1) or O(path_length)

  • Filesystem I/O operations depend on underlying system performance

Thread safety:

  • Path objects are not thread-safe for modification

  • Const operations are thread-safe

  • Filesystem operations may have race conditions inherent to the filesystem

// Basic path operations
path config_path{"~/.config/myapp"};
if (auto expanded = config_path.expand()) {
    std::cout << "Expanded path: " << expanded->string() << std::endl;
}

// Directory creation
path new_dir{"/tmp/myapp/data"};
if (auto result = path::mkdir(new_dir.string())) {
    std::cout << "Directory created: " << result->string() << std::endl;
} else {
    std::cerr << "Failed to create directory: " << result.error().what() << std::endl;
}

// Path building
if (auto home = path::home()) {
    path config_file = home->append("myapp").append("config.toml");
    if (config_file.exists()) {
        // Process config file
    }
}

Public Functions

path()

Default constructor creating an empty path.

path(const std::string &path_str)

Construct a path from a string.

Creates a path object from the given string. The string is interpreted using the native path format for the current platform.

Parameters:

path_str – The path string to construct from

path(const std::filesystem::path &fs_path)

Construct a path from a std::filesystem::path.

Creates a path object wrapping the given std::filesystem::path.

Parameters:

fs_path – The filesystem path to construct from

path(const path &other)

Copy constructor.

Parameters:

other – The path to copy from

bool exists() const

Check if the path exists in the filesystem.

Checks whether the path refers to an existing filesystem entity. This includes files, directories, symbolic links, and other filesystem objects.

Returns:

true if the path exists (file or directory), false otherwise

path append(const std::string &component) const

Append a path component to this path.

Creates a new path by appending the given component using the platform-appropriate path separator. Does not modify this path object.

path base{"/usr/local"};
path full = base.append("bin").append("myapp");
// Result: "/usr/local/bin/myapp"
Parameters:

component – The path component to append

Returns:

New path object with the component appended

std::string string() const

Get the string representation of the path.

Returns the path as a string using the native format for the current platform. On Unix-like systems, uses forward slashes. On Windows, uses backslashes.

Returns:

String representation using native path format

std::expected<path, std::filesystem::filesystem_error> expand() const

Expand user home directory (~) in the path.

Expands tilde (~) notation to the actual home directory path. Only processes paths that start with “~” or “~/”.

path user_config{"~/.config/myapp"};
if (auto expanded = user_config.expand()) {
    // expanded contains something like "/home/user/.config/myapp"
}
Returns:

Expected containing the expanded path on success, or filesystem_error on failure

std::expected<path, std::filesystem::filesystem_error> resolve() const

Resolve the path to an absolute, canonical form.

Converts the path to an absolute path and resolves any symbolic links, “.” and “..” components. The resulting path is in canonical form.

path relative{"../config/../data/file.txt"};
if (auto resolved = relative.resolve()) {
    // resolved contains the canonical absolute path
}
Returns:

Expected containing the resolved path on success, or filesystem_error on failure

operator std::string() const

Convert to string representation.

Implicit conversion to string for convenient usage with APIs that expect string paths.

Returns:

String representation of the path

operator std::filesystem::path() const

Convert to std::filesystem::path.

Provides access to the underlying std::filesystem::path for interoperability with standard library filesystem operations.

Returns:

The underlying std::filesystem::path object

Public Static Functions

static std::expected<path, std::filesystem::filesystem_error> mkdir(const std::string &dir_path)

Create a directory from a string path.

Creates the specified directory and any necessary parent directories. If the directory already exists, the operation succeeds.

if (auto result = path::mkdir("/tmp/myapp/data")) {
    std::cout << "Created: " << result->string() << std::endl;
} else {
    std::cerr << "Error: " << result.error().what() << std::endl;
}
Parameters:

dir_path – The directory path to create as a string

Returns:

Expected containing the created path on success, or filesystem_error on failure

static std::expected<path, std::filesystem::filesystem_error> mkdir(const std::filesystem::path &dir_path)

Create a directory from a filesystem::path.

Creates the specified directory and any necessary parent directories. If the directory already exists, the operation succeeds.

Parameters:

dir_path – The directory path to create as a filesystem::path

Returns:

Expected containing the created path on success, or filesystem_error on failure

static std::optional<path> home()

Get the user’s home directory.

Attempts to determine the user’s home directory using platform-appropriate methods:

  • Unix-like systems: $HOME environment variable

  • Windows: USERPROFILE% or HOMEDRIVE%HOMEPATH%

if (auto home = path::home()) {
    path config = home->append(".config");
} else {
    // Handle case where home directory cannot be determined
}
Returns:

Optional containing the home directory path, or std::nullopt if not determinable

static std::string separator()

Get the platform-appropriate path separator.

Returns the native path separator for the current platform. Useful for building paths manually or for display purposes.

Returns:

String containing the path separator (“/” on Unix, “\” on Windows)

The path class provides filesystem path operations:

#include <dross/path.h>

// Join path components
auto config_path = dross::path::join("/home/user", ".config", "app");

// Get absolute path
auto abs_path = dross::path::absolute("../file.txt");

// Check if path exists
if (dross::path::exists("/etc/passwd")) {
    std::cout << "System has passwd file" << std::endl;
}

// Read file contents
auto result = dross::path::read_file("/path/to/file.txt");
if (result) {
    std::cout << "Content: " << *result << std::endl;
} else {
    std::cerr << "Error: " << result.error().message() << std::endl;
}

// Write file contents
auto write_result = dross::path::write_file("/path/to/output.txt",
                                            "Hello, World!");
if (!write_result) {
    std::cerr << "Write failed: " << write_result.error().message() << std::endl;
}

Path Operations

Common path operations include:

  • join() - Join multiple path components

  • dirname() - Get directory part of path

  • basename() - Get filename part of path

  • extension() - Get file extension

  • stem() - Get filename without extension

  • absolute() - Convert to absolute path

  • normalize() - Normalize path (remove . and ..)

  • relative() - Get relative path between two paths

File Operations

File and directory operations:

  • exists() - Check if path exists

  • is_file() - Check if path is a regular file

  • is_directory() - Check if path is a directory

  • file_size() - Get file size in bytes

  • read_file() - Read entire file contents

  • write_file() - Write data to file

  • create_directory() - Create directory (with parents)

  • remove() - Remove file or empty directory

  • remove_all() - Remove recursively

xdg

class xdg

XDG Base Directory Specification implementation for proper app data storage.

The xdg class implements the XDG Base Directory Specification, which defines standard locations for application data, configuration, cache, and state files on Unix-like systems. This ensures applications store their data in appropriate locations that integrate well with the desktop environment and user expectations.

Key features:

  • XDG Base Directory Specification compliance

  • Automatic fallback to standard directories when XDG variables are unset

  • Application-specific subdirectory creation

  • Cross-platform compatibility (Unix-like systems primarily)

XDG directories:

  • Config: User-specific configuration files

  • Data: User-specific data files

  • Cache: User-specific non-essential cached data

  • State: User-specific state data (logs, history, etc.)

Environment variables used:

  • XDG_CONFIG_HOME (default: ~/.config)

  • XDG_DATA_HOME (default: ~/.local/share)

  • XDG_CACHE_HOME (default: ~/.cache)

  • XDG_STATE_HOME (default: ~/.local/state)

// Application-specific XDG directories
xdg app_dirs{"myapp"};

// Get configuration directory
if (auto config_dir = app_dirs.config_home()) {
    path config_file = path{*config_dir} / "config.toml";
    // Store configuration in ~/.config/myapp/config.toml
}

// Get data directory
if (auto data_dir = app_dirs.data_home()) {
    path db_file = path{*data_dir} / "database.sqlite";
    // Store data in ~/.local/share/myapp/database.sqlite
}

// Get cache directory
if (auto cache_dir = app_dirs.cache_home()) {
    path cache_file = path{*cache_dir} / "thumbnails";
    // Store cache in ~/.cache/myapp/thumbnails
}

Platform compatibility:

  • Full support on Linux and other Unix-like systems

  • Limited support on macOS (uses similar directory structure)

  • Not applicable on Windows (consider using appropriate Windows APIs)

Public Functions

xdg(const std::string &app_name)

Construct XDG directory helper for a specific application.

Creates an XDG directory helper that will create application-specific subdirectories within the standard XDG base directories. The application name is used as the subdirectory name.

The application name should be:

  • A valid directory name (no path separators)

  • Unique to your application

  • Following naming conventions (lowercase, hyphens for separation)

xdg app_dirs{"my-awesome-app"};
// Will create directories like ~/.config/my-awesome-app/
Parameters:

app_name – The name of the application for directory creation

std::optional<std::string> config_home() const

Get the application’s configuration directory.

Returns the application-specific configuration directory according to XDG Base Directory Specification:

  • Uses $XDG_CONFIG_HOME/app_name if XDG_CONFIG_HOME is set

  • Falls back to $HOME/.config/app_name

  • Returns std::nullopt if HOME cannot be determined

The returned directory may not exist yet. Use path::mkdir() to create it.

xdg app{"myapp"};
if (auto config_dir = app.config_home()) {
    // Typically returns something like "/home/user/.config/myapp"
    path config_path{*config_dir};
    if (auto result = path::mkdir(config_path.string())) {
        // Directory created and ready for config files
    }
}
Returns:

Optional containing the config directory path, or std::nullopt on error

std::optional<std::string> data_home() const

Get the application’s data directory.

Returns the application-specific data directory according to XDG Base Directory Specification:

  • Uses $XDG_DATA_HOME/app_name if XDG_DATA_HOME is set

  • Falls back to $HOME/.local/share/app_name

  • Returns std::nullopt if HOME cannot be determined

Use this directory for application data files, databases, etc.

Returns:

Optional containing the data directory path, or std::nullopt on error

std::optional<std::string> cache_home() const

Get the application’s cache directory.

Returns the application-specific cache directory according to XDG Base Directory Specification:

  • Uses $XDG_CACHE_HOME/app_name if XDG_CACHE_HOME is set

  • Falls back to $HOME/.cache/app_name

  • Returns std::nullopt if HOME cannot be determined

Use this directory for non-essential cached data that can be regenerated. Cache files may be deleted by system cleanup tools.

Returns:

Optional containing the cache directory path, or std::nullopt on error

std::optional<std::string> state_home() const

Get the application’s state directory.

Returns the application-specific state directory according to XDG Base Directory Specification:

  • Uses $XDG_STATE_HOME/app_name if XDG_STATE_HOME is set

  • Falls back to $HOME/.local/state/app_name

  • Returns std::nullopt if HOME cannot be determined

Use this directory for state data like logs, history, recently used files, etc. This data should persist between application runs but is not user configuration.

Returns:

Optional containing the state directory path, or std::nullopt on error

The xdg class implements the XDG Base Directory Specification:

#include <dross/xdg.h>

// Get user-specific data directory
auto data_home = dross::xdg::data_home();
// Default: $HOME/.local/share

// Get user-specific configuration directory
auto config_home = dross::xdg::config_home();
// Default: $HOME/.config

// Get user-specific cache directory
auto cache_home = dross::xdg::cache_home();
// Default: $HOME/.cache

// Get user-specific state directory
auto state_home = dross::xdg::state_home();
// Default: $HOME/.local/state

// Get runtime directory
if (auto runtime_dir = dross::xdg::runtime_dir()) {
    std::cout << "Runtime dir: " << *runtime_dir << std::endl;
}

// Get system data directories
auto data_dirs = dross::xdg::data_dirs();
// Default: /usr/local/share:/usr/share

// Get system config directories
auto config_dirs = dross::xdg::config_dirs();
// Default: /etc/xdg

XDG Directories

The XDG Base Directory Specification defines standard locations for:

  • Data files - Application data that should persist

  • Configuration - User-specific configuration files

  • Cache - Non-essential cached data

  • State - Application state data (logs, history, etc.)

  • Runtime - Runtime files (sockets, PIDs, etc.)

Example Usage

Creating application directories:

#include <dross/xdg.h>
#include <dross/path.h>

// Create app-specific directories
auto app_config = dross::path::join(dross::xdg::config_home(), "myapp");
auto app_data = dross::path::join(dross::xdg::data_home(), "myapp");
auto app_cache = dross::path::join(dross::xdg::cache_home(), "myapp");

// Create directories if they don't exist
dross::path::create_directory(app_config);
dross::path::create_directory(app_data);
dross::path::create_directory(app_cache);

// Store configuration
auto config_file = dross::path::join(app_config, "settings.json");
dross::path::write_file(config_file, config_json);

// Store application data
auto data_file = dross::path::join(app_data, "database.db");

// Store cached data
auto cache_file = dross::path::join(app_cache, "thumbnails.cache");

Platform Considerations

Windows Support

On Windows systems:

  • XDG directories map to appropriate Windows locations

  • Path separators are handled automatically

  • Environment variables use Windows conventions

macOS Support

On macOS:

  • XDG directories follow macOS conventions where appropriate

  • ~/Library paths are used for some directories

  • Full POSIX compatibility is maintained

Error Handling

All filesystem operations return std::expected for error handling:

auto result = dross::path::read_file("/nonexistent/file");
if (!result) {
    switch (result.error().code()) {
        case dross::error_code::file_not_found:
            std::cerr << "File not found" << std::endl;
            break;
        case dross::error_code::permission_denied:
            std::cerr << "Permission denied" << std::endl;
            break;
        default:
            std::cerr << "Error: " << result.error().message() << std::endl;
    }
}