swarm-engine-core 0.1.6

Core types and orchestration for SwarmEngine
Documentation
//! パス解決
//!
//! SwarmEngine のディレクトリ構造を管理します。
//!
//! ## 分離の原則
//!
//! | カテゴリ | パス | 用途 |
//! |---------|------|------|
//! | システム設定 | `~/.swarm-engine/` | 設定・キャッシュ・ログ |
//! | ユーザーデータ | `~/swarm-engine/` | シナリオ・レポート |
//! | プロジェクトローカル | `./swarm-engine/` | プロジェクト固有設定 |

use std::path::PathBuf;

/// パス解決ユーティリティ
pub struct PathResolver;

impl PathResolver {
    // =========================================================================
    // システム設定 (~/.swarm-engine/)
    // =========================================================================

    /// システム設定ディレクトリ (`~/.swarm-engine/`)
    pub fn system_config_dir() -> PathBuf {
        dirs::home_dir()
            .map(|h| h.join(".swarm-engine"))
            .expect("Could not determine home directory")
    }

    /// グローバル設定ファイル (`~/.swarm-engine/config.toml`)
    pub fn global_config_file() -> PathBuf {
        Self::system_config_dir().join("config.toml")
    }

    /// キャッシュディレクトリ (`~/.swarm-engine/cache/`)
    pub fn cache_dir() -> PathBuf {
        Self::system_config_dir().join("cache")
    }

    /// ログディレクトリ (`~/.swarm-engine/logs/`)
    pub fn logs_dir() -> PathBuf {
        Self::system_config_dir().join("logs")
    }

    /// ステートディレクトリ (`~/.swarm-engine/state/`)
    pub fn state_dir() -> PathBuf {
        Self::system_config_dir().join("state")
    }

    // =========================================================================
    // ユーザーデータ (~/swarm-engine/)
    // =========================================================================

    /// ユーザーデータディレクトリ (`~/swarm-engine/`)
    ///
    /// 環境変数 `SWARM_ENGINE_USER_DATA_DIR` でオーバーライド可能
    pub fn user_data_dir() -> PathBuf {
        if let Ok(dir) = std::env::var("SWARM_ENGINE_USER_DATA_DIR") {
            return PathBuf::from(dir);
        }

        dirs::home_dir()
            .map(|h| h.join("swarm-engine"))
            .expect("Could not determine home directory")
    }

    /// ユーザーシナリオディレクトリ (`~/swarm-engine/scenarios/`)
    pub fn user_scenarios_dir() -> PathBuf {
        Self::user_data_dir().join("scenarios")
    }

    /// Eval用シナリオディレクトリ (`~/swarm-engine/scenarios/eval/`)
    pub fn user_eval_scenarios_dir() -> PathBuf {
        Self::user_scenarios_dir().join("eval")
    }

    /// Gym用シナリオディレクトリ (`~/swarm-engine/scenarios/gym/`)
    pub fn user_gym_scenarios_dir() -> PathBuf {
        Self::user_scenarios_dir().join("gym")
    }

    /// レポート出力ディレクトリ (`~/swarm-engine/reports/`)
    pub fn reports_dir() -> PathBuf {
        Self::user_data_dir().join("reports")
    }

    /// エクスポートディレクトリ (`~/swarm-engine/exports/`)
    pub fn exports_dir() -> PathBuf {
        Self::user_data_dir().join("exports")
    }

    /// テンプレートディレクトリ (`~/swarm-engine/templates/`)
    pub fn templates_dir() -> PathBuf {
        Self::user_data_dir().join("templates")
    }

    // =========================================================================
    // プロジェクトローカル (./swarm-engine/)
    // =========================================================================

    /// プロジェクトディレクトリ (`./swarm-engine/`)
    ///
    /// カレントディレクトリに `swarm-engine/` が存在する場合のみ `Some` を返す
    pub fn project_dir() -> Option<PathBuf> {
        std::env::current_dir()
            .ok()
            .map(|d| d.join("swarm-engine"))
            .filter(|p| p.exists())
    }

    /// プロジェクト設定ファイル (`./swarm-engine/config.toml`)
    pub fn project_config_file() -> Option<PathBuf> {
        Self::project_dir().map(|d| d.join("config.toml"))
    }

    /// プロジェクトシナリオディレクトリ (`./swarm-engine/scenarios/`)
    pub fn project_scenarios_dir() -> Option<PathBuf> {
        Self::project_dir().map(|d| d.join("scenarios"))
    }

    /// プロジェクトEval用シナリオディレクトリ (`./swarm-engine/scenarios/eval/`)
    pub fn project_eval_scenarios_dir() -> Option<PathBuf> {
        Self::project_dir().map(|d| d.join("scenarios").join("eval"))
    }

    /// プロジェクトGym用シナリオディレクトリ (`./swarm-engine/scenarios/gym/`)
    pub fn project_gym_scenarios_dir() -> Option<PathBuf> {
        Self::project_dir().map(|d| d.join("scenarios").join("gym"))
    }

    /// プロジェクトレポートディレクトリ (`./swarm-engine/reports/`)
    pub fn project_reports_dir() -> Option<PathBuf> {
        Self::project_dir().map(|d| d.join("reports"))
    }

    // =========================================================================
    // ユーティリティ
    // =========================================================================

    /// 標準ディレクトリを作成
    ///
    /// 初期化時に呼び出してディレクトリ構造を構築
    pub fn ensure_dirs() -> std::io::Result<()> {
        let dirs = [
            Self::system_config_dir(),
            Self::cache_dir(),
            Self::logs_dir(),
            Self::state_dir(),
            Self::user_data_dir(),
            Self::user_eval_scenarios_dir(),
            Self::user_gym_scenarios_dir(),
            Self::reports_dir(),
        ];

        for dir in dirs {
            if !dir.exists() {
                std::fs::create_dir_all(&dir)?;
                tracing::info!("Created directory: {}", dir.display());
            }
        }

        Ok(())
    }

    /// Eval シナリオ検索パスを取得(優先順位順)
    ///
    /// 低い優先順位から高い優先順位の順で返す:
    /// 1. ユーザーグローバル (`~/swarm-engine/scenarios/eval/`)
    /// 2. プロジェクトローカル (`./swarm-engine/scenarios/eval/`)
    pub fn eval_scenario_search_paths() -> Vec<PathBuf> {
        let mut paths = vec![Self::user_eval_scenarios_dir()];

        if let Some(project_dir) = Self::project_eval_scenarios_dir() {
            if project_dir.exists() {
                paths.push(project_dir);
            }
        }

        paths
    }

    /// Gym シナリオ検索パスを取得(優先順位順)
    pub fn gym_scenario_search_paths() -> Vec<PathBuf> {
        let mut paths = vec![Self::user_gym_scenarios_dir()];

        if let Some(project_dir) = Self::project_gym_scenarios_dir() {
            if project_dir.exists() {
                paths.push(project_dir);
            }
        }

        paths
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_system_config_dir() {
        let dir = PathResolver::system_config_dir();
        assert!(dir.ends_with(".swarm-engine"));
    }

    #[test]
    fn test_user_data_dir() {
        // 1. デフォルト動作(環境変数なし)
        std::env::remove_var("SWARM_ENGINE_USER_DATA_DIR");
        let dir = PathResolver::user_data_dir();
        assert!(dir.ends_with("swarm-engine"));
        assert!(!dir.to_string_lossy().contains("/.swarm-engine")); // ドットなし

        // 2. 環境変数オーバーライド
        let test_dir = "/tmp/test-swarm-engine";
        std::env::set_var("SWARM_ENGINE_USER_DATA_DIR", test_dir);
        let dir = PathResolver::user_data_dir();
        assert_eq!(dir, PathBuf::from(test_dir));

        // クリーンアップ
        std::env::remove_var("SWARM_ENGINE_USER_DATA_DIR");
    }

    #[test]
    fn test_eval_scenario_search_paths() {
        let paths = PathResolver::eval_scenario_search_paths();
        assert!(!paths.is_empty());
        // 最初はユーザーグローバル
        assert!(paths[0].ends_with("scenarios/eval"));
    }
}