whiteout/transform/
smudge.rs

1use anyhow::Result;
2use std::path::Path;
3
4use crate::{
5    config::Config,
6    parser::{Decoration, Parser},
7    storage::LocalStorage,
8};
9
10pub fn apply(
11    content: &str,
12    file_path: &Path,
13    storage: &LocalStorage,
14    _config: &Config,
15) -> Result<String> {
16    let parser = Parser::new();
17    let mut decorations = parser.parse(content)?;
18    
19    if decorations.is_empty() {
20        return Ok(content.to_string());
21    }
22    
23    for decoration in &mut decorations {
24        match decoration {
25            Decoration::Inline { line, local_value, .. } => {
26                if let Ok(stored_value) = storage.get_value(
27                    file_path,
28                    &format!("inline_{}", line),
29                ) {
30                    *local_value = stored_value;
31                }
32            }
33            Decoration::Block { start_line, local_content, .. } => {
34                if let Ok(stored_value) = storage.get_value(
35                    file_path,
36                    &format!("block_{}", start_line),
37                ) {
38                    *local_content = stored_value;
39                }
40            }
41            Decoration::Partial { line, replacements } => {
42                for (idx, replacement) in replacements.iter_mut().enumerate() {
43                    // Try both the current line number and nearby lines
44                    // This handles cases where line numbers shift due to block decorations
45                    let mut found = false;
46                    for line_offset in 0..=5 {
47                        for try_line in [*line + line_offset, line.saturating_sub(line_offset)] {
48                            if let Ok(stored_value) = storage.get_value(
49                                file_path,
50                                &format!("partial_{}_{}", try_line, idx),
51                            ) {
52                                replacement.local_value = stored_value;
53                                found = true;
54                                break;
55                            }
56                        }
57                        if found { break; }
58                    }
59                }
60            }
61        }
62    }
63    
64    let smudged = parser.apply_decorations(content, &decorations, true);
65    
66    Ok(smudged)
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use tempfile::TempDir;
73
74    #[test]
75    fn test_smudge_inline() -> Result<()> {
76        let temp_dir = TempDir::new()?;
77        let storage = LocalStorage::new(temp_dir.path())?;
78        let config = Config::default();
79        let file_path = Path::new("test.rs");
80        
81        storage.store_value(file_path, "inline_1", "let api_key = \"sk-12345\";")?;
82        
83        let content = r#"let api_key = "ENV_VAR"; // @whiteout: "ENV_VAR""#;
84        
85        let smudged = apply(content, file_path, &storage, &config)?;
86        assert!(smudged.contains("sk-12345"));
87        // The decoration should be preserved
88        assert!(smudged.contains("@whiteout:"));
89        
90        Ok(())
91    }
92
93    #[test]
94    fn test_smudge_block() -> Result<()> {
95        let temp_dir = TempDir::new()?;
96        let storage = LocalStorage::new(temp_dir.path())?;
97        let config = Config::default();
98        let file_path = Path::new("test.rs");
99        
100        storage.store_value(file_path, "block_2", "const DEBUG = true;")?;
101        
102        let content = r#"
103// @whiteout-start
104const DEBUG = false;
105// @whiteout-end
106const DEBUG = false;
107"#;
108        
109        let smudged = apply(content, file_path, &storage, &config)?;
110        assert!(smudged.contains("true"));
111        
112        Ok(())
113    }
114}