1pub mod ast;
4pub mod lexer;
5pub mod parser;
6pub mod segment;
7pub mod syntax_kind;
8
9pub use lexer::lex;
10pub use parser::{Parse, ParseError, parse, parse_with_cache};
11pub use segment::{Segment, SegmentKind, segment_file};
12pub use syntax_kind::{InkLanguage, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken};
13
14impl Parse {
15 #[must_use]
17 #[expect(
18 clippy::expect_used,
19 reason = "parse() always produces SOURCE_FILE root"
20 )]
21 pub fn tree(&self) -> ast::SourceFile {
22 use ast::AstNode as _;
23 ast::SourceFile::cast(self.syntax()).expect("parse always produces a SOURCE_FILE root")
24 }
25}
26
27#[must_use]
39pub fn extract_includes(source: &str) -> Vec<String> {
40 parse(source)
41 .tree()
42 .includes()
43 .filter_map(|inc| inc.file_path())
44 .map(|fp| fp.text().trim().to_string())
45 .filter(|s| !s.is_empty())
46 .collect()
47}
48
49#[cfg(test)]
50mod extract_includes_tests {
51 use super::*;
52
53 #[test]
54 fn extracts_top_level_includes() {
55 let src = "INCLUDE helper.ink\nINCLUDE other.ink\n";
56 assert_eq!(extract_includes(src), vec!["helper.ink", "other.ink"]);
57 }
58
59 #[test]
60 fn empty_source_returns_empty() {
61 assert!(extract_includes("").is_empty());
62 }
63
64 #[test]
65 fn no_includes_returns_empty() {
66 assert!(extract_includes("=== knot ===\nhello\n").is_empty());
67 }
68
69 #[test]
70 fn ignores_commented_out_include() {
71 let src = "// INCLUDE commented.ink\nINCLUDE real.ink\n";
72 assert_eq!(extract_includes(src), vec!["real.ink"]);
73 }
74}