use crate::error::CliError;
#[derive(Debug, PartialEq)]
pub struct Path<'a, const MAX_DEPTH: usize> {
is_absolute: bool,
segments: heapless::Vec<&'a str, MAX_DEPTH>,
}
impl<'a, const MAX_DEPTH: usize> Path<'a, MAX_DEPTH> {
pub fn parse(input: &'a str) -> Result<Self, CliError> {
if input.is_empty() {
return Err(CliError::InvalidPath);
}
let is_absolute = input.starts_with('/');
let path_str = if is_absolute { &input[1..] } else { input };
let mut segments = heapless::Vec::new();
if path_str.is_empty() {
if is_absolute {
return Ok(Self {
is_absolute,
segments,
});
} else {
return Err(CliError::InvalidPath);
}
}
for segment in path_str.split('/') {
if segment.is_empty() {
continue;
}
segments.push(segment).map_err(|_| CliError::PathTooDeep)?;
}
Ok(Self {
is_absolute,
segments,
})
}
pub fn is_absolute(&self) -> bool {
self.is_absolute
}
pub fn segments(&self) -> &[&'a str] {
&self.segments
}
pub fn segment_count(&self) -> usize {
self.segments.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{DefaultConfig, MinimalConfig, ShellConfig};
type TestPath<'a> = Path<'a, { DefaultConfig::MAX_PATH_DEPTH }>;
#[test]
fn test_empty_path_is_invalid() {
let result = TestPath::parse("");
assert_eq!(result, Err(CliError::InvalidPath));
}
#[test]
fn test_absolute_root() {
let path = TestPath::parse("/").unwrap();
assert!(path.is_absolute());
assert_eq!(path.segments(), &[] as &[&str]);
assert_eq!(path.segment_count(), 0);
}
#[test]
fn test_absolute_single_segment() {
let path = TestPath::parse("/system").unwrap();
assert!(path.is_absolute());
assert_eq!(path.segments(), &["system"]);
assert_eq!(path.segment_count(), 1);
}
#[test]
fn test_absolute_multiple_segments() {
let path = TestPath::parse("/system/network/status").unwrap();
assert!(path.is_absolute());
assert_eq!(path.segments(), &["system", "network", "status"]);
assert_eq!(path.segment_count(), 3);
}
#[test]
fn test_relative_single_segment() {
let path = TestPath::parse("help").unwrap();
assert!(!path.is_absolute());
assert_eq!(path.segments(), &["help"]);
}
#[test]
fn test_relative_multiple_segments() {
let path = TestPath::parse("system/network").unwrap();
assert!(!path.is_absolute());
assert_eq!(path.segments(), &["system", "network"]);
}
#[test]
fn test_parent_navigation() {
let path = TestPath::parse("..").unwrap();
assert!(!path.is_absolute());
assert_eq!(path.segments(), &[".."]);
let path = TestPath::parse("../system").unwrap();
assert_eq!(path.segments(), &["..", "system"]);
let path = TestPath::parse("../../hw/led").unwrap();
assert_eq!(path.segments(), &["..", "..", "hw", "led"]);
}
#[test]
fn test_current_directory() {
let path = TestPath::parse(".").unwrap();
assert!(!path.is_absolute());
assert_eq!(path.segments(), &["."]);
let path = TestPath::parse("./cmd").unwrap();
assert_eq!(path.segments(), &[".", "cmd"]);
}
#[test]
fn test_mixed_navigation() {
let path = TestPath::parse("../system/./network").unwrap();
assert_eq!(path.segments(), &["..", "system", ".", "network"]);
}
#[test]
fn test_trailing_slash_ignored() {
let path = TestPath::parse("/system/").unwrap();
assert!(path.is_absolute());
assert_eq!(path.segments(), &["system"]);
let path = TestPath::parse("network/").unwrap();
assert!(!path.is_absolute());
assert_eq!(path.segments(), &["network"]);
}
#[test]
fn test_double_slash_treated_as_single() {
let path = TestPath::parse("/system//network").unwrap();
assert_eq!(path.segments(), &["system", "network"]);
let path = TestPath::parse("//system").unwrap();
assert!(path.is_absolute());
assert_eq!(path.segments(), &["system"]);
}
#[test]
fn test_path_too_deep() {
let deep_path = "a/b/c/d/e/f/g/h/i/j/k";
let result = TestPath::parse(deep_path);
assert_eq!(result, Err(CliError::PathTooDeep));
}
#[test]
fn test_max_depth_exactly() {
let path = TestPath::parse("a/b/c/d/e/f/g/h").unwrap();
assert_eq!(path.segment_count(), 8);
}
#[test]
fn test_absolute_path_with_parent() {
let path = TestPath::parse("/../system").unwrap();
assert!(path.is_absolute());
assert_eq!(path.segments(), &["..", "system"]);
}
#[test]
fn test_minimal_config_respects_depth() {
type MinimalPath<'a> = Path<'a, { MinimalConfig::MAX_PATH_DEPTH }>;
let path = MinimalPath::parse("a/b/c/d").unwrap();
assert_eq!(path.segment_count(), 4);
let result = MinimalPath::parse("a/b/c/d/e");
assert_eq!(result, Err(CliError::PathTooDeep));
}
#[test]
fn test_default_config_allows_deeper_paths() {
let path = TestPath::parse("a/b/c/d/e/f/g/h").unwrap();
assert_eq!(path.segment_count(), 8);
type MinimalPath<'a> = Path<'a, { MinimalConfig::MAX_PATH_DEPTH }>;
let result = MinimalPath::parse("a/b/c/d/e/f/g/h");
assert_eq!(result, Err(CliError::PathTooDeep));
}
}