Skip to main content

scrybe_cli/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Shawn Hartsock and contributors
3
4//! Scrybe CLI — library of extracted, testable logic.
5//!
6//! `main.rs` contains the thin clap shell; the real work lives here so it
7//! can be unit-tested without spawning a process.
8
9pub mod rpc_client;
10pub mod wrap;
11
12// Re-export the primary public API for convenience. The linter now lives in
13// `scrybe-tools` (the shared CLI+MCP tool registry, #122); it is re-exported
14// here so the CLI's public surface — `scrybe_cli::lint_document` etc. — is
15// unchanged for `main.rs` and downstream users.
16pub use scrybe_tools::lint::{lint_document, BrokenLink, LintReport};
17pub use wrap::wrap_full_html;
18
19// ---------------------------------------------------------------------------
20// Version info
21// ---------------------------------------------------------------------------
22
23/// Returns the crate version string.
24pub fn version_string() -> &'static str {
25    env!("CARGO_PKG_VERSION")
26}
27
28/// Returns a list of active Cargo feature flags (as a static string).
29pub fn active_features() -> &'static str {
30    "(no optional features)"
31}
32
33// ---------------------------------------------------------------------------
34// Tests
35// ---------------------------------------------------------------------------
36
37#[cfg(test)]
38mod tests {
39    use super::wrap::wrap_full_html;
40    use crate::lint_document;
41    use scrybe_core::Document;
42    use scrybe_render::{render_html, Theme};
43
44    #[test]
45    fn test_lint_word_count() {
46        let doc = Document::new("Hello world foo bar");
47        let r = lint_document(&doc);
48        assert_eq!(r.word_count, 4);
49    }
50
51    #[test]
52    fn test_lint_headings() {
53        let doc = Document::new("# H1\n\n## H2\n\n### H3\n");
54        let r = lint_document(&doc);
55        assert_eq!(r.heading_count, 3);
56        assert_eq!(r.max_heading_depth, 3);
57    }
58
59    #[test]
60    fn test_lint_broken_links() {
61        let doc = Document::new("[empty]() and [hash](#) and [ok](https://example.com)");
62        let r = lint_document(&doc);
63        assert_eq!(r.broken_links.len(), 2);
64    }
65
66    #[test]
67    fn test_lint_code_blocks() {
68        let doc = Document::new("```rust\nfn main(){}\n```\n\n```python\npass\n```");
69        let r = lint_document(&doc);
70        assert_eq!(r.code_block_count, 2);
71        assert!(r.code_block_langs.contains(&"rust".to_string()));
72    }
73
74    #[test]
75    fn test_wrap_full_html_has_doctype() {
76        let doc = Document::new("# Hi");
77        let out = render_html(&doc, Theme::Default);
78        let html = wrap_full_html(&out, "Test");
79        assert!(html.starts_with("<!DOCTYPE html>"));
80        assert!(html.contains("katex"));
81        assert!(html.contains("mermaid"));
82    }
83
84    #[test]
85    fn test_lint_detects_mermaid() {
86        let doc = Document::new("```mermaid\ngraph TD; A-->B;\n```\n");
87        let r = lint_document(&doc);
88        assert!(r.has_mermaid);
89        assert!(!r.code_block_langs.contains(&"mermaid".to_string()));
90    }
91
92    #[test]
93    fn test_lint_detects_math() {
94        let doc = Document::new("Here is $x^2$.\n");
95        let r = lint_document(&doc);
96        assert!(r.has_math);
97    }
98
99    #[test]
100    fn test_lint_clean_document() {
101        let doc = Document::new("# Title\n\nSome [link](https://example.com).\n");
102        let r = lint_document(&doc);
103        assert!(r.is_clean());
104    }
105}