use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
#[derive(Default)]
pub struct Config {
pub splitrs: SplitRsConfig,
pub naming: NamingConfig,
pub output: OutputConfig,
}
impl Config {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let contents =
fs::read_to_string(path.as_ref()).context("Failed to read configuration file")?;
let config: Config =
toml::from_str(&contents).context("Failed to parse TOML configuration")?;
Ok(config)
}
pub fn load_from_current_dir() -> Self {
Self::find_and_load(".").unwrap_or_default()
}
pub fn find_and_load<P: AsRef<Path>>(start_dir: P) -> Option<Self> {
let mut current_dir = start_dir.as_ref().to_path_buf();
loop {
let config_path = current_dir.join(".splitrs.toml");
if config_path.exists() {
return Self::from_file(&config_path).ok();
}
if !current_dir.pop() {
break;
}
}
None
}
#[allow(dead_code)]
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let toml_string =
toml::to_string_pretty(self).context("Failed to serialize configuration to TOML")?;
fs::write(path.as_ref(), toml_string).context("Failed to write configuration file")?;
Ok(())
}
pub fn merge_with_args(
&mut self,
max_lines: Option<usize>,
max_impl_lines: Option<usize>,
split_impl_blocks: Option<bool>,
) {
if let Some(max_lines) = max_lines {
self.splitrs.max_lines = max_lines;
}
if let Some(max_impl_lines) = max_impl_lines {
self.splitrs.max_impl_lines = max_impl_lines;
}
if let Some(split_impl_blocks) = split_impl_blocks {
self.splitrs.split_impl_blocks = split_impl_blocks;
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct SplitRsConfig {
pub max_lines: usize,
pub max_impl_lines: usize,
pub split_impl_blocks: bool,
pub incremental: bool,
pub generate_tests: bool,
}
impl Default for SplitRsConfig {
fn default() -> Self {
Self {
max_lines: 1000,
max_impl_lines: 500,
split_impl_blocks: false,
incremental: false,
generate_tests: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct NamingConfig {
pub strategy: String,
pub type_module_suffix: String,
pub impl_module_suffix: String,
pub trait_module_suffix: String,
pub use_snake_case: bool,
#[serde(default)]
pub custom_type_mappings: std::collections::HashMap<String, String>,
#[serde(default)]
pub custom_pattern_mappings: std::collections::HashMap<String, String>,
}
impl Default for NamingConfig {
fn default() -> Self {
Self {
strategy: "snake_case".to_string(),
type_module_suffix: "_type".to_string(),
impl_module_suffix: "_impl".to_string(),
trait_module_suffix: "_traits".to_string(),
use_snake_case: true,
custom_type_mappings: std::collections::HashMap::new(),
custom_pattern_mappings: std::collections::HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct OutputConfig {
pub module_doc_template: String,
pub preserve_comments: bool,
pub format_output: bool,
}
impl Default for OutputConfig {
fn default() -> Self {
Self {
module_doc_template: "//! Auto-generated module\n".to_string(),
preserve_comments: true,
format_output: true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
#[test]
fn test_default_config() {
let config = Config::default();
assert_eq!(config.splitrs.max_lines, 1000);
assert_eq!(config.splitrs.max_impl_lines, 500);
assert!(!config.splitrs.split_impl_blocks);
}
#[test]
fn test_config_serialization() {
let config = Config::default();
let toml_string = toml::to_string(&config).unwrap();
assert!(toml_string.contains("max_lines"));
assert!(toml_string.contains("max_impl_lines"));
}
#[test]
fn test_config_deserialization() {
let toml_str = r#"
[splitrs]
max_lines = 800
max_impl_lines = 400
split_impl_blocks = true
[naming]
type_module_suffix = "_types"
impl_module_suffix = "_methods"
[output]
preserve_comments = false
"#;
let config: Config = toml::from_str(toml_str).unwrap();
assert_eq!(config.splitrs.max_lines, 800);
assert_eq!(config.splitrs.max_impl_lines, 400);
assert!(config.splitrs.split_impl_blocks);
assert_eq!(config.naming.type_module_suffix, "_types");
assert!(!config.output.preserve_comments);
}
#[test]
fn test_config_merge_with_args() {
let mut config = Config::default();
config.merge_with_args(Some(1500), Some(600), Some(true));
assert_eq!(config.splitrs.max_lines, 1500);
assert_eq!(config.splitrs.max_impl_lines, 600);
assert!(config.splitrs.split_impl_blocks);
}
#[test]
fn test_config_save_and_load() {
let temp_dir = env::temp_dir();
let config_path = temp_dir.join("test_splitrs.toml");
let config = Config::default();
config.save_to_file(&config_path).unwrap();
let loaded_config = Config::from_file(&config_path).unwrap();
assert_eq!(loaded_config.splitrs.max_lines, config.splitrs.max_lines);
let _ = fs::remove_file(config_path);
}
}