asciidoc-parser 0.19.0

Parser for AsciiDoc format
Documentation
use std::fmt;

/// Generated by preprocessor: Sparse map of result ([`Span`]) line number to a
/// tuple of file name and line number. This allows us to work backwards from
/// the pre-processed and unified [`Span`] to the various inputs that were used
/// to construct it. Line numbers here are 1-based.
///
/// [`Span`]: crate::Span
#[derive(Clone, Default, Eq, PartialEq)]
pub struct SourceMap(pub Vec<(usize, SourceLine)>);

/// A `SourceLine` represents the original file and line number where a line of
/// Asciidoc text was found before [include file] and [conditional]
/// pre-processing occurred.
///
/// The first member is the file name as specified on the [include file]
/// directive. The second member is the 1-based line number.
///
/// [include file]: https://docs.asciidoctor.org/asciidoc/latest/directives/include/
/// [conditional]: https://docs.asciidoctor.org/asciidoc/latest/directives/conditionals/
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct SourceLine(pub Option<String>, pub usize);

impl SourceMap {
    pub(crate) fn append(&mut self, preprocessed_line: usize, source_line: SourceLine) {
        // IMPORTANT: These _should_ be added in increasing order of
        // `preprocessed_line`, but this is not enforced.
        self.0.push((preprocessed_line, source_line));
    }

    /// Given a 1-based line number in the preprocessed source file, translate
    /// that to a file name and line number as original inputs to the parsing
    /// process.
    pub fn original_file_and_line(&self, key: usize) -> Option<SourceLine> {
        match self.0.binary_search_by_key(&key, |(k, _)| *k) {
            Ok(i) => self.0.get(i).map(|(_k, v)| v.clone()),
            Err(0) => Some(SourceLine(None, key)),
            Err(i) => self
                .0
                .get(i - 1)
                .map(|(k, v)| SourceLine(v.0.clone(), v.1 + key - k)),
        }
    }
}

impl fmt::Debug for SourceMap {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("SourceMap(&")?;
        f.debug_list()
            .entries(self.0.iter().map(|(k, v)| (k, v)))
            .finish()?;
        f.write_str(")")
    }
}

#[cfg(test)]
mod tests {
    #![allow(clippy::unwrap_used)]

    use crate::parser::{SourceLine, SourceMap};

    #[test]
    fn empty() {
        let sm = SourceMap::default();
        assert_eq!(sm.original_file_and_line(1), Some(SourceLine(None, 1)));
    }

    #[test]
    fn one_entry() {
        let mut sm = SourceMap::default();
        sm.append(1, SourceLine(None, 1));

        assert_eq!(sm.original_file_and_line(0), Some(SourceLine(None, 0)));
        assert_eq!(sm.original_file_and_line(1), Some(SourceLine(None, 1)));
        assert_eq!(sm.original_file_and_line(4), Some(SourceLine(None, 4)));

        assert_eq!(
            sm.original_file_and_line(4000),
            Some(SourceLine(None, 4000))
        );
    }

    #[test]
    fn multiple_entries() {
        let mut sm = SourceMap::default();
        sm.append(1, SourceLine(None, 1));
        sm.append(10, SourceLine(Some("foo.adoc".to_owned()), 1));
        sm.append(20, SourceLine(Some("bar.adoc".to_owned()), 18));
        sm.append(30, SourceLine(None, 11));

        assert_eq!(sm.original_file_and_line(1), Some(SourceLine(None, 1)));
        assert_eq!(sm.original_file_and_line(4), Some(SourceLine(None, 4)));

        assert_eq!(
            sm.original_file_and_line(10),
            Some(SourceLine(Some("foo.adoc".to_owned()), 1))
        );
        assert_eq!(
            sm.original_file_and_line(19),
            Some(SourceLine(Some("foo.adoc".to_owned()), 10))
        );

        assert_eq!(
            sm.original_file_and_line(20),
            Some(SourceLine(Some("bar.adoc".to_owned()), 18))
        );
        assert_eq!(
            sm.original_file_and_line(21),
            Some(SourceLine(Some("bar.adoc".to_owned()), 19))
        );
        assert_eq!(
            sm.original_file_and_line(29),
            Some(SourceLine(Some("bar.adoc".to_owned()), 27))
        );

        assert_eq!(sm.original_file_and_line(30), Some(SourceLine(None, 11)));
        assert_eq!(sm.original_file_and_line(40), Some(SourceLine(None, 21)));
    }

    #[test]
    fn impl_debug() {
        let mut sm = SourceMap::default();
        sm.append(1, SourceLine(None, 1));

        assert_eq!(
            format!("{sm:#?}"),
            "SourceMap(&[\n    (\n        1,\n        SourceLine(\n            None,\n            1,\n        ),\n    ),\n])"
        );
    }
}