sphinx_ultra/
config.rs

1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3use std::path::PathBuf;
4
5use crate::python_config::PythonConfigParser;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct BuildConfig {
9    /// Number of parallel jobs to use (defaults to number of CPU cores)
10    pub parallel_jobs: Option<usize>,
11
12    /// Maximum cache size in MB
13    pub max_cache_size_mb: usize,
14
15    /// Cache expiration time in hours
16    pub cache_expiration_hours: u64,
17
18    /// Output format configuration
19    pub output: OutputConfig,
20
21    /// Theme configuration
22    pub theme: ThemeConfig,
23
24    /// Extension configuration
25    pub extensions: Vec<String>,
26
27    /// Custom template directories
28    pub template_dirs: Vec<PathBuf>,
29
30    /// Static file directories
31    pub static_dirs: Vec<PathBuf>,
32
33    /// Build optimization settings
34    pub optimization: OptimizationConfig,
35
36    // Sphinx-compatible fields
37    /// Project name
38    pub project: String,
39
40    /// Project version
41    pub version: Option<String>,
42
43    /// Project release
44    pub release: Option<String>,
45
46    /// Copyright notice
47    pub copyright: Option<String>,
48
49    /// Language code
50    pub language: Option<String>,
51
52    /// Root document
53    pub root_doc: Option<String>,
54
55    /// HTML theme style files
56    pub html_style: Vec<String>,
57
58    /// HTML CSS files
59    pub html_css_files: Vec<String>,
60
61    /// HTML JavaScript files
62    pub html_js_files: Vec<String>,
63
64    /// HTML static paths
65    pub html_static_path: Vec<PathBuf>,
66
67    /// HTML logo file
68    pub html_logo: Option<String>,
69
70    /// HTML favicon file
71    pub html_favicon: Option<String>,
72
73    /// HTML title
74    pub html_title: Option<String>,
75
76    /// HTML short title
77    pub html_short_title: Option<String>,
78
79    /// Show copyright in HTML
80    pub html_show_copyright: Option<bool>,
81
82    /// Show Sphinx attribution
83    pub html_show_sphinx: Option<bool>,
84
85    /// Copy source files
86    pub html_copy_source: Option<bool>,
87
88    /// Show source links
89    pub html_show_sourcelink: Option<bool>,
90
91    /// Source link suffix
92    pub html_sourcelink_suffix: Option<String>,
93
94    /// Use index
95    pub html_use_index: Option<bool>,
96
97    /// Use OpenSearch
98    pub html_use_opensearch: Option<bool>,
99
100    /// Last updated format
101    pub html_last_updated_fmt: Option<String>,
102
103    /// Templates path
104    pub templates_path: Vec<PathBuf>,
105
106    /// Turn warnings into errors
107    pub fail_on_warning: bool,
108
109    /// Glob-style patterns for file inclusion (Sphinx compatibility)
110    /// Default: ["**"] (include all files)
111    pub include_patterns: Vec<String>,
112
113    /// Glob-style patterns for file exclusion (Sphinx compatibility)
114    /// Default: [] (exclude nothing)
115    /// Exclusions have priority over inclusions
116    pub exclude_patterns: Vec<String>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct OutputConfig {
121    /// Output HTML format
122    pub html_theme: String,
123
124    /// Enable syntax highlighting
125    pub syntax_highlighting: bool,
126
127    /// Syntax highlighting theme
128    pub highlight_theme: String,
129
130    /// Generate search index
131    pub search_index: bool,
132
133    /// Minify output HTML
134    pub minify_html: bool,
135
136    /// Compress output files
137    pub compress_output: bool,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct ThemeConfig {
142    /// Theme name
143    pub name: String,
144
145    /// Theme-specific configuration
146    pub options: serde_json::Value,
147
148    /// Custom CSS files
149    pub custom_css: Vec<PathBuf>,
150
151    /// Custom JavaScript files
152    pub custom_js: Vec<PathBuf>,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct OptimizationConfig {
157    /// Enable parallel processing
158    pub parallel_processing: bool,
159
160    /// Enable incremental builds
161    pub incremental_builds: bool,
162
163    /// Cache parsed documents
164    pub document_caching: bool,
165
166    /// Optimize images
167    pub image_optimization: bool,
168
169    /// Bundle assets
170    pub asset_bundling: bool,
171}
172
173impl Default for BuildConfig {
174    fn default() -> Self {
175        Self {
176            parallel_jobs: None,
177            max_cache_size_mb: 500,
178            cache_expiration_hours: 24,
179            output: OutputConfig::default(),
180            theme: ThemeConfig::default(),
181            extensions: vec![
182                "sphinx.ext.autodoc".to_string(),
183                "sphinx.ext.viewcode".to_string(),
184                "sphinx.ext.intersphinx".to_string(),
185            ],
186            template_dirs: vec![],
187            static_dirs: vec![],
188            optimization: OptimizationConfig::default(),
189
190            // Sphinx-compatible defaults
191            project: "Sphinx Ultra Project".to_string(),
192            version: Some("1.0.0".to_string()),
193            release: Some("1.0.0".to_string()),
194            copyright: Some("2024, Sphinx Ultra".to_string()),
195            language: Some("en".to_string()),
196            root_doc: Some("index".to_string()),
197            html_style: vec!["sphinx_rtd_theme.css".to_string()],
198            html_css_files: vec![],
199            html_js_files: vec![],
200            html_static_path: vec![PathBuf::from("_static")],
201            html_logo: None,
202            html_favicon: None,
203            html_title: None,
204            html_short_title: None,
205            html_show_copyright: Some(true),
206            html_show_sphinx: Some(true),
207            html_copy_source: Some(true),
208            html_show_sourcelink: Some(true),
209            html_sourcelink_suffix: Some(".txt".to_string()),
210            html_use_index: Some(true),
211            html_use_opensearch: Some(false),
212            html_last_updated_fmt: Some("%b %d, %Y".to_string()),
213            templates_path: vec![PathBuf::from("_templates")],
214
215            // Warning handling
216            fail_on_warning: false,
217
218            // File pattern matching (Sphinx compatibility)
219            include_patterns: vec!["**".to_string()],
220            exclude_patterns: vec![],
221        }
222    }
223}
224
225impl Default for OutputConfig {
226    fn default() -> Self {
227        Self {
228            html_theme: "sphinx_rtd_theme".to_string(),
229            syntax_highlighting: true,
230            highlight_theme: "github".to_string(),
231            search_index: true,
232            minify_html: false,
233            compress_output: false,
234        }
235    }
236}
237
238impl Default for ThemeConfig {
239    fn default() -> Self {
240        Self {
241            name: "sphinx_rtd_theme".to_string(),
242            options: serde_json::json!({}),
243            custom_css: vec![],
244            custom_js: vec![],
245        }
246    }
247}
248
249impl Default for OptimizationConfig {
250    fn default() -> Self {
251        Self {
252            parallel_processing: true,
253            incremental_builds: true,
254            document_caching: true,
255            image_optimization: false,
256            asset_bundling: false,
257        }
258    }
259}
260
261impl BuildConfig {
262    pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
263        let path = path.as_ref();
264        let content = std::fs::read_to_string(path)?;
265        let config = if path.extension().and_then(|s| s.to_str()) == Some("yaml")
266            || path.extension().and_then(|s| s.to_str()) == Some("yml")
267        {
268            serde_yaml::from_str(&content)?
269        } else {
270            serde_json::from_str(&content)?
271        };
272        Ok(config)
273    }
274
275    /// Load configuration from a Sphinx conf.py file
276    pub fn from_conf_py<P: AsRef<std::path::Path>>(conf_py_path: P) -> Result<Self> {
277        let mut parser = PythonConfigParser::new()?;
278        let conf_py_config = parser.parse_conf_py(conf_py_path)?;
279        Ok(conf_py_config.to_build_config())
280    }
281
282    /// Try to auto-detect and load configuration from various sources
283    pub fn auto_detect<P: AsRef<std::path::Path>>(source_dir: P) -> Result<Self> {
284        let source_dir = source_dir.as_ref();
285
286        // Try conf.py first (Sphinx standard)
287        let conf_py_path = source_dir.join("conf.py");
288        if conf_py_path.exists() {
289            return Self::from_conf_py(conf_py_path);
290        }
291
292        // Try sphinx-ultra.yaml
293        let yaml_path = source_dir.join("sphinx-ultra.yaml");
294        if yaml_path.exists() {
295            return Self::from_file(yaml_path);
296        }
297
298        // Try sphinx-ultra.yml
299        let yml_path = source_dir.join("sphinx-ultra.yml");
300        if yml_path.exists() {
301            return Self::from_file(yml_path);
302        }
303
304        // Try sphinx-ultra.json
305        let json_path = source_dir.join("sphinx-ultra.json");
306        if json_path.exists() {
307            return Self::from_file(json_path);
308        }
309
310        // Return default configuration
311        Ok(Self::default())
312    }
313
314    #[allow(dead_code)]
315    pub fn save_to_file<P: AsRef<std::path::Path>>(&self, path: P) -> Result<()> {
316        let content = if path.as_ref().extension().and_then(|s| s.to_str()) == Some("yaml")
317            || path.as_ref().extension().and_then(|s| s.to_str()) == Some("yml")
318        {
319            serde_yaml::to_string(self)?
320        } else {
321            serde_json::to_string_pretty(self)?
322        };
323        std::fs::write(path, content)?;
324        Ok(())
325    }
326}