diff options
-rw-r--r-- | dhall/src/phase/binary.rs | 29 | ||||
-rw-r--r-- | dhall/src/phase/resolve.rs | 11 | ||||
-rw-r--r-- | dhall_syntax/src/core/import.rs | 122 | ||||
-rw-r--r-- | dhall_syntax/src/parser.rs | 41 | ||||
-rw-r--r-- | 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 f4a9cc1..6bdc3f9 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}; @@ -290,7 +291,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( @@ -298,6 +299,11 @@ fn cbor_value_to_dhall( )), }) .collect::<Result<_, _>>()?; + 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, @@ -316,7 +322,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( @@ -324,6 +330,11 @@ fn cbor_value_to_dhall( )), }) .collect::<Result<_, _>>()?; + 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 => { @@ -585,8 +596,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, }; @@ -641,8 +652,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)?, @@ -650,8 +661,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 abcee7e..ac264e6 100644 --- a/dhall/src/phase/resolve.rs +++ b/dhall/src/phase/resolve.rs @@ -16,6 +16,7 @@ type ImportCache = HashMap<Import, Normalized>; pub type ImportStack = Vec<Import>; + 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 d41eae2..82bb7ff 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<String>, +} + +impl IntoIterator for Directory { + type Item = String; + type IntoIter = ::std::vec::IntoIter<Self::Item>; + + 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<Self::Item>; + + 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<String>), + 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<String>, + pub path: File, pub query: Option<String>, // TODO: implement inline headers pub headers: Option<Box<ImportHashed>>, @@ -61,3 +92,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 9ae6dc8..a2495ee 100644 --- a/dhall_syntax/src/parser.rs +++ b/dhall_syntax/src/parser.rs @@ -147,6 +147,14 @@ fn debug_pair(pair: Pair<Rule>) -> String { s } +fn to_file(path: Vec<String>) -> Result<File, String> { + let mut path = path; + let file_name: Option<String> = 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); @@ -630,19 +638,25 @@ make_parser! { }); rule!(http_raw<URL>; 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, + } }, )); @@ -696,7 +710,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 70b224e..5312f23 100644 --- a/dhall_syntax/src/printer.rs +++ b/dhall_syntax/src/printer.rs @@ -365,17 +365,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)?; |