Options
PathOptions controls how lexical path transformations should interpret and rebuild path strings. It is used by operations such as normalize(), lexically_normal(), join(), absolute(), relative(), lexically_relative(), and lexically_proximate().
For normal use, include the public header:
#include <vix/path.hpp>All public APIs live in the vix::path namespace.
The options structure
PathOptions is intentionally small. It keeps the common path transformation choices in one place instead of spreading separator rules and normalization behavior across many call sites.
struct PathOptions
{
PathStyle style{PathStyle::Native};
bool collapse_separators{true};
bool remove_dot_segments{true};
bool resolve_dot_dot_segments{true};
bool preserve_trailing_separator{false};
};2
3
4
5
6
7
8
9
The default options use the native path style of the current platform, collapse repeated separators, remove . segments, resolve .. segments when possible, and do not preserve a trailing separator.
vix::path::PathOptions options;
auto path = vix::path::normalize("src//./main.cpp", options);2
3
For documentation examples and predictable tooling behavior, it is usually better to set style explicitly.
PathStyle
PathStyle controls the syntax rules and preferred separator used by path operations.
enum class PathStyle
{
Native,
Posix,
Windows
};2
3
4
5
6
PathStyle::Native follows the platform where the code is running. PathStyle::Posix uses / as the preferred separator. PathStyle::Windows uses \ and recognizes Windows drive roots and UNC-style roots.
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Posix;2
Use an explicit style when the output format matters. This is common in Vix tooling because the generated path format may need to stay stable across platforms.
POSIX options
With POSIX style, generated output uses /.
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Posix;
auto path = vix::path::normalize(
"/a//b/./c/../d",
options
);
if (!path) {
return path.error();
}
// path.value() == "/a/b/d"2
3
4
5
6
7
8
9
10
11
12
13
POSIX style treats a path starting with / as absolute.
bool absolute = vix::path::is_absolute(
"/usr/bin",
vix::path::PathStyle::Posix
);
// absolute == true2
3
4
5
6
Windows options
With Windows style, generated output uses \.
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Windows;
auto path = vix::path::normalize(
"C:\\temp\\\\foo\\.\\bar\\..\\file.txt",
options
);
if (!path) {
return path.error();
}
// path.value() == "C:\\temp\\foo\\file.txt"2
3
4
5
6
7
8
9
10
11
12
13
Windows style recognizes drive-root paths and UNC-style paths.
bool drive = vix::path::is_absolute(
"C:\\Windows",
vix::path::PathStyle::Windows
);
// drive == true2
3
4
5
6
bool unc = vix::path::is_absolute(
"\\\\server\\share",
vix::path::PathStyle::Windows
);
// unc == true2
3
4
5
6
This allows Vix code to reason about Windows paths even when the program is running on another platform.
Native options
PathStyle::Native follows the current platform.
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Native;2
Native style is useful when the path should match the machine running the command. For generated files, manifests, documentation examples, or cross-platform tests, explicit POSIX or Windows style is clearer because it makes the expected output independent from the host platform.
Repeated separators
collapse_separators is part of the options structure and its default value is true.
vix::path::PathOptions options;
options.collapse_separators = true;2
In the current implementation, normalization collapses repeated separators while reading the path segments.
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Posix;
auto path = vix::path::normalize(
"src//core///main.cpp",
options
);
if (!path) {
return path.error();
}
// path.value() == "src/core/main.cpp"2
3
4
5
6
7
8
9
10
11
12
13
Treat collapse_separators as part of the public options model, but do not rely on setting it to false to preserve repeated separators in the current implementation. The active behavior today is that normalized output is rebuilt in a clean lexical form.
Dot segments
remove_dot_segments controls whether . segments are removed during normalization.
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Posix;
options.remove_dot_segments = true;
auto path = vix::path::normalize(
"src/./main.cpp",
options
);
if (!path) {
return path.error();
}
// path.value() == "src/main.cpp"2
3
4
5
6
7
8
9
10
11
12
13
14
When this option is disabled, . segments are kept in the normalized segment list.
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Posix;
options.remove_dot_segments = false;
auto path = vix::path::normalize(
"src/./main.cpp",
options
);
if (!path) {
return path.error();
}
// the dot segment is preserved2
3
4
5
6
7
8
9
10
11
12
13
14
Most Vix code should keep the default. Removing . segments gives cleaner output and simpler diagnostics.
Dot-dot segments
resolve_dot_dot_segments controls whether .. segments are resolved when possible.
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Posix;
options.resolve_dot_dot_segments = true;
auto path = vix::path::normalize(
"src/core/../main.cpp",
options
);
if (!path) {
return path.error();
}
// path.value() == "src/main.cpp"2
3
4
5
6
7
8
9
10
11
12
13
14
When this option is disabled, .. segments are preserved.
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Posix;
options.resolve_dot_dot_segments = false;
auto path = vix::path::normalize(
"src/core/../main.cpp",
options
);
if (!path) {
return path.error();
}
// the dot-dot segment is preserved2
3
4
5
6
7
8
9
10
11
12
13
14
For absolute paths, resolving .. cannot move above the root. When that would happen, normalization returns a structured path error.
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Posix;
auto path = vix::path::normalize(
"/../outside",
options
);
if (!path) {
const auto& err = path.error();
// traversal above root is not allowed
}2
3
4
5
6
7
8
9
10
11
12
For relative paths, unresolved leading .. segments can remain because there is no root boundary to cross.
auto path = vix::path::normalize(
"../src/../include",
options
);
if (!path) {
return path.error();
}
// path.value() == "../include"2
3
4
5
6
7
8
9
10
Trailing separators
preserve_trailing_separator controls whether normalization keeps a trailing separator when the input path had one.
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Posix;
options.preserve_trailing_separator = false;
auto path = vix::path::normalize(
"build/generated/",
options
);
if (!path) {
return path.error();
}
// path.value() == "build/generated"2
3
4
5
6
7
8
9
10
11
12
13
14
Set the option to true when the trailing separator should remain visible in the output.
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Posix;
options.preserve_trailing_separator = true;
auto path = vix::path::normalize(
"build/generated/",
options
);
if (!path) {
return path.error();
}
// path.value() == "build/generated/"2
3
4
5
6
7
8
9
10
11
12
13
14
The option preserves a trailing separator only when the input already ended with one. It does not add a trailing separator to paths that did not have one.
Options with join
join() accepts PathOptions and applies them to the final normalized result.
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Windows;
auto path = vix::path::join(
"C:\\Users\\",
"\\gaspard",
options
);
if (!path) {
return path.error();
}
// path.value() == "C:\\Users\\gaspard"2
3
4
5
6
7
8
9
10
11
12
13
14
This makes join() useful for cross-platform tooling. The caller can choose the output style instead of relying on the platform where the code happens to run.
Options with absolute paths
absolute() uses PathOptions to decide whether the input and base are absolute, which separator to use, and how the final path should be normalized.
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Posix;
auto path = vix::path::absolute(
"docs/readme.md",
"/home/gaspard/project",
options
);
if (!path) {
return path.error();
}
// path.value() == "/home/gaspard/project/docs/readme.md"2
3
4
5
6
7
8
9
10
11
12
13
14
When the input path is already absolute, the base is ignored and the path is normalized.
auto path = vix::path::absolute(
"/home/gaspard/./project",
"/ignored",
options
);
if (!path) {
return path.error();
}
// path.value() == "/home/gaspard/project"2
3
4
5
6
7
8
9
10
11
When the input path is relative, the base must be non-empty and absolute for the selected style.
Options with relative paths
relative(), lexically_relative(), and lexically_proximate() use PathOptions when normalizing both paths and comparing their roots.
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Posix;
auto path = vix::path::relative(
"/a/b/c/file.txt",
"/a/b/d",
options
);
if (!path) {
return path.error();
}
// path.value() == "../c/file.txt"2
3
4
5
6
7
8
9
10
11
12
13
14
With Windows style, drive roots are compared using Windows rules.
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Windows;
auto path = vix::path::lexically_proximate(
"D:\\docs\\file.txt",
"C:\\base",
options
);
if (!path) {
return path.error();
}
// path.value() == "D:\\docs\\file.txt"2
3
4
5
6
7
8
9
10
11
12
13
14
A relative path cannot be computed between incompatible roots, but lexically_proximate() can still return the normalized target path.
A practical options workflow
The following example prepares a POSIX-style generated path, preserves the trailing separator for a directory-like display string, and then builds a file path inside it.
#include <iostream>
#include <vix/path.hpp>
int main()
{
vix::path::PathOptions options;
options.style = vix::path::PathStyle::Posix;
options.preserve_trailing_separator = true;
auto directory = vix::path::normalize(
"build//generated/",
options
);
if (!directory) {
std::cerr << directory.error().message() << '\n';
return 1;
}
options.preserve_trailing_separator = false;
auto file = vix::path::join(
directory.value(),
"./main.cpp",
options
);
if (!file) {
std::cerr << file.error().message() << '\n';
return 1;
}
std::cout << "directory: " << directory.value() << '\n';
std::cout << "file: " << file.value() << '\n';
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
The workflow is still lexical. It prepares strings only. It does not create build, does not create generated, and does not check whether main.cpp exists.
Practical rule
Use PathOptions when the output style or normalization behavior matters. Set style explicitly in tooling code, keep dot and dot-dot normalization enabled for ordinary generated paths, and preserve trailing separators only when the representation needs to show a directory-like path. For real filesystem operations, pass the final path string to the FS module after the lexical work is complete.
The next page explains path errors and how they integrate with the Vix error model.