1use std::path::Path;
4
5use crate::parser::ParseError;
6use crate::types::{Metadata, Parameter};
7
8#[derive(Debug, Clone, PartialEq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub struct OslQuery {
12 shader_name: String,
14 shader_type: String,
16 parameters: Vec<Parameter>,
18 metadata: Vec<Metadata>,
20}
21
22impl OslQuery {
23 pub fn new() -> Self {
25 OslQuery {
26 shader_name: String::new(),
27 shader_type: String::new(),
28 parameters: Vec::new(),
29 metadata: Vec::new(),
30 }
31 }
32
33 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, ParseError> {
35 Self::open_with_searchpath(path, "")
36 }
37
38 pub fn open_with_searchpath<P: AsRef<Path>>(
40 path: P,
41 searchpath: &str,
42 ) -> Result<Self, ParseError> {
43 let path = path.as_ref();
44
45 if path.extension().and_then(|s| s.to_str()) != Some("oso") {
47 let mut path_with_ext = path.to_path_buf();
49 path_with_ext.set_extension("oso");
50
51 if path_with_ext.exists() {
52 return crate::parser::OsoReader::new().parse_file(path_with_ext);
53 }
54 }
55
56 if path.exists() {
58 return crate::parser::OsoReader::new().parse_file(path);
59 }
60
61 if !searchpath.is_empty() {
63 for search_dir in searchpath.split(':') {
64 let search_path = Path::new(search_dir).join(path);
65 if search_path.exists() {
66 return crate::parser::OsoReader::new().parse_file(search_path);
67 }
68
69 let mut search_path_with_ext = search_path.clone();
71 search_path_with_ext.set_extension("oso");
72 if search_path_with_ext.exists() {
73 return crate::parser::OsoReader::new().parse_file(search_path_with_ext);
74 }
75 }
76 }
77
78 Err(ParseError::Io(format!("Shader file not found: {:?}", path)))
79 }
80
81 pub fn from_string(content: &str) -> Result<Self, ParseError> {
83 crate::parser::OsoReader::new().parse_string(content)
84 }
85
86 pub(crate) fn set_shader_info(&mut self, shader_type: &str, shader_name: String) {
89 self.shader_type = shader_type.to_string();
90 self.shader_name = shader_name;
91 }
92
93 pub(crate) fn add_parameter(&mut self, param: Parameter) {
94 self.parameters.push(param);
95 }
96
97 pub(crate) fn add_metadata(&mut self, meta: Metadata) {
98 self.metadata.push(meta);
99 }
100
101 pub fn shader_name(&self) -> &str {
103 &self.shader_name
104 }
105
106 pub fn shader_type(&self) -> &str {
108 &self.shader_type
109 }
110
111 pub fn param_count(&self) -> usize {
113 self.parameters.len()
114 }
115
116 pub fn param_at(&self, index: usize) -> Option<&Parameter> {
118 self.parameters.get(index)
119 }
120
121 pub fn param_by_name(&self, name: &str) -> Option<&Parameter> {
123 self.parameters.iter().find(|p| p.name.as_str() == name)
124 }
125
126 pub fn params(&self) -> &[Parameter] {
128 &self.parameters
129 }
130
131 pub fn input_params(&self) -> impl Iterator<Item = &Parameter> {
133 self.parameters.iter().filter(|p| !p.is_output())
134 }
135
136 pub fn output_params(&self) -> impl Iterator<Item = &Parameter> {
138 self.parameters.iter().filter(|p| p.is_output())
139 }
140
141 pub fn metadata(&self) -> &[Metadata] {
143 &self.metadata
144 }
145
146 pub fn find_metadata(&self, name: &str) -> Option<&Metadata> {
148 self.metadata.iter().find(|m| m.name.as_str() == name)
149 }
150
151 pub fn is_valid(&self) -> bool {
153 !self.shader_name.is_empty() && !self.shader_type.is_empty()
154 }
155}
156
157impl Default for OslQuery {
158 fn default() -> Self {
159 Self::new()
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use crate::types::TypedParameter;
167
168 #[test]
169 fn test_empty_query() {
170 let query = OslQuery::new();
171 assert!(!query.is_valid());
172 assert_eq!(query.param_count(), 0);
173 assert_eq!(query.shader_name(), "");
174 assert_eq!(query.shader_type(), "");
175 }
176
177 #[test]
178 fn test_from_string() {
179 let oso_content = r#"
180OpenShadingLanguage 1.12
181surface "test_shader"
182param float Kd 0.5
183code ___main___
184"#;
185
186 let query = OslQuery::from_string(oso_content).unwrap();
187 assert!(query.is_valid());
188 assert_eq!(query.shader_name(), "test_shader");
189 assert_eq!(query.shader_type(), "surface");
190 assert_eq!(query.param_count(), 1);
191
192 let param = query.param_by_name("Kd");
193 assert!(param.is_some());
194 let param = param.unwrap();
195 assert_eq!(param.name.as_str(), "Kd");
196 assert!(!param.is_output());
197
198 match param.typed_param() {
200 TypedParameter::Float { default: Some(val) } => {
201 assert_eq!(*val, 0.5);
202 }
203 _ => panic!("Expected Float parameter with default"),
204 }
205 }
206
207 #[test]
208 fn test_type_safety() {
209 let oso_content = r#"
210OpenShadingLanguage 1.12
211shader test
212param color rgb 1 0 0
213param int count 42
214param float[3] values 1.0 2.0 3.0
215code ___main___
216"#;
217
218 let query = OslQuery::from_string(oso_content).unwrap();
219
220 let rgb = query.param_by_name("rgb").unwrap();
222 match rgb.typed_param() {
223 TypedParameter::Color {
224 default: Some([r, g, b]),
225 ..
226 } => {
227 assert_eq!(*r, 1.0);
228 assert_eq!(*g, 0.0);
229 assert_eq!(*b, 0.0);
230 }
231 _ => panic!("Expected Color parameter"),
232 }
233
234 let count = query.param_by_name("count").unwrap();
236 match count.typed_param() {
237 TypedParameter::Int { default: Some(val) } => {
238 assert_eq!(*val, 42);
239 }
240 _ => panic!("Expected Int parameter"),
241 }
242
243 let values = query.param_by_name("values").unwrap();
245 match values.typed_param() {
246 TypedParameter::FloatArray {
247 size: 3,
248 default: Some(vals),
249 } => {
250 assert_eq!(vals, &vec![1.0, 2.0, 3.0]);
251 }
252 _ => panic!("Expected FloatArray[3] parameter"),
253 }
254 }
255
256 #[test]
257 fn test_input_output_separation() {
258 let oso_content = r#"
259OpenShadingLanguage 1.12
260surface test
261param float input1 0.5
262param color input2 1 0 0
263oparam color result
264code ___main___
265"#;
266
267 let query = OslQuery::from_string(oso_content).unwrap();
268
269 let inputs: Vec<_> = query.input_params().collect();
270 let outputs: Vec<_> = query.output_params().collect();
271
272 assert_eq!(inputs.len(), 2);
273 assert_eq!(outputs.len(), 1);
274
275 let result = outputs[0];
277 match result.typed_param() {
278 TypedParameter::Color { default, .. } => {
279 assert!(default.is_none(), "Output parameter should have no default");
280 }
281 _ => panic!("Expected Color output parameter"),
282 }
283 }
284}