Skip to main content

sakurs_cli/commands/
generate_config.rs

1//! Generate config command implementation
2
3use anyhow::{Context, Result};
4use clap::Args;
5use std::path::PathBuf;
6
7/// Arguments for the generate-config command
8#[derive(Debug, Args)]
9pub struct GenerateConfigArgs {
10    /// Language code for the new configuration
11    #[arg(short = 'l', long, value_name = "CODE", required = true)]
12    pub language_code: String,
13
14    /// Output file path
15    #[arg(short, long, value_name = "FILE", required = true)]
16    pub output: PathBuf,
17}
18
19impl GenerateConfigArgs {
20    /// Execute the generate-config command
21    pub fn execute(&self) -> Result<()> {
22        use std::fs;
23
24        println!("Generating language configuration template...");
25        println!("  Language code: {}", self.language_code);
26        println!("  Output file: {}", self.output.display());
27
28        // Generate template configuration
29        let template = self.generate_template();
30
31        // Write to file
32        fs::write(&self.output, template)
33            .with_context(|| format!("Failed to write to {}", self.output.display()))?;
34
35        println!("✓ Configuration template generated successfully!");
36        println!();
37        println!("Next steps:");
38        println!("1. Edit the configuration file to customize language rules");
39        println!("2. Validate your configuration:");
40        println!(
41            "   sakurs validate --language-config {}",
42            self.output.display()
43        );
44        println!("3. Use it for processing:");
45        println!(
46            "   sakurs process -i input.txt --language-config {}",
47            self.output.display()
48        );
49
50        Ok(())
51    }
52
53    /// Generate template configuration content
54    fn generate_template(&self) -> String {
55        format!(
56            r#"# Language configuration for {}
57
58[metadata]
59code = "{}"
60name = "Custom Language"
61
62# Sentence terminator characters
63[terminators]
64# Basic sentence-ending punctuation
65chars = [".", "!", "?"]
66
67# Multi-character terminator patterns (optional)
68patterns = [
69    # Example: {{ pattern = "!?", name = "surprised_question" }}
70]
71
72# Ellipsis handling
73[ellipsis]
74# Whether ellipsis should be treated as sentence boundary by default
75treat_as_boundary = true
76
77# Ellipsis patterns to recognize
78patterns = ["...", "…"]
79
80# Context-based rules for ellipsis
81context_rules = [
82    {{ condition = "followed_by_capital", boundary = true }},
83    {{ condition = "followed_by_lowercase", boundary = false }}
84]
85
86# Exception patterns (regex) - patterns where ellipsis should NOT be a boundary
87exceptions = [
88    # Example: {{ regex = "\\b(um|uh|er)\\.\\.\\.", boundary = false }}
89]
90
91# Enclosure pairs (quotes, parentheses, etc.)
92[enclosures]
93pairs = [
94    {{ open = "(", close = ")" }},
95    {{ open = "[", close = "]" }},
96    {{ open = "{{", close = "}}" }},
97    {{ open = '"', close = '"', symmetric = true }},
98    {{ open = "'", close = "'", symmetric = true }}
99]
100
101# Suppression rules for preventing false boundaries
102[suppression]
103# Fast pattern matching for enclosure suppression
104fast_patterns = [
105    # Apostrophes in contractions
106    {{ char = "'", before = "alpha", after = "alpha" }},
107    # List items at line start: 1) item
108    {{ char = ")", line_start = true, before = "alnum" }}
109]
110
111# Regex patterns for more complex suppression (optional)
112regex_patterns = [
113    # Example: {{ pattern = "\\d+'\\d+\"", description = "Feet and inches like 5'9\"" }}
114]
115
116# Abbreviations organized by category
117[abbreviations]
118# Add your abbreviations here, organized by category
119# Category names are arbitrary - choose what makes sense for your language
120
121titles = ["Dr", "Mr", "Mrs", "Ms", "Prof"]
122academic = ["Ph.D", "M.D", "B.A", "M.A"]
123business = ["Inc", "Corp", "Ltd", "LLC", "Co"]
124common = ["etc", "vs", "e.g", "i.e"]
125
126# Add more categories as needed:
127# geographic = ["St", "Ave", "Blvd"]
128# measurement = ["oz", "lb", "kg", "km"]
129# month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
130
131# Sentence starters configuration
132[sentence_starters]
133# Whether to require whitespace after the sentence starter (default: true)
134# When true, "The patient" matches but "Theater" does not
135require_following_space = true
136
137# Minimum word length to consider (default: 1)
138min_word_length = 1
139
140# Categories of sentence starter words
141# Words should be written exactly as they should be matched
142pronouns = ["I", "You", "He", "She", "It", "We", "They"]
143articles = ["The", "A", "An"]
144demonstratives = ["This", "That", "These", "Those"]
145conjunctions = ["However", "But", "And", "So", "Therefore"]
146interrogatives = ["What", "When", "Where", "Why", "How", "Who"]
147# Add more categories as needed
148"#,
149            self.language_code, self.language_code
150        )
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use tempfile::TempDir;
158
159    #[test]
160    fn test_generate_config_args_debug() {
161        let args = GenerateConfigArgs {
162            language_code: "fr".to_string(),
163            output: PathBuf::from("french.toml"),
164        };
165
166        let debug_str = format!("{:?}", args);
167        assert!(debug_str.contains("GenerateConfigArgs"));
168        assert!(debug_str.contains("fr"));
169        assert!(debug_str.contains("french.toml"));
170    }
171
172    #[test]
173    fn test_generate_template() {
174        let args = GenerateConfigArgs {
175            language_code: "test".to_string(),
176            output: PathBuf::from("test.toml"),
177        };
178
179        let template = args.generate_template();
180        assert!(template.contains("code = \"test\""));
181        assert!(template.contains("[metadata]"));
182        assert!(template.contains("[terminators]"));
183        assert!(template.contains("[abbreviations]"));
184        assert!(template.contains("[sentence_starters]"));
185    }
186
187    #[test]
188    fn test_execute_success() {
189        let temp_dir = TempDir::new().unwrap();
190        let output_path = temp_dir.path().join("test_config.toml");
191
192        let args = GenerateConfigArgs {
193            language_code: "test".to_string(),
194            output: output_path.clone(),
195        };
196
197        assert!(args.execute().is_ok());
198        assert!(output_path.exists());
199
200        // Verify the generated file contains expected content
201        let content = std::fs::read_to_string(&output_path).unwrap();
202        assert!(content.contains("code = \"test\""));
203    }
204}