Skip to main content

rust_config_tree/
path.rs

1//! Lexical path normalization and include path resolution.
2//!
3//! These helpers normalize paths without consulting the file system. They are
4//! used by both tree loading and template target collection.
5
6use std::path::{Component, Path, PathBuf};
7
8use crate::{ConfigTreeError, Result};
9
10/// Converts a path to an absolute path and normalizes it lexically.
11///
12/// The path does not need to exist. `.` and `..` components are simplified
13/// without resolving symbolic links.
14///
15/// # Arguments
16///
17/// - `path`: Path to convert to an absolute normalized path.
18///
19/// # Returns
20///
21/// Returns the normalized absolute path.
22pub fn absolutize_lexical(path: impl AsRef<Path>) -> Result<PathBuf> {
23    let path = path.as_ref();
24    let path = if path.is_absolute() {
25        path.to_path_buf()
26    } else {
27        std::env::current_dir()
28            .map_err(|source| ConfigTreeError::CurrentDir { source })?
29            .join(path)
30    };
31
32    Ok(normalize_lexical(path))
33}
34
35/// Resolves an include path relative to the file that declared it.
36///
37/// Absolute include paths are only normalized. Relative include paths are joined
38/// to the parent directory of `parent_path` and then normalized.
39///
40/// # Arguments
41///
42/// - `parent_path`: Path of the config file that declared the include.
43/// - `include_path`: Include path declared by `parent_path`.
44///
45/// # Returns
46///
47/// Returns the normalized resolved include path.
48pub fn resolve_include_path(
49    parent_path: impl AsRef<Path>,
50    include_path: impl AsRef<Path>,
51) -> PathBuf {
52    let parent_path = parent_path.as_ref();
53    let include_path = include_path.as_ref();
54
55    if include_path.is_absolute() {
56        return normalize_lexical(include_path);
57    }
58
59    let base_dir = parent_path.parent().unwrap_or_else(|| Path::new("."));
60    normalize_lexical(base_dir.join(include_path))
61}
62
63/// Normalizes a path by removing lexical `.` and `..` components.
64///
65/// This function does not touch the file system and does not resolve symbolic
66/// links.
67///
68/// # Arguments
69///
70/// - `path`: Path to normalize.
71///
72/// # Returns
73///
74/// Returns `path` with lexical current-directory and parent-directory
75/// components simplified.
76pub fn normalize_lexical(path: impl AsRef<Path>) -> PathBuf {
77    let mut normalized = PathBuf::new();
78
79    for component in path.as_ref().components() {
80        match component {
81            Component::CurDir => {}
82            Component::ParentDir => {
83                normalized.pop();
84            }
85            Component::Prefix(_) | Component::RootDir | Component::Normal(_) => {
86                normalized.push(component.as_os_str());
87            }
88        }
89    }
90
91    normalized
92}
93
94#[cfg(test)]
95#[path = "unit_tests/path.rs"]
96mod unit_tests;