diff --git a/Cargo.toml b/Cargo.toml index 2b50bc0..225ca56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ keywords = ["ara", "file-source", "source-map"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +rustc-hash = { version = "1.1.0" } diff --git a/src/hash.rs b/src/hash.rs new file mode 100644 index 0000000..34c002e --- /dev/null +++ b/src/hash.rs @@ -0,0 +1,28 @@ +use std::hash::Hasher; + +pub trait ContentHasher: Send + Sync { + fn hash(&self, content: &str) -> u64; +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct FxHasher; + +impl FxHasher { + pub fn new() -> Self { + Self + } +} + +impl Default for FxHasher { + fn default() -> Self { + Self::new() + } +} + +impl ContentHasher for FxHasher { + fn hash(&self, content: &str) -> u64 { + let mut hasher = rustc_hash::FxHasher::default(); + hasher.write(content.as_bytes()); + hasher.finish() + } +} diff --git a/src/lib.rs b/src/lib.rs index 41bebc7..9232695 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ use crate::error::Error; use crate::source::Source; pub mod error; +pub mod hash; pub mod loader; pub mod source; @@ -62,13 +63,13 @@ mod tests { map.add(Source::new( SourceKind::Script, + "/Documents/Project", "foo.ara", - "function foo(): void {}", )); map.add(Source::new( SourceKind::Script, + "/Documents/Project", "bar.ara", - "function bar(): void {}", )); assert_eq!(map.get(1).unwrap().origin, Some("foo.ara".to_string())); @@ -89,8 +90,8 @@ mod tests { other.add(Source::new( SourceKind::Script, + "/Documents/Project", "baz.ara", - "function baz(): void {}", )); map.merge(&mut other); diff --git a/src/loader.rs b/src/loader.rs index 66d8b62..ed2a9af 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -109,18 +109,18 @@ impl SourceLoader for FileSourceLoader { file.to_path_buf() }; - let content = std::fs::read_to_string(&file)?; let origin = file .strip_prefix(&self.root) .map(|path| path.to_string_lossy()) .unwrap(); + let kind = if origin.ends_with(ARA_DEFINTION_EXTENSION) { SourceKind::Definition } else { SourceKind::Script }; - Ok(SourceMap::new(vec![Source::new(kind, origin, content)])) + Ok(SourceMap::new(vec![Source::new(kind, &self.root, origin)])) } } diff --git a/src/source.rs b/src/source.rs index 5368e32..9db0bba 100644 --- a/src/source.rs +++ b/src/source.rs @@ -1,9 +1,18 @@ +use std::fs; +use std::io::BufReader; +use std::io::Read; +use std::path::PathBuf; +use std::sync::Arc; + +use crate::hash::ContentHasher; +use crate::hash::FxHasher; + pub const DEFAULT_NAME: &str = ""; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum SourceKind { /// A definition is a piece of code that is not executed, but can be used - /// to define foriegn symbols ( e.g from PHP ). + /// to define foreign symbols ( e.g from PHP ). Definition, /// A script is a piece of code that is executed. @@ -13,8 +22,10 @@ pub enum SourceKind { #[derive(Debug, PartialEq, Eq, Clone)] pub struct Source { pub kind: SourceKind, + pub root: Option, pub origin: Option, - pub content: String, + pub content: Option>, + hasher: FxHasher, } /// A source. @@ -27,40 +38,24 @@ pub struct Source { /// use ara_source::source::Source; /// use ara_source::source::SourceKind; /// -/// let source = Source::new(SourceKind::Script, "main.ara", "function main(): void {}"); +/// let source = Source::new(SourceKind::Script, "/Documents/Project", "src/main.ara"); /// /// assert_eq!(source.kind, SourceKind::Script); -/// assert_eq!(source.origin, Some("main.ara".to_string())); -/// assert_eq!(source.content, "function main(): void {}"); +/// assert_eq!(source.origin, Some("src/main.ara".to_string())); +/// assert_eq!(source.root, Some("/Documents/Project".into())); +/// assert_eq!(source.content, None); /// -/// assert_eq!(source.name(), "main.ara"); +/// assert_eq!(source.name(), "src/main.ara"); /// ``` impl Source { - /// Create a new source with the given content. - /// - /// Example: - /// - /// ```rust - /// use ara_source::source::Source; - /// use ara_source::source::SourceKind; - /// - /// let source = Source::inline(SourceKind::Definition, "function main(): void {}"); - /// - /// assert_eq!(source.kind, SourceKind::Definition); - /// assert_eq!(source.origin, None); - /// assert_eq!(source.content, "function main(): void {}"); - /// - /// assert_eq!(source.name(), ""); - /// ``` - pub fn new, C: Into>( - kind: SourceKind, - origin: O, - content: C, - ) -> Source { + /// Create a new source with the given origin. + pub fn new, R: Into>(kind: SourceKind, root: R, origin: O) -> Source { Source { kind, + root: Some(root.into()), origin: Some(origin.into()), - content: content.into(), + content: None, + hasher: FxHasher::new(), } } @@ -75,14 +70,19 @@ impl Source { /// let source = Source::inline(SourceKind::Definition, "function main(): void {}"); /// /// assert_eq!(source.kind, SourceKind::Definition); + /// assert_eq!(source.root, None); /// assert_eq!(source.origin, None); - /// assert_eq!(source.content, "function main(): void {}"); + /// assert_eq!(source.content.as_ref().unwrap().as_str(), "function main(): void {}"); + /// + /// assert_eq!(source.name(), ""); /// ``` pub fn inline>(kind: SourceKind, content: C) -> Source { Source { kind, + root: None, origin: None, - content: content.into(), + content: Some(Arc::new(content.into())), + hasher: FxHasher::new(), } } @@ -97,16 +97,66 @@ impl Source { /// use ara_source::source::Source; /// use ara_source::source::SourceKind; /// - /// let source = Source::new(SourceKind::Definition, "main.ara", "function main(): void {}"); - /// assert_eq!(source.name(), "main.ara"); + /// let source = Source::new(SourceKind::Definition, "/Documents/Project", "src/Foo/main.ara"); + /// assert_eq!(source.name(), "src/Foo/main.ara"); /// /// let source = Source::inline(SourceKind::Definition, "function main(): void {}"); /// assert_eq!(source.name(), ""); /// ``` pub fn name(&self) -> &str { - match self.origin { - Some(ref origin) => origin, + match &self.origin { + Some(origin) => origin, None => DEFAULT_NAME, } } + + /// Returns the complete path of the source. + /// + /// Example: + /// + /// ```rust + /// use ara_source::source::Source; + /// use ara_source::source::SourceKind; + /// + /// let source = Source::new(SourceKind::Definition, "/Documents/Project", "src/Foo/main.ara"); + /// assert_eq!(source.source_path(), Some("/Documents/Project/src/Foo/main.ara".into())); + /// ``` + pub fn source_path(&self) -> Option { + self.root + .as_ref() + .map(|root| root.join(self.origin.as_ref().unwrap())) + } + + /// Returns the content of the source. + /// If the source has no content, the content is read from the file system. + pub fn content(&mut self) -> std::io::Result> { + if let Some(content) = self.content.as_ref() { + return Ok(content.clone()); + } + + let path = self + .source_path() + .expect("Both root and origin must be present in order to read the source content"); + + let mut reader = BufReader::new(fs::File::open(path)?); + let mut file_contents = String::new(); + reader.read_to_string(&mut file_contents)?; + + let content_reference = Arc::new(file_contents); + self.content = Some(content_reference.clone()); + + Ok(content_reference) + } + + /// Returns the hash of the source content. + pub fn hash(&mut self) -> std::io::Result { + let content = self.content()?; + + Ok(self.hasher.hash(&content)) + } + + /// Dispose the content of the source. + pub fn dispose_content(&mut self) { + self.content = None; + } }