Skip to main content

rab/builtin/
mod.rs

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