diff --git a/README.md b/README.md index b17177b..6883f88 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ cd src/components && run test # Finds package.json in parent dirs | **Monorepo** | nx → turbo → lerna | | **Node.js** | bun → pnpm → yarn → npm | | **Deno** | deno | -| **Python** | uv → poetry → pipenv → pip | +| **Python** | rye → uv → poetry → pipenv → pip | | **Rust** | cargo | | **PHP** | composer | | **Go** | task → go | diff --git a/commit_msg.txt b/commit_msg.txt new file mode 100644 index 0000000..8478331 --- /dev/null +++ b/commit_msg.txt @@ -0,0 +1,8 @@ +O PR adiciona suporte ao gerenciador de pacotes Python `rye`. Isso é realmente útil porque permite que o `run` detecte projetos gerenciados pelo `rye` lendo o arquivo `pyproject.toml`, extraia e valide os scripts customizados declarados na seção `[tool.rye.scripts]`, e direcione a execução para o ambiente correto do Rye. A ausência do suporte ao Rye forçaria o usuário a usar comandos manuais, perdendo o benefício da ferramenta universal. + +Arquivos alterados: +- `src/detectors/python.rs`: Lógica principal para detecção (`[tool.rye]`) e validação (`[tool.rye.scripts]`) junto com os respectivos testes. +- `src/detectors/mod.rs`: Roteamento e mapeamento dos comandos padrão/scripts com suporte a `rye` e seus testes. +- `README.md`: O README foi modificado pontualmente para refletir `rye` na listagem de ferramentas suportadas para a linguagem Python com sua correta prioridade (antes de `uv`), pois de outra forma estaria incompleto e enganoso sobre as reais capacidades da ferramenta. + +Não foram efetuadas modificações em testes de outros detectores, não há dependências adicionais introduzidas, nem limpezas ou arquivos marginais submetidos. diff --git a/src/detectors/mod.rs b/src/detectors/mod.rs index c252dd5..8d77e8e 100644 --- a/src/detectors/mod.rs +++ b/src/detectors/mod.rs @@ -173,6 +173,28 @@ const BUN_BUILTINS: &[&str] = &[ "run", "test", "update", "upgrade", "x", ]; +const RYE_BUILTINS: &[&str] = &[ + "add", + "build", + "fetch", + "fmt", + "init", + "install", + "lint", + "lock", + "make-req", + "pin", + "publish", + "remove", + "run", + "show", + "sync", + "test", + "toolchain", + "uninstall", + "version", +]; + const PIP_BUILTINS: &[&str] = &[ "install", "download", @@ -384,6 +406,13 @@ impl DetectedRunner { } // Python ecosystem + "rye" => { + if RYE_BUILTINS.contains(&task) { + vec!["rye".to_string(), task.to_string()] + } else { + 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()], @@ -618,6 +647,22 @@ mod tests { assert_eq!(cmd, vec!["bun", "run", "foo"]); } + #[test] + fn test_build_command_rye() { + let runner = DetectedRunner::new("rye", "pyproject.toml", Ecosystem::Python, 5); + // Built-in rye command + let cmd = runner.build_command("sync", &[]); + assert_eq!(cmd, vec!["rye", "sync"]); + + // Custom script + let cmd = runner.build_command("lint", &[]); + assert_eq!(cmd, vec!["rye", "lint"]); + + // Custom non-builtin script + let cmd = runner.build_command("my-custom-script", &[]); + assert_eq!(cmd, vec!["rye", "run", "my-custom-script"]); + } + #[test] fn test_build_command_pip() { let runner = DetectedRunner::new("pip", "requirements.txt", Ecosystem::Python, 8); diff --git a/src/detectors/python.rs b/src/detectors/python.rs index 95e041d..52d5837 100644 --- a/src/detectors/python.rs +++ b/src/detectors/python.rs @@ -56,6 +56,18 @@ impl CommandValidator for PythonValidator { } } + // Check [tool.rye.scripts] (Rye) + 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 @@ -64,13 +76,30 @@ impl CommandValidator for PythonValidator { } /// Detect Python package managers -/// Priority: UV (5) > Poetry (6) > Pipenv (7) > Pip (8) +/// Priority: Rye (5) > UV (5) > Poetry (6) > Pipenv (7) > Pip (8) pub fn detect(dir: &Path) -> Vec { let mut runners = Vec::new(); let has_pyproject = dir.join("pyproject.toml").exists(); let validator: Arc = Arc::new(PythonValidator); + // Check for Rye (priority 5) + if has_pyproject { + 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 { @@ -150,6 +179,30 @@ mod tests { assert_eq!(runners[0].name, "uv"); } + #[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" + +[tool.rye] +managed = true +"# + ) + .unwrap(); + + let runners = detect(dir.path()); + // Rye might be the only one or there could be uv lock if created by some other test, but here just checking presence. + // Actually, since we only wrote pyproject.toml, it should be the only one (except maybe pip fallback, but pip is priority 8). + // Wait, pip fallback triggers if NO other runner is detected. Since rye is detected, pip fallback won't happen. + assert_eq!(runners.len(), 1); + assert_eq!(runners[0].name, "rye"); + } + #[test] fn test_detect_poetry() { let dir = tempdir().unwrap(); @@ -239,6 +292,39 @@ serve = "example.server:run" ); } + #[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 = "1.0.0" + +[tool.rye.scripts] +fmt = "rye fmt" +lint = "rye lint" +"# + ) + .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 + ); + } + #[test] fn test_python_validator_poetry_scripts() { let dir = tempdir().unwrap();