From 572f6d7a427f256f25c337813143937d977a7263 Mon Sep 17 00:00:00 2001 From: Josha Inglis Date: Thu, 13 Mar 2025 21:38:22 +1100 Subject: [PATCH] Add support for node prefix filtering in graph traversal Introduces `--include-prefix` and `--exclude-prefix` options to enable filtering of graph nodes based on name prefixes. This ensures more flexible and precise control over which nodes are included or excluded during traversal. Also fixes minor typos in error messages for consistency. --- src/config.rs | 2 ++ src/exceptions.rs | 4 ++-- src/file_dag.rs | 44 +++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 16 ++++++++++++++++ 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/config.rs b/src/config.rs index e1d52d2..6a67ddc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,5 +12,7 @@ pub struct Config<'a> { pub file_end_str: String, pub verbose: bool, pub dry_run: bool, + pub include_node_prefixes: Option<&'a [String]>, + pub exclude_node_prefixes: Option<&'a [String]>, pub include_hidden: bool, } diff --git a/src/exceptions.rs b/src/exceptions.rs index 34244e9..d21a5da 100644 --- a/src/exceptions.rs +++ b/src/exceptions.rs @@ -23,8 +23,8 @@ impl fmt::Display for TopCatError { Self::GraphMissing => write!(f, "Graph is None"), Self::InvalidFileHeader(x, s) => write!(f, "Invalid file header in {}: {}", x.display(), s), Self::NameClash(name, f1, f2) => write!(f, "Name {} found in both {} and {}", name, f1.display(), f2.display()), - Self::MissingExist(x, s) => write!(f, "MissingExist: {} expects {} to exist but it is not found", s, x), - Self::MissingDependency(x, s) => write!(f, "MissingDependency: {} depends on {} bit it is missing", s, x), + Self::MissingExist(x, s) => write!(f, "MissingExist: {} expects {} to exist but it is not found", x, s), + Self::MissingDependency(x, s) => write!(f, "MissingDependency: {} depends on {} but it is missing", x, s), Self::InvalidDependency(x, s) => write!(f, "InvalidDependency: {} is marked as prepend so it cannot depend on {} which isn't marked as prepend", s, x), Self::CyclicDependency(x) => { let mut error_message = "Cyclic dependency detected:\n".to_string(); diff --git a/src/file_dag.rs b/src/file_dag.rs index 889793c..7502eec 100644 --- a/src/file_dag.rs +++ b/src/file_dag.rs @@ -277,6 +277,8 @@ pub struct TCGraph { pub include_globs: Option>, pub include_extensions: Option>, pub exclude_extensions: Option>, + pub include_node_prefixes: Option>, + pub exclude_node_prefixes: Option>, normal_graph: DiGraph, prepend_graph: DiGraph, append_graph: DiGraph, @@ -301,6 +303,11 @@ impl TCGraph { string_slice_to_array(config.include_extensions); let exclude_extensions: Option> = string_slice_to_array(config.exclude_extensions); + let include_node_prefixes: Option> = + string_slice_to_array(config.include_node_prefixes); + let exclude_node_prefixes: Option> = + string_slice_to_array(config.exclude_node_prefixes); + TCGraph { comment_str: config.comment_str.clone(), file_dirs: config.input_dirs.clone(), @@ -308,6 +315,8 @@ impl TCGraph { include_globs, include_extensions, exclude_extensions, + include_node_prefixes, + exclude_node_prefixes, normal_graph: DiGraph::new(), prepend_graph: DiGraph::new(), append_graph: DiGraph::new(), @@ -439,7 +448,40 @@ impl TCGraph { None => return Err(TopCatError::UnknownError("Node not found".to_string())), }; trace!("{} node: {:?}", graph_type.as_str(), file_node.name); - sorted_files.push(file_node.path.clone()); + + // Apply prefix filtering + let should_include = + match (&self.include_node_prefixes, &self.exclude_node_prefixes) { + (Some(include_prefixes), Some(exclude_prefixes)) => { + // If both are specified, include nodes that match include prefixes but not exclude prefixes + include_prefixes + .iter() + .any(|prefix| file_node.name.starts_with(prefix)) + && !exclude_prefixes + .iter() + .any(|prefix| file_node.name.starts_with(prefix)) + } + (Some(include_prefixes), None) => { + // If include prefixes are specified, only include nodes with matching prefixes + include_prefixes + .iter() + .any(|prefix| file_node.name.starts_with(prefix)) + } + (None, Some(exclude_prefixes)) => { + // If exclude prefixes are specified, exclude nodes with matching prefixes + !exclude_prefixes + .iter() + .any(|prefix| file_node.name.starts_with(prefix)) + } + (None, None) => { + // If no prefix filters are specified, include all nodes + true + } + }; + + if should_include { + sorted_files.push(file_node.path.clone()); + } } } Ok(sorted_files) diff --git a/src/main.rs b/src/main.rs index c130003..d75b699 100644 --- a/src/main.rs +++ b/src/main.rs @@ -101,6 +101,20 @@ struct Opt { #[structopt(short = "v", long = "verbose", help = "Print debug information")] verbose: bool, + #[structopt( + long = "include-prefix", + help = "Only include nodes with the given prefixes in the output", + value_name = "PREFIXES" + )] + include_node_prefixes: Option>, + + #[structopt( + long = "exclude-prefix", + help = "Exclude nodes with the given prefixes from the output", + value_name = "PREFIXES" + )] + exclude_node_prefixes: Option>, + #[structopt( short = "d", long = "dry-run", @@ -128,6 +142,8 @@ fn main() -> Result<(), TopCatError> { file_end_str: opt.ensure_each_file_ends_with_str, include_hidden: opt.include_hidden_files_and_directories, verbose: opt.verbose, + include_node_prefixes: opt.include_node_prefixes.as_deref(), + exclude_node_prefixes: opt.exclude_node_prefixes.as_deref(), dry_run: opt.dry_run, };