Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: file task integration with usage spec #2614

Merged
merged 1 commit into from
Sep 25, 2024
Merged
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
15 changes: 12 additions & 3 deletions .mise/tasks/filetask
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
#!/usr/bin/env bash
# mise description="This is a test build script"
# shellcheck disable=SC2154
#USAGE flag "-f --force" help="Overwrite existing <file>"
#USAGE flag "-u --user <user>" help="User to run as"
#USAGE arg "<file>" help="The file to write" default="file.txt"

# mise description="This is a test build script"
# mise depends=["lint", "build"]
# mise sources=[".test-tool-versions"]
# mise outputs=["$MISE_PROJECT_ROOT/test/test-build-output.txt"]
# mise env={TEST_BUILDSCRIPT_ENV_VAR = "VALID"}
# mise dir="{{config_root}}"

set -euxo pipefail
set -exo pipefail
cd "$MISE_PROJECT_ROOT" || exit 1
echo "running test-build script"
echo "TEST_BUILDSCRIPT_ENV_VAR: $TEST_BUILDSCRIPT_ENV_VAR" > test/test-build-output.txt
echo "TEST_BUILDSCRIPT_ENV_VAR: $TEST_BUILDSCRIPT_ENV_VAR" >test/test-build-output.txt
echo "ARGS:" "$@"

echo "usage_force: $usage_force"
echo "usage_user: $usage_user"
echo "usage_file: $usage_file"
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ tokio = { version = "1.37.0", features = [
toml = { version = "0.8", features = ["parse"] }
toml_edit = { version = "0.22", features = ["parse"] }
url = "2.5.0"
usage-lib = { version = "0.3", features = ["clap"] }
usage-lib = { version = "0.5", features = ["clap"] }
versions = { version = "6.2.0", features = ["serde"] }
vfox = "0.1"
walkdir = "2.5.0"
Expand Down
23 changes: 23 additions & 0 deletions docs/tasks/script-tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,26 @@ build .../.mise/tasks/build
test:integration .../.mise/tasks/test/integration
test:units .../.mise/tasks/test/units
```

### Argument parsing with usage

[usage](https://usage.jdx.dev) spec can be used within these files to provide argument parsing, autocompletion, and help documentation.

Here is an example of a script task that builds a Rust CLI using some of the features of usage:

```bash
#!/usr/bin/env -S usage bash
set -e

#USAGE flag "-c --clean" help="Clean the build directory before building"
#USAGE flag "-p --profile <profile>" help="Build with the specified profile"
#USAGE arg "<target>" help="The target to build"

if [ "$usage_clean" = "true" ]; then
cargo clean
fi

cargo build --profile "${usage_profile:-debug}" --target "$usage_target"
```

(Note that autocompletion and help are not yet implemented in mise as of this writing but that is planned.)
11 changes: 11 additions & 0 deletions e2e/cli/test_run
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ run = 'echo "configtask:"'
run = 'echo "linting!"'
[tasks.test]
run = 'echo "testing!"'
[tasks.test-with-args]
run = 'echo "{{arg()}} {{flag(name="force")}} {{option(name="user")}}"'
EOF

mkdir -p .mise/tasks
Expand All @@ -32,3 +34,12 @@ assert "cat test-e2e/test-build-output.txt" "TEST_BUILDSCRIPT_ENV_VAR: VALID
ARGS: arg1 arg2"

assert "mise run test arg1 arg2 arg3" "testing! arg1 arg2 arg3"
assert "mise run test-with-args foo --force --user=user" "foo true user"

cat <<'EOF' >.mise/tasks/filetask
#!/usr/bin/env bash
#USAGE flag "-u --user <user>" help="User to run as"

echo "user=$usage_user"
EOF
assert "mise run filetask --user=jdx" "user=jdx"
4 changes: 2 additions & 2 deletions src/cli/alias/ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ mod tests {
#[test]
fn test_alias_ls() {
reset();
assert_cli_snapshot!("aliases", @r###"
assert_cli_snapshot!("aliases", @r#"
java lts 21
node lts 20
node lts-argon 4
Expand All @@ -91,7 +91,7 @@ mod tests {
tiny lts 3.1.0
tiny lts-prev 2.0.0
tiny my/alias 3.0
"###);
"#);
}

#[test]
Expand Down
4 changes: 2 additions & 2 deletions src/cli/alias/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub mod tests {
reset();
assert_cli!("alias", "set", "tiny", "my/alias", "3.0");

assert_cli_snapshot!("aliases", @r###"
assert_cli_snapshot!("aliases", @r#"
java lts 21
node lts 20
node lts-argon 4
Expand All @@ -57,7 +57,7 @@ pub mod tests {
tiny lts 3.1.0
tiny lts-prev 2.0.0
tiny my/alias 3.0
"###);
"#);
reset();
}
}
4 changes: 2 additions & 2 deletions src/cli/alias/unset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ mod tests {
reset();

assert_cli!("alias", "unset", "tiny", "my/alias");
assert_cli_snapshot!("aliases", @r###"
assert_cli_snapshot!("aliases", @r#"
java lts 21
node lts 20
node lts-argon 4
Expand All @@ -54,7 +54,7 @@ mod tests {
node lts-iron 20
tiny lts 3.1.0
tiny lts-prev 2.0.0
"###);
"#);

reset();
}
Expand Down
5 changes: 2 additions & 3 deletions src/cli/plugins/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,11 @@ mod tests {
fn test_plugin_link() {
reset();
assert_cli_snapshot!("plugin", "link", "-f", "tiny-link", "../data/plugins/tiny", @"");
assert_cli_snapshot!("plugins", "ls", @r###"
assert_cli_snapshot!("plugins", "ls", @r#"
dummy
tiny
tiny-link
mise hint see available plugins with mise registry
"###);
"#);
assert_cli_snapshot!("plugin", "uninstall", "tiny-link", @"");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@ expression: output
---
dummy
tiny
mise hint see available plugins with mise registry
34 changes: 16 additions & 18 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ use crate::errors::Error;
use crate::errors::Error::ScriptFailed;
use crate::file::display_path;
use crate::task::{Deps, GetMatchingExt, Task};
use crate::task_parser::TaskParser;
use crate::toolset::{InstallOptions, ToolsetBuilder};
use crate::ui::{ctrlc, style};
use crate::{env, file, ui};
Expand Down Expand Up @@ -206,6 +205,9 @@ impl Run {
};
let rx = tasks.lock().unwrap().subscribe();
while let Some(task) = rx.recv().unwrap() {
if exit_status.lock().unwrap().is_some() {
break;
}
run(&task);
}
});
Expand Down Expand Up @@ -252,20 +254,8 @@ impl Run {
if let Some(file) = &task.file {
self.exec_file(file, task, &env, &prefix)?;
} else {
let parser = TaskParser::new(self.cd.clone()).parse_run_scripts(&task.run)?;
if parser.has_any_args_defined() {
for script in parser.render(&self.args) {
self.exec_script(&script, &[], task, &env, &prefix)?;
}
} else {
for (i, script) in task.run.iter().enumerate() {
// only pass args to the last script if no formal args are defined
let args = match i == task.run.len() - 1 {
true => task.args.iter().cloned().collect_vec(),
false => vec![],
};
self.exec_script(script, &args, task, &env, &prefix)?;
}
for (script, args) in task.render_run_scripts_with_args(self.cd.clone(), &task.args)? {
self.exec_script(&script, &args, task, &env, &prefix)?;
}
}

Expand Down Expand Up @@ -327,14 +317,23 @@ impl Run {
env: &BTreeMap<String, String>,
prefix: &str,
) -> Result<()> {
let mut env = env.clone();
let command = file.to_string_lossy().to_string();
let args = task.args.iter().cloned().collect_vec();
let (spec, _) = task.parse_usage_spec(self.cd.clone())?;
if !spec.cmd.args.is_empty() || !spec.cmd.flags.is_empty() {
let args = once(command.clone()).chain(args.clone()).collect_vec();
let po = usage::cli::parse(&spec, &args).map_err(|err| eyre!(err))?;
for (k, v) in po.as_env() {
env.insert(k, v);
}
}

let cmd = format!("{} {}", display_path(file), args.join(" "));
let cmd = style::ebold(format!("$ {cmd}")).bright().to_string();
info_unprefix_trunc!("{prefix} {cmd}");

self.exec(&command, &args, task, env, prefix)
self.exec(&command, &args, task, &env, prefix)
}

fn exec(
Expand Down Expand Up @@ -624,8 +623,7 @@ mod tests {
assert_cli_snapshot!(
"r",
"filetask",
"arg1",
"arg2",
"--user=jdx",
":::",
"configtask",
"arg3",
Expand Down
3 changes: 1 addition & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ mod runtime_symlinks;
mod shell;
mod shims;
mod shorthands;
mod task;
mod task_parser;
pub(crate) mod task;
pub(crate) mod tera;
pub(crate) mod timeout;
mod toml;
Expand Down
17 changes: 3 additions & 14 deletions src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,6 @@ macro_rules! miseprint {
}}
}

#[cfg(test)]
#[macro_export]
macro_rules! hint {
($id:expr, $message:expr, $example_cmd:expr) => {{
let mut stderr = $crate::output::tests::STDERR.lock().unwrap();
if !$crate::output::should_display_hint($id) {
let prefix = console::style("mise hint").dim().for_stderr();
let cmd = console::style($example_cmd).bold().for_stderr();
stderr.push(format!("{} {} {}", prefix, format!($message), cmd));
}
}};
}

#[cfg(test)]
#[macro_export]
macro_rules! info {
Expand Down Expand Up @@ -107,6 +94,9 @@ macro_rules! debug {
}

pub fn should_display_hint(id: &str) -> bool {
if cfg!(test) {
return false;
}
if SETTINGS
.disable_hints
.iter()
Expand All @@ -127,7 +117,6 @@ pub fn should_display_hint(id: &str) -> bool {
}
}

#[cfg(not(test))]
#[macro_export]
macro_rules! hint {
($id:expr, $message:expr, $example_cmd:expr) => {{
Expand Down
46 changes: 46 additions & 0 deletions src/task.rs → src/task/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ use serde_derive::Deserialize;
use crate::config::config_file::toml::{deserialize_arr, TomlParser};
use crate::config::Config;
use crate::file;
use crate::task::task_script_parser::{
has_any_args_defined, replace_template_placeholders_with_args, TaskScriptParser,
};
use crate::tera::{get_tera, BASE_CONTEXT};
use crate::ui::tree::TreeItem;

mod task_script_parser;

#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize)]
pub struct Task {
#[serde(skip)]
Expand Down Expand Up @@ -157,6 +162,47 @@ impl Task {
.filter_ok(|t| t.name != self.name)
.collect()
}

pub fn parse_usage_spec(&self, cwd: Option<PathBuf>) -> Result<(usage::Spec, Vec<String>)> {
if let Some(file) = &self.file {
let spec = usage::Spec::parse_script(file)
.inspect_err(|e| debug!("failed to parse task file with usage: {e}"))
.unwrap_or_default();
Ok((spec, vec![]))
} else {
let (scripts, spec) = TaskScriptParser::new(cwd).parse_run_scripts(&self.run)?;
Ok((spec, scripts))
}
}

pub fn render_run_scripts_with_args(
&self,
cwd: Option<PathBuf>,
args: &[String],
) -> Result<Vec<(String, Vec<String>)>> {
let (spec, scripts) = self.parse_usage_spec(cwd)?;
if has_any_args_defined(&spec) {
Ok(
replace_template_placeholders_with_args(&spec, &scripts, args)
.into_iter()
.map(|s| (s, vec![]))
.collect(),
)
} else {
Ok(self
.run
.iter()
.enumerate()
.map(|(i, script)| {
// only pass args to the last script if no formal args are defined
match i == self.run.len() - 1 {
true => (script.clone(), args.iter().cloned().collect_vec()),
false => (script.clone(), vec![]),
}
})
.collect())
}
}
}

fn name_from_path(root: impl AsRef<Path>, path: impl AsRef<Path>) -> Result<String> {
Expand Down
Loading
Loading