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
11pub fn resolve_path(path: &str, cwd: &Path) -> PathBuf {
14 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
35pub 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
44pub 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
55pub 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
61fn 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}