1mod relations;
7
8pub use relations::PuppetGraphBuilder;
9
10use anyhow::Result;
11use sqry_core::ast::{Scope, ScopeId, link_nested_scopes};
12use sqry_core::plugin::error::{ParseError, ScopeError};
13use sqry_core::plugin::{LanguageMetadata, LanguagePlugin};
14use std::path::Path;
15use tree_sitter::{Language, Node, Parser, Tree};
16
17pub struct PuppetPlugin {
19 graph_builder: PuppetGraphBuilder,
20}
21
22impl PuppetPlugin {
23 #[must_use]
24 pub fn new() -> Self {
25 Self {
26 graph_builder: PuppetGraphBuilder,
27 }
28 }
29}
30
31impl Default for PuppetPlugin {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl PuppetPlugin {
38 fn walk_ast_scopes(node: Node, content: &[u8], file_path: &Path, scopes: &mut Vec<Scope>) {
40 match node.kind() {
41 "class_definition" | "defined_resource_type" => {
42 let mut cursor = node.walk();
43 let mut name = None;
44
45 for child in node.children(&mut cursor) {
46 if child.kind() == "identifier" || child.kind() == "class_identifier" {
47 name = child
48 .utf8_text(content)
49 .ok()
50 .map(std::string::ToString::to_string);
51 break;
52 }
53 }
54
55 if let Some(name) = name {
56 let start = node.start_position();
57 let end = node.end_position();
58 let scope_type = if node.kind() == "class_definition" {
59 "class"
60 } else {
61 "define"
62 };
63
64 scopes.push(Scope {
65 id: ScopeId::new(0), scope_type: scope_type.to_string(),
67 name,
68 file_path: file_path.to_path_buf(),
69 start_line: start.row + 1,
70 start_column: start.column + 1,
71 end_line: end.row + 1,
72 end_column: end.column + 1,
73 parent_id: None,
74 });
75 }
76 }
77 _ => {}
78 }
79
80 let mut cursor = node.walk();
82 for child in node.children(&mut cursor) {
83 Self::walk_ast_scopes(child, content, file_path, scopes);
84 }
85 }
86}
87
88impl LanguagePlugin for PuppetPlugin {
89 fn metadata(&self) -> LanguageMetadata {
90 LanguageMetadata {
91 id: "puppet",
92 name: "Puppet",
93 version: env!("CARGO_PKG_VERSION"),
94 author: "Verivus Pty Ltd",
95 description: "Puppet language support with graph-native extraction",
96 tree_sitter_version: "0.25",
97 }
98 }
99
100 fn extensions(&self) -> &'static [&'static str] {
101 &["pp"]
102 }
103
104 fn language(&self) -> Language {
105 tree_sitter_puppet::LANGUAGE.into()
106 }
107
108 fn parse_ast(&self, content: &[u8]) -> Result<Tree, ParseError> {
109 let mut parser = Parser::new();
110 let lang = self.language();
111 parser
112 .set_language(&lang)
113 .map_err(|e| ParseError::LanguageSetFailed(e.to_string()))?;
114 parser
115 .parse(content, None)
116 .ok_or(ParseError::TreeSitterFailed)
117 }
118
119 fn extract_scopes(
120 &self,
121 tree: &Tree,
122 content: &[u8],
123 file_path: &Path,
124 ) -> Result<Vec<Scope>, ScopeError> {
125 let root = tree.root_node();
126 let mut scopes = Vec::new();
127 Self::walk_ast_scopes(root, content, file_path, &mut scopes);
128
129 scopes.sort_by_key(|s| (s.start_line, s.start_column));
131
132 link_nested_scopes(&mut scopes);
134
135 Ok(scopes)
136 }
137
138 fn graph_builder(&self) -> Option<&dyn sqry_core::graph::GraphBuilder> {
139 Some(&self.graph_builder)
140 }
141}