diff --git a/.mise.toml b/.mise.toml index 1798e6262..7802a18bb 100644 --- a/.mise.toml +++ b/.mise.toml @@ -22,6 +22,7 @@ jq = "latest" "npm:prettier" = "3" direnv = "latest" actionlint = "latest" +ripgrep = "latest" "pipx:toml-sort" = "latest" #python = { version = "latest", virtualenv = "{{env.HOME}}/.cache/venv" } #ruby = "3.1" diff --git a/.mise/tasks/lint/ripgrep b/.mise/tasks/lint/ripgrep new file mode 100755 index 000000000..6ebb254fb --- /dev/null +++ b/.mise/tasks/lint/ripgrep @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +found=0 +rg "dbg!" src && found=1 + +if [[ $found == 1 ]]; then + echo "dbg! macro found" + exit 1 +fi diff --git a/docs/configuration.md b/docs/configuration.md index de3d586ef..a6779a0d9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -247,6 +247,10 @@ version files since they're version files not specific to asdf/mise and can be u See [Settings](/settings) for the full list of settings. +## Tasks + +See [Tasks](/tasks/) for the full list of configuration options. + ## Environment variables mise can also be configured via environment variables. The following options are available: diff --git a/docs/tasks/file-tasks.md b/docs/tasks/file-tasks.md index 4ff4e7ee7..8f72ff099 100644 --- a/docs/tasks/file-tasks.md +++ b/docs/tasks/file-tasks.md @@ -67,7 +67,7 @@ test:integration .../.mise/tasks/test/integration test:units .../.mise/tasks/test/units ``` -### Argument parsing with usage +## Argument parsing with usage [usage](https://usage.jdx.dev) spec can be used within these files to provide argument parsing, autocompletion, documentation when running mise and can be exported to markdown. Essentially this turns tasks into diff --git a/docs/tasks/index.md b/docs/tasks/index.md index 265980fd3..6309716f6 100644 --- a/docs/tasks/index.md +++ b/docs/tasks/index.md @@ -1,8 +1,8 @@ # Tasks -You can define tasks in `.mise.toml` files or as standalone shell scripts. These are useful for things like +You can define tasks in `mise.toml` files or as standalone shell scripts. These are useful for things like running linters, tests, builders, servers, and other tasks that are specific to a project. Of course, -tasks launched with mise will include the mise environment—your tools and env vars defined in `.mise.toml`. +tasks launched with mise will include the mise environment—your tools and env vars defined in `mise.toml`. Here's my favorite features about mise's task runner: @@ -19,4 +19,30 @@ Please give feedback early since while it's experimental it's much easier to cha ## Task Environment Variables -- `root` - the root of the project, defaults to the directory of the `.mise.toml` file +- `root` - the root of the project, defaults to the directory of the `mise.toml` file + +## Task Configuration + +You can configure how tasks are used in mise with the `[task_config]` section of `mise.toml`: + +```toml +[task_config] + +# add toml files containing toml tasks, or file tasks to include when looking for tasks +includes = [ + "tasks.toml", # a task toml file + "mytasks" # a directory containing file tasks +] +``` + +If using a toml file for tasks, the file should be the same format as the `[tasks]` section of `mise.toml` +but without the `[task]` prefix: + +```toml +task1 = "echo task1" +task2 = "echo task2" +task3 = "echo task3" + +[task4] +run = "echo task4" +``` diff --git a/docs/tasks/toml-tasks.md b/docs/tasks/toml-tasks.md index d12c5a67e..171bfbd85 100644 --- a/docs/tasks/toml-tasks.md +++ b/docs/tasks/toml-tasks.md @@ -1,6 +1,6 @@ # TOML-based Tasks -Tasks can also be defined in `.mise.toml` files in different ways. This is a more "traditional" method of defining tasks: +Tasks can be defined in `mise.toml` files in different ways: ```toml [tasks.cleancache] @@ -64,6 +64,11 @@ run = [ ``` Then running `mise run test foo bar` will pass `foo bar` to `cargo test`. `mise run test --e2e-args baz` will pass `baz` to `./scripts/test-e2e.sh`. +If any arguments are defined with templates then mise will not pass the arguments to the last script in the `run` array. + +:::tip +Using templates to define arguments will make them work with completion and help messages. +::: ### Positional Arguments diff --git a/e2e/tasks/test_task_ls b/e2e/tasks/test_task_ls new file mode 100644 index 000000000..674c8be9a --- /dev/null +++ b/e2e/tasks/test_task_ls @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +cat <mise.toml +[task_config] +includes = [ + "tasks.toml", + "mytasks", +] +[tasks.lint] +run = 'echo "linting!"' +EOF + +cat <tasks.toml +[test] +run = 'echo "testing!"' +[test-with-args] +run = 'echo "{{arg()}} {{flag(name="force")}} {{option(name="user")}}"' +EOF + +mkdir -p .mise/tasks +cat <<'EOF' >.mise/tasks/do-not-show +#!/usr/bin/env bash +EOF +chmod +x .mise/tasks/do-not-show + +mkdir -p mytasks +cat <<'EOF' >mytasks/filetask2 +#!/usr/bin/env bash +EOF +chmod +x mytasks/filetask2 + +assert "mise task ls" "filetask2 ~/workdir/mytasks/filetask2 +lint ~/workdir/mise.toml +test ~/workdir/tasks.toml +test-with-args ~/workdir/tasks.toml" diff --git a/e2e/cli/test_run b/e2e/tasks/test_task_run similarity index 100% rename from e2e/cli/test_run rename to e2e/tasks/test_task_run diff --git a/schema/mise.json b/schema/mise.json index 735bec1a0..1bab0cb7b 100644 --- a/schema/mise.json +++ b/schema/mise.json @@ -563,6 +563,21 @@ } ] }, + "task_config": { + "description": "configration for task execution/management", + "type": "object", + "additionalProperties": false, + "properties": { + "includes": { + "description": "files/directories to include searching for tasks", + "items": { + "description": "file/directory root to include in task execution", + "type": "string" + }, + "type": "array" + } + } + }, "tool": { "oneOf": [ { diff --git a/schema/mise.json.hbs b/schema/mise.json.hbs index 907b8552d..0e0d22e1f 100644 --- a/schema/mise.json.hbs +++ b/schema/mise.json.hbs @@ -233,6 +233,21 @@ } ] }, + "task_config": { + "description": "configration for task execution/management", + "type": "object", + "additionalProperties": false, + "properties": { + "includes": { + "description": "files/directories to include searching for tasks", + "items": { + "description": "file/directory root to include in task execution", + "type": "string" + }, + "type": "array" + } + } + }, "tool": { "oneOf": [ { diff --git a/src/cli/tasks/ls.rs b/src/cli/tasks/ls.rs index 4e292a0c6..a0e310778 100644 --- a/src/cli/tasks/ls.rs +++ b/src/cli/tasks/ls.rs @@ -6,6 +6,7 @@ use tabled::Tabled; use crate::config::{Config, Settings}; use crate::file::display_path; use crate::task::Task; +use crate::ui::info::trim_line_end_whitespace; use crate::ui::{style, table}; /// [experimental] List available tasks to execute @@ -91,7 +92,8 @@ impl TasksLs { if !self.extended { table::disable_columns(&mut table, vec![1]); } - miseprintln!("{table}"); + let table = format!("{table}"); + miseprintln!("{}", trim_line_end_whitespace(&table)); Ok(()) } @@ -191,12 +193,12 @@ mod tests { #[test] fn test_task_ls() { reset(); - assert_cli_snapshot!("t", "--no-headers", @r###" - configtask ~/config/config.toml - filetask This is a test build script ~/cwd/.mise/tasks/filetask - lint ~/config/config.toml + assert_cli_snapshot!("t", "--no-headers", @r#" + configtask ~/config/config.toml + filetask This is a test build script ~/cwd/.mise/tasks/filetask + lint ~/config/config.toml test ~/config/config.toml - "###); + "#); } #[test] diff --git a/src/cli/toml/mod.rs b/src/cli/toml/mod.rs index 2e29766e2..6ce44cd0d 100644 --- a/src/cli/toml/mod.rs +++ b/src/cli/toml/mod.rs @@ -1,6 +1,7 @@ use crate::config::{load_config_paths, DEFAULT_CONFIG_FILENAMES}; +use crate::file; use clap::Subcommand; -use eyre::Result; +use eyre::{bail, Result}; use once_cell::sync::Lazy; use std::path::PathBuf; @@ -47,8 +48,15 @@ impl Commands { impl Toml { pub fn run(self) -> Result<()> { - let cmd = self.command.unwrap(); + if let Some(cmd) = self.command { + return cmd.run(); + } + if let Some(toml_config) = top_toml_config() { + miseprintln!("{}", file::read_to_string(&toml_config)?); + } else { + bail!("No mise.toml file found"); + } - cmd.run() + Ok(()) } } diff --git a/src/config/mod.rs b/src/config/mod.rs index b4ebd17dc..412db261f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -244,18 +244,40 @@ impl Config { let includes = configs .iter() .find_map(|cf| cf.task_config().includes.clone()) - .unwrap_or_else(default_task_includes); + .unwrap_or_else(default_task_includes) + .into_iter() + .map(|p| if p.is_absolute() { p } else { dir.join(p) }) + .collect_vec(); + let extra_tasks = includes + .iter() + .filter(|p| { + p.is_file() && p.extension().unwrap_or_default().to_string_lossy() == "toml" + }) + .map(|p| self.load_task_file(p)) + .flatten_ok() + .collect::>>()?; let file_tasks = includes.into_iter().flat_map(|p| { - let p = match p.is_absolute() { - true => p, - false => dir.join(p), - }; self.load_tasks_includes(&p).unwrap_or_else(|err| { warn!("loading tasks in {}: {err}", display_path(&p)); vec![] }) }); - Ok(file_tasks.into_iter().chain(config_tasks).collect()) + Ok(file_tasks + .into_iter() + .chain(config_tasks) + .chain(extra_tasks) + .collect()) + } + + fn load_task_file(&self, path: &Path) -> Result> { + let raw = file::read_to_string(path)?; + let mut tasks = toml::from_str::>(&raw) + .map_err(|e| eyre!("Error parsing task file: {} {e}", display_path(path)))?; + for (name, task) in &mut tasks { + task.name = name.clone(); + task.config_source = path.to_path_buf(); + } + Ok(tasks.into_values().collect()) } fn load_global_tasks(&self) -> Result> { @@ -313,14 +335,14 @@ impl Config { if !root.is_dir() { return Ok(vec![]); } - let files: Vec = WalkDir::new(root) + WalkDir::new(root) .follow_links(true) .into_iter() - .filter_entry(|e| !e.file_name().to_string_lossy().starts_with('.')) + // skip hidden directories (if the root is hidden that's ok) + .filter_entry(|e| e.path() == root || !e.file_name().to_string_lossy().starts_with('.')) .filter_ok(|e| e.file_type().is_file()) .map_ok(|e| e.path().to_path_buf()) - .try_collect()?; - files + .try_collect::<_, Vec, _>()? .into_par_iter() .filter(|p| file::is_executable(p)) .map(|path| Task::from_path(&path)) diff --git a/src/ui/info.rs b/src/ui/info.rs index c20d7666a..9cf5c4a08 100644 --- a/src/ui/info.rs +++ b/src/ui/info.rs @@ -23,6 +23,6 @@ pub fn indent_by(s: S, ind: &'static str) -> String { out } -fn trim_line_end_whitespace(s: &str) -> String { +pub fn trim_line_end_whitespace(s: &str) -> String { s.lines().map(str::trim_end).collect::>().join("\n") } diff --git a/tasks b/tasks new file mode 120000 index 000000000..1d02a36bd --- /dev/null +++ b/tasks @@ -0,0 +1 @@ +.mise/tasks \ No newline at end of file