-
-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
523 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
//! This module contains code for working with paths entered as arguments to the | ||
//! application. | ||
//! | ||
//! Since `pls` can accept multiple paths as positional arguments, the module | ||
//! expresses them in terms of [`inputs`](Input) and [`groups`](Group). | ||
//! | ||
//! Each individual path is treated as one input. All directories given as | ||
//! inputs are mapped to [`one group each`](Group::Dir). All files given as | ||
//! input are collected into a [`single group`](Group::Files). | ||
//! | ||
//! The public interface of the module consists of two structs: | ||
//! | ||
//! * [`Group`] | ||
//! * [`Input`] | ||
mod dir_group; | ||
mod files_group; | ||
mod group; | ||
mod input; | ||
|
||
pub use group::Group; | ||
pub use input::Input; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
use crate::args::input::Input; | ||
use crate::config::Args; | ||
use crate::enums::DetailField; | ||
use crate::models::{Node, OwnerMan}; | ||
use crate::traits::Imp; | ||
use log::debug; | ||
use std::collections::HashMap; | ||
use std::fs::DirEntry; | ||
use std::os::unix::ffi::OsStrExt; | ||
|
||
// ====== | ||
// Models | ||
// ====== | ||
|
||
/// Represents a group that renders children of the specified directory. | ||
#[derive(Debug)] | ||
pub struct DirGroup { | ||
pub input: Input, | ||
} | ||
|
||
// =============== | ||
// Implementations | ||
// =============== | ||
|
||
impl DirGroup { | ||
// =========== | ||
// Constructor | ||
// =========== | ||
|
||
pub fn new(input: Input) -> Self { | ||
Self { input } | ||
} | ||
|
||
// ====== | ||
// Public | ||
// ====== | ||
|
||
/// Convert this directory's children into entries for the output layout. | ||
/// | ||
/// Since nodes can be nested, the function uses the flattened output of | ||
/// each node's [`Node::entries`]. | ||
pub fn entries( | ||
&self, | ||
owner_man: &mut OwnerMan, | ||
args: &Args, | ||
) -> Vec<HashMap<DetailField, String>> { | ||
let nodes = self.nodes(args); | ||
let mut nodes = Self::make_tree(nodes); | ||
Self::re_sort(&mut nodes, owner_man, args); | ||
|
||
nodes | ||
.iter() | ||
.flat_map(|node| { | ||
node.entries( | ||
owner_man, | ||
&self.input.conf, | ||
&self.input.conf.app_const, | ||
&self.input.conf.entry_const, | ||
args, | ||
&[], | ||
None, | ||
) | ||
}) | ||
.collect() | ||
} | ||
|
||
// ======= | ||
// Private | ||
// ======= | ||
|
||
/// Convert the directory entry into a [`Node`] instance. | ||
/// | ||
/// This option converts the directory entry into a `Node` instance, | ||
/// associates it with the right set of specs and then returns it if the | ||
/// entry matches the following criteria: | ||
/// | ||
/// * passes the name-based `--only` and `--exclude` filters | ||
/// * is of a type accepted by the `--typ` filter | ||
/// * is above the minimum importance cutoff for visibility | ||
/// | ||
/// If any criteria is not met, the node is not to be rendered and `None` is | ||
/// returned. | ||
fn node(&self, entry: DirEntry, args: &Args) -> Option<Node> { | ||
let name = entry.file_name(); | ||
debug!("Checking visibility of name {name:?}."); | ||
let haystack = name.as_bytes(); | ||
|
||
let include = args | ||
.only | ||
.as_ref() | ||
.map_or(true, |pat| pat.is_match(haystack)); | ||
if !include { | ||
debug!("Name {name:?} did not match `--only`."); | ||
return None; | ||
} | ||
|
||
let exclude = args | ||
.exclude | ||
.as_ref() | ||
.map_or(false, |pat| pat.is_match(haystack)); | ||
if exclude { | ||
debug!("Name {name:?} matched `--exclude`."); | ||
return None; | ||
} | ||
|
||
let mut node = Node::new(&entry.path()); | ||
|
||
debug!("Checking visibility of typ {:?}.", node.typ); | ||
if !args.typs.contains(&node.typ) { | ||
return None; | ||
} | ||
|
||
node.match_specs(&self.input.conf.specs); | ||
|
||
if !node.is_visible(&self.input.conf, args) { | ||
return None; | ||
} | ||
|
||
Some(node) | ||
} | ||
|
||
/// Get a list of all nodes that are a children of this directory. | ||
/// | ||
/// Unlike [`FilesGroup`](crate::args::files_group::FilesGroup), this | ||
/// function filters out nodes based on visibility. | ||
fn nodes(&self, args: &Args) -> Vec<Node> { | ||
self.input | ||
.path | ||
.read_dir() | ||
.unwrap() | ||
.filter_map(|entry| entry.ok().and_then(|entry| self.node(entry, args))) | ||
.collect() | ||
} | ||
|
||
// ====== | ||
// Static | ||
// ====== | ||
|
||
/// Recursively sort the given list of nodes and their children. | ||
/// | ||
/// This function iterates over all the sort bases and sorts the given list | ||
/// of nodes. It is invoked both from the top-level and from each parent | ||
/// node to sort its children. | ||
fn re_sort(nodes: &mut [Node], owner_man: &mut OwnerMan, args: &Args) { | ||
if nodes.len() <= 1 { | ||
return; | ||
} | ||
args.sort_bases.iter().rev().for_each(|field| { | ||
nodes.sort_by(|a, b| field.compare(a, b, owner_man)); | ||
}); | ||
for node in nodes { | ||
Self::re_sort(&mut node.children, owner_man, args); | ||
} | ||
} | ||
|
||
/// Recursively move children nodes into their parent nodes. | ||
fn re_make_node<'a>( | ||
node: Node<'a>, | ||
child_map: &mut HashMap<String, Vec<Node<'a>>>, | ||
) -> Node<'a> { | ||
let mut children = vec![]; | ||
if let Some((_id, child_nodes)) = child_map.remove_entry(&node.name) { | ||
for child_node in child_nodes { | ||
children.push(Self::re_make_node(child_node.tree_child(), child_map)); | ||
} | ||
} | ||
node.tree_parent(children) | ||
} | ||
|
||
/// Move children nodes into their parent nodes and return only top-level nodes. | ||
/// | ||
/// Currently, this is specifically tailored to the collapse feature and not a | ||
/// generic tree implementation. | ||
fn make_tree(nodes: Vec<Node>) -> Vec<Node> { | ||
if nodes.len() <= 1 { | ||
return nodes; | ||
} | ||
|
||
let nodes: Vec<_> = nodes | ||
.into_iter() | ||
.map(|mut node| { | ||
node.find_collapse(); | ||
node | ||
}) | ||
.collect(); | ||
|
||
let mut roots = vec![]; | ||
let mut child_map: HashMap<String, Vec<Node>> = HashMap::new(); | ||
nodes.into_iter().for_each(|node| { | ||
if let Some(collapse) = node.collapse_name.clone() { | ||
let children = child_map.entry(collapse).or_insert(vec![]); | ||
children.push(node); | ||
} else { | ||
roots.push(node); | ||
} | ||
}); | ||
|
||
if child_map.is_empty() { | ||
return roots; | ||
} | ||
|
||
roots = roots | ||
.into_iter() | ||
.map(|root| Self::re_make_node(root, &mut child_map)) | ||
.collect(); | ||
roots.extend(child_map.into_values().flatten()); | ||
roots | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
use crate::args::input::Input; | ||
use crate::config::{Args, Conf, ConfMan}; | ||
use crate::enums::DetailField; | ||
use crate::models::{Node, OwnerMan}; | ||
use crate::utils::paths::common_ancestor; | ||
use log::debug; | ||
use std::collections::HashMap; | ||
use std::path::PathBuf; | ||
|
||
// ====== | ||
// Models | ||
// ====== | ||
|
||
/// Represents a group that renders the given collection of individual files. | ||
/// | ||
/// A group of files will use the UI configuration of their common ancestor | ||
/// while still using their individual configurations for their entry in the | ||
/// layout. | ||
pub struct FilesGroup { | ||
pub inputs: Vec<Input>, | ||
|
||
pub common_ancestor: Option<PathBuf>, | ||
pub parent_conf: Conf, | ||
} | ||
|
||
// =============== | ||
// Implementations | ||
// =============== | ||
|
||
impl FilesGroup { | ||
// =========== | ||
// Constructor | ||
// =========== | ||
|
||
pub fn new(inputs: Vec<Input>, conf_man: &ConfMan) -> Self { | ||
let abs: Vec<_> = inputs.iter().map(|input| input.abs.as_path()).collect(); | ||
let common_ancestor = common_ancestor(&abs); | ||
let mut conf = conf_man.get(common_ancestor.as_ref()).unwrap_or_default(); | ||
conf.app_const.massage_imps(); | ||
|
||
Self { | ||
inputs, | ||
common_ancestor, | ||
parent_conf: conf, | ||
} | ||
} | ||
|
||
// ====== | ||
// Public | ||
// ====== | ||
|
||
/// Convert this list of files into entries for the output layout. | ||
/// | ||
/// Since individual nodes are not nested, the function uses each node's | ||
/// [`Node::row`] instead of the flattened output of each node's | ||
/// [`Node::entries`]. | ||
pub fn entries( | ||
&self, | ||
owner_man: &mut OwnerMan, | ||
args: &Args, | ||
) -> Vec<HashMap<DetailField, String>> { | ||
self.nodes() | ||
.iter() | ||
.map(|(node, conf)| { | ||
node.row( | ||
owner_man, | ||
conf, | ||
&self.parent_conf.app_const, | ||
&conf.entry_const, | ||
args, | ||
&[], | ||
) | ||
}) | ||
.collect() | ||
} | ||
|
||
// ======= | ||
// Private | ||
// ======= | ||
|
||
/// Get a list of nodes from the individual files in this group. | ||
/// | ||
/// Unlike [`DirGroup`](crate::args::dir_group::DirGroup), this function | ||
/// does not filter out nodes based on their visibility. This is because the | ||
/// files in this group have been explicitly provided by the user and should | ||
/// be rendered regardless of their visibility. | ||
fn nodes(&self) -> Vec<(Node, &Conf)> { | ||
self.inputs | ||
.iter() | ||
.map(|input| { | ||
let display_name = input.path.to_string_lossy().to_string(); | ||
let mut node = Node::new(&input.path).solo_file(display_name); | ||
debug!("Currently {} specs", input.conf.specs.len()); | ||
node.match_specs(&input.conf.specs); | ||
(node, &input.conf) | ||
}) | ||
.collect() | ||
} | ||
} | ||
|
||
impl std::fmt::Debug for FilesGroup { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
f.debug_struct("FilesGroup") | ||
.field("inputs", &self.inputs) | ||
.field("common_ancestor", &self.common_ancestor) | ||
.finish() | ||
} | ||
} |
Oops, something went wrong.