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
10pub fn resolve_path(path: &str, cwd: &Path) -> PathBuf {
13 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
34pub 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
43pub 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
54pub 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
60fn 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}