cargo-plot 0.3.0-beta.3

Szwajcarski scyzoryk do wizualizacji struktury projektu i generowania dokumentacji bezpośrednio z poziomu Cargo.
Documentation
/*
 * use std::{
 * 	collections::BTreeMap,
 * 	fs,
 * 	io::Read,
 * 	path::{Path, PathBuf},
 * };
 *
 * use chrono::{DateTime, Local};
 *
 * use crate::lib::logic::{
 * 	AnchoredPathsDatum,
 * 	MatchLabel,
 * 	Partition,
 * 	TabColumn,
 * 	TabPathStructure,
 * 	TabSortBy,
 * 	TabSortOrder,
 * 	TabSpec,
 * };
 *
 * #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
 * pub enum FileKind {
 * 	Dir,
 * 	Text,
 * 	Binary,
 * 	Other,
 * }
 *
 * /// Idiomatyczny wiersz danych
 * #[derive(Debug, Clone)]
 * pub struct TableRow {
 * 	pub path: String,
 * 	pub size: u64,
 * 	pub modified: DateTime<Local>,
 * 	pub kind: FileKind,
 * }
 *
 * /// Idiomatyczny kontener zebranych danych
 * #[derive(Debug, Clone)]
 * pub struct TableData {
 * 	pub rows: Vec<TableRow>,
 * 	pub structure: TabPathStructure,
 * }
 *
 * /// Ostateczny wynik materializacji
 * pub struct TableSotcTreeOutput {
 * 	pub data: TableData,
 * 	pub columns: Vec<TabColumn>,
 * 	pub trim_size: Option<usize>,
 * 	pub trim_page: usize,
 * 	pub more_icons: bool,
 * }
 *
 * fn is_binary(path: &std::path::Path) -> std::io::Result<bool> {
 * 	let mut file = fs::File::open(path)?;
 * 	let mut buffer = [0u8; 1024];
 * 	let n = file.read(&mut buffer)?;
 * 	Ok(buffer[..n].contains(&0))
 * }
 *
 * fn get_dir_size(path: &std::path::Path) -> u64 {
 * 	walkdir::WalkDir::new(path)
 * 		.into_iter()
 * 		.filter_map(|e| e.ok())
 * 		.filter(|e| e.file_type().is_file())
 * 		.filter_map(|e| e.metadata().ok())
 * 		.map(|m| m.len())
 * 		.sum()
 * }
 *
 * impl TableData {
 * 	pub fn gather<L: MatchLabel>(list: &Partition<L>) -> Self {
 * 		let rows = list.paths.iter().filter_map(|p| Self::inspect(p, &list.entry).ok()).collect();
 * 		Self { rows, structure: TabPathStructure::Tree }
 * 	}
 *
 * 	fn inspect(rel_path: &str, relation: &AnchoredPathsDatum) -> anyhow::Result<TableRow> {
 * 		let clean_rel = rel_path.strip_prefix("./").unwrap_or(rel_path);
 * 		let absolute_path = relation.workspace_dir.buf.join(clean_rel);
 *
 * 		let metadata = fs::metadata(&absolute_path)?;
 * 		let modified = DateTime::from(metadata.modified()?);
 *
 * 		let (kind, size) = if metadata.is_dir() {
 * 			(FileKind::Dir, get_dir_size(&absolute_path))
 * 		} else {
 * 			let k = if is_binary(&absolute_path)? { FileKind::Binary } else { FileKind::Text };
 * 			(k, metadata.len())
 * 		};
 *
 * 		Ok(TableRow { path: rel_path.to_string(), size, modified, kind })
 * 	}
 *
 * 	pub fn sort(mut self, by: TabSortBy, order: TabSortOrder, structure: TabPathStructure) -> Self {
 * 		self.structure = structure;
 *
 * 		fn get_merge_key(path: &str) -> &str {
 * 			let trimmed = path.trim_end_matches('/');
 * 			if let Some(idx) = trimmed.rfind('.')
 * 				&& idx > 0 && trimmed.as_bytes()[idx - 1] != b'/'
 * 			{
 * 				return &trimmed[..idx];
 * 			}
 * 			trimmed
 * 		}
 *
 * 		let compare = |a: &TableRow, b: &TableRow| {
 * 			let a_is_dir = a.kind == FileKind::Dir;
 * 			let b_is_dir = b.kind == FileKind::Dir;
 * 			let a_merge = get_merge_key(&a.path);
 * 			let b_merge = get_merge_key(&b.path);
 *
 * 			let mut cmp = match by {
 * 				TabSortBy::Name => a.path.cmp(&b.path),
 * 				TabSortBy::Size => a.size.cmp(&b.size),
 * 				TabSortBy::Date => a.modified.cmp(&b.modified),
 * 				TabSortBy::Kind => (a.kind.clone() as u8).cmp(&(b.kind.clone() as u8)),
 * 				TabSortBy::FileFirst => (a_is_dir, &a.path).cmp(&(b_is_dir, &b.path)),
 * 				TabSortBy::DirFirst => (!a_is_dir, &a.path).cmp(&(!b_is_dir, &b.path)),
 * 				TabSortBy::FileFirstMerge => (a_merge, a_is_dir, &a.path).cmp(&(b_merge, b_is_dir, &b.path)),
 * 				TabSortBy::DirFirstMerge => (a_merge, !a_is_dir, &a.path).cmp(&(b_merge, !b_is_dir, &b.path)),
 * 			};
 * 			if matches!(order, TabSortOrder::Desc) {
 * 				cmp = cmp.reverse();
 * 			}
 * 			cmp
 * 		};
 *
 * 		match structure {
 * 			TabPathStructure::Tree => {
 * 				let clean_paths: Vec<PathBuf> =
 * 					self.rows.iter().map(|r| PathBuf::from(r.path.trim_end_matches('/'))).collect();
 * 				let mut tree_map: BTreeMap<PathBuf, Vec<usize>> = BTreeMap::new();
 *
 * 				for (i, p) in clean_paths.iter().enumerate() {
 * 					let parent = p.parent().map_or_else(|| PathBuf::from("."), Path::to_path_buf);
 * 					tree_map.entry(parent).or_default().push(i);
 * 				}
 *
 * 				for indices in tree_map.values_mut() {
 * 					indices.sort_by(|&a, &b| compare(&self.rows[a], &self.rows[b]));
 * 				}
 *
 * 				let mut root_indices = Vec::new();
 * 				for (i, p) in clean_paths.iter().enumerate() {
 * 					let parent = p.parent().map_or_else(|| PathBuf::from("."), Path::to_path_buf);
 * 					let is_root = parent == Path::new(".") || parent == Path::new("") || !clean_paths.contains(&parent);
 * 					if is_root {
 * 						root_indices.push(i);
 * 					}
 * 				}
 * 				root_indices.sort_by(|&a, &b| compare(&self.rows[a], &self.rows[b]));
 *
 * 				let mut flat_indices = Vec::with_capacity(self.rows.len());
 * 				fn flatten(
 * 					indices: &[usize],
 * 					tree_map: &BTreeMap<PathBuf, Vec<usize>>,
 * 					clean_paths: &[PathBuf],
 * 					out: &mut Vec<usize>,
 * 				) {
 * 					for &idx in indices {
 * 						out.push(idx);
 * 						if let Some(children) = tree_map.get(&clean_paths[idx]) {
 * 							flatten(children, tree_map, clean_paths, out);
 * 						}
 * 					}
 * 				}
 *
 * 				flatten(&root_indices, &tree_map, &clean_paths, &mut flat_indices);
 *
 * 				let old_rows = std::mem::take(&mut self.rows);
 * 				let mut temp_rows: Vec<Option<TableRow>> = old_rows.into_iter().map(Some).collect();
 * 				let mut new_rows = Vec::with_capacity(temp_rows.len());
 *
 * 				for idx in flat_indices {
 * 					new_rows.push(temp_rows[idx].take().unwrap());
 * 				}
 * 				self.rows = new_rows;
 * 			}
 *
 * 			TabPathStructure::List => {
 * 				// ⚡ LISTA: Czysty sort bez cudowania z mapami
 * 				self.rows.sort_by(compare);
 * 			}
 * 		}
 *
 * 		self
 * 	}
 *
 * 	pub fn into_output(self, spec: &TabSpec) -> TableSotcTreeOutput {
 * 		TableSotcTreeOutput {
 * 			data: self,
 * 			columns: spec.columns.clone(),
 * 			trim_size: spec.trim_size,
 * 			trim_page: spec.trim_page,
 * 			more_icons: spec.more_icons,
 * 		}
 * 	}
 * }
 * */