From db31e770be89cc3e693ffef470878267efeff406 Mon Sep 17 00:00:00 2001 From: Irene Knapp Date: Mon, 28 Dec 2020 16:29:24 -0800 Subject: the which builtin now sorta works. yay! --- src/error.rs | 42 +++++++++++++--- src/main.rs | 62 +++++++++++++++++++++-- src/path.rs | 145 ++++++++++++++++++++++++++++++++++++++++++++++++------ src/path/error.rs | 35 +++++++++++++ 4 files changed, 259 insertions(+), 25 deletions(-) create mode 100644 src/path/error.rs (limited to 'src') diff --git a/src/error.rs b/src/error.rs index 8e51880..018d812 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,5 @@ use crate::path::GenericPath; +use crate::path::error::{FileNameError, DirectoryNameError}; type ParseError<'a> = @@ -8,10 +9,13 @@ type ParseError<'a> = pub enum Error { IO(std::io::Error), Parse(String), + FileName(FileNameError), + DirectoryName(DirectoryNameError), PathListHasEmptyComponents(String), - PathIsAFile(GenericPath), - PathIsRelative(GenericPath), - PathInvalid(GenericPath), + PathLexicallyDirectory(GenericPath), + PathLexicallyRelative(GenericPath), + PathLexicallyInvalid(GenericPath), + PathEmpiricallyFile(GenericPath), } impl std::error::Error for Error { } @@ -21,26 +25,39 @@ impl std::fmt::Display for Error { match self { Error::IO(e) => e.fmt(f), Error::Parse(e) => e.fmt(f), + Error::FileName(e) => e.fmt(f), + Error::DirectoryName(e) => e.fmt(f), Error::PathListHasEmptyComponents(path_list) => f.write_fmt(format_args!( "Path list has empty components: {}", path_list)), - Error::PathIsAFile(path) => + Error::PathLexicallyDirectory(path) => f.write_fmt(format_args!( - "There's a file at {}, not a directory.", + "The path {} ends in a slash, but is supposed to refer to a file, \ + not a directory.", path)), - Error::PathIsRelative(path) => + Error::PathLexicallyRelative(path) => f.write_fmt(format_args!( "The path {} is relative, not absolute.", path)), - Error::PathInvalid(path) => + Error::PathLexicallyInvalid(path) => f.write_fmt(format_args!( "This isn't a valid path. {}", path)), + Error::PathEmpiricallyFile(path) => + f.write_fmt(format_args!( + "There's a file at {}, not a directory.", + path)), } } } +impl From<()> for Error { + fn from(_: ()) -> Error { + unreachable!() + } +} + impl From for Error { fn from(e: std::io::Error) -> Error { Error::IO(e) @@ -53,3 +70,14 @@ impl From> for Error { } } +impl From for Error { + fn from(e: FileNameError) -> Error { + Error::FileName(e) + } +} + +impl From for Error { + fn from(e: DirectoryNameError) -> Error { + Error::DirectoryName(e) + } +} diff --git a/src/main.rs b/src/main.rs index 60c14ca..3b7d26d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,9 @@ use crate::prelude::*; +use std::collections::HashMap; +use std::collections::HashSet; use std::io; use std::io::prelude::*; +use std::os::unix::fs::PermissionsExt; #[macro_use] extern crate lalrpop_util; @@ -71,10 +74,29 @@ fn execute(input: &str) -> Result<()> { println!("{}", input); match invocation.as_slice() { - ["paths", path_list, ..] => { - let paths = path::parse_path_list(path_list)?; - for path in &paths { - println!("{}", path); + ["environment", ..] => { + let environment = read_environment()?; + println!("{:?}", environment); + } + ["which", command_name, ..] => { + let file_name: path::FileName = command_name.parse()?; + let search_paths = get_search_paths()?; + let mut executable_path: Option = None; + for search_path in &search_paths { + let candidate_path = search_path.concat_file_name(&file_name); + match candidate_path.to_sys_path().metadata() { + Ok(metadata) => { + if metadata.is_file() + && metadata.permissions().mode() & 0o111 != 0 + { + println!("{} {:?}", candidate_path, metadata); + executable_path = Some(candidate_path); + break; + } + }, + Err(_) => { }, + } + } }, _ => { @@ -84,3 +106,35 @@ fn execute(input: &str) -> Result<()> { Ok(()) } + + +fn read_environment() -> Result> { + Ok(std::env::vars().collect()) +} + + +fn get_environment(variable_name: &str) -> Result> { + Ok(std::env::vars() + .find(|(key, _)| key == variable_name) + .map(|(_, value)| value)) +} + + +fn get_search_paths() -> Result> { + let paths = get_environment("PATH")?.unwrap_or_default(); + let paths = path::parse_path_list(&paths)?; + + let mut result = Vec::new(); + let mut seen = HashSet::new(); + for path in paths { + if seen.contains(&path) { + continue; + } + + seen.insert(path.clone()); + result.push(path); + } + + Ok(result) +} + diff --git a/src/path.rs b/src/path.rs index 56855ac..2e599bf 100644 --- a/src/path.rs +++ b/src/path.rs @@ -7,29 +7,37 @@ */ use crate::prelude::*; +use crate::path::error::*; lalrpop_mod!(pub parser, "/path/parser.rs"); +pub mod error; -#[derive(Debug)] +#[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] pub struct AbsoluteDirectoryPath { - components: Vec, + directory_names: Vec, } -#[derive(Debug)] +#[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] +pub struct AbsoluteFilePath { + directory_names: Vec, + file_name: FileName, +} + +#[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] pub struct FileName(String); -#[derive(Debug)] +#[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] pub struct DirectoryName(String); -#[derive(Debug)] +#[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] pub struct GenericPath { components: Vec, starts_with_slash: bool, ends_with_slash: bool, } -#[derive(Debug)] +#[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] pub enum GenericPathComponent { FileOrDirectoryName(String), CurrentDirectory, @@ -39,13 +47,29 @@ pub enum GenericPathComponent { impl std::fmt::Display for AbsoluteDirectoryPath { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for component in &self.components { + for directory_name in &self.directory_names { f.write_str("/")?; - component.fmt(f)?; + directory_name.fmt(f)?; + } + + f.write_str("/")?; + + Ok(()) + } +} + + +impl std::fmt::Display for AbsoluteFilePath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for directory_name in &self.directory_names { + f.write_str("/")?; + directory_name.fmt(f)?; } f.write_str("/")?; + self.file_name.fmt(f)?; + Ok(()) } } @@ -125,6 +149,57 @@ impl std::fmt::Display for GenericPathComponent { } +impl std::str::FromStr for FileName { + type Err = FileNameError; + + fn from_str(input: &str) -> std::result::Result { + if input.find('/').is_some() { + Err(FileNameError::ContainsSlash(input.to_string())) + } else { + Ok(FileName(input.to_string())) + } + } +} + + +impl std::str::FromStr for DirectoryName { + type Err = DirectoryNameError; + + fn from_str(input: &str) -> std::result::Result { + if input.find('/').is_some() { + Err(DirectoryNameError::ContainsSlash(input.to_string())) + } else { + Ok(DirectoryName(input.to_string())) + } + } +} + + +impl AbsoluteDirectoryPath { + pub fn to_sys_path(&self) -> std::path::PathBuf { + let mut result = std::path::PathBuf::new(); + result.push(format!("{}", self)); + result + } + + pub fn concat_file_name(&self, file_name: &FileName) -> AbsoluteFilePath { + AbsoluteFilePath { + directory_names: self.directory_names.clone(), + file_name: file_name.clone(), + } + } +} + + +impl AbsoluteFilePath { + pub fn to_sys_path(&self) -> std::path::PathBuf { + let mut result = std::path::PathBuf::new(); + result.push(format!("{}", self)); + result + } +} + + pub fn parse_path_list(path_list: &str) -> Result> { @@ -157,7 +232,7 @@ pub fn absolute_directory_path(generic_path: GenericPath) -> Result { if !generic_path.starts_with_slash { - return Err(Error::PathIsRelative(generic_path)); + return Err(Error::PathLexicallyRelative(generic_path)); } let mut flattened_components = Vec::new(); @@ -168,7 +243,7 @@ pub fn absolute_directory_path(generic_path: GenericPath) if flattened_components.len() > 0 { flattened_components.pop(); } else { - return Err(Error::PathInvalid(generic_path)); + return Err(Error::PathLexicallyInvalid(generic_path)); } }, GenericPathComponent::FileOrDirectoryName(name) => { @@ -177,12 +252,54 @@ pub fn absolute_directory_path(generic_path: GenericPath) } } - if flattened_components.len() == 0 { - return Err(Error::PathInvalid(generic_path)); + Ok(AbsoluteDirectoryPath { + directory_names: flattened_components, + }) +} + + +pub fn absolute_file_path(generic_path: GenericPath) + -> Result +{ + if !generic_path.starts_with_slash { + return Err(Error::PathLexicallyRelative(generic_path)); } - Ok(AbsoluteDirectoryPath { - components: flattened_components, + if generic_path.ends_with_slash { + return Err(Error::PathLexicallyDirectory(generic_path)); + } + + let mut iterator = generic_path.components.iter(); + + let file_name = match iterator.next_back() { + Some(GenericPathComponent::FileOrDirectoryName(name)) => { + FileName(name.to_string()) + } + _ => { + return Err(Error::PathLexicallyInvalid(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::PathLexicallyInvalid(generic_path)); + } + }, + GenericPathComponent::FileOrDirectoryName(name) => { + flattened_components.push(DirectoryName(name.to_string())); + }, + } + } + + Ok(AbsoluteFilePath { + directory_names: flattened_components, + file_name: file_name, }) } diff --git a/src/path/error.rs b/src/path/error.rs new file mode 100644 index 0000000..1162a3b --- /dev/null +++ b/src/path/error.rs @@ -0,0 +1,35 @@ +#[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] +pub enum FileNameError { + ContainsSlash(String), +} + +#[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] +pub enum DirectoryNameError { + ContainsSlash(String), +} + + +impl std::error::Error for FileNameError { } + +impl std::error::Error for DirectoryNameError { } + +impl std::fmt::Display for FileNameError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FileNameError::ContainsSlash(s) => + f.write_fmt(format_args!( + "File names cannot contain slashes, but {:?} does.", s)), + } + } +} + +impl std::fmt::Display for DirectoryNameError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DirectoryNameError::ContainsSlash(s) => + f.write_fmt(format_args!( + "File names cannot contain slashes, but {:?} does.", s)), + } + } +} + -- cgit 1.4.1