From efc68f54e3476de0bd209995c36043c26131b8df Mon Sep 17 00:00:00 2001 From: Irene Knapp Date: Fri, 25 Dec 2020 21:10:37 -0800 Subject: refactor path parsing into a separate file, implement some Display traits, etc --- src/error.rs | 35 ++++++---- src/main.rs | 28 ++------ src/path.rs | 169 +++++++++++++++++++++++++++++++++++++++++++++--- src/path/parser.lalrpop | 18 ++++-- src/prelude.rs | 2 + src/result.rs | 3 + 6 files changed, 209 insertions(+), 46 deletions(-) create mode 100644 src/prelude.rs create mode 100644 src/result.rs diff --git a/src/error.rs b/src/error.rs index 96be297..6fe18db 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,6 @@ +use crate::path::GenericPath; + + type ParseError<'a> = lalrpop_util::ParseError, &'a str>; @@ -5,6 +8,10 @@ type ParseError<'a> = pub enum Error { IO(std::io::Error), Parse(String), + PathListHasEmptyComponents(String), + PathIsAFile(String), + PathIsRelative(GenericPath), + PathInvalid(GenericPath), } impl std::error::Error for Error { } @@ -14,18 +21,22 @@ impl std::fmt::Display for Error { match self { Error::IO(e) => e.fmt(f), Error::Parse(e) => e.fmt(f), - } - } -} - -impl std::cmp::PartialEq for Error { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Error::IO(_), Error::IO(_)) => - false, - (Error::Parse(_), Error::Parse(_)) => - false, - _ => false, + Error::PathListHasEmptyComponents(path_list) => + f.write_fmt(format_args!( + "Path list has empty components: {}", + path_list)), + Error::PathIsAFile(path) => + f.write_fmt(format_args!( + "There's a file at {}, not a directory.", + path)), + Error::PathIsRelative(path) => + f.write_fmt(format_args!( + "The path {} is relative, not absolute.", + path)), + Error::PathInvalid(path) => + f.write_fmt(format_args!( + "This isn't a valid path. {}", + path)), } } } diff --git a/src/main.rs b/src/main.rs index 2a5234f..36fb284 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use error::Error; +use crate::prelude::*; use std::io; use std::io::prelude::*; @@ -7,10 +7,10 @@ use std::io::prelude::*; lalrpop_mod!(pub commandline); pub mod error; pub mod path; +pub mod prelude; +pub mod result; -pub type Result = std::result::Result; - pub enum Input { String(String), End, @@ -72,25 +72,9 @@ fn execute(input: &str) -> Result<()> { match invocation.as_slice() { ["paths", path_list, ..] => { - println!("{:?}", path_list); - match path::parser::PathListParser::new().parse(path_list) { - Ok(parsed_paths) => { - println!("paths '{:?}'", parsed_paths); - }, - Err(_) => { - match path::parser::PathListAllowingEmptyPathsParser::new() - .parse(path_list) - { - Ok(parsed) => { - println!("path list has empty component"); - println!("{:?}", parsed); - }, - Err(error) => { - println!("path list is okay, does not have empty component"); - println!("{:?}", error); - }, - } - }, + let paths = path::parse_path_list(path_list)?; + for path in &paths { + println!("{}", path); } }, _ => { diff --git a/src/path.rs b/src/path.rs index bfb5719..7a81873 100644 --- a/src/path.rs +++ b/src/path.rs @@ -1,15 +1,24 @@ +use crate::prelude::*; + lalrpop_mod!(pub parser, "/path/parser.rs"); #[derive(Debug)] -pub struct DirectoryName(String); +pub struct AbsoluteDirectoryPath { + components: Vec, +} #[derive(Debug)] pub struct FileName(String); #[derive(Debug)] -pub struct AbsoluteDirectoryPath { - components: Vec, +pub struct DirectoryName(String); + +#[derive(Debug)] +pub struct GenericPath { + components: Vec, + starts_with_slash: bool, + ends_with_slash: bool, } #[derive(Debug)] @@ -19,9 +28,153 @@ pub enum GenericPathComponent { ParentDirectory, } -#[derive(Debug)] -pub struct GenericPath { - components: Vec, - starts_with_slash: bool, - ends_with_slash: bool, + +impl std::fmt::Display for AbsoluteDirectoryPath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for component in &self.components { + f.write_str("/")?; + component.fmt(f)?; + } + + f.write_str("/")?; + + Ok(()) + } +} + + +impl std::fmt::Display for FileName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FileName(name) => { + let std_path = std::path::Path::new(&name); + f.write_fmt(format_args!("{}", std_path.display()))?; + }, + } + + Ok(()) + } } + + +impl std::fmt::Display for DirectoryName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DirectoryName(name) => { + let std_path = std::path::Path::new(&name); + f.write_fmt(format_args!("{}", std_path.display()))?; + }, + } + + Ok(()) + } +} + + +impl std::fmt::Display for GenericPath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.starts_with_slash { + f.write_str("/")?; + } + + let mut is_first = true; + for component in &self.components { + if !is_first { + f.write_str("/")?; + } + + component.fmt(f)?; + + is_first = false; + } + + if self.ends_with_slash { + f.write_str("/")?; + } + + Ok(()) + } +} + + +impl std::fmt::Display for GenericPathComponent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + GenericPathComponent::FileOrDirectoryName(name) => { + let std_path = std::path::Path::new(&name); + f.write_fmt(format_args!("{}", std_path.display()))?; + }, + GenericPathComponent::CurrentDirectory => { + f.write_str(".")?; + }, + GenericPathComponent::ParentDirectory => { + f.write_str("..")?; + }, + } + + Ok(()) + } +} + + +pub fn parse_path_list(path_list: &str) + -> Result> +{ + match parser::PathListParser::new().parse(path_list) { + Ok(parsed_paths) => { + let mut result = Vec::new(); + for generic_path in parsed_paths { + let path = absolute_directory_path(generic_path)?; + result.push(path); + } + Ok(result) + }, + Err(original_error) => { + match parser::PathListAllowingEmptyPathsParser::new() + .parse(path_list) + { + Ok(_) => { + Err(Error::PathListHasEmptyComponents(path_list.to_string())) + }, + Err(_) => { + Err(Error::Parse(original_error.to_string())) + }, + } + }, + } +} + + +pub fn absolute_directory_path(generic_path: GenericPath) + -> Result +{ + if !generic_path.starts_with_slash { + return Err(Error::PathIsRelative(generic_path)); + } + + let mut flattened_components = Vec::new(); + for component in &generic_path.components { + match component { + GenericPathComponent::CurrentDirectory => { }, + GenericPathComponent::ParentDirectory => { + if flattened_components.len() > 0 { + flattened_components.pop(); + } else { + return Err(Error::PathInvalid(generic_path)); + } + }, + GenericPathComponent::FileOrDirectoryName(name) => { + flattened_components.push(DirectoryName(name.to_string())); + }, + } + } + + if flattened_components.len() == 0 { + return Err(Error::PathInvalid(generic_path)); + } + + Ok(AbsoluteDirectoryPath { + components: flattened_components, + }) +} + diff --git a/src/path/parser.lalrpop b/src/path/parser.lalrpop index 0b3be9a..3547444 100644 --- a/src/path/parser.lalrpop +++ b/src/path/parser.lalrpop @@ -63,14 +63,20 @@ pub PathNoColons: GenericPath = { } PathNoColons2: Vec = { - => - vec![GenericPathComponent::FileOrDirectoryName(<>.to_string())], - SLASH => { - left.push(GenericPathComponent::FileOrDirectoryName(right.to_string())); + => vec![<>], + SLASH => { + left.push(right); left } } +PathComponent: GenericPathComponent = { + DOT => GenericPathComponent::CurrentDirectory, + DOT_DOT => GenericPathComponent::ParentDirectory, + => + GenericPathComponent::FileOrDirectoryName(<>.to_string()) +} + // Whitespace is not allowed. match { r"[^z:/]+" => PATH_COMPONENT_NO_COLONS, @@ -78,5 +84,9 @@ match { r"/" => SLASH, ":" => COLON, + + "." => DOT, + + ".." => DOT_DOT, } diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..b44fe02 --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,2 @@ +pub use crate::error::Error; +pub use crate::result::Result; diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 0000000..8207680 --- /dev/null +++ b/src/result.rs @@ -0,0 +1,3 @@ +use crate::error::Error; + +pub type Result = std::result::Result; -- cgit 1.4.1