pinto/service/
lifecycle.rs1use crate::config::Config;
4use crate::error::{Error, Result};
5use std::path::{Path, PathBuf};
6use tokio::fs;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum InitOutcome {
11 Created(PathBuf),
13 AlreadyInitialized(PathBuf),
15}
16
17pub async fn init_board(project_dir: &Path) -> Result<InitOutcome> {
22 let board_dir = project_dir.join(".pinto");
23 let config_path = board_dir.join("config.toml");
24 if fs::try_exists(&config_path)
25 .await
26 .map_err(|e| Error::io(&config_path, &e))?
27 {
28 return Ok(InitOutcome::AlreadyInitialized(board_dir));
29 }
30
31 let tasks_dir = board_dir.join("tasks");
32 fs::create_dir_all(&tasks_dir)
33 .await
34 .map_err(|e| Error::io(&tasks_dir, &e))?;
35
36 let mut config = Config::default();
37 if let Some(name) = project_dir.file_name().and_then(|s| s.to_str()) {
38 config.project.name = name.to_string();
39 }
40 config.save(&config_path).await?;
41
42 Ok(InitOutcome::Created(board_dir))
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48 use tempfile::TempDir;
49
50 #[tokio::test]
51 async fn init_creates_config_and_tasks_dir() {
52 let dir = TempDir::new().expect("temp dir");
53 let board = dir.path().join(".pinto");
54
55 let outcome = init_board(dir.path()).await.expect("init succeeds");
56
57 assert_eq!(outcome, InitOutcome::Created(board.clone()));
58 assert!(board.join("config.toml").is_file());
59 assert!(board.join("tasks").is_dir());
60 }
61
62 #[tokio::test]
63 async fn init_is_idempotent_and_preserves_config() {
64 let dir = TempDir::new().expect("temp dir");
65 init_board(dir.path()).await.expect("first init");
66 let config_path = dir.path().join(".pinto").join("config.toml");
67 let before = std::fs::read_to_string(&config_path).expect("read config");
68
69 let outcome = init_board(dir.path()).await.expect("second init");
70
71 assert!(matches!(outcome, InitOutcome::AlreadyInitialized(_)));
72 let after = std::fs::read_to_string(&config_path).expect("read config");
73 assert_eq!(before, after, "existing config must not be modified");
74 }
75
76 #[tokio::test]
77 async fn init_sets_project_name_from_directory() {
78 let dir = TempDir::new().expect("temp dir");
79 let project = dir.path().join("myproject");
80 std::fs::create_dir(&project).expect("mkdir");
81
82 init_board(&project).await.expect("init succeeds");
83
84 let config = Config::load(&project.join(".pinto").join("config.toml"))
85 .await
86 .expect("load");
87 assert_eq!(config.project.name, "myproject");
88 }
89}