use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FormatConfig {
pub indent_style: IndentStyle,
pub indent_width: usize,
pub max_line_length: usize,
pub operator_spacing: OperatorSpacing,
pub bracket_spacing: BracketSpacing,
pub line_ending: LineEnding,
pub comment_style: CommentStyle,
pub trailing_comma: TrailingComma,
pub align_declarations: bool,
pub align_assignments: bool,
pub case_spacing: CaseSpacing,
pub blank_lines: BlankLines,
}
impl Default for FormatConfig {
fn default() -> Self {
Self {
indent_style: IndentStyle::Spaces,
indent_width: 2,
max_line_length: 120,
operator_spacing: OperatorSpacing::default(),
bracket_spacing: BracketSpacing::default(),
line_ending: LineEnding::Lf,
comment_style: CommentStyle::Line,
trailing_comma: TrailingComma::Never,
align_declarations: false,
align_assignments: false,
case_spacing: CaseSpacing::default(),
blank_lines: BlankLines::default(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum IndentStyle {
Spaces,
Tabs,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OperatorSpacing {
pub assignment: bool,
pub arithmetic: bool,
pub comparison: bool,
pub logical: bool,
pub colon: bool,
pub semicolon: bool,
pub comma: bool,
}
impl Default for OperatorSpacing {
fn default() -> Self {
Self {
assignment: true,
arithmetic: true,
comparison: true,
logical: true,
colon: false,
semicolon: true,
comma: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BracketSpacing {
pub paren_open: bool,
pub paren_close: bool,
pub bracket_open: bool,
pub bracket_close: bool,
pub brace_open: bool,
pub brace_close: bool,
}
impl Default for BracketSpacing {
fn default() -> Self {
Self {
paren_open: false,
paren_close: false,
bracket_open: false,
bracket_close: false,
brace_open: true,
brace_close: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum LineEnding {
Lf,
CrLf,
Cr,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum CommentStyle {
Line,
Block,
Mixed,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum TrailingComma {
Never,
Always,
MultilineOnly,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CaseSpacing {
pub before_case: bool,
pub after_case: bool,
pub indent_labels: bool,
}
impl Default for CaseSpacing {
fn default() -> Self {
Self {
before_case: false,
after_case: true,
indent_labels: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlankLines {
pub between_declarations: usize,
pub between_routines: usize,
pub before_begin: usize,
pub after_end: usize,
pub before_case: usize,
pub after_case: usize,
pub max_consecutive: usize,
}
impl Default for BlankLines {
fn default() -> Self {
Self {
between_declarations: 1,
between_routines: 2,
before_begin: 0,
after_end: 1,
before_case: 1,
after_case: 1,
max_consecutive: 1,
}
}
}
pub fn load_config<P: AsRef<Path>>(path: P) -> Result<FormatConfig> {
let content = std::fs::read_to_string(path)?;
let config: FormatConfig = toml::from_str(&content)?;
Ok(config)
}
pub fn save_config<P: AsRef<Path>>(config: &FormatConfig, path: P) -> Result<()> {
let content = toml::to_string_pretty(config)?;
std::fs::write(path, content)?;
Ok(())
}
pub fn find_config<P: AsRef<Path>>(start_dir: P) -> Result<Option<FormatConfig>> {
let mut current_dir = start_dir.as_ref();
loop {
let config_path = current_dir.join("pascalfmt.toml");
if config_path.exists() {
return Ok(Some(load_config(config_path)?));
}
let config_path = current_dir.join(".pascalfmt.toml");
if config_path.exists() {
return Ok(Some(load_config(config_path)?));
}
match current_dir.parent() {
Some(parent) => current_dir = parent,
None => break,
}
}
Ok(None)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_default_config() {
let config = FormatConfig::default();
assert_eq!(config.indent_style, IndentStyle::Spaces);
assert_eq!(config.indent_width, 2);
assert_eq!(config.max_line_length, 120);
}
#[test]
fn test_save_load_config() {
let dir = tempdir().unwrap();
let config_path = dir.path().join("config.toml");
let original = FormatConfig {
indent_width: 4,
max_line_length: 100,
..Default::default()
};
save_config(&original, &config_path).unwrap();
let loaded = load_config(&config_path).unwrap();
assert_eq!(original, loaded);
}
#[test]
fn test_find_config_not_found() {
let dir = tempdir().unwrap();
let config = find_config(dir.path()).unwrap();
assert!(config.is_none());
}
}