Skip to content

Commit

Permalink
Parse paths as inputs and groups
Browse files Browse the repository at this point in the history
  • Loading branch information
dhruvkb committed Oct 23, 2023
1 parent 048c930 commit 789e6e4
Show file tree
Hide file tree
Showing 6 changed files with 523 additions and 0 deletions.
22 changes: 22 additions & 0 deletions src/args.rs
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;
209 changes: 209 additions & 0 deletions src/args/dir_group.rs
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
}
}
108 changes: 108 additions & 0 deletions src/args/files_group.rs
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()
}
}
Loading

0 comments on commit 789e6e4

Please sign in to comment.