Skip to main content

plot/lib/logic/
file_markdown_docs.rs

1use std::{
2	fs,
3	path::{Path, PathBuf},
4};
5
6use console::strip_ansi_codes;
7
8#[allow(clippy::upper_case_acronyms)]
9enum ScOrCs {
10	SOTC,
11	COTS,
12}
13
14use crate::lib::logic::{LangMapper, PathNode, TableOutput, TagTime};
15
16pub struct DocMarkdown {
17	cwd: PathNode,
18	wrk: PathNode,
19	table: TableOutput,
20	timetag: TagTime,
21	content: String,
22}
23
24impl DocMarkdown {
25	pub fn new(content: impl Into<String>, table: TableOutput, cwd: PathNode, wrk: PathNode, timetag: TagTime) -> Self {
26		let raw = content.into();
27
28		let clean_content = strip_ansi_codes(&raw).into_owned().replace('\t', "    ");
29
30		Self { cwd, wrk, table, timetag, content: clean_content }
31	}
32
33	// ============================================================================
34	// BUILD & RENDER
35	// ============================================================================
36
37	fn structure_of_the_content_render(&self, title: Option<&str>) -> String {
38		let title_str = title.map_or(String::new(), |t| format!("{} ", t));
39		format!(
40			"# {}(STRUCTURE OF THE CONTENT v:{})\n\n```plaintext\n{}\n```\n",
41			title_str, self.timetag.0, self.content
42		)
43	}
44
45	fn content_of_the_structure_render(&self, code_blocks: &str, title: Option<&str>) -> String {
46		let title_str = title.map_or(String::new(), |t| format!("{} ", t));
47		format!(
48			"# {}(CONTENT OF THE STRUCTURE v:{})\n\n```plaintext\n{}\n```\n\n{}",
49			title_str, self.timetag.0, self.content, code_blocks
50		)
51	}
52
53	pub fn content_of_the_structure_build(&self, target_dir: &Path) -> String {
54		let mut content = String::new();
55
56		let (rows_to_show, index_offset) = if let Some(size) = self.table.trim_size {
57			let start = self.table.trim_page.saturating_sub(1) * size;
58			(self.table.data.rows.iter().skip(start).take(size).collect::<Vec<_>>(), start)
59		} else {
60			(self.table.data.rows.iter().collect::<Vec<_>>(), 0)
61		};
62
63		for (i, row) in rows_to_show.iter().enumerate() {
64			let actual_idx = index_offset + i + 1;
65			let p_str = &row.path;
66
67			if p_str.ends_with('/') {
68				continue;
69			}
70
71			let clean_rel = p_str.strip_prefix("./").unwrap_or(p_str);
72			let absolute_path = target_dir.join(clean_rel);
73
74			let ext = absolute_path.extension().unwrap_or_default().to_string_lossy().to_string();
75
76			let lang = LangMapper::get_md_lang(&ext);
77
78			if LangMapper::is_blacklisted(&ext) {
79				content.push_str(&format!(
80					"## {:03}: `{}`\n\n> *(Plik binarny/graficzny - pominięto zawartość)*\n\n",
81					actual_idx, p_str
82				));
83				continue;
84			}
85
86			match fs::read_to_string(&absolute_path) {
87				Ok(file_content) => {
88					let safe_content = file_content.replace('\t', "    ");
89					content.push_str(&format!(
90						"## {:03}: `{}`\n\n```{}\n{}\n```\n\n",
91						actual_idx, p_str, lang, safe_content
92					));
93				}
94				Err(_) => {
95					content.push_str(&format!(
96						"## {:03}: `{}`\n\n> *(Błąd odczytu / plik nie jest UTF-8)*\n\n",
97						actual_idx, p_str
98					));
99				}
100			}
101		}
102
103		content
104	}
105
106	// ============================================================================
107	// SAVE AS
108	// ============================================================================
109
110	pub fn structure_of_the_content_save_as(&self, relpath: &str, title: Option<&str>) -> std::io::Result<()> {
111		let file_path = self.path_build(relpath, ScOrCs::SOTC)?;
112		let content = self.structure_of_the_content_render(title);
113
114		Self::fs_write(&file_path, content)?;
115		Ok(())
116	}
117
118	pub fn content_of_the_structure_save_as(&self, relpath: &str, title: Option<&str>) -> std::io::Result<()> {
119		let file_path = self.path_build(relpath, ScOrCs::COTS)?;
120		let code_blocks = self.content_of_the_structure_build(&self.wrk.buf);
121		let content = self.content_of_the_structure_render(&code_blocks, title);
122
123		Self::fs_write(&file_path, content)?;
124		Ok(())
125	}
126
127	// ============================================================================
128	// UTILS "DRY"
129	// ============================================================================
130
131	fn fs_write(file_path: &PathBuf, content: String) -> std::io::Result<()> {
132		fs::write(file_path, content)?;
133		println!("📦 Zapisano archiwum kodu do: {}", file_path.display());
134		Ok(())
135	}
136
137	fn path_build(&self, relpath: &str, suffix: ScOrCs) -> std::io::Result<PathBuf> {
138		let base_dir_path: PathBuf = self.cwd.buf.join(relpath);
139
140		let stem = base_dir_path.file_stem().unwrap_or_else(|| std::ffi::OsStr::new("output")).to_string_lossy();
141
142		let suffix_str = match suffix {
143			ScOrCs::COTS => "COTS",
144			ScOrCs::SOTC => "SOTC",
145		};
146
147		let file_name = format!("{}_{}_{}.md", stem, self.timetag.0, suffix_str);
148
149		let file_path = base_dir_path.with_file_name(file_name);
150
151		if let Some(parent) = file_path.parent() {
152			std::fs::create_dir_all(parent)?;
153		}
154
155		Ok(file_path)
156	}
157}