git_rs 0.1.0

A (work in progress) pure rust git implementation.
Documentation
use std::collections::BTreeMap;
use crate::errors::Result;
use crate::id::Id;

#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub struct FileMode(u32);

#[derive(Debug)]
pub struct TreeEntry {
    pub mode: FileMode,
    pub id: Id
}

#[derive(Debug)]
pub struct Tree {
    entries: BTreeMap<Vec<u8>, TreeEntry>
}

impl Tree {
    pub fn entries (&self) -> &BTreeMap<Vec<u8>, TreeEntry> {
        &self.entries
    }
}

impl IntoIterator for Tree {
    type Item = (Vec<u8>, TreeEntry);
    type IntoIter = std::collections::btree_map::IntoIter<Vec<u8>, TreeEntry>;

    fn into_iter(self) -> Self::IntoIter {
        self.entries.into_iter()
    }
}

impl Tree {
    pub fn load<T: std::io::Read>(handle: &mut T) -> Result<Tree> {
        let mut vec = Vec::new();
        handle.read_to_end(&mut vec)?;
        let buf = &vec;

        #[derive(Debug)] 
        enum Mode {
            FindSpace,
            FindNull,
            CollectHash
        }
        let mut entries = BTreeMap::new();
        let mut anchor = 0;
        let mut space = 0;
        let mut null = 0;
        let mut mode = Mode::FindSpace;

        for (idx, byte) in buf.iter().enumerate() {
            let next = match mode {
                Mode::FindSpace => {
                    if *byte == 0x20 {
                        space = idx;
                        Mode::FindNull
                    } else {
                        Mode::FindSpace
                    }
                },
                Mode::FindNull => {
                    if *byte == 0 {
                        null = idx;
                        Mode::CollectHash
                    } else {
                        Mode::FindNull
                    }
                },
                Mode::CollectHash => {
                    if idx - null < 20 {
                        Mode::CollectHash
                    } else {
                        let name = buf[space + 1..null].to_vec();
                        let mode_str = std::str::from_utf8(&buf[anchor..space])?;
                        let mode = u32::from_str_radix(mode_str, 8)?;

                        entries.insert(name, TreeEntry {
                            mode: FileMode(mode),
                            id: Id::from(&buf[null + 1..=idx])
                        });

                        anchor = idx + 1;
                        Mode::FindSpace
                    }
                }
            };
            mode = next
        }

        Ok(Tree {
            entries
        })
    }
}

#[cfg(test)]
mod tests {
    use crate::id::Id;
    use std::str::FromStr;
    use crate::objects::tree::FileMode;

    #[test]
    fn tree_read_works() {
        let bytes = include_bytes!("../../fixtures/tree");
        let tree = super::Tree::load(&mut bytes.as_ref()).expect("oh no");
        let tree_entry = tree.entries.get("src".as_bytes()).unwrap();
        assert_eq!(tree_entry.id, Id::from_str("dc222eddb8d03b5f7cac5e7909dd400f3ce33935").unwrap());
        assert_eq!(tree_entry.mode, FileMode(0o40000));
    }

    #[test]
    fn tree_complex_read_works() {
        let bytes = include_bytes!("../../fixtures/tree_1");
        let tree = super::Tree::load(&mut bytes.as_ref()).expect("oh no");
        let mut entries: Vec<&str> = tree.entries.keys()
            .map(|xs| ::std::str::from_utf8(xs).expect("valid utf8"))
            .collect();
        entries.sort();

        assert_eq!(entries.join("\n"), "errors.rs\nid.rs\nlib.rs\nobjects");
        let tree_entries = entries.iter().map(|xs| tree.entries.get(xs.as_bytes()).unwrap());
        let modes: Vec<FileMode> = tree_entries.clone().map(|xs| xs.mode.clone()).collect();
        assert_eq!(
            modes,
            vec![FileMode(0o100644), FileMode(0o100644), FileMode(0o100644), FileMode(0o40000)]
        );
        let ids: Vec<Id> = tree_entries.map(|xs| xs.id.clone()).collect();
        assert_eq!(
            ids,
            vec![
                Id::from_str("b83365ae6f3559a72061c9c8e0ff55c781017c00").unwrap(),
                Id::from_str("fb96e2a4b5142b7acc136a3a653af831c7a3fcf3").unwrap(),
                Id::from_str("b31c74004136914690c436cde6a3c60023ea825c").unwrap(),
                Id::from_str("fbd42d37eddf656834a9a3e470ddb0dfa8a65a32").unwrap(),
            ]
        );
    }
}