Skip to main content

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}