diff --git a/e2e/env/test_env_file b/e2e/env/test_env_file index 7c66d12c4a..15706d761c 100644 --- a/e2e/env/test_env_file +++ b/e2e/env/test_env_file @@ -51,7 +51,7 @@ cat <mise.toml [[env]] _.file = 'a' [[env]] -_.file = [{ path = 'b.json', tools = true }] +_.file = [{ path = ['b.json'], tools = true }] EOF echo 'export A=1' >a echo '{"B": 2}' >b.json diff --git a/e2e/env/test_env_path b/e2e/env/test_env_path index b68db987aa..dcec738014 100644 --- a/e2e/env/test_env_path +++ b/e2e/env/test_env_path @@ -8,6 +8,12 @@ assert_contains "mise env -s bash | grep PATH" "/installs/dummy/2.0.0/bin" assert_contains "mise env -s bash dummy@1.0.1 | grep PATH" "/installs/dummy/1.0.1/bin" +cat <<'EOF' >mise.toml +[env] +_.path = 'a' +EOF +assert "mise dr path" "$PWD/a" + cat <<'EOF' >mise.toml [env] _.path = ['a', 'b'] @@ -15,6 +21,52 @@ EOF assert "mise dr path" "$PWD/a $PWD/b" +cat <<'EOF' >mise.toml +[env] +_.path = { path = "a", tools = true } +EOF +assert "mise dr path" "$PWD/a" + +cat <<'EOF' >mise.toml +[env] +_.path = { path = ["a"], tools = true } +EOF +assert "mise dr path" "$PWD/a" + +cat <<'EOF' >mise.toml +[env] +_.path = [{ path = "a", tools = true }, "b"] +EOF +assert "mise dr path" "$PWD/a +$PWD/b" + +cat <<'EOF' >mise.toml +[env] +_.path = { path = "a", tools = true } +EOF +assert "mise dr path" "$PWD/a" + +cat <<'EOF' >mise.toml +[env] +_.path = [{ path = ["a", "b"], tools = true }] +EOF +assert "mise dr path" "$PWD/a +$PWD/b" + +cat <<'EOF' >mise.toml +[[env]] +_.path = [{ path = "a", tools = true }, "b"] +[[env]] +_.path = "c" +EOF +assert "mise dr path" "$PWD/a +$PWD/b +$PWD/c" + +cat <<'EOF' >mise.toml +[env] +_.path = ['a', 'b'] +EOF mkdir -p sub_dir cd sub_dir cat <<'EOF' >mise.toml diff --git a/schema/mise-task.json b/schema/mise-task.json index bca8abe5e7..2e2f281c43 100644 --- a/schema/mise-task.json +++ b/schema/mise-task.json @@ -363,6 +363,32 @@ "additionalProperties": true, "properties": { "file": { + "oneOf": [ + { + "$ref": "#/$defs/env_directive" + }, + { + "description": "dotenv file to load", + "type": "string" + }, + { + "description": "dotenv files to load", + "type": "array", + "items": { + "oneOf": [ + { + "description": "dotenv file to load", + "type": "string" + }, + { + "$ref": "#/$defs/env_directive" + } + ] + } + } + ] + }, + "path": { "oneOf": [ { "type": "object", @@ -380,27 +406,37 @@ } ] }, + "paths": { + "type": "array", + "items": { + "type": "string" + } + }, "tools": { "type": "boolean", "description": "load tools before resolving" + } + }, + "oneOf": [ + { + "required": ["path"] }, - "redact": { - "type": "boolean", - "description": "redact the value from logs" + { + "required": ["paths"] } - } + ] }, { - "description": "dotenv file to load", + "description": "PATH entry to add", "type": "string" }, { - "description": "dotenv files to load", + "description": "PATH entries to add", "type": "array", "items": { "oneOf": [ { - "description": "dotenv file to load", + "description": "PATH entry to add", "type": "string" }, { @@ -422,10 +458,6 @@ "tools": { "type": "boolean", "description": "load tools before resolving" - }, - "redact": { - "type": "boolean", - "description": "redact the value from logs" } } } @@ -434,40 +466,6 @@ } ] }, - "path": { - "oneOf": [ - { - "type": "object", - "properties": { - "path": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - } - } - }, - { - "description": "PATH entry to add", - "type": "string" - }, - { - "description": "PATH entries to add", - "items": { - "description": "PATH entry to add", - "type": "string" - }, - "type": "array" - } - ] - }, "python": { "description": "python environment", "properties": { @@ -519,30 +517,7 @@ "source": { "oneOf": [ { - "type": "object", - "properties": { - "path": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "tools": { - "type": "boolean", - "description": "load tools before resolving" - }, - "redact": { - "type": "boolean", - "description": "redact the value from logs" - } - } + "$ref": "#/$defs/env_directive" }, { "description": "bash script to load", @@ -550,11 +525,18 @@ }, { "description": "bash scripts to load", + "type": "array", "items": { - "description": "bash script to load", - "type": "string" - }, - "type": "array" + "oneOf": [ + { + "description": "bash script to load", + "type": "string" + }, + { + "$ref": "#/$defs/env_directive" + } + ] + } } ] } @@ -563,6 +545,46 @@ } }, "type": "object" + }, + "env_directive": { + "type": "object", + "properties": { + "path": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "paths": { + "type": "array", + "items": { + "type": "string" + } + }, + "tools": { + "type": "boolean", + "description": "load tools before resolving" + }, + "redact": { + "type": "boolean", + "description": "redact the value from logs" + } + }, + "oneOf": [ + { + "required": ["path"] + }, + { + "required": ["paths"] + } + ] } }, "description": "Config file for included mise tasks (https://mise.jdx.dev/tasks/#task-configuration)", diff --git a/schema/mise.json b/schema/mise.json index f192ff55f7..4b145a0af9 100644 --- a/schema/mise.json +++ b/schema/mise.json @@ -4,6 +4,46 @@ "title": "mise", "type": "object", "$defs": { + "env_directive": { + "type": "object", + "properties": { + "path": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "paths": { + "type": "array", + "items": { + "type": "string" + } + }, + "tools": { + "type": "boolean", + "description": "load tools before resolving" + }, + "redact": { + "type": "boolean", + "description": "redact the value from logs" + } + }, + "oneOf": [ + { + "required": ["path"] + }, + { + "required": ["paths"] + } + ] + }, "env": { "additionalProperties": { "oneOf": [ @@ -51,6 +91,32 @@ "additionalProperties": true, "properties": { "file": { + "oneOf": [ + { + "$ref": "#/$defs/env_directive" + }, + { + "description": "dotenv file to load", + "type": "string" + }, + { + "description": "dotenv files to load", + "type": "array", + "items": { + "oneOf": [ + { + "description": "dotenv file to load", + "type": "string" + }, + { + "$ref": "#/$defs/env_directive" + } + ] + } + } + ] + }, + "path": { "oneOf": [ { "type": "object", @@ -68,27 +134,37 @@ } ] }, + "paths": { + "type": "array", + "items": { + "type": "string" + } + }, "tools": { "type": "boolean", "description": "load tools before resolving" + } + }, + "oneOf": [ + { + "required": ["path"] }, - "redact": { - "type": "boolean", - "description": "redact the value from logs" + { + "required": ["paths"] } - } + ] }, { - "description": "dotenv file to load", + "description": "PATH entry to add", "type": "string" }, { - "description": "dotenv files to load", + "description": "PATH entries to add", "type": "array", "items": { "oneOf": [ { - "description": "dotenv file to load", + "description": "PATH entry to add", "type": "string" }, { @@ -110,10 +186,6 @@ "tools": { "type": "boolean", "description": "load tools before resolving" - }, - "redact": { - "type": "boolean", - "description": "redact the value from logs" } } } @@ -122,40 +194,6 @@ } ] }, - "path": { - "oneOf": [ - { - "type": "object", - "properties": { - "path": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - } - } - }, - { - "description": "PATH entry to add", - "type": "string" - }, - { - "description": "PATH entries to add", - "items": { - "description": "PATH entry to add", - "type": "string" - }, - "type": "array" - } - ] - }, "python": { "description": "python environment", "properties": { @@ -207,30 +245,7 @@ "source": { "oneOf": [ { - "type": "object", - "properties": { - "path": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "tools": { - "type": "boolean", - "description": "load tools before resolving" - }, - "redact": { - "type": "boolean", - "description": "redact the value from logs" - } - } + "$ref": "#/$defs/env_directive" }, { "description": "bash script to load", @@ -238,11 +253,18 @@ }, { "description": "bash scripts to load", + "type": "array", "items": { - "description": "bash script to load", - "type": "string" - }, - "type": "array" + "oneOf": [ + { + "description": "bash script to load", + "type": "string" + }, + { + "$ref": "#/$defs/env_directive" + } + ] + } } ] } diff --git a/src/config/config_file/mise_toml.rs b/src/config/config_file/mise_toml.rs index 3b3d2fc9b8..fd8a6f8681 100644 --- a/src/config/config_file/mise_toml.rs +++ b/src/config/config_file/mise_toml.rs @@ -83,12 +83,6 @@ pub struct MiseTomlTool { pub options: Option, } -#[derive(Debug, Clone)] -pub struct MiseTomlEnvDirective { - pub value: String, - pub options: EnvDirectiveOptions, -} - #[derive(Debug, Default, Clone)] pub struct Tasks(pub BTreeMap); @@ -775,6 +769,7 @@ impl<'de> de::Deserialize<'de> for EnvList { } Ok(EnvList(env)) } + fn visit_map(self, mut map: M) -> std::result::Result where M: de::MapAccess<'de>, @@ -783,6 +778,33 @@ impl<'de> de::Deserialize<'de> for EnvList { while let Some(key) = map.next_key::()? { match key.as_str() { "_" | "mise" => { + #[derive(Deserialize)] + #[serde(untagged)] + enum MiseTomlEnvDirective { + Single { + #[serde(alias = "path")] + value: String, + #[serde(flatten)] + options: EnvDirectiveOptions, + }, + Multiple { + #[serde(alias = "value", alias = "path", alias = "paths")] + values: Vec, + #[serde(flatten)] + options: EnvDirectiveOptions, + }, + } + + impl FromStr for MiseTomlEnvDirective { + type Err = String; + fn from_str(s: &str) -> Result { + Ok(MiseTomlEnvDirective::Single { + value: s.to_string(), + options: Default::default(), + }) + } + } + struct EnvDirectivePythonVenv { path: String, create: bool, @@ -901,17 +923,29 @@ impl<'de> de::Deserialize<'de> for EnvList { } } + fn flatten_directives( + directives: Vec, + constructor: F, + ) -> impl Iterator + where + F: Fn(String, EnvDirectiveOptions) -> EnvDirective + 'static, + { + directives.into_iter().flat_map(move |d| match d { + MiseTomlEnvDirective::Single { value, options } => { + vec![constructor(value, options)] + } + MiseTomlEnvDirective::Multiple { values, options } => values + .into_iter() + .map(|v| constructor(v, options.clone())) + .collect(), + }) + } + let directives = map.next_value::()?; // TODO: parse these in the order they're defined somehow - for d in directives.path { - env.push(EnvDirective::Path(d.value, d.options)); - } - for d in directives.file { - env.push(EnvDirective::File(d.value, d.options)); - } - for d in directives.source { - env.push(EnvDirective::Source(d.value, d.options)); - } + env.extend(flatten_directives(directives.path, EnvDirective::Path)); + env.extend(flatten_directives(directives.file, EnvDirective::File)); + env.extend(flatten_directives(directives.source, EnvDirective::Source)); for (key, value) in directives.other { env.push(EnvDirective::Module(key, value, Default::default())); } @@ -1229,74 +1263,6 @@ impl<'de> de::Deserialize<'de> for MiseTomlToolList { } } -impl FromStr for MiseTomlEnvDirective { - type Err = eyre::Report; - - fn from_str(s: &str) -> eyre::Result { - Ok(MiseTomlEnvDirective { - value: s.into(), - options: Default::default(), - }) - } -} - -impl<'de> de::Deserialize<'de> for MiseTomlEnvDirective { - fn deserialize(deserializer: D) -> std::result::Result - where - D: de::Deserializer<'de>, - { - struct MiseTomlEnvDirectiveVisitor; - - impl<'de> Visitor<'de> for MiseTomlEnvDirectiveVisitor { - type Value = MiseTomlEnvDirective; - fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { - formatter.write_str("env directive") - } - - fn visit_str(self, v: &str) -> Result - where - E: de::Error, - { - Ok(MiseTomlEnvDirective { - value: v.into(), - options: Default::default(), - }) - } - - fn visit_map(self, mut map: M) -> std::result::Result - where - M: de::MapAccess<'de>, - { - let mut options: EnvDirectiveOptions = Default::default(); - let mut value = None; - while let Some((k, v)) = map.next_entry::()? { - match k.as_str() { - "value" | "path" => { - value = Some(v.as_str().unwrap().to_string()); - } - "tools" => { - options.tools = v.as_bool().unwrap(); - } - "redact" => { - options.redact = v.as_bool().unwrap(); - } - _ => { - return Err(de::Error::custom("invalid key")); - } - } - } - if let Some(value) = value { - Ok(MiseTomlEnvDirective { value, options }) - } else { - Err(de::Error::custom("missing value")) - } - } - } - - deserializer.deserialize_any(MiseTomlEnvDirectiveVisitor) - } -} - impl<'de> de::Deserialize<'de> for MiseTomlTool { fn deserialize(deserializer: D) -> std::result::Result where diff --git a/src/config/config_file/toml.rs b/src/config/config_file/toml.rs index fd9b2068d3..4e80cdd7d0 100644 --- a/src/config/config_file/toml.rs +++ b/src/config/config_file/toml.rs @@ -104,7 +104,22 @@ where where S: de::SeqAccess<'de>, { - Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq)) + #[derive(Deserialize)] + #[serde(untagged)] + enum StringOrValue { + String(String), + Value(T), + } + let mut seq = seq; + let mut values = Vec::with_capacity(seq.size_hint().unwrap_or(0)); + while let Some(element) = seq.next_element::>()? { + let value = match element { + StringOrValue::String(s) => s.parse().map_err(de::Error::custom)?, + StringOrValue::Value(v) => v, + }; + values.push(value); + } + Ok(values) } } diff --git a/src/config/env_directive/mod.rs b/src/config/env_directive/mod.rs index 7646b0a613..74c9cd92fd 100644 --- a/src/config/env_directive/mod.rs +++ b/src/config/env_directive/mod.rs @@ -21,9 +21,11 @@ mod path; mod source; mod venv; -#[derive(Debug, Clone, Default, PartialEq, serde::Serialize)] +#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)] pub struct EnvDirectiveOptions { + #[serde(default)] pub(crate) tools: bool, + #[serde(default)] pub(crate) redact: bool, } diff --git a/xtasks/render/settings.ts b/xtasks/render/settings.ts index 47023179c6..6d224a97d5 100644 --- a/xtasks/render/settings.ts +++ b/xtasks/render/settings.ts @@ -125,6 +125,7 @@ const taskSchema = JSON.parse( ); taskSchema["$defs"].task = schema["$defs"].task; taskSchema["$defs"].env = schema["$defs"].env; +taskSchema["$defs"].env_directive = schema["$defs"].env_directive; fs.writeFileSync( "schema/mise-task.json.tmp", JSON.stringify(taskSchema, null, 2),