diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 00000000..01769619 --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1,168 @@ +#![allow(dead_code)] + +use std::cmp::PartialEq; +use std::env::Args; +use std::fs; +use std::process; + +const HELP: &'static str = r#" +Usage: + erdtree [options] + +OPTIONS: +-d Directory to traverse. Defaults to current working directory. +-l Unsigned integer indicating many nested directory levels to display. Defaults to all. +-p Comma-separated list of prefixes. Directories containing any of + these prefixes will not be traversed. They're memory size will also be ignored. +-h Displays help prompt. +"#; + +/// Struct over valid command line options. +pub struct CommandLineArgs { + pub directory: Option, + pub depth: Option, + pub prefixes: Option +} + +impl Default for CommandLineArgs { + fn default() -> Self { + CommandLineArgs { directory: None, depth: None, prefixes: None } + } +} + +impl CommandLineArgs { + fn set_directory(&mut self, directory: String) { + self.directory = Some(directory); + } + + fn set_depth(&mut self, depth: u64) { + self.depth = Some(depth); + } + + fn set_prefixes(&mut self, prefixes: String) { + self.prefixes = Some(prefixes); + } +} + +/// Enumerations of valid command line options used for finite state automata. +enum CommandLineOption { + Directory, + Depth, + Patterns, + None +} + +impl PartialEq for CommandLineOption { + fn eq(&self, rhs: &str) -> bool { + match self { + Self::Directory => rhs == "-d", + Self::Depth => rhs == "-l", + Self::Patterns => rhs == "-p", + Self::None => false, + } + } +} + +impl CommandLineOption { + /// Parses Args for valid command line options and returns a CommandLineArgs struct + /// containing provided options. Writes to stderr and exits if malformed cl-args. + pub fn parse_args(mut args: Args) -> CommandLineArgs { + if let Some(_) = args.find(|i| i == "-h" ) { + println!("{}", HELP); + process::exit(0); + } + + let mut cli_arguments = CommandLineArgs::default(); + let mut current_state = CommandLineOption::None; + + for arg in args { + match current_state { + CommandLineOption::None => match Self::ascertain_option(&arg) { + Some(opt) => current_state = opt, + None => { + eprintln!("{} is not a valid option.", &arg); + process::exit(1); + } + }, + + CommandLineOption::Directory => { + Self::validate_arg(&arg); + let directory = Self::get_directory_from_arg(&arg); + cli_arguments.set_directory(directory.to_string()); + }, + + CommandLineOption::Depth => { + Self::validate_arg(&arg); + let depth = Self::get_depth_from_arg(&arg); + cli_arguments.set_depth(depth); + }, + + CommandLineOption::Patterns => { + Self::validate_arg(&arg); + cli_arguments.set_prefixes(arg.clone()); + } + } + } + + cli_arguments + } + + /// Takes a command line flag such as '-d' and tries to determine which + /// CommandLineOption it said flag corresponds to. + fn ascertain_option(flag: &str) -> Option { + if &CommandLineOption::Directory == flag { + Some(CommandLineOption::Directory) + } else if &CommandLineOption::Depth == flag { + Some(CommandLineOption::Depth) + } else if &CommandLineOption::Patterns == flag { + Some(CommandLineOption::Patterns) + } else { + None + } + } + + fn get_directory_from_arg(arg: &str) -> &str { + match fs::metadata(arg) { + Ok(_) => arg, + _ => { + eprintln!("'{}' is not a valid directory.", arg); + process::exit(1); + } + } + } + + fn get_depth_from_arg(arg: &str) -> u64 { + match u64::from_str_radix(arg, 10) { + Ok(depth) => depth, + _ => { + eprintln!("'{}' is not an unsigned integer.", arg); + process::exit(1); + } + } + } + + /// Ensures that cl-args are formatted properly otherwise writes + /// to stderr and exists process. + fn validate_arg(arg: &str) { + if ["-d", "-l", "-p"].iter().any(|i| i == &arg) { + eprintln!("Malformed command line arguments."); + process::exit(1); + } + } +} + + +#[cfg(test)] +mod test { + #[test] + fn test_command_line_option() { + use super::CommandLineOption; + + assert!(&CommandLineOption::Directory == "-d"); + assert!(&CommandLineOption::Directory != "-b"); + assert!(&CommandLineOption::Depth == "-l"); + assert!(&CommandLineOption::Depth != "aldsjfh"); + assert!(&CommandLineOption::Patterns == "-p"); + assert!(&CommandLineOption::Patterns != "aldsjfh"); + } +} diff --git a/src/file_tree/mod.rs b/src/file_tree/mod.rs index cd31f62b..92aa2f60 100644 --- a/src/file_tree/mod.rs +++ b/src/file_tree/mod.rs @@ -19,6 +19,12 @@ pub struct FileTree<'a> { depth: Option } +impl<'a> Default for FileTree<'a> { + fn default() -> Self { + FileTree::new(".", Some("."), None).unwrap() + } +} + impl<'a> FileTree<'a> { pub fn new(root_location: &'a S, ignore_patterns: Option<&'a str>, depth: Option) -> FileTreeResult<'a> where S: AsRef + ?Sized @@ -119,7 +125,7 @@ mod test { use super::FileTree; use super::tree_node::FileType; - let file_tree = FileTree::new("./assets/", Some(".")).unwrap(); + let file_tree = FileTree::new("./assets/", Some("."), None).unwrap(); let root_node = file_tree.get_root_node(); assert_eq!(root_node.get_generation(), 0); assert_eq!(root_node.num_children(), 3); diff --git a/src/file_tree/tree_node.rs b/src/file_tree/tree_node.rs index 1065d419..3fe4617c 100644 --- a/src/file_tree/tree_node.rs +++ b/src/file_tree/tree_node.rs @@ -126,22 +126,23 @@ impl TreeNode { let entry = possible_entry.unwrap(); let fname = entry.file_name().into_string().unwrap(); + let ftype = match Self::ascertain_file_type(&entry) { + Ok(file_type) => file_type, + _ => continue + }; match ignore_patterns { Some(ref patterns) => { for i in patterns.iter() { - if fname.starts_with(i) { continue 'entries } + if fname.starts_with(i) && ftype == FileType::Dir { + continue 'entries + } } }, _ => () } let epath = entry.path(); - let ftype = match Self::ascertain_file_type(&entry) { - Ok(file_type) => file_type, - _ => continue - }; - let new_node = Self::new(&epath, ftype, fname, &None, generation + 1); self.len += new_node.len(); diff --git a/src/main.rs b/src/main.rs index 91dbf1fb..85957bf4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,13 @@ +use std::env; + +mod cli; mod file_tree; mod utils; use file_tree::FileTree; fn main() { - let file_tree = FileTree::new(".", Some("."), None).unwrap(); - file_tree.display(); + //let args = env::args(); + + FileTree::default().display() }