Skip to main content

taskgraph/
config.rs

1//! Configuration file support.
2
3use serde::{Deserialize, Serialize};
4use std::path::{Path, PathBuf};
5
6/// TaskGraph configuration.
7#[derive(Debug, Default, Serialize, Deserialize)]
8pub struct Config {
9    /// Project configuration.
10    #[serde(default)]
11    pub project: ProjectConfig,
12}
13
14#[derive(Debug, Serialize, Deserialize)]
15pub struct ProjectConfig {
16    /// Path to tasks directory.
17    #[serde(default = "default_tasks_dir")]
18    pub tasks_dir: String,
19}
20
21impl Default for ProjectConfig {
22    fn default() -> Self {
23        Self {
24            tasks_dir: default_tasks_dir(),
25        }
26    }
27}
28
29fn default_tasks_dir() -> String {
30    "tasks".to_string()
31}
32
33impl Config {
34    /// Load configuration from a file.
35    pub fn from_file(path: &Path) -> crate::Result<Self> {
36        let content = std::fs::read_to_string(path)?;
37        let config: Config = toml::from_str(&content)
38            .map_err(|e| crate::Error::Graph(format!("Config parse error: {}", e)))?;
39        Ok(config)
40    }
41
42    /// Find and load config file, searching up directory tree.
43    pub fn find_and_load() -> Option<Self> {
44        let mut current_dir = std::env::current_dir().ok()?;
45
46        loop {
47            let config_path = current_dir.join(".taskgraph.toml");
48            if config_path.exists() {
49                return Self::from_file(&config_path).ok();
50            }
51
52            // Try parent directory
53            match current_dir.parent() {
54                Some(parent) => current_dir = parent.to_path_buf(),
55                None => break,
56            }
57        }
58
59        None
60    }
61
62    /// Get the tasks directory path.
63    pub fn tasks_path(&self) -> PathBuf {
64        PathBuf::from(&self.project.tasks_dir)
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use std::io::Write;
72    use tempfile::NamedTempFile;
73
74    #[test]
75    fn test_default_config() {
76        let config = Config::default();
77        assert_eq!(config.project.tasks_dir, "tasks");
78    }
79
80    #[test]
81    fn test_load_config() {
82        let mut file = NamedTempFile::new().unwrap();
83        writeln!(
84            file,
85            r#"
86[project]
87tasks_dir = "my-tasks"
88"#
89        )
90        .unwrap();
91
92        let config = Config::from_file(file.path()).unwrap();
93        assert_eq!(config.project.tasks_dir, "my-tasks");
94    }
95}