use ignore::overrides::OverrideBuilder;
use crate::cli::Imprint;
use crate::fs_wrappers;
use crate::mkdev_error::Context;
use crate::mkdev_error::Error;
use std::cmp::Ordering;
use std::path::Path;
use std::path::PathBuf;
use ignore::{Walk, WalkBuilder};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum RecipeItem {
File(File),
Directory(PathBuf),
}
impl RecipeItem {
pub fn name(&self) -> String {
let name = match self {
RecipeItem::File(file) => file.name.to_string_lossy(),
RecipeItem::Directory(dir) => dir.to_string_lossy(),
};
name.into()
}
fn dir(name: PathBuf) -> Self {
Self::Directory(name)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Hash)]
pub struct File {
pub name: PathBuf,
pub content: String,
}
pub fn make_contents(walk: Walk, root: &Path) -> Result<Vec<RecipeItem>, Error> {
let mut out = vec![];
for file in walk.flatten() {
if file.path() == root {
continue;
}
let data = file
.file_type()
.expect("only none for stdin, which is not allowed.");
let mut path = file.into_path();
if path.starts_with(root) {
path = path
.strip_prefix(root)
.expect("prefix is confirmed to exist")
.into();
}
match data {
data if data.is_symlink() => continue, data if data.is_dir() => out.push(RecipeItem::dir(path)),
data if data.is_file() => out.push(RecipeItem::File(File {
name: path.clone(),
content: fs_wrappers::read_to_string(root.join(path), Context::Imprint)?,
})),
_ => unreachable!(),
}
}
out.sort();
Ok(out)
}
pub fn build_walk(args: &Imprint) -> Result<Walk, Error> {
let cwd = fs_wrappers::current_dir()?;
let mut ob = OverrideBuilder::new(&cwd);
for over in &args.exclude {
ob.add(&format!("!{over}"))?;
}
let user_filters = ob.build()?;
Ok(WalkBuilder::new(&cwd)
.standard_filters(!args.no_filter)
.overrides(user_filters)
.build())
}
impl std::fmt::Display for RecipeItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
impl PartialEq for RecipeItem {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for RecipeItem {}
impl PartialOrd for RecipeItem {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for RecipeItem {
fn cmp(&self, other: &Self) -> Ordering {
fn key(r: &RecipeItem) -> (bool, String) {
(matches!(r, RecipeItem::File(_)), r.name())
}
key(self).cmp(&key(other))
}
}
impl std::hash::Hash for RecipeItem {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
RecipeItem::File(f) => {
0u8.hash(state);
f.name.hash(state);
}
RecipeItem::Directory(d) => {
1u8.hash(state);
d.hash(state);
}
}
}
}