knave-expand 0.14.22

The reading, scoping and expansion stages of knave
Documentation
use std::rc::Rc;

use crate::interpret::Value;

pub(super) struct DocstringNotes {
    key: Rc<str>,
    documented_terms_key: Rc<str>,
    noted_term: Value,
}

impl DocstringNotes {
    pub(super) const PAGE_MACRO: &'static str = "doc-page";
    pub(super) const DOCSTRINGS_KEY: &'static str = "docstrings-key";
    pub(super) const DOCUMENTED_TERMS_KEY: &'static str = "documented-terms-key";
    pub(super) const NOTED_TERM: &'static str = "noted-term";

    pub(super) fn of(key: &Value, documented_terms_key: &Value, noted_term: Value) -> Option<Self> {
        Some(Self {
            key: Self::symbol_name(key)?,
            documented_terms_key: Self::symbol_name(documented_terms_key)?,
            noted_term,
        })
    }

    pub(super) fn keys(&self) -> [&Rc<str>; 2] {
        [&self.key, &self.documented_terms_key]
    }

    pub(super) fn noted_term(&self) -> &Value {
        &self.noted_term
    }

    fn symbol_name(value: &Value) -> Option<Rc<str>> {
        Some(value.as_form()?.as_symbol()?.shared_name())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::reader::{Form, FormKind, Symbol};
    use crate::source::{SourceId, Span};

    struct NotesFixture;

    impl NotesFixture {
        const KEY: &str = "docstrings";
        const DOCUMENTED_TERMS_KEY: &str = "documented-terms";
        const ACCESSOR: &str = "accessor";

        fn symbol(name: &str) -> Value {
            let span = Span::new(SourceId::new(0), 0..name.len());
            Value::Form(Form::new(FormKind::Symbol(Symbol::new(name)), span))
        }

        fn notes() -> Option<DocstringNotes> {
            DocstringNotes::of(
                &Self::symbol(Self::KEY),
                &Self::symbol(Self::DOCUMENTED_TERMS_KEY),
                Self::symbol(Self::ACCESSOR),
            )
        }
    }

    #[test]
    fn the_keys_are_the_names_of_both_key_symbols_in_order() {
        let notes = NotesFixture::notes().expect("both keys are symbols");
        let keys: Vec<&str> = notes.keys().into_iter().map(|key| &**key).collect();
        assert_eq!(
            keys,
            [NotesFixture::KEY, NotesFixture::DOCUMENTED_TERMS_KEY]
        );
    }

    #[test]
    fn the_noted_term_is_the_accessor_it_was_given() {
        let notes = NotesFixture::notes().expect("both keys are symbols");
        let accessor = notes.noted_term().as_form().and_then(Form::as_symbol);
        assert_eq!(accessor.map(Symbol::name), Some(NotesFixture::ACCESSOR));
    }

    #[test]
    fn a_key_that_is_not_a_symbol_gives_no_notes() {
        let notes = DocstringNotes::of(
            &Value::Bool(true),
            &NotesFixture::symbol(NotesFixture::DOCUMENTED_TERMS_KEY),
            NotesFixture::symbol(NotesFixture::ACCESSOR),
        );
        assert!(notes.is_none());
    }
}