semtree-core 0.5.0

Core types for semtree: Language, Span, Node, Chunk
Documentation
use serde::{Deserialize, Serialize};
use std::fmt;
use std::path::PathBuf;

use crate::{Language, Span};

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChunkKind {
    Function,
    Method,
    Struct,
    Enum,
    Trait,
    Impl,
    Module,
    Class,
    File,
}

impl ChunkKind {
    /// Parse a user-facing kind name, as accepted by a `--kind` filter.
    /// Case-insensitive; returns `None` for anything unrecognized.
    pub fn from_name(name: &str) -> Option<Self> {
        Some(match name.to_lowercase().as_str() {
            "function" | "fn" | "func" => Self::Function,
            "method" => Self::Method,
            "struct" => Self::Struct,
            "enum" => Self::Enum,
            "trait" => Self::Trait,
            "impl" => Self::Impl,
            "module" | "mod" => Self::Module,
            "class" => Self::Class,
            "file" => Self::File,
            _ => return None,
        })
    }

    /// Every kind the parser can produce, in a stable order.
    pub const ALL: &'static [Self] = &[
        Self::Function,
        Self::Method,
        Self::Struct,
        Self::Enum,
        Self::Trait,
        Self::Impl,
        Self::Module,
        Self::Class,
        Self::File,
    ];
}

impl fmt::Display for ChunkKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            Self::Function => "function",
            Self::Method => "method",
            Self::Struct => "struct",
            Self::Enum => "enum",
            Self::Trait => "trait",
            Self::Impl => "impl",
            Self::Module => "module",
            Self::Class => "class",
            Self::File => "file",
        };
        f.write_str(s)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Chunk {
    /// Unique identifier (hash of path + span)
    pub id: String,
    /// Source file
    pub path: PathBuf,
    /// Programming language
    pub language: Language,
    /// Kind of code construct
    pub kind: ChunkKind,
    /// Name of the construct (e.g. function name)
    pub name: Option<String>,
    /// Raw source text
    pub content: String,
    /// Location in source file
    pub span: Span,
    /// Docstring / leading comment if any
    pub doc: Option<String>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn every_kind_parses_back_from_its_own_name() {
        for kind in ChunkKind::ALL {
            assert_eq!(
                ChunkKind::from_name(&kind.to_string()),
                Some(*kind),
                "{kind} does not round-trip through from_name"
            );
        }
    }

    #[test]
    fn kind_names_are_case_insensitive_and_accept_short_forms() {
        assert_eq!(ChunkKind::from_name("FN"), Some(ChunkKind::Function));
        assert_eq!(ChunkKind::from_name("Mod"), Some(ChunkKind::Module));
        assert_eq!(ChunkKind::from_name("nope"), None);
    }
}