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;