Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
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),
}
Comment thread
lucascolley marked this conversation as resolved.

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)
}
}
}))),
Comment thread
lucascolley marked this conversation as resolved.
);
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" }] }]"###);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: crates/pixi_manifest/src/toml/task.rs
expression: "expect_parse_failure(r#\"\n cmd = \"test\"\n depends-on = [{ task = \"foo\", args = [{ \"foo\" = \"bar\", \"baz\" = \"qux\" }] }]\n \"#)"
---
× 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 │
╰────
62 changes: 58 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> {
Comment thread
lucascolley marked this conversation as resolved.
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,20 @@ mod test {
"#
));
}

#[test]
fn test_named_arg_multiple_fields() {
insta::assert_snapshot!(expect_parse_failure(
Comment thread
lucascolley marked this conversation as resolved.
r#"
cmd = "test"
depends-on = [{ task = "foo", args = [{ "foo" = "bar", "baz" = "qux" }] }]
"#
));
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