From 0d92b6309fe42c18f990119e578d704b51d33e30 Mon Sep 17 00:00:00 2001 From: insign <1113045+insign@users.noreply.github.com> Date: Sat, 14 Feb 2026 03:22:59 +0000 Subject: [PATCH] feat: add support for Rye package manager - Add `rye` detector in `src/detectors/python.rs`. - Identify Rye projects by `[tool.rye]` in `pyproject.toml`. - Validate commands in `[tool.rye.scripts]`. - Map `rye` runner to `rye run ` command. - Add unit tests for detection and validation. --- src/detectors/mod.rs | 8 ++++ src/detectors/python.rs | 90 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/src/detectors/mod.rs b/src/detectors/mod.rs index fcac6e1..0b1e991 100644 --- a/src/detectors/mod.rs +++ b/src/detectors/mod.rs @@ -191,6 +191,7 @@ impl DetectedRunner { "npm" => vec!["npm".to_string(), "run".to_string(), task.to_string()], // Python ecosystem + "rye" => vec!["rye".to_string(), "run".to_string(), task.to_string()], "uv" => vec!["uv".to_string(), "run".to_string(), task.to_string()], "poetry" => vec!["poetry".to_string(), "run".to_string(), task.to_string()], "pipenv" => vec!["pipenv".to_string(), "run".to_string(), task.to_string()], @@ -369,6 +370,13 @@ mod tests { assert_eq!(cmd, vec!["npm", "run", "test", "--coverage"]); } + #[test] + fn test_build_command_rye() { + let runner = DetectedRunner::new("rye", "pyproject.toml", Ecosystem::Python, 5); + let cmd = runner.build_command("test", &[]); + assert_eq!(cmd, vec!["rye", "run", "test"]); + } + #[test] fn test_build_command_cargo() { let runner = DetectedRunner::new("cargo", "Cargo.toml", Ecosystem::Rust, 9); diff --git a/src/detectors/python.rs b/src/detectors/python.rs index 95e041d..bfefcba 100644 --- a/src/detectors/python.rs +++ b/src/detectors/python.rs @@ -56,6 +56,18 @@ impl CommandValidator for PythonValidator { } } + // Check [tool.rye.scripts] + if let Some(scripts) = toml_value + .get("tool") + .and_then(|t| t.get("rye")) + .and_then(|p| p.get("scripts")) + .and_then(|s| s.as_table()) + { + if scripts.contains_key(command) { + return CommandSupport::Supported; + } + } + // Python is extensible - uv run / poetry run can also execute // commands from the virtual environment (pytest, mypy, etc.) // So we return Unknown to allow fallback behavior @@ -71,6 +83,28 @@ pub fn detect(dir: &Path) -> Vec { let has_pyproject = dir.join("pyproject.toml").exists(); let validator: Arc = Arc::new(PythonValidator); + // Check for Rye (priority 5) + if has_pyproject { + // Check for [tool.rye] in pyproject.toml + if let Ok(content) = fs::read_to_string(dir.join("pyproject.toml")) { + if let Ok(toml_value) = toml::from_str::(&content) { + if toml_value + .get("tool") + .and_then(|t| t.get("rye")) + .is_some() + { + runners.push(DetectedRunner::with_validator( + "rye", + "pyproject.toml", + Ecosystem::Python, + 5, + Arc::clone(&validator), + )); + } + } + } + } + // Check for UV (priority 5) let uv_lock = dir.join("uv.lock"); if uv_lock.exists() && has_pyproject { @@ -337,4 +371,60 @@ myapp = "example:main" CommandSupport::Unknown ); } + + #[test] + fn test_detect_rye() { + let dir = tempdir().unwrap(); + let mut file = File::create(dir.path().join("pyproject.toml")).unwrap(); + writeln!( + file, + r#" +[project] +name = "example" +version = "0.1.0" + +[tool.rye] +managed = true +"# + ) + .unwrap(); + + let runners = detect(dir.path()); + assert_eq!(runners.len(), 1); + assert_eq!(runners[0].name, "rye"); + assert_eq!(runners[0].detected_file, "pyproject.toml"); + } + + #[test] + fn test_python_validator_rye_scripts() { + let dir = tempdir().unwrap(); + let mut file = File::create(dir.path().join("pyproject.toml")).unwrap(); + writeln!( + file, + r#" +[project] +name = "example" +version = "0.1.0" + +[tool.rye.scripts] +fmt = "ruff format" +lint = "ruff check" +"# + ) + .unwrap(); + + let validator = PythonValidator; + assert_eq!( + validator.supports_command(dir.path(), "fmt"), + CommandSupport::Supported + ); + assert_eq!( + validator.supports_command(dir.path(), "lint"), + CommandSupport::Supported + ); + assert_eq!( + validator.supports_command(dir.path(), "unknown"), + CommandSupport::Unknown + ); + } }