Skip to main content

rab/builtin/
mod.rs

1pub mod bash;
2pub mod commands;
3pub mod edit;
4pub mod file_mutation_queue;
5pub mod read;
6pub mod write;
7
8use std::path::{Path, PathBuf};
9
10/// Resolve a path (relative or absolute) against a working directory.
11/// Expands `~` to the user's home directory.
12pub fn resolve_path(path: &str, cwd: &Path) -> PathBuf {
13    // Expand ~ prefix to home directory
14    let expanded = if let Some(rest) = path.strip_prefix("~/").or_else(|| path.strip_prefix("~")) {
15        let home = std::env::var("HOME")
16            .map(PathBuf::from)
17            .unwrap_or_else(|_| PathBuf::from("/"));
18        if rest.is_empty() {
19            home
20        } else {
21            home.join(rest.strip_prefix('/').unwrap_or(rest))
22        }
23    } else {
24        Path::new(path).to_path_buf()
25    };
26
27    if expanded.is_absolute() {
28        expanded
29    } else {
30        cwd.join(&expanded)
31    }
32}
33
34/// Shorten a path by replacing home directory with `~`.
35pub fn shorten_path(path: &str) -> String {
36    if let Ok(home) = std::env::var("HOME") {
37        path.replacen(&home, "~", 1)
38    } else {
39        path.to_string()
40    }
41}
42
43/// Wrap a styled path string in an OSC 8 hyperlink if the terminal supports it.
44/// The `raw_path` is resolved against `cwd` to produce the file:// URL.
45pub fn link_path(styled_text: &str, raw_path: &str, cwd: &Path) -> String {
46    if !crate::tui::components::markdown::hyperlinks_supported() {
47        return styled_text.to_string();
48    }
49    let abs_path = resolve_path(raw_path, cwd);
50    let url = urlencoding(abs_path.to_string_lossy().as_ref());
51    crate::tui::components::markdown::hyperlink(styled_text, &format!("file://{}", url))
52}
53
54/// Decode a base64-encoded string to raw bytes.
55pub fn base64_decode(input: &str) -> Result<Vec<u8>, base64::DecodeError> {
56    use base64::Engine as _;
57    base64::engine::general_purpose::STANDARD.decode(input)
58}
59
60/// URL-encode a file path for use in a file:// URL.
61fn urlencoding(path: &str) -> String {
62    let mut result = String::with_capacity(path.len());
63    for byte in path.bytes() {
64        match byte {
65            b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' | b'/' => {
66                result.push(byte as char);
67            }
68            _ => {
69                result.push_str(&format!("%{:02X}", byte));
70            }
71        }
72    }
73    result
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_resolve_path_absolute() {
82        let cwd = Path::new("/home/user");
83        let result = resolve_path("/tmp/foo", cwd);
84        assert_eq!(result, Path::new("/tmp/foo"));
85    }
86
87    #[test]
88    fn test_resolve_path_relative() {
89        let cwd = Path::new("/home/user");
90        let result = resolve_path("foo/bar", cwd);
91        assert_eq!(result, Path::new("/home/user/foo/bar"));
92    }
93
94    #[test]
95    fn test_resolve_path_tilde() {
96        let home = std::env::var("HOME").unwrap_or_else(|_| "/home/user".to_string());
97        let cwd = Path::new("/tmp");
98        let result = resolve_path("~/foo", cwd);
99        assert_eq!(result, Path::new(&format!("{}/foo", home)));
100    }
101
102    #[test]
103    fn test_resolve_path_tilde_only() {
104        let home = std::env::var("HOME").unwrap_or_else(|_| "/home/user".to_string());
105        let cwd = Path::new("/tmp");
106        let result = resolve_path("~", cwd);
107        assert_eq!(result, Path::new(&home));
108    }
109
110    #[test]
111    fn test_shorten_path() {
112        let home = std::env::var("HOME").unwrap_or_else(|_| "/home/user".to_string());
113        let result = shorten_path(&format!("{}/foo/bar", home));
114        assert_eq!(result, "~/foo/bar");
115    }
116
117    #[test]
118    fn test_shorten_path_no_match() {
119        let result = shorten_path("/tmp/foo");
120        assert_eq!(result, "/tmp/foo");
121    }
122
123    #[test]
124    fn test_urlencoding() {
125        let result = urlencoding("/home/user/file.txt");
126        assert_eq!(result, "/home/user/file.txt");
127    }
128
129    #[test]
130    fn test_urlencoding_spaces() {
131        let result = urlencoding("/home/user/my file.txt");
132        assert_eq!(result, "/home/user/my%20file.txt");
133    }
134}