/* * We implement most aspects of paths independently, not relying on * std::path, on the theory that path syntax is such an important part of a * shell that it doesn't make sense to try to integrate with non-Unix syntaxes. * However, we do use std::path to print individual path components, in order * to get the benefit of its functionality for handling non-Unicode filenames. */ use crate::prelude::*; use crate::path::error::*; lalrpop_mod!(pub parser, "/path/parser.rs"); pub mod error; #[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] pub struct AbsoluteDirectoryPath { directory_names: Vec, } #[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(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] pub struct DirectoryName(String); #[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] pub struct GenericPath { components: Vec, starts_with_slash: bool, ends_with_slash: bool, } #[derive(Clone,Debug,Eq,Hash,Ord,PartialEq,PartialOrd)] pub enum GenericPathComponent { FileOrDirectoryName(String), CurrentDirectory, ParentDirectory, } impl std::fmt::Display for AbsoluteDirectoryPath { 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("/")?; 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(()) } } 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(()) } } 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> { 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::PathLexicallyRelative(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(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)); } 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, }) }