Repository: kdr-aus/ogma (rust)

Issue:
[TASK] Walk fs and parse `.ogma` files
## Goal

Build a structure by walking filesystem hierarchy for `*.ogma` files.

## Aspects to Develop
- Structure could be `BTreeMap<PathBuf, Vec<File>>`
- `PathBuf` is used as partition name/path
- Clashes with `name.ogma` and `name/foo.ogma` should error out with informative error message
- Set up a partition test hierarchy which tests all this things


Pull request description:
Introduce fs structure builder for partitions
Closes #175 

Patch:
diff --git a/ogma/partitions-structure-tests/test01/empty.txt b/ogma/partitions-structure-tests/test01/empty.txt
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test02/bar.ogma b/ogma/partitions-structure-tests/test02/bar.ogma
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test02/foo.ogma b/ogma/partitions-structure-tests/test02/foo.ogma
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test03/bar.ogma b/ogma/partitions-structure-tests/test03/bar.ogma
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test03/barzog/text.txt b/ogma/partitions-structure-tests/test03/barzog/text.txt
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test03/foo.ogma b/ogma/partitions-structure-tests/test03/foo.ogma
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test03/yo.txt b/ogma/partitions-structure-tests/test03/yo.txt
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test03/zog/1.ogma b/ogma/partitions-structure-tests/test03/zog/1.ogma
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test03/zog/2.ogma b/ogma/partitions-structure-tests/test03/zog/2.ogma
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test04/bar/foo/1.ogma b/ogma/partitions-structure-tests/test04/bar/foo/1.ogma
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test04/bar/foo/2.ogma b/ogma/partitions-structure-tests/test04/bar/foo/2.ogma
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test04/foo/1.ogma b/ogma/partitions-structure-tests/test04/foo/1.ogma
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test04/foo/2.ogma b/ogma/partitions-structure-tests/test04/foo/2.ogma
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test04/foo/bar/1.ogma b/ogma/partitions-structure-tests/test04/foo/bar/1.ogma
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test04/foo/foo/1.ogma b/ogma/partitions-structure-tests/test04/foo/foo/1.ogma
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test04/foo/foo/2.ogma b/ogma/partitions-structure-tests/test04/foo/foo/2.ogma
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test05/foo.ogma b/ogma/partitions-structure-tests/test05/foo.ogma
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/partitions-structure-tests/test05/foo/1.ogma b/ogma/partitions-structure-tests/test05/foo/1.ogma
new file mode 100644
index 0000000..e69de29
diff --git a/ogma/src/lang/defs2/mod.rs b/ogma/src/lang/defs2/mod.rs
index f294f40..bf8914a 100644
--- a/ogma/src/lang/defs2/mod.rs
+++ b/ogma/src/lang/defs2/mod.rs
@@ -1,8 +1,16 @@
 //! This handles definitions (fns, structs, enums)
 use crate::prelude::*;
-use std::fmt;
+use ast::Location;
+use lang::parse::File;
+use std::{
+    collections::BTreeMap,
+    fmt,
+    path::{Path, PathBuf},
+};
 
 mod partitions;
+#[cfg(test)]
+mod tests;
 
 use partitions::*;
 
@@ -74,3 +82,82 @@ impl fmt::Display for ImplNode {
         write!(f, "{}", self.index())
     }
 }
+
+impl Definitions {}
+
+type FsMap = BTreeMap<PathBuf, Vec<File>>;
+
+fn build_fs_map(root: &Path) -> Result<FsMap> {
+    fn ioerr(e: std::io::Error) -> Error {
+        Error {
+            cat: err::Category::Parsing,
+            desc: format!("IO error: {e}"),
+            ..Default::default()
+        }
+    }
+
+    let mut map1 = FsMap::default();
+    let mut dirs = Vec::new();
+
+    // do adjacent .ogma files first
+    for e in root.read_dir().map_err(ioerr)? {
+        let e = e.map_err(ioerr)?;
+        let path = e.path();
+        if path.extension().map(|e| e == "ogma").unwrap_or_default() && path.is_file() {
+            let pt = path
+                .strip_prefix(root)
+                .expect("to be prefixed with root")
+                .with_extension("");
+            let file = parse_file(path.clone())?;
+            assert!(map1.insert(pt, vec![file]).is_none());
+        } else if path.is_dir() {
+            dirs.push(path);
+        }
+    }
+
+    // do the directories and subdirs
+    while let Some(dir) = dirs.pop() {
+        let pt = dir
+            .strip_prefix(root)
+            .expect("to be prefixed with root")
+            .to_path_buf();
+        if map1.contains_key(&pt) {
+            return Err(Error {
+                cat: err::Category::Parsing,
+                desc: format!(
+                    "the partition '{}' is defined adjacent and as a directory",
+                    pt.display()
+                ),
+                ..Default::default()
+            });
+        }
+
+        let mut v = Vec::new();
+        for e in dir.read_dir().map_err(ioerr)? {
+            let e = e.map_err(ioerr)?;
+            let path = e.path();
+            if path.extension().map(|e| e == "ogma").unwrap_or_default() && path.is_file() {
+                v.push(parse_file(path)?);
+            } else if path.is_dir() {
+                dirs.push(path);
+            }
+        }
+
+        if !v.is_empty() {
+            assert!(map1.insert(pt, v).is_none());
+        }
+    }
+
+    Ok(map1)
+}
+
+fn parse_file(file: PathBuf) -> Result<File> {
+    let s = std::fs::read_to_string(&file).map_err(|e| Error {
+        cat: err::Category::Parsing,
+        desc: format!("failed to read '{}' as string: {e}", file.display()),
+        hard: true,
+        ..Default::default()
+    })?;
+    let loc = Location::File(file.into(), 0);
+    lang::parse::file(&s, loc)
+}
diff --git a/ogma/src/lang/syntax/parse/file.rs b/ogma/src/lang/syntax/parse/file.rs
index 4138b6f..6773c39 100644
--- a/ogma/src/lang/syntax/parse/file.rs
+++ b/ogma/src/lang/syntax/parse/file.rs
@@ -54,6 +54,19 @@ pub struct Import {
     pub glob: Glob,
 }
 
+impl File {
+    /// Iterate over the import directives.
+    pub fn iter_imports(&self) -> impl Iterator<Item = &Import> {
+        self.directives
+            .iter()
+            .filter_map(|d| match d {
+                Directive::Import(i) => Some(i),
+                _ => None,
+            })
+            .flatten()
+    }
+}
+
 /// Parse a complete file to return the _item pointers_ and directives that exist in the file.
 ///
 /// Note that the items themselves are not parsed, only the outline of the file.


Test patch:
diff --git a/ogma/src/lang/defs2/tests.rs b/ogma/src/lang/defs2/tests.rs
new file mode 100644
index 0000000..03a11bd
--- /dev/null
+++ b/ogma/src/lang/defs2/tests.rs
@@ -0,0 +1,44 @@
+use super::*;
+
+#[test]
+fn fs_structure_building() {
+    fn keys(i: Vec<&str>) -> Vec<PathBuf> {
+        i.into_iter().map(PathBuf::from).collect()
+    }
+
+    let m = build_fs_map(Path::new("partitions-structure-tests/test01")).unwrap();
+    assert_eq!(m.keys().cloned().collect::<Vec<_>>(), keys(vec![]));
+
+    let m = build_fs_map(Path::new("partitions-structure-tests/test02")).unwrap();
+    assert_eq!(
+        m.keys().cloned().collect::<Vec<_>>(),
+        keys(vec!["bar", "foo"])
+    );
+    assert_eq!(m.values().map(Vec::len).collect::<Vec<_>>(), vec![1, 1]);
+
+    let m = build_fs_map(Path::new("partitions-structure-tests/test03")).unwrap();
+    assert_eq!(
+        m.keys().cloned().collect::<Vec<_>>(),
+        keys(vec!["bar", "foo", "zog"])
+    );
+    assert_eq!(m.values().map(Vec::len).collect::<Vec<_>>(), vec![1, 1, 2]);
+
+    let m = build_fs_map(Path::new("partitions-structure-tests/test04")).unwrap();
+    assert_eq!(
+        m.keys().cloned().collect::<Vec<_>>(),
+        keys(vec!["bar/foo", "foo", "foo/bar", "foo/foo"])
+    );
+    assert_eq!(
+        m.values().map(Vec::len).collect::<Vec<_>>(),
+        vec![2, 2, 1, 2]
+    );
+
+    let x = build_fs_map(Path::new("partitions-structure-tests/test05"))
+        .unwrap_err()
+        .to_string();
+    println!("{x}");
+    assert_eq!(
+        &x,
+        "Parsing Error: the partition 'foo' is defined adjacent and as a directory\n"
+    );
+}