plot/lib/logic/anchored_paths_datum.rs
1use std::{
2 env,
3 ffi::OsStr,
4 fs,
5 io,
6 path::{Path, PathBuf},
7};
8
9use anyhow::{Context, Result};
10
11/// Reprezentacja pojedynczego węzła ścieżki (buf + string)
12#[derive(Debug, Clone)]
13pub struct PathNode {
14 pub buf: PathBuf,
15 pub str: String,
16}
17
18fn normalize_path<P: AsRef<Path>>(p: P) -> String {
19 p.as_ref().to_string_lossy().trim_start_matches(r"\\?\").replace('\\', "/").replace("/./", "/")
20}
21
22impl PathNode {
23 pub fn new(buf: PathBuf) -> Self {
24 let str = normalize_path(&buf);
25 Self { buf, str }
26 }
27}
28
29/// Relacja między katalogiem wykonania a katalogiem docelowym
30#[derive(Debug, Clone)]
31pub struct AnchoredPathsDatum {
32 pub execution_dir: PathNode,
33 pub workspace_dir: PathNode,
34}
35
36impl AnchoredPathsDatum {
37 pub fn new<P: AsRef<Path>>(input: P) -> Result<Self> {
38 let input = input.as_ref();
39
40 let execution_dir_buf = env::current_dir().context("Nie można odczytać katalogu roboczego (CWD)")?;
41
42 let workspace_dir_buf = fs::canonicalize(input)
43 .with_context(|| format!("Nie można ustalić ścieżki '{}'", input.to_string_lossy()))?;
44
45 Ok(Self { execution_dir: PathNode::new(execution_dir_buf), workspace_dir: PathNode::new(workspace_dir_buf) })
46 }
47
48 /// Metoda obliczająca względną ścieżkę między execution_dir a workspace_dir.
49 pub fn to_relative_path(&self) -> String {
50 match self.workspace_dir.buf.strip_prefix(&self.execution_dir.buf) {
51 Ok(rel) => {
52 let s = rel.to_string_lossy().replace('\\', "/");
53 if s.is_empty() { "./".to_string() } else { format!("./{}/", s) }
54 }
55 Err(_) => normalize_path(&self.workspace_dir.buf),
56 }
57 }
58 /// Prywatna metoda pomocnicza: Wyznacza fizyczną lokalizację dla pliku raportu i gwarantuje istnienie folderów nadrzędnych.
59 #[allow(dead_code)]
60 fn build_output_file_path(&self, relpath: &str, time_tag: String, suffix: &str) -> io::Result<PathBuf> {
61 // Wszystkie raporty lądują relatywnie do execution_dir
62 let base_dir_path = self.execution_dir.buf.join(relpath);
63
64 let stem = base_dir_path.file_stem().unwrap_or_else(|| OsStr::new("output")).to_string_lossy();
65
66 // Składamy pełną nazwę pliku w jednym kroku
67 let file_name = format!("{}_{}{}.md", stem, time_tag, suffix);
68 let raw_file_path = base_dir_path.with_file_name(file_name);
69 let file_path = PathBuf::from(normalize_path(&raw_file_path));
70
71 if let Some(parent) = file_path.parent() {
72 fs::create_dir_all(parent)?;
73 }
74
75 Ok(file_path)
76 }
77
78 /*
79 * /// Buduje ścieżkę dla dokumentu SOTC (Structure of the content) - i zapisuje
80 * pub fn save_sotc_tree(&self, relpath: &str, title: Option<&str>, table_sotc_tree_cli: String) -> io::Result<()> {
81 * let tt: TagTime = tag_time();
82 * let file_path = self.build_output_file_path(relpath, tt.0.clone(), "SOTC")?;
83 * let title_str = title.map_or(String::new(), |t| format!("{} ", t));
84 * let content = format!(
85 * "# {}(v:{} [Structure of the content])\n{}",
86 * title_str,
87 * tt.0,
88 * Self::md_plaintext(table_sotc_tree_cli)
89 * );
90 * //
91 * Self::fs_write(&file_path, content)?;
92 * Ok(())
93 * }
94 *
95 * /// Buduje ścieżkę dla dokumentu COTS (Content of the structure) - i zapisuje
96 * pub fn save_cots_plot(
97 * &self,
98 * relpath: &str,
99 * title: Option<&str>,
100 * table_sotc_tree_cli: String,
101 * table_sotc_tree_raw: TableSotcTreeOutput,
102 * ) -> io::Result<()> {
103 * let tt: TagTime = tag_time();
104 * let file_path = self.build_output_file_path(relpath, tt.0.clone(), "COTS")?;
105 * let title_str = title.map_or(String::new(), |t| format!("{} ", t));
106 * let content = format!(
107 * "# {}(v:{} [Content of the structure])\n{}\n{}",
108 * title_str,
109 * tt.0,
110 * Self::md_plaintext(table_sotc_tree_cli),
111 * self.md_build(table_sotc_tree_raw)
112 * );
113 * Self::fs_write(&file_path, content)?;
114 * Ok(())
115 * }
116 *
117 * fn md_plaintext(raw: String) -> String { format!("\n```plaintext\n{}\n```\n", Self::md_clean_txt(raw)) }
118 * fn md_clean_txt(content: impl Into<String>) -> String {
119 * let raw = content.into();
120 * strip_ansi_codes(&raw).into_owned().replace('\t', " ")
121 * }
122 *
123 * fn md_build(&self, tabtree: TableSotcTreeOutput) -> String {
124 * let mut content = String::new();
125 *
126 * let (rows_to_show, index_offset) = if let Some(size) = tabtree.trim_size {
127 * let start = tabtree.trim_page.saturating_sub(1) * size;
128 * (tabtree.data.rows.iter().skip(start).take(size).collect::<Vec<_>>(), start)
129 * } else {
130 * (tabtree.data.rows.iter().collect::<Vec<_>>(), 0)
131 * };
132 *
133 * for (i, row) in rows_to_show.iter().enumerate() {
134 * let actual_idx = index_offset + i + 1;
135 * let p_str = &row.path;
136 *
137 * if p_str.ends_with('/') {
138 * continue;
139 * }
140 *
141 * let clean_rel = p_str.strip_prefix("./").unwrap_or(p_str);
142 * let absolute_path = self.workspace_dir.buf.join(clean_rel);
143 *
144 * let ext = absolute_path.extension().unwrap_or_default().to_string_lossy().to_string();
145 *
146 * let lang = LangMapper::get_md_lang(&ext);
147 *
148 * if LangMapper::is_blacklisted(&ext) {
149 * content.push_str(&format!(
150 * "## {:03}: `{}`\n\n> *(Plik binarny/graficzny - pominięto zawartość)*\n\n",
151 * actual_idx, p_str
152 * ));
153 * continue;
154 * }
155 *
156 * match fs::read_to_string(&absolute_path) {
157 * Ok(file_content) => {
158 * let safe_content = file_content.replace('\t', " ");
159 * content.push_str(&format!(
160 * "## {:03}: `{}`\n\n```{}\n{}\n```\n\n",
161 * actual_idx, p_str, lang, safe_content
162 * ));
163 * }
164 * Err(_) => {
165 * content.push_str(&format!(
166 * "## {:03}: `{}`\n\n> *(Błąd odczytu / plik nie jest UTF-8)*\n\n",
167 * actual_idx, p_str
168 * ));
169 * }
170 * }
171 * }
172 *
173 * content
174 * }
175 *
176 * fn fs_write(file_path: &PathBuf, content: String) -> std::io::Result<()> {
177 * fs::write(file_path, content)?;
178 * println!("📦 Zapisano archiwum kodu do: {}", file_path.display());
179 * Ok(())
180 * }
181 * */
182}