Skip to main content

waggle_lens_code/
lib.rs

1//! # waggle-lens-code — the symbol lens (design doc `20`)
2//!
3//! Source-code structure, computed **once at mint** where the artifact is
4//! at hand, stored content-addressed beside the snapshot, served
5//! everywhere as plain data. This crate is the extraction half:
6//! tree-sitter parses the snapshot, each grammar's `tags.scm` query marks
7//! the definitions, and the result is a flat, compact [`SymbolOutline`] —
8//! never an AST, never an index, never retained.
9//!
10//! The performance discipline (doc `20 §4`) is structural here:
11//! compiled queries are cached once per process ([`std::sync::OnceLock`]),
12//! parsers are reused per thread, the outline is an arena (one `Vec`, one
13//! shared name buffer, parent links by index), and the parse tree drops
14//! before extraction returns. `extract` is a pure function of its inputs —
15//! no clock, no I/O — so the sans-I/O law holds even though this crate
16//! lives outside `waggle-core` (it must: grammars are C, and this crate is
17//! **never compiled to wasm**; the edge serves precomputed outlines).
18
19mod extract;
20mod outline;
21
22pub use extract::extract;
23pub use outline::{SymbolOutline, WIRE_VERSION};
24
25/// The languages the v1 lens ships (doc `20 §9`): chosen by agent
26/// traffic; each addition pays its binary-size cost consciously.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum Lang {
29    /// Rust (`.rs`).
30    Rust,
31    /// Python (`.py`, `.pyi`).
32    Python,
33    /// TypeScript (`.ts`, `.mts`, `.cts`).
34    TypeScript,
35    /// TSX (`.tsx`).
36    Tsx,
37    /// JavaScript (`.js`, `.mjs`, `.cjs`, `.jsx`).
38    JavaScript,
39    /// Go (`.go`).
40    Go,
41}
42
43/// Detect a supported language from a path — extension first, then the
44/// well-known extension-less basenames have no grammar here (they are
45/// text-sniffed upstream); a `None` simply means "no outline", never an
46/// error: the lens degrades to the plain text loop (doc `20 §9`).
47#[must_use]
48pub fn detect(path: &str) -> Option<Lang> {
49    let basename = path.rsplit(['/', '\\']).next().unwrap_or(path);
50    let ext = basename.rsplit_once('.').map(|(_, e)| e)?;
51    match ext.to_ascii_lowercase().as_str() {
52        "rs" => Some(Lang::Rust),
53        "py" | "pyi" => Some(Lang::Python),
54        "ts" | "mts" | "cts" => Some(Lang::TypeScript),
55        "tsx" => Some(Lang::Tsx),
56        "js" | "mjs" | "cjs" | "jsx" => Some(Lang::JavaScript),
57        "go" => Some(Lang::Go),
58        _ => None,
59    }
60}
61
62/// The outline blob's content type (doc `20 §3`).
63pub const OUTLINE_CONTENT_TYPE: &str = "application/waggle-outline+json";
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn detection_is_extension_driven_and_total() {
71        assert_eq!(detect("crates/waggle-core/src/mint.rs"), Some(Lang::Rust));
72        assert_eq!(detect("a/b/app.test.TSX"), Some(Lang::Tsx));
73        assert_eq!(detect("script.mjs"), Some(Lang::JavaScript));
74        assert_eq!(detect("cmd/main.go"), Some(Lang::Go));
75        assert_eq!(detect("README.md"), None, "no grammar is not an error");
76        assert_eq!(detect("Makefile"), None, "extension-less: text loop only");
77        assert_eq!(detect(".hidden"), None);
78    }
79}