sphinx_inv 0.3.0

A rust library to parse Sphinx `objects.inv` files
Documentation
use std::io::Write;

use flate2::{Compression, write::ZlibEncoder};

use crate::{InventoryHeader, SphinxReference};

#[derive(Debug)]
pub struct PlainTextSphinxInventoryWriter<'a> {
    header: &'a InventoryHeader,
    buffer: Vec<&'a SphinxReference>,
    minimize: bool,
}

impl<'a> PlainTextSphinxInventoryWriter<'a> {
    #[must_use]
    pub fn from_header(header: &'a InventoryHeader, capacity: usize, minimize: bool) -> Self {
        Self {
            header,
            buffer: Vec::with_capacity(capacity),
            minimize,
        }
    }

    pub fn add_reference(&mut self, reference: &'a SphinxReference) {
        self.buffer.push(reference);
    }

    pub fn finalize<W: Write>(self, writer: &mut W) -> Result<(), std::io::Error> {
        writer.write_all(format!("{}", self.header).as_bytes())?;
        for reference in self.buffer {
            if self.minimize {
                writer.write_all(format!("{}\n", reference.fmt_minified()).as_bytes())?;
            } else {
                writer.write_all(format!("{}\n", reference.fmt_expanded()).as_bytes())?;
            }
        }
        Ok(())
    }
}

pub struct SphinxInventoryWriter<'a> {
    header: &'a InventoryHeader,
    buffer: Vec<&'a SphinxReference>,
    minimize: bool,
}

impl<'a> SphinxInventoryWriter<'a> {
    pub fn from_header(header: &'a InventoryHeader, capacity: usize, minimize: bool) -> Self {
        Self {
            header,
            buffer: Vec::with_capacity(capacity),
            minimize,
        }
    }

    pub fn add_reference(&mut self, reference: &'a SphinxReference) {
        self.buffer.push(reference);
    }

    pub fn finalize<W: Write>(self, writer: &mut W) -> Result<&mut W, std::io::Error> {
        writer.write_all(format!("{}", self.header).as_bytes())?;
        writer.flush()?;
        let mut zlib_writer = ZlibEncoder::new(writer, Compression::fast());
        for reference in self.buffer {
            if self.minimize {
                zlib_writer.write_all(format!("{}\n", reference.fmt_minified()).as_bytes())?;
            } else {
                zlib_writer.write_all(format!("{}\n", reference.fmt_expanded()).as_bytes())?;
            }
        }
        zlib_writer.finish()
    }
}

#[cfg(test)]
mod test {
    #![allow(clippy::unwrap_used)]
    use pretty_assertions::assert_eq;
    use std::io::Cursor;

    use crate::{
        InventoryHeader, SphinxInventoryReader, SphinxReference, SphinxType,
        error::SphinxInvError,
        priority::SphinxPriority,
        roles::PyRole,
        writers::{PlainTextSphinxInventoryWriter, SphinxInventoryWriter},
    };

    #[test]
    fn write_simple_plain_text_inventory_minified() -> Result<(), SphinxInvError> {
        let mut write_buffer = Vec::new();
        let expected = String::from(
            "# Sphinx inventory version 2
# Project: foo
# Version: 0.24.24
# The remainder of this file is compressed using zlib.
str.join py:method 1 library/stdtypes.html#$ -
str.lower py:method 1 library/stdtypes.html#$ -
",
        );
        let header = InventoryHeader {
            project_name: "foo".to_string(),
            project_version: "0.24.24".to_string(),
            inventory_version: 2,
            compression_method_description: "zlib".to_string(),
        };

        let str_lower_ref = SphinxReference::new(
            "str.lower",
            SphinxType::Python(PyRole::Method),
            SphinxPriority::Standard,
            "library/stdtypes.html#$",
            "-",
        );

        let str_join_ref = SphinxReference::new(
            "str.join",
            SphinxType::Python(PyRole::Method),
            SphinxPriority::Standard,
            "library/stdtypes.html#$",
            "-",
        );

        let mut writer = PlainTextSphinxInventoryWriter::from_header(&header, 2, true);

        writer.add_reference(&str_join_ref);
        writer.add_reference(&str_lower_ref);

        let mut cursor = Cursor::new(&mut write_buffer);

        writer.finalize(&mut cursor)?;

        assert_eq!(String::from_utf8(write_buffer).unwrap(), expected);
        Ok(())
    }
    #[test]
    fn write_simple_plain_text_inventory_expanded() -> Result<(), SphinxInvError> {
        let mut write_buffer = Vec::new();
        let expected = String::from(
            "# Sphinx inventory version 2
# Project: foo
# Version: 0.24.24
# The remainder of this file is compressed using zlib.
str.join py:method 1 library/stdtypes.html#str.join str.join
str.lower py:method 1 library/stdtypes.html#str.lower str.lower
",
        );
        let header = InventoryHeader {
            project_name: "foo".to_string(),
            project_version: "0.24.24".to_string(),
            inventory_version: 2,
            compression_method_description: "zlib".to_string(),
        };

        let str_lower_ref = SphinxReference::new(
            "str.lower",
            SphinxType::Python(PyRole::Method),
            SphinxPriority::Standard,
            "library/stdtypes.html#str.lower",
            "str.lower",
        );

        let str_join_ref = SphinxReference::new(
            "str.join",
            SphinxType::Python(PyRole::Method),
            SphinxPriority::Standard,
            "library/stdtypes.html#str.join",
            "-",
        );

        let mut writer = PlainTextSphinxInventoryWriter::from_header(&header, 2, false);

        writer.add_reference(&str_join_ref);
        writer.add_reference(&str_lower_ref);

        let mut cursor = Cursor::new(&mut write_buffer);

        writer.finalize(&mut cursor)?;

        assert_eq!(String::from_utf8(write_buffer).unwrap(), expected);
        Ok(())
    }

    #[test]
    fn write_read_round_trip() -> Result<(), SphinxInvError> {
        let buffer = Vec::new();
        let mut cursor = Cursor::new(buffer);
        let header = InventoryHeader {
            project_name: "foo".to_string(),
            project_version: "0.24.24".to_string(),
            inventory_version: 2,
            compression_method_description: "zlib".to_string(),
        };

        let str_lower_ref = SphinxReference::new(
            "str.lower",
            SphinxType::Python(PyRole::Method),
            SphinxPriority::Standard,
            "library/stdtypes.html#str.lower",
            "str.lower",
        );

        let str_join_ref = SphinxReference::new(
            "str.join",
            SphinxType::Python(PyRole::Method),
            SphinxPriority::Standard,
            "library/stdtypes.html#str.join",
            "-",
        );
        let mut writer = SphinxInventoryWriter::from_header(&header, 2, true);

        writer.add_reference(&str_join_ref);
        writer.add_reference(&str_lower_ref);
        writer.finalize(&mut cursor)?;

        cursor.set_position(0);

        let mut reader = SphinxInventoryReader::from_reader(cursor)?;

        assert_eq!(reader.header(), &header);

        assert_eq!(reader.next().unwrap()?, str_join_ref);

        assert_eq!(reader.next().unwrap()?, str_lower_ref);

        Ok(())
    }
}