Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
8 changes: 8 additions & 0 deletions commit_msg.txt
Original file line number Diff line number Diff line change
@@ -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.
45 changes: 45 additions & 0 deletions src/detectors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,28 @@ const BUN_BUILTINS: &[&str] = &[
"run", "test", "update", "upgrade", "x",
];

const RYE_BUILTINS: &[&str] = &[
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include all Rye top-level commands in builtin list

The new RYE_BUILTINS table is incomplete, so build_command falls back to rye run <task> for valid Rye CLI commands that are not listed. In practice, commands like config, tools, and self (documented as top-level Rye commands) will be routed as scripts (rye run config) and fail unless a script with that name exists, which breaks normal run <rye-command> usage in Rye projects.

Useful? React with 👍 / 👎.

"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",
Expand Down Expand Up @@ -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()],
Expand Down Expand Up @@ -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);
Expand Down
88 changes: 87 additions & 1 deletion src/detectors/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<DetectedRunner> {
let mut runners = Vec::new();

let has_pyproject = dir.join("pyproject.toml").exists();
let validator: Arc<dyn CommandValidator> = 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::<toml::Value>(&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 {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down
Loading