Skip to main content

swarm_engine_core/config/
paths.rs

1//! パス解決
2//!
3//! SwarmEngine のディレクトリ構造を管理します。
4//!
5//! ## 分離の原則
6//!
7//! | カテゴリ | パス | 用途 |
8//! |---------|------|------|
9//! | システム設定 | `~/.swarm-engine/` | 設定・キャッシュ・ログ |
10//! | ユーザーデータ | `~/swarm-engine/` | シナリオ・レポート |
11//! | プロジェクトローカル | `./swarm-engine/` | プロジェクト固有設定 |
12
13use std::path::PathBuf;
14
15/// パス解決ユーティリティ
16pub struct PathResolver;
17
18impl PathResolver {
19    // =========================================================================
20    // システム設定 (~/.swarm-engine/)
21    // =========================================================================
22
23    /// システム設定ディレクトリ (`~/.swarm-engine/`)
24    pub fn system_config_dir() -> PathBuf {
25        dirs::home_dir()
26            .map(|h| h.join(".swarm-engine"))
27            .expect("Could not determine home directory")
28    }
29
30    /// グローバル設定ファイル (`~/.swarm-engine/config.toml`)
31    pub fn global_config_file() -> PathBuf {
32        Self::system_config_dir().join("config.toml")
33    }
34
35    /// キャッシュディレクトリ (`~/.swarm-engine/cache/`)
36    pub fn cache_dir() -> PathBuf {
37        Self::system_config_dir().join("cache")
38    }
39
40    /// ログディレクトリ (`~/.swarm-engine/logs/`)
41    pub fn logs_dir() -> PathBuf {
42        Self::system_config_dir().join("logs")
43    }
44
45    /// ステートディレクトリ (`~/.swarm-engine/state/`)
46    pub fn state_dir() -> PathBuf {
47        Self::system_config_dir().join("state")
48    }
49
50    // =========================================================================
51    // ユーザーデータ (~/swarm-engine/)
52    // =========================================================================
53
54    /// ユーザーデータディレクトリ (`~/swarm-engine/`)
55    ///
56    /// 環境変数 `SWARM_ENGINE_USER_DATA_DIR` でオーバーライド可能
57    pub fn user_data_dir() -> PathBuf {
58        if let Ok(dir) = std::env::var("SWARM_ENGINE_USER_DATA_DIR") {
59            return PathBuf::from(dir);
60        }
61
62        dirs::home_dir()
63            .map(|h| h.join("swarm-engine"))
64            .expect("Could not determine home directory")
65    }
66
67    /// ユーザーシナリオディレクトリ (`~/swarm-engine/scenarios/`)
68    pub fn user_scenarios_dir() -> PathBuf {
69        Self::user_data_dir().join("scenarios")
70    }
71
72    /// Eval用シナリオディレクトリ (`~/swarm-engine/scenarios/eval/`)
73    pub fn user_eval_scenarios_dir() -> PathBuf {
74        Self::user_scenarios_dir().join("eval")
75    }
76
77    /// Gym用シナリオディレクトリ (`~/swarm-engine/scenarios/gym/`)
78    pub fn user_gym_scenarios_dir() -> PathBuf {
79        Self::user_scenarios_dir().join("gym")
80    }
81
82    /// レポート出力ディレクトリ (`~/swarm-engine/reports/`)
83    pub fn reports_dir() -> PathBuf {
84        Self::user_data_dir().join("reports")
85    }
86
87    /// エクスポートディレクトリ (`~/swarm-engine/exports/`)
88    pub fn exports_dir() -> PathBuf {
89        Self::user_data_dir().join("exports")
90    }
91
92    /// テンプレートディレクトリ (`~/swarm-engine/templates/`)
93    pub fn templates_dir() -> PathBuf {
94        Self::user_data_dir().join("templates")
95    }
96
97    // =========================================================================
98    // プロジェクトローカル (./swarm-engine/)
99    // =========================================================================
100
101    /// プロジェクトディレクトリ (`./swarm-engine/`)
102    ///
103    /// カレントディレクトリに `swarm-engine/` が存在する場合のみ `Some` を返す
104    pub fn project_dir() -> Option<PathBuf> {
105        std::env::current_dir()
106            .ok()
107            .map(|d| d.join("swarm-engine"))
108            .filter(|p| p.exists())
109    }
110
111    /// プロジェクト設定ファイル (`./swarm-engine/config.toml`)
112    pub fn project_config_file() -> Option<PathBuf> {
113        Self::project_dir().map(|d| d.join("config.toml"))
114    }
115
116    /// プロジェクトシナリオディレクトリ (`./swarm-engine/scenarios/`)
117    pub fn project_scenarios_dir() -> Option<PathBuf> {
118        Self::project_dir().map(|d| d.join("scenarios"))
119    }
120
121    /// プロジェクトEval用シナリオディレクトリ (`./swarm-engine/scenarios/eval/`)
122    pub fn project_eval_scenarios_dir() -> Option<PathBuf> {
123        Self::project_dir().map(|d| d.join("scenarios").join("eval"))
124    }
125
126    /// プロジェクトGym用シナリオディレクトリ (`./swarm-engine/scenarios/gym/`)
127    pub fn project_gym_scenarios_dir() -> Option<PathBuf> {
128        Self::project_dir().map(|d| d.join("scenarios").join("gym"))
129    }
130
131    /// プロジェクトレポートディレクトリ (`./swarm-engine/reports/`)
132    pub fn project_reports_dir() -> Option<PathBuf> {
133        Self::project_dir().map(|d| d.join("reports"))
134    }
135
136    // =========================================================================
137    // ユーティリティ
138    // =========================================================================
139
140    /// 標準ディレクトリを作成
141    ///
142    /// 初期化時に呼び出してディレクトリ構造を構築
143    pub fn ensure_dirs() -> std::io::Result<()> {
144        let dirs = [
145            Self::system_config_dir(),
146            Self::cache_dir(),
147            Self::logs_dir(),
148            Self::state_dir(),
149            Self::user_data_dir(),
150            Self::user_eval_scenarios_dir(),
151            Self::user_gym_scenarios_dir(),
152            Self::reports_dir(),
153        ];
154
155        for dir in dirs {
156            if !dir.exists() {
157                std::fs::create_dir_all(&dir)?;
158                tracing::info!("Created directory: {}", dir.display());
159            }
160        }
161
162        Ok(())
163    }
164
165    /// Eval シナリオ検索パスを取得(優先順位順)
166    ///
167    /// 低い優先順位から高い優先順位の順で返す:
168    /// 1. ユーザーグローバル (`~/swarm-engine/scenarios/eval/`)
169    /// 2. プロジェクトローカル (`./swarm-engine/scenarios/eval/`)
170    pub fn eval_scenario_search_paths() -> Vec<PathBuf> {
171        let mut paths = vec![Self::user_eval_scenarios_dir()];
172
173        if let Some(project_dir) = Self::project_eval_scenarios_dir() {
174            if project_dir.exists() {
175                paths.push(project_dir);
176            }
177        }
178
179        paths
180    }
181
182    /// Gym シナリオ検索パスを取得(優先順位順)
183    pub fn gym_scenario_search_paths() -> Vec<PathBuf> {
184        let mut paths = vec![Self::user_gym_scenarios_dir()];
185
186        if let Some(project_dir) = Self::project_gym_scenarios_dir() {
187            if project_dir.exists() {
188                paths.push(project_dir);
189            }
190        }
191
192        paths
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_system_config_dir() {
202        let dir = PathResolver::system_config_dir();
203        assert!(dir.ends_with(".swarm-engine"));
204    }
205
206    #[test]
207    fn test_user_data_dir() {
208        // 1. デフォルト動作(環境変数なし)
209        std::env::remove_var("SWARM_ENGINE_USER_DATA_DIR");
210        let dir = PathResolver::user_data_dir();
211        assert!(dir.ends_with("swarm-engine"));
212        assert!(!dir.to_string_lossy().contains("/.swarm-engine")); // ドットなし
213
214        // 2. 環境変数オーバーライド
215        let test_dir = "/tmp/test-swarm-engine";
216        std::env::set_var("SWARM_ENGINE_USER_DATA_DIR", test_dir);
217        let dir = PathResolver::user_data_dir();
218        assert_eq!(dir, PathBuf::from(test_dir));
219
220        // クリーンアップ
221        std::env::remove_var("SWARM_ENGINE_USER_DATA_DIR");
222    }
223
224    #[test]
225    fn test_eval_scenario_search_paths() {
226        let paths = PathResolver::eval_scenario_search_paths();
227        assert!(!paths.is_empty());
228        // 最初はユーザーグローバル
229        assert!(paths[0].ends_with("scenarios/eval"));
230    }
231}