Skip to content
Merged
98 changes: 81 additions & 17 deletions crates/pixi_manifest/src/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use itertools::Itertools;
use miette::{Diagnostic, SourceSpan};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use toml_edit::{Array, Item, Table, Value};
use toml_edit::{Array, InlineTable, Item, Table, Value};

use crate::EnvironmentName;

Expand Down Expand Up @@ -49,14 +49,20 @@ impl From<String> for TaskName {
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
pub struct Dependency {
pub task_name: TaskName,
pub args: Option<Vec<TemplateString>>,
pub args: Option<Vec<DependencyArg>>,
pub environment: Option<EnvironmentName>,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
pub enum DependencyArg {
Positional(TemplateString),
Named(String, TemplateString),
}

impl Dependency {
pub fn new(
s: &str,
args: Option<Vec<TemplateString>>,
args: Option<Vec<DependencyArg>>,
environment: Option<EnvironmentName>,
) -> Self {
Dependency {
Expand All @@ -66,7 +72,7 @@ impl Dependency {
}
}

pub fn new_without_env(s: &str, args: Option<Vec<TemplateString>>) -> Self {
pub fn new_without_env(s: &str, args: Option<Vec<DependencyArg>>) -> Self {
Dependency {
task_name: TaskName(s.to_string()),
args,
Expand All @@ -76,12 +82,19 @@ impl Dependency {
pub fn render_args(
&self,
args: Option<&ArgValues>,
) -> Result<Option<Vec<String>>, TemplateStringError> {
) -> Result<Option<Vec<TypedDependencyArg>>, TemplateStringError> {
match &self.args {
Some(task_args) => {
let mut result = Vec::new();
for arg in task_args {
result.push(arg.render(args)?);
match arg {
DependencyArg::Positional(val) => {
result.push(TypedDependencyArg::Positional(val.render(args)?));
}
DependencyArg::Named(key, val) => {
result.push(TypedDependencyArg::Named(key.clone(), val.render(args)?));
}
}
}
Ok(Some(result))
}
Expand Down Expand Up @@ -116,10 +129,16 @@ impl FromStr for TaskName {
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct TypedDependency {
pub task_name: TaskName,
pub args: Option<Vec<String>>,
pub args: Option<Vec<TypedDependencyArg>>,
pub environment: Option<EnvironmentName>,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, PartialOrd, Ord)]
pub enum TypedDependencyArg {
Positional(String),
Named(String, String),
}

impl TypedDependency {
pub fn from_dependency(
dependency: &Dependency,
Expand Down Expand Up @@ -722,10 +741,21 @@ impl From<Task> for Item {
table.insert("task", dep.task_name.to_string().into());
table.insert(
"args",
Value::Array(Array::from_iter(
args.iter()
.map(|arg| Value::from(arg.source().to_string())),
)),
Value::Array(Array::from_iter(args.iter().map(|arg| {
match arg {
DependencyArg::Positional(val) => {
Value::from(val.source().to_string())
}
DependencyArg::Named(name, val) => {
let mut table = InlineTable::new();
table.insert(
name,
Value::from(val.source().to_string()),
);
Value::InlineTable(table)
}
}
}))),
);
Value::InlineTable(table)
}
Expand Down Expand Up @@ -774,9 +804,16 @@ impl From<Task> for Item {
if let Some(args) = &dep.args {
dep_table.insert(
"args",
Value::Array(Array::from_iter(
args.iter().map(|arg| Value::from(arg.source().to_string())),
)),
Value::Array(Array::from_iter(args.iter().map(|arg| match arg {
DependencyArg::Positional(val) => {
Value::from(val.source().to_string())
}
DependencyArg::Named(name, val) => {
let mut table = InlineTable::new();
table.insert(name, Value::from(val.source().to_string()));
Value::InlineTable(table)
}
}))),
);
}

Expand All @@ -802,9 +839,16 @@ impl From<Task> for Item {
if let Some(args) = &dep.args {
table.insert(
"args",
Value::Array(Array::from_iter(
args.iter().map(|arg| Value::from(arg.source().to_string())),
)),
Value::Array(Array::from_iter(args.iter().map(|arg| match arg {
DependencyArg::Positional(val) => {
Value::from(val.source().to_string())
}
DependencyArg::Named(name, val) => {
let mut table = InlineTable::new();
table.insert(name, Value::from(val.source().to_string()));
Value::InlineTable(table)
}
}))),
);
}

Expand Down Expand Up @@ -832,6 +876,10 @@ impl From<Task> for Item {

#[cfg(test)]
mod tests {
use insta::assert_snapshot;

use crate::task::{Alias, Dependency, DependencyArg, Task};

use super::quote;

#[test]
Expand All @@ -848,4 +896,20 @@ mod tests {
);
assert_eq!(quote("name=[64,64]"), "\"name=[64,64]\"");
}

#[test]
fn test_table_from_dependency_args() {
let positional_arg = DependencyArg::Positional("foo".into());
let named_arg = DependencyArg::Named("bar".into(), "baz".into());
let args = vec![positional_arg, named_arg];
let dep = Dependency::new("depTask", Some(args), None);
let alias = Alias {
depends_on: vec![dep],
description: None,
args: None,
};
let task = Task::Alias(alias);
let toml = toml_edit::Item::from(task);
assert_snapshot!(toml.to_string(), @r###"[{ task = "depTask", args = ["foo", { bar = "baz" }] }]"###);
}
}
70 changes: 66 additions & 4 deletions crates/pixi_manifest/src/toml/task.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::borrow::Cow;
use std::str::FromStr;

use itertools::Itertools;
use pixi_toml::{TomlFromStr, TomlIndexMap};
use toml_span::{
DeserError, ErrorKind, Value,
Expand All @@ -9,7 +11,10 @@ use toml_span::{

use crate::{
EnvironmentName, Task, TaskName, WithWarnings,
task::{Alias, ArgName, CmdArgs, Dependency, Execute, GlobPatterns, TaskArg, TemplateString},
task::{
Alias, ArgName, CmdArgs, Dependency, DependencyArg, Execute, GlobPatterns, TaskArg,
TemplateString,
},
warning::Deprecation,
};

Expand Down Expand Up @@ -71,6 +76,30 @@ impl<'de> toml_span::Deserialize<'de> for TaskArg {
/// A task defined in the manifest.
pub type TomlTask = WithWarnings<Task>;

impl<'de> toml_span::Deserialize<'de> for DependencyArg {
fn deserialize(value: &mut Value<'de>) -> Result<Self, DeserError> {
match value.take() {
ValueInner::String(s) => Ok(DependencyArg::Positional(TemplateString::new(
s.into_owned(),
))),
ValueInner::Table(table) => {
let (k, mut v) = table.into_iter().exactly_one().map_err(|e| {
toml_span::Error::from((
toml_span::ErrorKind::Custom(Cow::Owned(e.to_string())),
value.span,
))
})?;
let inner = v.take_string(None)?;
Ok(DependencyArg::Named(
k.to_string(),
TemplateString::new(inner.into_owned()),
))
}
other => Err(expected("string or { string = string }", other, value.span).into()),
}
}
}

impl<'de> toml_span::Deserialize<'de> for TomlTask {
fn deserialize(value: &mut toml_span::Value<'de>) -> Result<Self, DeserError> {
let mut th = match value.take() {
Expand All @@ -83,7 +112,7 @@ impl<'de> toml_span::Deserialize<'de> for TomlTask {
ValueInner::Table(table) => {
let mut th = TableHelper::from((table, item.span));
let name = th.required::<String>("task")?;
let args = th.optional::<Vec<TemplateString>>("args");
let args = th.optional::<Vec<DependencyArg>>("args");
let environment = th
.optional::<TomlFromStr<EnvironmentName>>("environment")
.map(TomlFromStr::into_inner);
Expand Down Expand Up @@ -121,10 +150,11 @@ impl<'de> toml_span::Deserialize<'de> for TomlTask {
ValueInner::Table(table) => {
let mut th = TableHelper::from((table, span));
let name = th.required::<String>("task")?;
let args = th.optional::<Vec<TemplateString>>("args");
let args = th.optional::<Vec<DependencyArg>>("args");
let environment = th
.optional::<TomlFromStr<EnvironmentName>>("environment")
.map(TomlFromStr::into_inner);
th.finalize(None)?;

Ok(Dependency::new(&name, args, environment))
}
Expand Down Expand Up @@ -158,7 +188,7 @@ impl<'de> toml_span::Deserialize<'de> for TomlTask {
ValueInner::Table(table) => {
let mut th = TableHelper::from((table, span));
let name = th.required::<String>("task")?;
let args = th.optional::<Vec<TemplateString>>("args");
let args = th.optional::<Vec<DependencyArg>>("args");
let environment = th
.optional::<TomlFromStr<EnvironmentName>>("environment")
.map(TomlFromStr::into_inner);
Expand Down Expand Up @@ -275,6 +305,14 @@ mod test {
format_parse_error(pixi_toml, parse_error)
}

fn expect_parse_success(pixi_toml: &str) -> String {
<TomlTask as crate::toml::FromTomlStr>::from_toml_str(pixi_toml)
.ok()
.unwrap()
.value
.to_string()
}

#[test]
fn test_depends_on_deprecation() {
let input = r#"
Expand Down Expand Up @@ -306,4 +344,28 @@ mod test {
"#
));
}

#[test]
fn test_named_arg_multiple_fields() {
insta::assert_snapshot!(expect_parse_failure(
r#"
cmd = "test"
depends-on = [{ task = "foo", args = [{ "foo" = "bar", "baz" = "qux" }] }]
"#
), @r###"
× got at least 2 elements when exactly one was expected
╭─[pixi.toml:3:51]
2 │ cmd = "test"
3 │ depends-on = [{ task = "foo", args = [{ "foo" = "bar", "baz" = "qux" }] }]
· ────────────────────────────────
4 │
╰────
"###);
insta::assert_snapshot!(expect_parse_success(
r#"
cmd = "test"
depends-on = [{ task = "foo", args = [{ "foo" = "bar" }, { "baz" = "qux" }] }]
"#
), @"test, depends-on = 'foo with args'");
}
}
4 changes: 3 additions & 1 deletion schema/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,9 @@ class DependsOn(StrictBaseModel):
"""The dependencies of a task."""

task: TaskName
args: list[NonEmptyStr] | None = Field(None, description="The arguments to pass to the task")
args: list[NonEmptyStr, dict[NonEmptyStr, NonEmptyStr]] | None = Field(
None, description="The (positional or named) arguments to pass to the task"
)
environment: EnvironmentName | None = Field(
None, description="The environment to use for the task"
)
Expand Down
2 changes: 1 addition & 1 deletion schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@
"properties": {
"args": {
"title": "Args",
"description": "The arguments to pass to the task",
"description": "The (positional or named) arguments to pass to the task",
"type": "array",
"items": {
"type": "string",
Expand Down
Loading
Loading