diff --git a/README.md b/README.md index d3f5b090..9d4dcadc 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Commands: publish Builds and uploads current project to a registry python Manage Python installations remove Remove dependencies from the project - run Run a command within the project's environment context + run Run a command with Huak test Test the project's Python code update Update the project's dependencies version Display the version of the project diff --git a/crates/huak-package-manager/src/environment.rs b/crates/huak-package-manager/src/environment.rs index 35966bee..64b15d80 100644 --- a/crates/huak-package-manager/src/environment.rs +++ b/crates/huak-package-manager/src/environment.rs @@ -24,10 +24,10 @@ pub struct Environment { impl Environment { /// Initialize an `Environment`. #[must_use] - pub fn new() -> Environment { - let interpreters = Environment::resolve_python_interpreters(); + pub fn new() -> Self { + let interpreters = Self::resolve_python_interpreters(); - Environment { interpreters } + Self { interpreters } } /// Get an `Iterator` over the Python `Interpreter` `PathBuf`s found. diff --git a/crates/huak-package-manager/src/error.rs b/crates/huak-package-manager/src/error.rs index aab42a26..c736316b 100644 --- a/crates/huak-package-manager/src/error.rs +++ b/crates/huak-package-manager/src/error.rs @@ -34,6 +34,10 @@ pub enum Error { InternalError(String), #[error("a checksum is invalid: {0}")] InvalidChecksum(String), + #[error("a program is invalid: {0}")] + InvalidProgram(String), + #[error("a run command is invalid: {0}")] + InvalidRunCommand(String), #[error("a version number could not be parsed: {0}")] InvalidVersionString(String), #[error("a problem occurred with json deserialization: {0}")] diff --git a/crates/huak-package-manager/src/lib.rs b/crates/huak-package-manager/src/lib.rs index 3aeac597..ca606148 100644 --- a/crates/huak-package-manager/src/lib.rs +++ b/crates/huak-package-manager/src/lib.rs @@ -35,7 +35,7 @@ //! lish Builds and uploads current project to a registry //! python Manage Python installations //! remove Remove dependencies from the project -//! run Run a command within the project's environment context +//! run Run a command with Huak //! test Test the project's Python code //! update Update the project's dependencies //! version Display the version of the project diff --git a/crates/huak-package-manager/src/manifest.rs b/crates/huak-package-manager/src/manifest.rs index 54c529c1..66fa5607 100644 --- a/crates/huak-package-manager/src/manifest.rs +++ b/crates/huak-package-manager/src/manifest.rs @@ -168,6 +168,13 @@ dev = [ "black ==22.8.0", "isort ==5.12.0", ] + +[tool.huak.task] +string = "python -c 'print(\"string\")'" +array = ["python", "-c", "print('array')"] +inline-cmd = { cmd = "python -c 'print(\"cmd\")'" } +inline-args = { args = ["python", "-c", "print('args')"] } +inline-program = { program = "python", args = ["-c", "print('program')"], env = { TESTING_HUAK = "test"} } "# ); } @@ -238,6 +245,13 @@ dev = [ "pytest == 7.4.3", "ruff", ] + +[tool.huak.task] +string = "python -c 'print(\"string\")'" +array = ["python", "-c", "print('array')"] +inline-cmd = { cmd = "python -c 'print(\"cmd\")'" } +inline-args = { args = ["python", "-c", "print('args')"] } +inline-program = { program = "python", args = ["-c", "print('program')"], env = { TESTING_HUAK = "test"} } "# ); } @@ -282,6 +296,13 @@ dev = [ new-group = [ "test2", ] + +[tool.huak.task] +string = "python -c 'print(\"string\")'" +array = ["python", "-c", "print('array')"] +inline-cmd = { cmd = "python -c 'print(\"cmd\")'" } +inline-args = { args = ["python", "-c", "print('args')"] } +inline-program = { program = "python", args = ["-c", "print('program')"], env = { TESTING_HUAK = "test"} } "# ); } @@ -317,6 +338,13 @@ dev = [ "pytest == 7.4.3", "ruff", ] + +[tool.huak.task] +string = "python -c 'print(\"string\")'" +array = ["python", "-c", "print('array')"] +inline-cmd = { cmd = "python -c 'print(\"cmd\")'" } +inline-args = { args = ["python", "-c", "print('args')"] } +inline-program = { program = "python", args = ["-c", "print('program')"], env = { TESTING_HUAK = "test"} } "# ); } @@ -353,6 +381,13 @@ email = "cnpryer@gmail.com" dev = [ "pytest == 7.4.3", ] + +[tool.huak.task] +string = "python -c 'print(\"string\")'" +array = ["python", "-c", "print('array')"] +inline-cmd = { cmd = "python -c 'print(\"cmd\")'" } +inline-args = { args = ["python", "-c", "print('args')"] } +inline-program = { program = "python", args = ["-c", "print('program')"], env = { TESTING_HUAK = "test"} } "# ); } diff --git a/crates/huak-package-manager/src/ops/build.rs b/crates/huak-package-manager/src/ops/build.rs index c285ff7d..a6746523 100644 --- a/crates/huak-package-manager/src/ops/build.rs +++ b/crates/huak-package-manager/src/ops/build.rs @@ -1,4 +1,4 @@ -use super::make_venv_command; +use super::add_venv_to_command; use crate::{Config, Dependency, HuakResult, InstallOptions}; use std::{process::Command, str::FromStr}; @@ -44,7 +44,7 @@ pub fn build_project(config: &Config, options: &BuildOptions) -> HuakResult<()> if let Some(it) = options.values.as_ref() { args.extend(it.iter().map(std::string::String::as_str)); } - make_venv_command(&mut cmd, &python_env)?; + add_venv_to_command(&mut cmd, &python_env)?; cmd.args(args).current_dir(workspace.root()); config.terminal().run_command(&mut cmd) diff --git a/crates/huak-package-manager/src/ops/format.rs b/crates/huak-package-manager/src/ops/format.rs index 1306bb65..2aa270bc 100644 --- a/crates/huak-package-manager/src/ops/format.rs +++ b/crates/huak-package-manager/src/ops/format.rs @@ -1,7 +1,8 @@ -use super::make_venv_command; use crate::{Config, Dependency, HuakResult, InstallOptions}; use std::{process::Command, str::FromStr}; +use super::add_venv_to_command; + pub struct FormatOptions { /// A values vector of format options typically used for passing on arguments. pub values: Option>, @@ -56,8 +57,8 @@ pub fn format_project(config: &Config, options: &FormatOptions) -> HuakResult<() let mut cmd = Command::new(python_env.python_path()); let mut ruff_cmd = Command::new(python_env.python_path()); let mut ruff_args = vec!["-m", "ruff", "check", ".", "--select", "I", "--fix"]; - make_venv_command(&mut cmd, &python_env)?; - make_venv_command(&mut ruff_cmd, &python_env)?; + add_venv_to_command(&mut cmd, &python_env)?; + add_venv_to_command(&mut ruff_cmd, &python_env)?; let mut args = vec!["-m", "ruff", "format", "."]; if let Some(v) = options.values.as_ref() { args.extend(v.iter().map(String::as_str)); diff --git a/crates/huak-package-manager/src/ops/lint.rs b/crates/huak-package-manager/src/ops/lint.rs index 283c22cd..8823b92b 100644 --- a/crates/huak-package-manager/src/ops/lint.rs +++ b/crates/huak-package-manager/src/ops/lint.rs @@ -1,4 +1,4 @@ -use super::make_venv_command; +use super::add_venv_to_command; use crate::{Config, Dependency, HuakResult, InstallOptions}; use std::{process::Command, str::FromStr}; @@ -35,7 +35,7 @@ pub fn lint_project(config: &Config, options: &LintOptions) -> HuakResult<()> { // Run `mypy` excluding the workspace's Python environment directory. let mut mypy_cmd = Command::new(python_env.python_path()); - make_venv_command(&mut mypy_cmd, &python_env)?; + add_venv_to_command(&mut mypy_cmd, &python_env)?; mypy_cmd .args(vec!["-m", "mypy", ".", "--exclude", &python_env.name()?]) .current_dir(workspace.root()); @@ -48,7 +48,7 @@ pub fn lint_project(config: &Config, options: &LintOptions) -> HuakResult<()> { if let Some(v) = options.values.as_ref() { args.extend(v.iter().map(String::as_str)); } - make_venv_command(&mut cmd, &python_env)?; + add_venv_to_command(&mut cmd, &python_env)?; cmd.args(args).current_dir(workspace.root()); terminal.run_command(&mut cmd)?; diff --git a/crates/huak-package-manager/src/ops/mod.rs b/crates/huak-package-manager/src/ops/mod.rs index 82774146..5a0d8fcd 100644 --- a/crates/huak-package-manager/src/ops/mod.rs +++ b/crates/huak-package-manager/src/ops/mod.rs @@ -57,7 +57,7 @@ if __name__ == "__main__": /// `PATH` environment variable. /// - Adds `VIRTUAL_ENV` environment variable to the command pointing at the virtual environment's /// root. -fn make_venv_command(cmd: &mut Command, venv: &PythonEnvironment) -> HuakResult<()> { +fn add_venv_to_command(cmd: &mut Command, venv: &PythonEnvironment) -> HuakResult<()> { let mut paths = env_path_values().unwrap_or_default(); paths.insert(0, venv.executables_dir_path().clone()); diff --git a/crates/huak-package-manager/src/ops/publish.rs b/crates/huak-package-manager/src/ops/publish.rs index 69c76078..acf71aec 100644 --- a/crates/huak-package-manager/src/ops/publish.rs +++ b/crates/huak-package-manager/src/ops/publish.rs @@ -1,4 +1,4 @@ -use super::make_venv_command; +use super::add_venv_to_command; use crate::{Config, Dependency, HuakResult, InstallOptions}; use std::{process::Command, str::FromStr}; @@ -44,7 +44,7 @@ pub fn publish_project(config: &Config, options: &PublishOptions) -> HuakResult< if let Some(v) = options.values.as_ref() { args.extend(v.iter().map(String::as_str)); } - make_venv_command(&mut cmd, &python_env)?; + add_venv_to_command(&mut cmd, &python_env)?; cmd.args(args).current_dir(workspace.root()); config.terminal().run_command(&mut cmd) } diff --git a/crates/huak-package-manager/src/ops/run.rs b/crates/huak-package-manager/src/ops/run.rs index 24e81e5e..dd6ddcdd 100644 --- a/crates/huak-package-manager/src/ops/run.rs +++ b/crates/huak-package-manager/src/ops/run.rs @@ -1,21 +1,463 @@ -use super::make_venv_command; -use crate::{shell_name, Config, HuakResult}; -use std::{env::consts::OS, process::Command}; +use super::add_venv_to_command; +use crate::{shell_name, sys::Terminal, Config, Error, HuakResult}; +use huak_pyproject_toml::{sanitize_str, value_to_sanitized_string}; +use std::{collections::HashMap, env::consts::OS, ffi::OsStr, ops::Deref, process::Command}; +use termcolor::Color; +use toml_edit::{Array, ArrayOfTables, Formatted, InlineTable, Item, Table, Value}; -pub fn run_command_str(command: &str, config: &Config) -> HuakResult<()> { - let workspace = config.workspace(); - let python_env = workspace.current_python_environment()?; +pub fn run_command_str(content: &str, config: &Config) -> HuakResult<()> { + let ws = config.workspace(); + let manifest = ws.current_local_manifest()?; + // Get any run commands listed in [tool.huak.run] + let task_table = manifest + .manifest_data() + .tool_table() + .and_then(|it| it.get("huak")) + .and_then(Item::as_table) + .and_then(|it| it.get("task")) + .and_then(Item::as_table); + + let trimmed = content.trim(); + + let trimmed = if trimmed.is_empty() { + None + } else { + Some(trimmed) + }; + + // If there is a task table and there's no program provided just print any available commands + // from the task table. + // If there is a task table and the program is found in the task table then attempt to run + // the command with Huak by building a command from the contents provided. + if let Some(table) = task_table { + if trimmed.map_or(true, str::is_empty) { + return print_task_table(&mut config.terminal(), table); + }; + + // Try to get the program from the content provided. + let maybe_task = trimmed.as_ref().and_then(|it| it.split(' ').next()); + + // If the program is in the task table then run the command from the task table. + if maybe_task.map_or(false, |name| { + task_table.map_or(false, |table| table.contains_key(name)) + }) { + let table = task_table.expect("task table"); + let task = maybe_task.expect("task name"); + return TaskRunner::from_table(table.to_owned()).run(task, config); + } + } + + // If a program is found or the contents still contain something to parse/run + // attempt to run the contents using the shell. + if let Some(s) = trimmed.filter(|it| !it.is_empty()) { + run_str(s, config) + } else { + Err(Error::InvalidProgram( + "could not resolve program".to_string(), + )) + } +} + +fn print_task_table(terminal: &mut Terminal, table: &Table) -> HuakResult<()> { + let commands = table + .get_values() + .into_iter() + .flat_map(|(k, _)| k) + .map(ToString::to_string) + .collect::>(); + + terminal.print_custom("Tasks", "", Color::Cyan, true)?; + + for (i, command) in commands.iter().enumerate() { + terminal.print_custom( + format!("{:>5})", i + 1), + format!("{command:<16}"), + Color::Green, + true, + )?; + } + + Ok(()) +} + +struct TaskRunner { + table: Table, +} + +impl TaskRunner { + fn from_table(table: Table) -> Self { + Self { table } + } + + fn get(&self, name: &str) -> Option<&Item> { + self.table.get(name) + } + + fn run(&self, name: &str, config: &Config) -> HuakResult<()> { + match self.get(name) { + None | Some(Item::None) => Err(Error::InvalidProgram(name.to_string())), + Some(Item::Value(value)) => run_value_task(self, value, config), + Some(Item::Table(table)) => run_table_task(self, table, config), + Some(Item::ArrayOfTables(array)) => run_array_of_tables_task(self, array, config), + } + } +} + +/// A task configured with a `Value` can include: +/// +/// - Strings +/// - Arrays +/// - Inline Tables +/// +/// ```toml +/// [tool.huak.task] +/// task1 = "this is a command" # ('this' is the program) +/// task2 = ["these", "are", "command", "arguments"] # ('these' is the program) +/// task3 = [ +/// { cmd = "this is a command", env = {KEY = "value"} }, # ('this' is the program) +/// { cmd = ["these", "are", "command", "arguments"] }, # ('these' is the program) +/// { chain = ["task1", "task2", "task3"] }, +/// ] +/// task4 = { program = "thing", args = ["some", "args"], env = {KEY = "value"} } # ('this' is the program) +/// task5 = { cmd = "this is a command", env = { KEY = "value" } } # ('this' is the program) +/// task6 = { chain = ["task1", "task2", "task3" } +/// ``` +fn run_value_task(runner: &TaskRunner, value: &Value, config: &Config) -> HuakResult<()> { + match value { + Value::String(string) => run_formatted_string_task(runner, string, config), + Value::Array(array) => run_array_task(runner, array, config), + Value::InlineTable(table) => run_inline_table_task(runner, table, config), + _ => Err(Error::InvalidProgram(format!("{value}"))), + } +} + +/// ```toml +/// # [tool.huak.task] +/// # task0 = "some command" +/// +/// [[tool.huak.task."task1"]] +/// task1-1 = "this is a command" # ('this' is the program) +/// task1-2 = ["these", "are", "command", "arguments"] # ('these' is the program) +/// task1-3 = [ +/// { cmd = "this is a command", env = {KEY = "value"} }, # ('this' is the program) +/// { cmd = ["these", "are", "command", "arguments"] }, # ('these' is the program) +/// { chain = ["task1", "task2", "task3"] }, +/// ] +/// ``` +fn run_array_of_tables_task( + _runner: &TaskRunner, + _array: &ArrayOfTables, + _config: &Config, +) -> HuakResult<()> { + todo!() +} + +/// ```toml +/// [tool.huak.task."task1"] +/// chain = [ +/// { cmd = "this is a command", env = {KEY = "value"} }, # ('this' is the program) +/// { cmd = ["these", "are", "command", "arguments"] }, # ('these' is the program) +/// { chain = ["task1", "task2", "task3" }, +/// ] +/// ``` +fn run_table_task(runner: &TaskRunner, table: &Table, config: &Config) -> HuakResult<()> { + let env = table.get("env"); + let program = table.get("program"); + let args = table.get("args"); + let cmd = table.get("cmd"); + let chain = table.get("chain"); + + // Run the task with configuration data. If no configuration data is provided expect the + // table to contain sub-tasks (TODO(cnpryer)). + if chain.is_some() || (program.is_some() || args.is_some() || cmd.is_some()) { + run_table_task_inner(runner, program, args, cmd, chain, env, config) + } else { + todo!() + } +} + +/// ```toml +/// [tool.huak.task] +/// task1 = { cmd = "this is a command", env = {KEY = "value"} } # ('this' is the program) +/// task2 = { cmd = ["these", "are", "command", "arguments"] } # ('these' is the program) +/// task3 = { chain = ["task1", "task2", "task3"] } +/// ``` +fn run_inline_table_task( + runner: &TaskRunner, + table: &InlineTable, + config: &Config, +) -> HuakResult<()> { + // TODO(cnpryer): Perf + let program = table + .get("program") + .map(|value| Item::Value(value.to_owned())); + let args = table.get("args").map(|value| Item::Value(value.to_owned())); + let cmd = table.get("cmd").map(|value| Item::Value(value.to_owned())); + let chain = table + .get("chain") + .map(|value| Item::Value(value.to_owned())); + let env = table.get("env").map(|value| Item::Value(value.to_owned())); + + // Run the task with configuration data. If no configuration data is provided expect the + // table to contain sub-tasks (TODO(cnpryer)). + if chain.is_some() || (program.is_some() || args.is_some() || cmd.is_some()) { + run_table_task_inner( + runner, + program.as_ref(), + args.as_ref(), + cmd.as_ref(), + chain.as_ref(), + env.as_ref(), + config, + ) + } else { + todo!() + } +} + +fn run_table_task_inner( + runner: &TaskRunner, + program: Option<&Item>, + args: Option<&Item>, + cmd: Option<&Item>, + chain: Option<&Item>, + env: Option<&Item>, + config: &Config, +) -> HuakResult<()> { + if cmd.is_some() && (args.is_some() || program.is_some()) { + return Err(Error::InvalidRunCommand( + "'cmd' cannot be used with 'args' or 'program'".to_string(), + )); + } + + // TODO(cnpryer): Configuration errors + let program = program.and_then(Item::as_str); + let args = args.and_then(item_as_args); + let chain = chain.and_then(Item::as_array); + let env = env.and_then(item_as_env); + + // TODO(cnpryer): Propagate env properly + env.as_ref() + .map(|it| it.iter().map(|(k, v)| std::env::set_var(k, v))); + + if chain.is_some() && (args.is_some() || program.is_some()) { + return Err(Error::InvalidRunCommand( + "only 'env' can be used with 'chain'".to_string(), + )); + } + + // Run each chained task + if let Some(chain) = chain { + let mut last = None; + for task in chain.iter().map(Value::as_str) { + if let Some(it) = task { + if last.map_or(false, |x| it == x) { + return Err(Error::InvalidRunCommand(format!( + "'{it}' cannot chain itself" + ))); + } + + // TODO(cnpryer): Propagate env + runner.run(it, config)?; + last = Some(it); + } else { + return Err(Error::InvalidRunCommand("invalid task chain".to_string())); + } + } + + return Ok(()); + } + + if let Some(args) = args { + // If a program is provided we do our best to use it with other configuration. + // If no program is provided we assume one from the configuration available. + let program_is_assumed = program.is_none(); + + let Some(program) = program.or(args.first().map(Deref::deref)) else { + return Err(Error::InvalidProgram("could not be resolved".to_string())); + }; + + // We exclude the first argument if the program needed to be assumed. + if program_is_assumed { + return run_program(program, &args[1..], env.as_ref(), config); + } + + return run_program(program, &args, env.as_ref(), config); + } + + if let Some(Item::Value(value)) = cmd { + match value { + Value::String(_) => { + let string = value_to_sanitized_string(value); + return run_str(&string, config); // TODO(cnpryer): Environment + } + Value::Array(array) => { + let mut args = Vec::with_capacity(array.len()); + for value in array { + match value { + Value::Array(_) | Value::InlineTable(_) => { + return Err(Error::InvalidRunCommand( + "unsupported 'cmd' configuration".to_string(), + )) + } + _ => value.as_str().map(|it| args.push(it.to_string())), + }; + } + + if args.is_empty() { + return Err(Error::InvalidRunCommand( + "could not resolve args".to_string(), + )); + } + + return run_program(&args.remove(0), args, env.as_ref(), config); + } + _ => { + return Err(Error::InvalidRunCommand( + "invalid 'cmd' configuration".to_string(), + )) + } + } + } + + if let Some(program) = program { + run_program(program, [""], env.as_ref(), config) // TODO(cnpryer): Use Option + } else { + Err(Error::InvalidRunCommand( + "failed to resolve configuration".to_string(), + )) + } +} + +/// ```toml +/// [tool.huak.task] +/// task1 = ["these", "are", "command", "arguments"] # ('these' is the program) +/// ``` +fn run_array_task(runner: &TaskRunner, array: &Array, config: &Config) -> HuakResult<()> { + let mut args = Vec::with_capacity(array.len()); + + // TODO(cnpryer): Arrays with multiple kinds of Values + for value in array { + match value { + Value::String(_) => args.push(value_to_sanitized_string(value)), + Value::Array(array) => return run_array_task(runner, array, config), + Value::InlineTable(table) => return run_inline_table_task(runner, table, config), + _ => return Err(Error::InvalidProgram(format!("{value}"))), + } + } + + if args.is_empty() { + Err(Error::InvalidRunCommand( + "failed to resolve program".to_string(), + )) + } else { + run_program(&args.remove(0), args, None, config) + } +} + +/// ```toml +/// [tool.huak.task] +/// task0 = "some command" +/// ```` +fn run_formatted_string_task( + _runner: &TaskRunner, + string: &Formatted, + config: &Config, +) -> HuakResult<()> { + let string = sanitize_str(string.value()); + + run_str(string.as_str(), config) +} + +fn run_str(s: &str, config: &Config) -> HuakResult<()> { let mut cmd = Command::new(shell_name()?); + let flag = match OS { "windows" => "/C", _ => "-c", }; - make_venv_command(&mut cmd, &python_env)?; - cmd.args([flag, command]).current_dir(&config.cwd); + + add_venv_to_command(&mut cmd, &config.workspace().current_python_environment()?)?; + + cmd.args([flag, s]).current_dir(&config.cwd); + config.terminal().run_command(&mut cmd) } +fn run_program( + program: &str, + args: I, + env: Option<&HashMap>, + config: &Config, +) -> HuakResult<()> +where + I: IntoIterator, + S: AsRef, +{ + let mut cmd = Command::new(program); + + add_venv_to_command(&mut cmd, &config.workspace().current_python_environment()?)?; + + if let Some(env) = env { + cmd.envs(env); + } + + cmd.args(args).current_dir(&config.cwd); + dbg!(&cmd); + + config.terminal().run_command(&mut cmd) +} + +fn item_as_args(item: &Item) -> Option> { + if let Item::Value(value) = item { + match value { + Value::String(_) => Some( + value_to_sanitized_string(value) + .split(' ') + .map(ToOwned::to_owned) + .collect::>(), + ), + Value::Integer(int) => Some(vec![format!("{int}")]), + Value::Float(float) => Some(vec![format!("{float}")]), + Value::Boolean(bool) => Some(vec![format!("{bool}")]), + Value::Datetime(datetime) => Some(vec![datetime.to_string()]), + Value::Array(array) => { + let mut args = Vec::new(); + for value in array { + if value.is_str() { + args.push(value_to_sanitized_string(value)); + } else { + // TODO(cnpryer): Errors + return None; + } + } + Some(args) + } + Value::InlineTable(_) => None, + } + } else { + None + } +} + +fn item_as_env(item: &Item) -> Option> { + // TODO(cnpryer): Errors? + let vars = match item { + Item::Value(Value::InlineTable(table)) => table.to_owned().into_table(), + Item::Table(table) => table.to_owned(), + _ => return None, + }; + + let mut env = HashMap::new(); + + for (k, v) in vars { + env.insert(sanitize_str(k.as_str()), sanitize_str(&v.to_string())); + } + + Some(env) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/huak-package-manager/src/ops/test.rs b/crates/huak-package-manager/src/ops/test.rs index 67016a3d..7f768b09 100644 --- a/crates/huak-package-manager/src/ops/test.rs +++ b/crates/huak-package-manager/src/ops/test.rs @@ -1,4 +1,4 @@ -use super::make_venv_command; +use super::add_venv_to_command; use crate::{Config, Dependency, HuakResult, InstallOptions}; use std::{process::Command, str::FromStr}; @@ -40,7 +40,7 @@ pub fn test_project(config: &Config, options: &TestOptions) -> HuakResult<()> { // Run `pytest` with the package directory added to the command's `PYTHONPATH`. let mut cmd = Command::new(python_env.python_path()); - make_venv_command(&mut cmd, &python_env)?; + add_venv_to_command(&mut cmd, &python_env)?; let python_path = if workspace.root().join("src").exists() { workspace.root().join("src") } else { diff --git a/crates/huak-pyproject-toml/src/lib.rs b/crates/huak-pyproject-toml/src/lib.rs index 60f5c128..fddf1dcf 100644 --- a/crates/huak-pyproject-toml/src/lib.rs +++ b/crates/huak-pyproject-toml/src/lib.rs @@ -66,8 +66,8 @@ pub use error::Error; use pep508_rs::Requirement; use std::{collections::HashMap, fmt::Display, path::Path, str::FromStr}; use toml_edit::{Array, Document, Formatted, Item, Table, Value}; -pub use utils::value_to_sanitized_string; -use utils::{format_array, format_table, sanitize_str}; +use utils::{format_array, format_table}; +pub use utils::{sanitize_str, value_to_sanitized_string}; mod error; mod utils; diff --git a/crates/huak-pyproject-toml/src/utils.rs b/crates/huak-pyproject-toml/src/utils.rs index baa3fcc0..a6b90c02 100644 --- a/crates/huak-pyproject-toml/src/utils.rs +++ b/crates/huak-pyproject-toml/src/utils.rs @@ -8,10 +8,12 @@ pub fn value_to_sanitized_string(value: &Value) -> String { } } -pub(crate) fn sanitize_str(s: &str) -> String { +#[must_use] +pub fn sanitize_str(s: &str) -> String { s.trim_matches('\n') .trim() .trim_start_matches(['\\', '\'', '"']) + .trim_end_matches(['"', '\'', '\\']) .to_string() } diff --git a/dev-resources/mock-project/pyproject.toml b/dev-resources/mock-project/pyproject.toml index e16cee2d..b54b87c6 100644 --- a/dev-resources/mock-project/pyproject.toml +++ b/dev-resources/mock-project/pyproject.toml @@ -17,3 +17,10 @@ dev = [ "pytest == 7.4.3", "ruff", ] + +[tool.huak.task] +string = "python -c 'print(\"string\")'" +array = ["python", "-c", "print('array')"] +inline-cmd = { cmd = "python -c 'print(\"cmd\")'" } +inline-args = { args = ["python", "-c", "print('args')"] } +inline-program = { program = "python", args = ["-c", "print('program')"], env = { TESTING_HUAK = "test"} } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 732aa634..4e52f319 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,4 +31,3 @@ requires = ["maturin>=0.14,<0.15"] build-backend = "maturin" [tool.huak] -toolchain = "default" \ No newline at end of file