From 405bc3d80c0e169ea74dd12422b9504b7383dab3 Mon Sep 17 00:00:00 2001 From: FintanH Date: Mon, 12 Aug 2019 22:22:24 +0100 Subject: Refactor of File to be the combination of Directory and the file name, where Directory is the Vector of component paths. The refactor meant changing some sections of the code where we were parsing and manipulating Files/Directories. This also includes a new trait Canonicalization which is needed for import logic. --- dhall/src/phase/binary.rs | 29 +++++++--- dhall/src/phase/resolve.rs | 11 ++-- dhall_syntax/src/core/import.rs | 122 +++++++++++++++++++++++++++++++++++++++- dhall_syntax/src/parser.rs | 41 +++++++++----- dhall_syntax/src/printer.rs | 10 ++-- 5 files changed, 180 insertions(+), 33 deletions(-) diff --git a/dhall/src/phase/binary.rs b/dhall/src/phase/binary.rs index 1812131..3acd2d4 100644 --- a/dhall/src/phase/binary.rs +++ b/dhall/src/phase/binary.rs @@ -1,12 +1,13 @@ use itertools::Itertools; use serde_cbor::value::value as cbor; use std::iter::FromIterator; +use std::vec; use dhall_syntax::map::DupTreeMap; use dhall_syntax::{ rc, ExprF, FilePrefix, Hash, Import, ImportHashed, ImportLocation, ImportMode, Integer, InterpolatedText, Label, Natural, Scheme, SubExpr, - URL, V, + URL, V, Directory, File, }; use crate::error::{DecodeError, EncodeError}; @@ -243,7 +244,7 @@ fn cbor_value_to_dhall( "import/remote/query".to_owned(), ))?, }; - let path = rest + let mut components: Vec<_> = rest .map(|s| match s.as_string() { Some(s) => Ok(s.clone()), None => Err(DecodeError::WrongFormatError( @@ -251,6 +252,11 @@ fn cbor_value_to_dhall( )), }) .collect::>()?; + let file = components.pop().ok_or( + DecodeError::WrongFormatError( + "import/remote/path".to_owned(), + ))?; + let path = File { directory: Directory { components: components }, file: file }; ImportLocation::Remote(URL { scheme, authority, @@ -269,7 +275,7 @@ fn cbor_value_to_dhall( "import/local/prefix".to_owned(), ))?, }; - let path = rest + let mut components: Vec<_> = rest .map(|s| match s.as_string() { Some(s) => Ok(s.clone()), None => Err(DecodeError::WrongFormatError( @@ -277,6 +283,11 @@ fn cbor_value_to_dhall( )), }) .collect::>()?; + let file = components.pop().ok_or( + DecodeError::WrongFormatError( + "import/remote/path".to_owned(), + ))?; + let path = File { directory: Directory { components: components }, file: file }; ImportLocation::Local(prefix, path) } 6 => { @@ -520,8 +531,8 @@ where use serde::ser::SerializeSeq; let count = 4 + match &import.location_hashed.location { - ImportLocation::Remote(url) => 3 + url.path.len(), - ImportLocation::Local(_, path) => path.len(), + ImportLocation::Remote(url) => 3 + url.path.clone().into_iter().len(), + ImportLocation::Local(_, path) => path.clone().into_iter().len(), ImportLocation::Env(_) => 1, ImportLocation::Missing => 0, }; @@ -573,8 +584,8 @@ where )?, }; ser_seq.serialize_element(&url.authority)?; - for p in &url.path { - ser_seq.serialize_element(p)?; + for p in url.path.clone().into_iter() { + ser_seq.serialize_element(&p)?; } match &url.query { None => ser_seq.serialize_element(&Null)?, @@ -582,8 +593,8 @@ where }; } ImportLocation::Local(_, path) => { - for p in path { - ser_seq.serialize_element(p)?; + for p in path.clone().into_iter() { + ser_seq.serialize_element(&p)?; } } ImportLocation::Env(env) => { diff --git a/dhall/src/phase/resolve.rs b/dhall/src/phase/resolve.rs index c4d7e5f..ba75be4 100644 --- a/dhall/src/phase/resolve.rs +++ b/dhall/src/phase/resolve.rs @@ -16,6 +16,7 @@ type ImportCache = HashMap; pub type ImportStack = Vec; + fn resolve_import( import: &Import, root: &ImportRoot, @@ -30,14 +31,14 @@ fn resolve_import( }; match &import.location_hashed.location { Local(prefix, path) => { - let path: PathBuf = path.iter().cloned().collect(); - let path = match prefix { + let path_buf: PathBuf = path.clone().into_iter().collect(); + let path_buf = match prefix { // TODO: fail gracefully - Parent => cwd.parent().unwrap().join(path), - Here => cwd.join(path), + Parent => cwd.parent().unwrap().join(path_buf), + Here => cwd.join(path_buf), _ => unimplemented!("{:?}", import), }; - Ok(load_import(&path, import_cache, import_stack).map_err(|e| { + Ok(load_import(&path_buf, import_cache, import_stack).map_err(|e| { ImportError::Recursive(import.clone(), Box::new(e)) })?) } diff --git a/dhall_syntax/src/core/import.rs b/dhall_syntax/src/core/import.rs index c328e34..ea42dbc 100644 --- a/dhall_syntax/src/core/import.rs +++ b/dhall_syntax/src/core/import.rs @@ -11,10 +11,41 @@ pub enum FilePrefix { Home, } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Directory { + pub components: Vec, +} + +impl IntoIterator for Directory { + type Item = String; + type IntoIter = ::std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.components.into_iter() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct File { + pub directory: Directory, + pub file: String, +} + +impl IntoIterator for File { + type Item = String; + type IntoIter = ::std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + let mut paths = self.directory.components; + paths.push(self.file); + paths.into_iter() + } +} + /// The location of import (i.e. local vs. remote vs. environment) #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ImportLocation { - Local(FilePrefix, Vec), + Local(FilePrefix, File), Remote(URL), Env(String), Missing, @@ -24,7 +55,7 @@ pub enum ImportLocation { pub struct URL { pub scheme: Scheme, pub authority: String, - pub path: Vec, + pub path: File, pub query: Option, pub headers: Option>, } @@ -59,3 +90,90 @@ pub struct Import { pub mode: ImportMode, pub location_hashed: ImportHashed, } + +pub trait Canonicalize { + fn canonicalize(&self) -> Self; +} + +impl Canonicalize for Directory { + fn canonicalize(&self) -> Directory { + let mut components = Vec::new(); + let mut dir_components = self.clone().into_iter(); + + loop { + let component = dir_components.next(); + match component.as_ref() { + // ─────────────────── + // canonicalize(ε) = ε + None => break, + + // canonicalize(directory₀) = directory₁ + // ─────────────────────────────────────── + // canonicalize(directory₀/.) = directory₁ + Some(c) if c == "." => continue, + + Some(c) if c == ".." => match dir_components.next() { + // canonicalize(directory₀) = ε + // ──────────────────────────── + // canonicalize(directory₀/..) = /.. + None => components.push("..".to_string()), + + // canonicalize(directory₀) = directory₁/.. + // ────────────────────────────────────────────── + // canonicalize(directory₀/..) = directory₁/../.. + Some(ref c) if c == ".." => { + components.push("..".to_string()); + components.push("..".to_string()); + }, + + // canonicalize(directory₀) = directory₁/component + // ─────────────────────────────────────────────── ; If "component" is not + // canonicalize(directory₀/..) = directory₁ ; ".." + Some(_) => continue, + }, + + // canonicalize(directory₀) = directory₁ + // ───────────────────────────────────────────────────────── ; If no other + // canonicalize(directory₀/component) = directory₁/component ; rule matches + Some(c) => components.push(c.clone()), + } + } + + Directory { components: components } + } +} + +impl Canonicalize for File { + fn canonicalize(&self) -> File { + File { directory: self.directory.canonicalize(), file: self.file.clone() } + } +} + +impl Canonicalize for ImportLocation { + fn canonicalize(&self) -> ImportLocation { + match self { + ImportLocation::Local(prefix, file) => ImportLocation::Local(*prefix, file.canonicalize()), + ImportLocation::Remote(url) => ImportLocation::Remote(URL { + scheme: url.scheme, + authority: url.authority.clone(), + path: url.path.canonicalize(), + query: url.query.clone(), + headers: url.headers.clone().map(|boxed_hash| Box::new(boxed_hash.canonicalize())), + }), + ImportLocation::Env(name) => ImportLocation::Env(name.to_string()), + ImportLocation::Missing => ImportLocation::Missing, + } + } +} + +impl Canonicalize for ImportHashed { + fn canonicalize(&self) -> ImportHashed { + ImportHashed { hash: self.hash.clone(), location: self.location.canonicalize() } + } +} + +impl Canonicalize for Import { + fn canonicalize(&self) -> Import { + Import { mode: self.mode, location_hashed: self.location_hashed.canonicalize() } + } +} diff --git a/dhall_syntax/src/parser.rs b/dhall_syntax/src/parser.rs index 9d9a374..7675622 100644 --- a/dhall_syntax/src/parser.rs +++ b/dhall_syntax/src/parser.rs @@ -146,6 +146,14 @@ fn debug_pair(pair: Pair) -> String { s } +fn to_file(path: Vec) -> Result { + let mut path = path; + let file_name: Option = path.pop(); + let file = file_name.ok_or("Empty file path was provided")?; + let directory = Directory { components: path }; + Ok(File { directory: directory, file: file }) +} + macro_rules! make_parser { (@pattern, rule, $name:ident) => (Rule::$name); (@pattern, token_rule, $name:ident) => (Rule::$name); @@ -589,19 +597,25 @@ make_parser! { }); rule!(http_raw; children!( - [scheme(sch), authority(auth), path(p)] => URL { - scheme: sch, - authority: auth, - path: p, - query: None, - headers: None, + [scheme(sch), authority(auth), path(p)] => { + let file = to_file(p)?; + URL { + scheme: sch, + authority: auth, + path: file, + query: None, + headers: None, + } }, - [scheme(sch), authority(auth), path(p), query(q)] => URL { - scheme: sch, - authority: auth, - path: p, - query: Some(q), - headers: None, + [scheme(sch), authority(auth), path(p), query(q)] => { + let file = to_file(p)?; + URL { + scheme: sch, + authority: auth, + path: file, + query: Some(q), + headers: None, + } }, )); @@ -655,7 +669,8 @@ make_parser! { ImportLocation::Remote(url) }, [local((prefix, p))] => { - ImportLocation::Local(prefix, p) + let file = to_file(p)?; + ImportLocation::Local(prefix, file) }, )); diff --git a/dhall_syntax/src/printer.rs b/dhall_syntax/src/printer.rs index dbed55d..8503a1e 100644 --- a/dhall_syntax/src/printer.rs +++ b/dhall_syntax/src/printer.rs @@ -380,17 +380,19 @@ impl Display for ImportHashed { Absolute => "", }; write!(f, "{}/", prefix)?; - let path: String = path - .iter() + let full_path: String = path + .clone() + .into_iter() .map(|c| fmt_local_path_component(c.as_ref())) .join("/"); - f.write_str(&path)?; + f.write_str(&full_path)?; } Remote(url) => { write!(f, "{}://{}/", url.scheme, url.authority,)?; let path: String = url .path - .iter() + .clone() + .into_iter() .map(|c| fmt_remote_path_component(c.as_ref())) .join("/"); f.write_str(&path)?; -- cgit v1.2.3