1use crate::types::ShaderMetadata;
20use std::collections::HashMap;
21use std::path::{Path, PathBuf};
22
23const METADATA_MARKER: &str = "/*! par-term shader metadata";
25
26pub fn parse_shader_metadata(source: &str) -> Option<ShaderMetadata> {
38 let start_marker = source.find(METADATA_MARKER)?;
40
41 let yaml_start = source[start_marker + METADATA_MARKER.len()..]
43 .find('\n')
44 .map(|i| start_marker + METADATA_MARKER.len() + i + 1)?;
45
46 let yaml_end = source[yaml_start..].find("*/")?;
48 let yaml_content = &source[yaml_start..yaml_start + yaml_end];
49
50 let yaml_trimmed = yaml_content.trim();
52
53 match serde_yaml::from_str(yaml_trimmed) {
55 Ok(metadata) => {
56 log::debug!("Parsed shader metadata: {:?}", metadata);
57 Some(metadata)
58 }
59 Err(e) => {
60 log::warn!("Failed to parse shader metadata YAML: {}", e);
61 log::debug!("YAML content was:\n{}", yaml_trimmed);
62 None
63 }
64 }
65}
66
67pub fn parse_shader_metadata_from_file(path: &Path) -> Option<ShaderMetadata> {
76 match std::fs::read_to_string(path) {
77 Ok(source) => parse_shader_metadata(&source),
78 Err(e) => {
79 log::warn!("Failed to read shader file '{}': {}", path.display(), e);
80 None
81 }
82 }
83}
84
85pub fn serialize_metadata_to_yaml(metadata: &ShaderMetadata) -> Result<String, String> {
93 serde_yaml::to_string(metadata).map_err(|e| format!("Failed to serialize metadata: {}", e))
94}
95
96pub fn format_metadata_block(metadata: &ShaderMetadata) -> Result<String, String> {
104 let yaml = serialize_metadata_to_yaml(metadata)?;
105 Ok(format!("{}\n{}\n*/", METADATA_MARKER, yaml.trim_end()))
106}
107
108pub fn update_shader_metadata(source: &str, metadata: &ShaderMetadata) -> Result<String, String> {
120 let new_block = format_metadata_block(metadata)?;
121
122 if let Some(start_pos) = source.find(METADATA_MARKER) {
124 if let Some(end_offset) = source[start_pos..].find("*/") {
126 let end_pos = start_pos + end_offset + 2; let mut result = String::with_capacity(source.len());
129 result.push_str(&source[..start_pos]);
130 result.push_str(&new_block);
131 result.push_str(&source[end_pos..]);
132 return Ok(result);
133 }
134 }
135
136 Ok(format!("{}\n\n{}", new_block, source))
138}
139
140pub fn update_shader_metadata_file(path: &Path, metadata: &ShaderMetadata) -> Result<(), String> {
149 let source = std::fs::read_to_string(path)
151 .map_err(|e| format!("Failed to read shader file '{}': {}", path.display(), e))?;
152
153 let updated_source = update_shader_metadata(&source, metadata)?;
155
156 std::fs::write(path, updated_source)
158 .map_err(|e| format!("Failed to write shader file '{}': {}", path.display(), e))?;
159
160 log::info!("Updated metadata in shader file: {}", path.display());
161 Ok(())
162}
163
164#[derive(Debug, Default)]
169pub struct ShaderMetadataCache {
170 cache: HashMap<String, Option<ShaderMetadata>>,
172 shaders_dir: Option<PathBuf>,
174}
175
176impl ShaderMetadataCache {
177 #[allow(dead_code)]
179 pub fn new() -> Self {
180 Self::default()
181 }
182
183 pub fn with_shaders_dir(shaders_dir: PathBuf) -> Self {
185 Self {
186 cache: HashMap::new(),
187 shaders_dir: Some(shaders_dir),
188 }
189 }
190
191 #[allow(dead_code)]
193 pub fn set_shaders_dir(&mut self, shaders_dir: PathBuf) {
194 self.shaders_dir = Some(shaders_dir);
195 }
196
197 pub fn get(&mut self, shader_name: &str) -> Option<&ShaderMetadata> {
206 if self.cache.contains_key(shader_name) {
208 return self.cache.get(shader_name).and_then(|m| m.as_ref());
209 }
210
211 let metadata = self.load_metadata(shader_name);
213 self.cache.insert(shader_name.to_string(), metadata);
214 self.cache.get(shader_name).and_then(|m| m.as_ref())
215 }
216
217 #[allow(dead_code)]
221 pub fn get_fresh(&self, shader_name: &str) -> Option<ShaderMetadata> {
222 self.load_metadata(shader_name)
223 }
224
225 fn load_metadata(&self, shader_name: &str) -> Option<ShaderMetadata> {
227 let path = self.resolve_shader_path(shader_name)?;
228 parse_shader_metadata_from_file(&path)
229 }
230
231 fn resolve_shader_path(&self, shader_name: &str) -> Option<PathBuf> {
233 let shader_path = PathBuf::from(shader_name);
234
235 if shader_path.is_absolute() && shader_path.exists() {
237 return Some(shader_path);
238 }
239
240 if let Some(ref shaders_dir) = self.shaders_dir {
242 let full_path = shaders_dir.join(shader_name);
243 if full_path.exists() {
244 return Some(full_path);
245 }
246 }
247
248 let default_path = crate::config::Config::shader_path(shader_name);
250 if default_path.exists() {
251 return Some(default_path);
252 }
253
254 None
255 }
256
257 #[allow(dead_code)]
261 pub fn invalidate(&mut self, shader_name: &str) {
262 self.cache.remove(shader_name);
263 log::debug!("Invalidated metadata cache for: {}", shader_name);
264 }
265
266 #[allow(dead_code)]
270 pub fn invalidate_all(&mut self) {
271 self.cache.clear();
272 log::debug!("Invalidated all metadata cache entries");
273 }
274
275 #[allow(dead_code)]
277 pub fn is_cached(&self, shader_name: &str) -> bool {
278 self.cache.contains_key(shader_name)
279 }
280
281 #[allow(dead_code)]
283 pub fn cache_size(&self) -> usize {
284 self.cache.len()
285 }
286}
287
288use crate::types::CursorShaderMetadata;
293
294pub fn parse_cursor_shader_metadata(source: &str) -> Option<CursorShaderMetadata> {
306 let start_marker = source.find(METADATA_MARKER)?;
308
309 let yaml_start = source[start_marker + METADATA_MARKER.len()..]
311 .find('\n')
312 .map(|i| start_marker + METADATA_MARKER.len() + i + 1)?;
313
314 let yaml_end = source[yaml_start..].find("*/")?;
316 let yaml_content = &source[yaml_start..yaml_start + yaml_end];
317
318 let yaml_trimmed = yaml_content.trim();
320
321 match serde_yaml::from_str(yaml_trimmed) {
323 Ok(metadata) => {
324 log::debug!("Parsed cursor shader metadata: {:?}", metadata);
325 Some(metadata)
326 }
327 Err(e) => {
328 log::warn!("Failed to parse cursor shader metadata YAML: {}", e);
329 log::debug!("YAML content was:\n{}", yaml_trimmed);
330 None
331 }
332 }
333}
334
335pub fn parse_cursor_shader_metadata_from_file(path: &Path) -> Option<CursorShaderMetadata> {
344 match std::fs::read_to_string(path) {
345 Ok(source) => parse_cursor_shader_metadata(&source),
346 Err(e) => {
347 log::warn!(
348 "Failed to read cursor shader file '{}': {}",
349 path.display(),
350 e
351 );
352 None
353 }
354 }
355}
356
357pub fn serialize_cursor_metadata_to_yaml(
365 metadata: &CursorShaderMetadata,
366) -> Result<String, String> {
367 serde_yaml::to_string(metadata).map_err(|e| format!("Failed to serialize metadata: {}", e))
368}
369
370pub fn format_cursor_metadata_block(metadata: &CursorShaderMetadata) -> Result<String, String> {
378 let yaml = serialize_cursor_metadata_to_yaml(metadata)?;
379 Ok(format!("{}\n{}\n*/", METADATA_MARKER, yaml.trim_end()))
380}
381
382pub fn update_cursor_shader_metadata(
394 source: &str,
395 metadata: &CursorShaderMetadata,
396) -> Result<String, String> {
397 let new_block = format_cursor_metadata_block(metadata)?;
398
399 if let Some(start_pos) = source.find(METADATA_MARKER) {
401 if let Some(end_offset) = source[start_pos..].find("*/") {
403 let end_pos = start_pos + end_offset + 2; let mut result = String::with_capacity(source.len());
406 result.push_str(&source[..start_pos]);
407 result.push_str(&new_block);
408 result.push_str(&source[end_pos..]);
409 return Ok(result);
410 }
411 }
412
413 Ok(format!("{}\n\n{}", new_block, source))
415}
416
417pub fn update_cursor_shader_metadata_file(
426 path: &Path,
427 metadata: &CursorShaderMetadata,
428) -> Result<(), String> {
429 let source = std::fs::read_to_string(path)
431 .map_err(|e| format!("Failed to read shader file '{}': {}", path.display(), e))?;
432
433 let updated_source = update_cursor_shader_metadata(&source, metadata)?;
435
436 std::fs::write(path, updated_source)
438 .map_err(|e| format!("Failed to write shader file '{}': {}", path.display(), e))?;
439
440 log::info!("Updated cursor shader metadata in file: {}", path.display());
441 Ok(())
442}
443
444#[derive(Debug, Default)]
449pub struct CursorShaderMetadataCache {
450 cache: HashMap<String, Option<CursorShaderMetadata>>,
452 shaders_dir: Option<PathBuf>,
454}
455
456impl CursorShaderMetadataCache {
457 #[allow(dead_code)]
459 pub fn new() -> Self {
460 Self::default()
461 }
462
463 pub fn with_shaders_dir(shaders_dir: PathBuf) -> Self {
465 Self {
466 cache: HashMap::new(),
467 shaders_dir: Some(shaders_dir),
468 }
469 }
470
471 #[allow(dead_code)]
473 pub fn set_shaders_dir(&mut self, shaders_dir: PathBuf) {
474 self.shaders_dir = Some(shaders_dir);
475 }
476
477 pub fn get(&mut self, shader_name: &str) -> Option<&CursorShaderMetadata> {
486 if self.cache.contains_key(shader_name) {
488 return self.cache.get(shader_name).and_then(|m| m.as_ref());
489 }
490
491 let metadata = self.load_metadata(shader_name);
493 self.cache.insert(shader_name.to_string(), metadata);
494 self.cache.get(shader_name).and_then(|m| m.as_ref())
495 }
496
497 #[allow(dead_code)]
501 pub fn get_fresh(&self, shader_name: &str) -> Option<CursorShaderMetadata> {
502 self.load_metadata(shader_name)
503 }
504
505 fn load_metadata(&self, shader_name: &str) -> Option<CursorShaderMetadata> {
507 let path = self.resolve_shader_path(shader_name)?;
508 parse_cursor_shader_metadata_from_file(&path)
509 }
510
511 fn resolve_shader_path(&self, shader_name: &str) -> Option<PathBuf> {
513 let shader_path = PathBuf::from(shader_name);
514
515 if shader_path.is_absolute() && shader_path.exists() {
517 return Some(shader_path);
518 }
519
520 if let Some(ref shaders_dir) = self.shaders_dir {
522 let full_path = shaders_dir.join(shader_name);
523 if full_path.exists() {
524 return Some(full_path);
525 }
526 }
527
528 let default_path = crate::config::Config::shader_path(shader_name);
530 if default_path.exists() {
531 return Some(default_path);
532 }
533
534 None
535 }
536
537 pub fn invalidate(&mut self, shader_name: &str) {
541 self.cache.remove(shader_name);
542 log::debug!(
543 "Invalidated cursor shader metadata cache for: {}",
544 shader_name
545 );
546 }
547
548 #[allow(dead_code)]
552 pub fn invalidate_all(&mut self) {
553 self.cache.clear();
554 log::debug!("Invalidated all cursor shader metadata cache entries");
555 }
556
557 #[allow(dead_code)]
559 pub fn is_cached(&self, shader_name: &str) -> bool {
560 self.cache.contains_key(shader_name)
561 }
562
563 #[allow(dead_code)]
565 pub fn cache_size(&self) -> usize {
566 self.cache.len()
567 }
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573
574 #[test]
575 fn test_parse_metadata_basic() {
576 let source = r#"/*! par-term shader metadata
577name: "Test Shader"
578author: "Test Author"
579description: "A test shader"
580version: "1.0.0"
581*/
582
583void mainImage(out vec4 fragColor, in vec2 fragCoord) {
584 fragColor = vec4(1.0);
585}
586"#;
587
588 let metadata = parse_shader_metadata(source).expect("Should parse metadata");
589 assert_eq!(metadata.name, Some("Test Shader".to_string()));
590 assert_eq!(metadata.author, Some("Test Author".to_string()));
591 assert_eq!(metadata.description, Some("A test shader".to_string()));
592 assert_eq!(metadata.version, Some("1.0.0".to_string()));
593 }
594
595 #[test]
596 fn test_parse_metadata_with_defaults() {
597 let source = r#"/*! par-term shader metadata
598name: "CRT Effect"
599defaults:
600 animation_speed: 0.5
601 brightness: 0.85
602 full_content: true
603 channel0: "textures/noise.png"
604*/
605
606void mainImage(out vec4 fragColor, in vec2 fragCoord) {
607 fragColor = vec4(1.0);
608}
609"#;
610
611 let metadata = parse_shader_metadata(source).expect("Should parse metadata");
612 assert_eq!(metadata.name, Some("CRT Effect".to_string()));
613 assert_eq!(metadata.defaults.animation_speed, Some(0.5));
614 assert_eq!(metadata.defaults.brightness, Some(0.85));
615 assert_eq!(metadata.defaults.full_content, Some(true));
616 assert_eq!(
617 metadata.defaults.channel0,
618 Some("textures/noise.png".to_string())
619 );
620 }
621
622 #[test]
623 fn test_parse_metadata_not_found() {
624 let source = r#"// Regular shader without metadata
625void mainImage(out vec4 fragColor, in vec2 fragCoord) {
626 fragColor = vec4(1.0);
627}
628"#;
629
630 let metadata = parse_shader_metadata(source);
631 assert!(metadata.is_none());
632 }
633
634 #[test]
635 fn test_parse_metadata_partial() {
636 let source = r#"/*! par-term shader metadata
637name: "Minimal Shader"
638*/
639
640void mainImage(out vec4 fragColor, in vec2 fragCoord) {
641 fragColor = vec4(1.0);
642}
643"#;
644
645 let metadata = parse_shader_metadata(source).expect("Should parse metadata");
646 assert_eq!(metadata.name, Some("Minimal Shader".to_string()));
647 assert!(metadata.author.is_none());
648 assert!(metadata.description.is_none());
649 assert!(metadata.defaults.animation_speed.is_none());
650 }
651
652 #[test]
653 fn test_cache_basic() {
654 let mut cache = ShaderMetadataCache::new();
655
656 assert!(!cache.is_cached("test.glsl"));
658 assert_eq!(cache.cache_size(), 0);
659
660 let _ = cache.get("nonexistent.glsl");
662 assert!(cache.is_cached("nonexistent.glsl"));
663 assert_eq!(cache.cache_size(), 1);
664
665 cache.invalidate("nonexistent.glsl");
667 assert!(!cache.is_cached("nonexistent.glsl"));
668 assert_eq!(cache.cache_size(), 0);
669 }
670
671 #[test]
672 fn test_update_metadata_existing_block() {
673 let source = r#"/*! par-term shader metadata
674name: "Old Name"
675version: "1.0.0"
676*/
677
678void mainImage(out vec4 fragColor, in vec2 fragCoord) {
679 fragColor = vec4(1.0);
680}
681"#;
682
683 let new_metadata = ShaderMetadata {
684 name: Some("New Name".to_string()),
685 author: Some("New Author".to_string()),
686 version: Some("2.0.0".to_string()),
687 ..Default::default()
688 };
689
690 let result = super::update_shader_metadata(source, &new_metadata).unwrap();
691
692 assert!(result.contains("New Name"));
694 assert!(result.contains("New Author"));
695 assert!(result.contains("2.0.0"));
696 assert!(!result.contains("Old Name"));
698 assert!(result.contains("void mainImage"));
700 }
701
702 #[test]
703 fn test_update_metadata_no_existing_block() {
704 let source = r#"// Simple shader without metadata
705void mainImage(out vec4 fragColor, in vec2 fragCoord) {
706 fragColor = vec4(1.0);
707}
708"#;
709
710 let new_metadata = ShaderMetadata {
711 name: Some("New Shader".to_string()),
712 version: Some("1.0.0".to_string()),
713 ..Default::default()
714 };
715
716 let result = super::update_shader_metadata(source, &new_metadata).unwrap();
717
718 assert!(result.starts_with("/*! par-term shader metadata"));
720 assert!(result.contains("New Shader"));
721 assert!(result.contains("void mainImage"));
723 assert!(result.contains("// Simple shader without metadata"));
724 }
725
726 #[test]
727 fn test_format_metadata_block() {
728 let metadata = ShaderMetadata {
729 name: Some("Test Shader".to_string()),
730 author: Some("Test Author".to_string()),
731 description: Some("A test shader".to_string()),
732 version: Some("1.0.0".to_string()),
733 defaults: Default::default(),
734 };
735
736 let block = super::format_metadata_block(&metadata).unwrap();
737
738 assert!(block.starts_with("/*! par-term shader metadata"));
739 assert!(block.ends_with("*/"));
740 assert!(block.contains("Test Shader"));
741 assert!(block.contains("Test Author"));
742 }
743
744 #[test]
749 fn test_parse_cursor_metadata_basic() {
750 let source = r#"/*! par-term shader metadata
751name: "Cursor Glow"
752author: "Test Author"
753description: "A cursor glow effect"
754version: "1.0.0"
755*/
756
757void mainImage(out vec4 fragColor, in vec2 fragCoord) {
758 fragColor = vec4(1.0);
759}
760"#;
761
762 let metadata =
763 super::parse_cursor_shader_metadata(source).expect("Should parse cursor metadata");
764 assert_eq!(metadata.name, Some("Cursor Glow".to_string()));
765 assert_eq!(metadata.author, Some("Test Author".to_string()));
766 assert_eq!(
767 metadata.description,
768 Some("A cursor glow effect".to_string())
769 );
770 assert_eq!(metadata.version, Some("1.0.0".to_string()));
771 }
772
773 #[test]
774 fn test_parse_cursor_metadata_with_defaults() {
775 let source = r#"/*! par-term shader metadata
776name: "Cursor Trail"
777defaults:
778 animation_speed: 2.0
779 glow_radius: 100.0
780 glow_intensity: 0.5
781 trail_duration: 1.0
782 cursor_color: [255, 128, 0]
783*/
784
785void mainImage(out vec4 fragColor, in vec2 fragCoord) {
786 fragColor = vec4(1.0);
787}
788"#;
789
790 let metadata =
791 super::parse_cursor_shader_metadata(source).expect("Should parse cursor metadata");
792 assert_eq!(metadata.name, Some("Cursor Trail".to_string()));
793 assert_eq!(metadata.defaults.base.animation_speed, Some(2.0));
794 assert_eq!(metadata.defaults.glow_radius, Some(100.0));
795 assert_eq!(metadata.defaults.glow_intensity, Some(0.5));
796 assert_eq!(metadata.defaults.trail_duration, Some(1.0));
797 assert_eq!(metadata.defaults.cursor_color, Some([255, 128, 0]));
798 }
799
800 #[test]
801 fn test_cursor_shader_cache_basic() {
802 let mut cache = super::CursorShaderMetadataCache::new();
803
804 assert!(!cache.is_cached("cursor_test.glsl"));
806 assert_eq!(cache.cache_size(), 0);
807
808 let _ = cache.get("nonexistent_cursor.glsl");
810 assert!(cache.is_cached("nonexistent_cursor.glsl"));
811 assert_eq!(cache.cache_size(), 1);
812
813 cache.invalidate("nonexistent_cursor.glsl");
815 assert!(!cache.is_cached("nonexistent_cursor.glsl"));
816 assert_eq!(cache.cache_size(), 0);
817 }
818
819 #[test]
820 fn test_update_cursor_metadata_existing_block() {
821 let source = r#"/*! par-term shader metadata
822name: "Old Cursor"
823version: "1.0.0"
824*/
825
826void mainImage(out vec4 fragColor, in vec2 fragCoord) {
827 fragColor = vec4(1.0);
828}
829"#;
830
831 let new_metadata = super::CursorShaderMetadata {
832 name: Some("New Cursor".to_string()),
833 author: Some("New Author".to_string()),
834 version: Some("2.0.0".to_string()),
835 ..Default::default()
836 };
837
838 let result = super::update_cursor_shader_metadata(source, &new_metadata).unwrap();
839
840 assert!(result.contains("New Cursor"));
842 assert!(result.contains("New Author"));
843 assert!(result.contains("2.0.0"));
844 assert!(!result.contains("Old Cursor"));
846 assert!(result.contains("void mainImage"));
848 }
849
850 #[test]
851 fn test_update_cursor_metadata_no_existing_block() {
852 let source = r#"// Cursor shader without metadata
853void mainImage(out vec4 fragColor, in vec2 fragCoord) {
854 fragColor = vec4(1.0);
855}
856"#;
857
858 let new_metadata = super::CursorShaderMetadata {
859 name: Some("New Cursor Shader".to_string()),
860 version: Some("1.0.0".to_string()),
861 ..Default::default()
862 };
863
864 let result = super::update_cursor_shader_metadata(source, &new_metadata).unwrap();
865
866 assert!(result.starts_with("/*! par-term shader metadata"));
868 assert!(result.contains("New Cursor Shader"));
869 assert!(result.contains("void mainImage"));
871 assert!(result.contains("// Cursor shader without metadata"));
872 }
873
874 #[test]
875 fn test_format_cursor_metadata_block() {
876 use crate::CursorShaderConfig;
877
878 let metadata = super::CursorShaderMetadata {
879 name: Some("Test Cursor".to_string()),
880 author: Some("Test Author".to_string()),
881 description: Some("A test cursor shader".to_string()),
882 version: Some("1.0.0".to_string()),
883 defaults: CursorShaderConfig {
884 glow_radius: Some(50.0),
885 glow_intensity: Some(0.8),
886 ..Default::default()
887 },
888 };
889
890 let block = super::format_cursor_metadata_block(&metadata).unwrap();
891
892 assert!(block.starts_with("/*! par-term shader metadata"));
893 assert!(block.ends_with("*/"));
894 assert!(block.contains("Test Cursor"));
895 assert!(block.contains("Test Author"));
896 assert!(block.contains("glow_radius"));
897 assert!(block.contains("glow_intensity"));
898 }
899}