Skip to content
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
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ It is implemented using [mq](https://github.com/harehare/mq), a jq-like command-
- Execute code blocks from specific sections in Markdown files
- Task dependencies with automatic execution ordering
- Named task parameters with defaults, plus positional args
- Per-run `--env`/`--dir` overrides, plus `meta`-declared defaults for environment variables and working directory
- Default task, aliases, and private (hidden) tasks
- Configurable runtimes for different programming languages, with real exit codes and interactive stdin
- Support for custom heading levels
Expand Down Expand Up @@ -97,6 +98,65 @@ echo "Second arg: $MX_ARG_1"
```
````

### Environment variables and working directory

Like `just`, you can set environment variables and a working directory for a task at invocation time — no declaration needed in the Markdown file:

```bash
# Set one or more environment variables (repeatable)
mq-task deploy --env REGION=eu --env DEBUG=1

# Run the task's commands in a specific working directory
mq-task deploy --dir ../other-project

# Combine both
mq-task run deploy --env REGION=eu --dir ../other-project
```

`--env KEY=VALUE` variables are available in the task's shell alongside `MX_ARGS`/`MX_PARAM_*`. `--dir` applies to every command in the task (and its dependencies) for that run. Both flags are available on the shorthand form, `run`, and `tui`.

You can also declare default environment variables directly in a task's `meta` block:

````markdown
## deploy

```meta
env = ["REGION=staging", "LOG_LEVEL=info"]
```

```bash
echo "deploying to $REGION ($LOG_LEVEL)"
```
````

A CLI `--env` flag of the same name overrides the `meta` default for that run:

```bash
mq-task deploy # REGION=staging
mq-task deploy --env REGION=prod # REGION=prod
```

A working directory can be declared the same way with `dir` (relative paths resolve against the directory `mq-task` was invoked from):

````markdown
## deploy

```meta
dir = "services/api"
```

```bash
pwd # services/api
```
````

The CLI `--dir` flag overrides a `meta`-declared `dir` for that run:

```bash
mq-task deploy # runs in services/api
mq-task deploy --dir services/worker # runs in services/worker instead
```

### Task dependencies

You can declare dependencies between tasks using a `meta` code block (TOML format). When a task is run, its dependencies are automatically executed first in the correct order.
Expand Down
4 changes: 4 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,8 @@ pub enum Error {
/// A required task parameter was not provided
#[error("Missing required parameter '{0}' for task '{1}'")]
MissingParameter(String, String),

/// A `--env` value was not in `KEY=VALUE` format
#[error("Invalid environment variable '{0}'. Expected format: 'KEY=VALUE'")]
InvalidEnv(String),
}
72 changes: 63 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use colored::*;
use std::path::PathBuf;
use std::process::ExitCode;

use mq_task::{Config, ExecutionMode, Error, Result, Runner};
use mq_task::{Config, Error, ExecutionMode, Result, Runner};

const DEFAULT_TASKS_FILE: &str = "README.md";

Expand Down Expand Up @@ -42,6 +42,14 @@ struct Cli {
#[arg(long)]
dry_run: bool,

/// Set an environment variable for the task (format: KEY=VALUE), repeatable
#[arg(long = "env", value_name = "KEY=VALUE")]
env: Vec<String>,

/// Working directory to run the task's commands in
#[arg(short = 'd', long = "dir", value_name = "PATH")]
dir: Option<PathBuf>,

/// Arguments to pass to the task (use -- to separate: mq_task task -- arg1 arg2)
#[arg(last = true)]
args: Vec<String>,
Expand Down Expand Up @@ -85,6 +93,14 @@ enum Commands {
#[arg(long)]
dry_run: bool,

/// Set an environment variable for the task (format: KEY=VALUE), repeatable
#[arg(long = "env", value_name = "KEY=VALUE")]
env: Vec<String>,

/// Working directory to run the task's commands in
#[arg(short = 'd', long = "dir", value_name = "PATH")]
dir: Option<PathBuf>,

/// Arguments to pass to the task (use -- to separate: mq_task run task -- arg1 arg2)
#[arg(last = true)]
args: Vec<String>,
Expand Down Expand Up @@ -127,6 +143,14 @@ enum Commands {
#[arg(long)]
dry_run: bool,

/// Set an environment variable for the task (format: KEY=VALUE), repeatable
#[arg(long = "env", value_name = "KEY=VALUE")]
env: Vec<String>,

/// Working directory to run the task's commands in
#[arg(short = 'd', long = "dir", value_name = "PATH")]
dir: Option<PathBuf>,

/// Include private tasks (name starts with `_` or `meta` has private = true)
#[arg(short, long)]
all: bool,
Expand Down Expand Up @@ -166,8 +190,21 @@ fn dispatch(cli: Cli) -> Result<()> {
execution_mode,
lang,
dry_run,
env,
dir,
args,
}) => run_task(
file,
task,
config,
runtime,
execution_mode,
lang,
dry_run,
env,
dir,
args,
}) => run_task(file, task, config, runtime, execution_mode, lang, dry_run, args)?,
)?,
Some(Commands::List {
file,
config,
Expand All @@ -179,8 +216,10 @@ fn dispatch(cli: Cli) -> Result<()> {
config,
lang,
dry_run,
env,
dir,
all,
}) => run_tui(file, config, lang, dry_run, all)?,
}) => run_tui(file, config, lang, dry_run, env, dir, all)?,
Some(Commands::Init { output }) => init_config(output)?,
None => {
// If no subcommand, check if task is provided
Expand All @@ -193,6 +232,8 @@ fn dispatch(cli: Cli) -> Result<()> {
cli.execution_mode,
cli.lang,
cli.dry_run,
cli.env,
cli.dir,
cli.args,
)?;
} else {
Expand All @@ -207,6 +248,8 @@ fn dispatch(cli: Cli) -> Result<()> {
cli.execution_mode,
cli.lang,
cli.dry_run,
cli.env,
cli.dir,
cli.args,
)?;
} else {
Expand All @@ -229,6 +272,8 @@ fn run_task(
execution_mode: Option<String>,
lang_filter: Option<String>,
dry_run: bool,
env: Vec<String>,
dir: Option<PathBuf>,
args: Vec<String>,
) -> Result<()> {
let mut config = load_config(config_path)?;
Expand All @@ -247,6 +292,8 @@ fn run_task(

let mut runner = Runner::new(config);
runner.set_dry_run(dry_run);
runner.set_env_overrides(Runner::parse_env_overrides(&env)?);
runner.set_working_dir(dir);

println!("Running task: {}\n", task_name);

Expand All @@ -256,15 +303,26 @@ fn run_task(
}

/// Launch the interactive TUI for task selection
#[allow(clippy::too_many_arguments)]
fn run_tui(
markdown_path: PathBuf,
config_path: Option<PathBuf>,
lang_filter: Option<String>,
dry_run: bool,
env: Vec<String>,
dir: Option<PathBuf>,
show_all: bool,
) -> Result<()> {
let config = load_config(config_path)?;
mq_task::tui::run_tui(markdown_path, config, lang_filter, dry_run, show_all)?;
mq_task::tui::run_tui(
markdown_path,
config,
lang_filter,
dry_run,
env,
dir,
show_all,
)?;
Ok(())
}

Expand Down Expand Up @@ -371,11 +429,7 @@ fn list_tasks(
format!("- {}", trimmed).bright_black()
));
} else {
output.push_str(&format!(
" {}{}\n",
title_display,
lang_info
));
output.push_str(&format!(" {}{}\n", title_display, lang_info));
}
} else {
output.push_str(&format!(" {}{}\n", title_display, lang_info));
Expand Down
Loading