diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eb5e4cd..bbbd7f4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,13 +35,13 @@ jobs: run: cargo build --release --target ${{ matrix.target }} - name: Rename artifact run: | - mv target/${{ matrix.target }}/release/mx${{ matrix.ext }} target/${{ matrix.target }}/release/mx-${{ matrix.asset_name}}${{ matrix.ext }} + mv target/${{ matrix.target }}/release/mq-task${{ matrix.ext }} target/${{ matrix.target }}/release/mq-task-${{ matrix.asset_name}}${{ matrix.ext }} - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: mx-${{ matrix.asset_name }} + name: mq-task-${{ matrix.asset_name }} path: | - target/${{ matrix.target }}/release/mx-${{ matrix.asset_name}}${{ matrix.ext }} + target/${{ matrix.target }}/release/mq-task-${{ matrix.asset_name}}${{ matrix.ext }} if-no-files-found: error retention-days: 1 @@ -56,12 +56,12 @@ jobs: - name: Download artifacts uses: actions/download-artifact@v4 with: - pattern: mx* + pattern: mq-task* path: target - name: Generate checksums run: | cd target - for file in mx*/*; do + for file in mq-task*/*; do sha256sum "$file" >> checksums.txt done mkdir checksums diff --git a/Cargo.lock b/Cargo.lock index 6c41535..c12fe8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -642,8 +642,9 @@ dependencies = [ [[package]] name = "mq-lang" -version = "0.3.0" -source = "git+https://github.com/harehare/mq.git#7f19dad90d5f50982db6d6768e33587f3b16a57b" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd548938813e0e936301dad6f03fb6ad34479ec2465531853e4bbe06300cc11" dependencies = [ "base64", "chrono", @@ -664,8 +665,9 @@ dependencies = [ [[package]] name = "mq-markdown" -version = "0.3.0" -source = "git+https://github.com/harehare/mq.git#7f19dad90d5f50982db6d6768e33587f3b16a57b" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7366687db3d93462aeb3209eabb7a28492b883a087894d02f0a1ec5bfd7aaac1" dependencies = [ "ego-tree", "itertools", @@ -678,15 +680,7 @@ dependencies = [ ] [[package]] -name = "mq-test" -version = "0.3.0" -source = "git+https://github.com/harehare/mq.git#7f19dad90d5f50982db6d6768e33587f3b16a57b" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "mx" +name = "mq-task" version = "0.1.1" dependencies = [ "clap", @@ -694,7 +688,6 @@ dependencies = [ "miette", "mq-lang", "mq-markdown", - "mq-test", "serde", "serde_json", "thiserror", @@ -912,9 +905,9 @@ dependencies = [ [[package]] name = "regex-lite" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943f41321c63ef1c92fd763bfe054d2668f7f225a5c29f0105903dc2fc04ba30" +checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" [[package]] name = "rustc-demangle" @@ -1087,12 +1080,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smol_str" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9676b89cd56310a87b93dec47b11af744f34d5fc9f367b829474eec0a891350d" +checksum = "3498b0a27f93ef1402f20eefacfaa1691272ac4eca1cdc8c596cb0a245d6cbf5" dependencies = [ "borsh", - "serde", + "serde_core", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index eba3419..8fd337c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,35 +1,33 @@ [package] authors = ["Takahiro Sato "] categories = ["command-line-utilities", "text-processing"] -description = "Core language implementation for mq query language" +description = "A task runner using Markdown" edition = "2024" homepage = "https://mqlang.org/" keywords = ["markdown", "jq", "task runner"] license = "MIT" -name = "mx" +name = "mq-task" readme = "README.md" repository = "https://github.com/harehare/mq" version = "0.1.1" [[bin]] -name = "mx" +name = "mq-task" path = "src/main.rs" [lib] -name = "mx" +name = "mq_task" path = "src/lib.rs" [dependencies] clap = {version = "4.5.48", features = ["derive"]} colored = "2.1" miette = {version = "7.6.0", features = ["fancy"]} -mq-lang = {git = "https://github.com/harehare/mq.git", package = "mq-lang"} -mq-markdown = {git = "https://github.com/harehare/mq.git", package = "mq-markdown"} +mq-lang = "0.5.5" +mq-markdown = "0.5.5" serde = {version = "1.0", features = ["derive"]} serde_json = "1.0" thiserror = "2.0.17" toml = "0.8.21" which = "7.0.1" -[dev-dependencies] -mq-test = {git = "https://github.com/harehare/mq.git", package = "mq-test"} diff --git a/README.md b/README.md index 6371acc..bce2fbd 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,19 @@ -

mx

+

mq-task

Markdown Task Runner -[![ci](https://github.com/harehare/mx/actions/workflows/ci.yml/badge.svg)](https://github.com/harehare/mx/actions/workflows/ci.yml) +[![ci](https://github.com/harehare/mq-task/actions/workflows/ci.yml/badge.svg)](https://github.com/harehare/mq-task/actions/workflows/ci.yml) -`mx` is a task runner that executes code blocks in Markdown files based on section titles. +`mq-task` is a task runner that executes code blocks in Markdown files based on section titles. It is implemented using [mq](https://github.com/harehare/mq), a jq-like command-line tool for Markdown processing, to parse and extract sections from Markdown documents. ![demo](assets/demo.gif) > [!WARNING] -> `mx` is currently under active development. +> `mq-task` is currently under active development. + +> [!NOTE] +> This project was previously named `mx` and has been renamed to `mq-task`. ## Features @@ -25,18 +28,18 @@ It is implemented using [mq](https://github.com/harehare/mq), a jq-like command- ### Quick Install ```bash -curl -sSL https://raw.githubusercontent.com/harehare/mx/refs/heads/main/bin/install.sh | bash +curl -sSL https://raw.githubusercontent.com/harehare/mq-task/refs/heads/main/bin/install.sh | bash ``` The installer will: - Download the latest mq binary for your platform -- Install it to `~/.mx/bin/` +- Install it to `~/.mq/bin/` - Update your shell profile to add mq to your PATH ### Cargo ```sh -$ cargo install --git https://github.com/harehare/mx.git +$ cargo install --git https://github.com/harehare/mq-task.git ``` ## Usage @@ -45,17 +48,17 @@ $ cargo install --git https://github.com/harehare/mx.git ```bash # Run from README.md (default) -mx "Task Name" +mq-task "Task Name" # Run from a specific file -mx -f tasks.md "Task Name" +mq-task -f tasks.md "Task Name" ``` ### Run a task (explicit) ```bash -mx run "Task Name" -mx run --file tasks.md "Task Name" +mq-task run "Task Name" +mq-task run --file tasks.md "Task Name" ``` ### Pass arguments to a task @@ -64,13 +67,13 @@ You can pass arguments to your task using `--` separator: ```bash # Pass arguments to a task -mx "Task Name" -- arg1 arg2 arg3 +mq-task "Task Name" -- arg1 arg2 arg3 # With explicit run command -mx run "Task Name" -- arg1 arg2 arg3 +mq-task run "Task Name" -- arg1 arg2 arg3 # From a specific file -mx -f tasks.md "Task Name" -- arg1 arg2 +mq-task -f tasks.md "Task Name" -- arg1 arg2 ``` Arguments are accessible via environment variables: @@ -93,24 +96,24 @@ echo "Second arg: $MX_ARG_1" ```bash # List tasks from README.md (default) -mx +mq-task # List tasks from a specific file -mx -f tasks.md -mx list --file tasks.md +mq-task -f tasks.md +mq-task list --file tasks.md ``` ### Initialize configuration ```bash -mx init +mq-task init ``` -This creates an `mx.toml` file with default runtime settings. +This creates an `mq-task.toml` file with default runtime settings. ## Configuration -Create an `mx.toml` file to customize runtime behavior: +Create an `mq-task.toml` file to customize runtime behavior: ```toml # Heading level for sections (default: 2, i.e., ## headings) @@ -163,14 +166,14 @@ execution_mode = "file" ```bash # Using shorthand (from tasks.md by default) -mx Build +mq-task Build # From a specific file -mx -f tasks.md Build +mq-task -f tasks.md Build # Using explicit run command -mx run Build -mx run --file tasks.md Build +mq-task run Build +mq-task run --file tasks.md Build ``` ## License diff --git a/assets/demo.tape b/assets/demo.tape index b2160d7..3c3fa59 100644 --- a/assets/demo.tape +++ b/assets/demo.tape @@ -17,22 +17,22 @@ Type "clear" Enter Show -Type `mx` +Type `mq-task` Enter Sleep 1.5s -Type `mx JavaScript` +Type `mq-task JavaScript` Enter Sleep 1.5s -Type `mx Go` +Type `mq-task Go` Enter Sleep 1.5s -Type `mx Python` +Type `mq-task Python` Enter Sleep 1.5s -Type `mx mq` +Type `mq-task mq` Enter Sleep 1.5s diff --git a/bin/install.sh b/bin/install.sh index dc6e09c..1489787 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -2,11 +2,11 @@ set -e -# mx installation script +# mq-task installation script -readonly MX_REPO="harehare/mx" -readonly MX_INSTALL_DIR="$HOME/.mq" -readonly MX_BIN_DIR="$MX_INSTALL_DIR/bin" +readonly MQ_TASK_REPO="harehare/mq-task" +readonly MQ_TASK_INSTALL_DIR="$HOME/.mq-task" +readonly MQ_TASK_BIN_DIR="$MQ_TASK_INSTALL_DIR/bin" # Colors for output @@ -33,23 +33,23 @@ error() { exit 1 } -# Display the mx logo +# Display the mq-task logo show_logo() { cat << 'EOF' - ███╗ ███╗ ██╗ ██╗ - ████╗ ████║ ╚██╗██╔╝ - ██╔████╔██║ ╚███╔╝ - ██║╚██╔╝██║ ██╔██╗ - ██║ ╚═╝ ██║ ██╔╝ ██╗ - ╚═╝ ╚═╝ ╚═╝ ╚═╝ + ███╗ ███╗ ██████╗ ████████╗ █████╗ ███████╗██╗ ██╗ + ████╗ ████║ ██╔═══██╗ ╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝ + ██╔████╔██║ ██║ ██║ █████╗ ██║ ███████║███████╗█████╔╝ + ██║╚██╔╝██║ ██║▄▄ ██║ ╚════╝ ██║ ██╔══██║╚════██║██╔═██╗ + ██║ ╚═╝ ██║ ╚██████╔╝ ██║ ██║ ██║███████║██║ ██╗ + ╚═╝ ╚═╝ ╚══▀▀═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ EOF - echo -e "${BOLD}${CYAN} Markdown Task Runner${NC}" - echo -e "${BLUE} mx is a task runner that executes code${NC}" + echo -e "${BOLD}${CYAN} Markdown Task Runner${NC}" + echo -e "${BLUE} mq-task is a task runner that executes code${NC}" echo -e "${BLUE} blocks in Markdown files based on sections${NC}" echo "" - echo -e "${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" } @@ -89,7 +89,7 @@ detect_arch() { # Get the latest release version from GitHub get_latest_version() { local version - version=$(curl -s "https://api.github.com/repos/$MX_REPO/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + version=$(curl -s "https://api.github.com/repos/$MQ_TASK_REPO/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') if [[ -z "$version" ]]; then error "Failed to get the latest version" @@ -114,13 +114,13 @@ get_download_url() { target="${arch}-unknown-linux-gnu" fi - echo "https://github.com/$MX_REPO/releases/download/$version/mx-${target}${ext}" + echo "https://github.com/$MQ_TASK_REPO/releases/download/$version/mq-task-${target}${ext}" } # Download checksums file download_checksums() { local version="$1" - local checksums_url="https://github.com/$MX_REPO/releases/download/$version/checksums.txt" + local checksums_url="https://github.com/$MQ_TASK_REPO/releases/download/$version/checksums.txt" local checksums_file checksums_file=$(mktemp) @@ -178,19 +178,19 @@ verify_checksum() { fi } -# Download and install mx -install_mx() { +# Download and install mq-task +install_mq_task() { local version="$1" local os="$2" local arch="$3" local download_url - local binary_name="mx" + local binary_name="mq-task" local ext="" local target="" if [[ "$os" == "windows" ]]; then ext=".exe" - binary_name="mx.exe" + binary_name="mq-task.exe" target="${arch}-pc-windows-msvc" elif [[ "$os" == "darwin" ]]; then target="${arch}-apple-darwin" @@ -200,7 +200,7 @@ install_mx() { download_url=$(get_download_url "$version" "$os" "$arch") - log "Downloading mx $version for $os/$arch..." + log "Downloading mq-task $version for $os/$arch..." log "Download URL: $download_url" # Download checksums file @@ -208,18 +208,18 @@ install_mx() { checksums_file=$(download_checksums "$version") # Create installation directory - mkdir -p "$MX_BIN_DIR" + mkdir -p "$MQ_TASK_BIN_DIR" # Download the binary local temp_file temp_file=$(mktemp) if ! curl -L --progress-bar "$download_url" -o "$temp_file"; then - error "Failed to download mx binary" + error "Failed to download mq-task binary" fi # Verify checksum - local release_binary_name="mx-${target}${ext}" + local release_binary_name="mq-task-${target}${ext}" if [[ -n "$checksums_file" && -f "$checksums_file" ]]; then if ! verify_checksum "$temp_file" "$checksums_file" "$release_binary_name"; then rm -f "$checksums_file" @@ -232,22 +232,22 @@ install_mx() { fi # Move and make executable - mv "$temp_file" "$MX_BIN_DIR/$binary_name" - chmod +x "$MX_BIN_DIR/$binary_name" + mv "$temp_file" "$MQ_TASK_BIN_DIR/$binary_name" + chmod +x "$MQ_TASK_BIN_DIR/$binary_name" - # Create mq-task symlink to mx + # Create mq-task symlink to mq-task local symlink_name="mq-task" if [[ "$os" == "windows" ]]; then symlink_name="mq-task.exe" fi - ln -sf "$MX_BIN_DIR/$binary_name" "$MX_BIN_DIR/$symlink_name" - log "Created symlink: $MX_BIN_DIR/$symlink_name -> $MX_BIN_DIR/$binary_name" + ln -sf "$MQ_TASK_BIN_DIR/$binary_name" "$MQ_TASK_BIN_DIR/$symlink_name" + log "Created symlink: $MQ_TASK_BIN_DIR/$symlink_name -> $MQ_TASK_BIN_DIR/$binary_name" - log "mx installed successfully to $MX_BIN_DIR/$binary_name" + log "mq-task installed successfully to $MQ_TASK_BIN_DIR/$binary_name" } -# Add mx to PATH by updating shell profile +# Add mq-task to PATH by updating shell profile update_shell_profile() { local shell_profile="" local shell_name @@ -277,36 +277,36 @@ update_shell_profile() { if [[ -n "$shell_profile" ]]; then local path_export if [[ "$shell_name" == "fish" ]]; then - path_export="set -gx PATH \$PATH $MX_BIN_DIR" + path_export="set -gx PATH \$PATH $MQ_TASK_BIN_DIR" else - path_export="export PATH=\"\$PATH:$MX_BIN_DIR\"" + path_export="export PATH=\"\$PATH:$MQ_TASK_BIN_DIR\"" fi - if ! grep -q "$MX_BIN_DIR" "$shell_profile" 2>/dev/null; then + if ! grep -q "$MQ_TASK_BIN_DIR" "$shell_profile" 2>/dev/null; then echo "" >> "$shell_profile" - echo "# Added by mx installer" >> "$shell_profile" + echo "# Added by mq-task installer" >> "$shell_profile" echo "$path_export" >> "$shell_profile" - log "Added $MX_BIN_DIR to PATH in $shell_profile" + log "Added $MQ_TASK_BIN_DIR to PATH in $shell_profile" else - warn "$MX_BIN_DIR already exists in $shell_profile" + warn "$MQ_TASK_BIN_DIR already exists in $shell_profile" fi else warn "Could not detect shell profile to update" - warn "Please manually add $MX_BIN_DIR to your PATH" + warn "Please manually add $MQ_TASK_BIN_DIR to your PATH" fi } # Verify installation verify_installation() { - # Check mx installation - if [[ -x "$MX_BIN_DIR/mx" ]] || [[ -x "$MX_BIN_DIR/mx.exe" ]]; then - log "✓ mx installation verified" + # Check mq-task installation + if [[ -x "$MQ_TASK_BIN_DIR/mq-task" ]] || [[ -x "$MQ_TASK_BIN_DIR/mq-task.exe" ]]; then + log "✓ mq-task installation verified" else - error "mx installation verification failed" + error "mq-task installation verification failed" fi # Check mq-task symlink - if [[ -L "$MX_BIN_DIR/mq-task" ]] || [[ -L "$MX_BIN_DIR/mq-task.exe" ]]; then + if [[ -L "$MQ_TASK_BIN_DIR/mq-task" ]] || [[ -L "$MQ_TASK_BIN_DIR/mq-task.exe" ]]; then log "✓ mq-task symlink verified" else error "mq-task symlink verification failed" @@ -320,7 +320,7 @@ verify_installation() { show_post_install() { echo "" echo -e "${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo -e "${BOLD}${GREEN}✨ mx installed successfully! ✨${NC}" + echo -e "${BOLD}${GREEN}✨ mq-task installed successfully! ✨${NC}" echo -e "${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" echo -e "${BOLD}${CYAN}🚀 Getting Started:${NC}" @@ -329,19 +329,19 @@ show_post_install() { echo -e " ${CYAN}source ~/.bashrc${NC} ${BLUE}(or your shell's profile)${NC}" echo "" echo -e " ${YELLOW}2.${NC} Verify the installation:" - echo -e " ${CYAN}mx --version${NC}" + echo -e " ${CYAN}mq-task --version${NC}" echo "" echo -e " ${YELLOW}3.${NC} Get help:" - echo -e " ${CYAN}mx --help${NC}" + echo -e " ${CYAN}mq-task --help${NC}" echo "" echo -e "${BOLD}${CYAN}⚡ Quick Examples:${NC}" - echo -e " ${GREEN}▶${NC} ${CYAN}mx # List tasks from README.md${NC}" - echo -e " ${GREEN}▶${NC} ${CYAN}mx \"Task Name\" # Run a task from README.md${NC}" - echo -e " ${GREEN}▶${NC} ${CYAN}mx -f tasks.md # List tasks from tasks.md${NC}" - echo -e " ${GREEN}▶${NC} ${CYAN}mx init # Initialize mx.toml configuration${NC}" + echo -e " ${GREEN}▶${NC} ${CYAN}mq-task # List tasks from README.md${NC}" + echo -e " ${GREEN}▶${NC} ${CYAN}mq-task \"Task Name\" # Run a task from README.md${NC}" + echo -e " ${GREEN}▶${NC} ${CYAN}mq-task -f tasks.md # List tasks from tasks.md${NC}" + echo -e " ${GREEN}▶${NC} ${CYAN}mq-task init # Initialize mq-task.toml configuration${NC}" echo "" echo -e "${BOLD}${CYAN}📚 Learn More:${NC}" - echo -e " ${GREEN}▶${NC} Repository: ${BLUE}https://github.com/$MX_REPO${NC}" + echo -e " ${GREEN}▶${NC} Repository: ${BLUE}https://github.com/$MQ_TASK_REPO${NC}" echo "" echo -e "${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" @@ -367,8 +367,8 @@ main() { version=$(get_latest_version) log "Latest version: $version" - # Install mx - install_mx "$version" "$os" "$arch" + # Install mq-task + install_mq_task "$version" "$os" "$arch" # Update shell profile update_shell_profile @@ -384,7 +384,7 @@ main() { while [[ $# -gt 0 ]]; do case $1 in --help|-h) - echo "mx installation script" + echo "mq-task installation script" echo "" echo "Usage: $0 [options]" echo "" @@ -394,7 +394,7 @@ while [[ $# -gt 0 ]]; do exit 0 ;; --version|-v) - echo "mx installer v1.0.0" + echo "mq-task installer v1.0.0" exit 0 ;; *) diff --git a/sections.mq b/sections.mq index ec1dab6..0c47caa 100644 --- a/sections.mq +++ b/sections.mq @@ -34,11 +34,12 @@ end def sections_with_code(md_nodes, level): let section_list = extract_sections(md_nodes, level) | map(section_list, fn(section): - let title = get(section, "title") - | let lvl = get(section, "level") - | let content = get(section, "content") + let title = section["title"] + | let level = section["level"] + | let content = section["content"] | let codes = extract_code_blocks(content) | let description = extract_description(content) - | {"title": title, "level": lvl, "codes": codes, "description": description} + | {"title": title, "level": level, "codes": codes, "description": description} end) end + diff --git a/src/config.rs b/src/config.rs index fa6b350..80dca67 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -//! Configuration for mx task runner +//! Configuration for mq_task task runner use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -74,7 +74,7 @@ impl RuntimeConfig { } } -/// Configuration for mx task runner +/// Configuration for mq_task task runner #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { /// Runtime mappings: language -> command or detailed config @@ -177,32 +177,59 @@ fn default_runtimes() -> HashMap { let mut runtimes = HashMap::new(); // Languages with stdin execution mode (default) - runtimes.insert("bash".to_string(), RuntimeConfig::Simple("bash".to_string())); + runtimes.insert( + "bash".to_string(), + RuntimeConfig::Simple("bash".to_string()), + ); runtimes.insert("sh".to_string(), RuntimeConfig::Simple("sh".to_string())); - runtimes.insert("python".to_string(), RuntimeConfig::Simple("python3".to_string())); - runtimes.insert("ruby".to_string(), RuntimeConfig::Simple("ruby".to_string())); - runtimes.insert("node".to_string(), RuntimeConfig::Simple("node".to_string())); - runtimes.insert("javascript".to_string(), RuntimeConfig::Simple("node".to_string())); + runtimes.insert( + "python".to_string(), + RuntimeConfig::Simple("python3".to_string()), + ); + runtimes.insert( + "ruby".to_string(), + RuntimeConfig::Simple("ruby".to_string()), + ); + runtimes.insert( + "node".to_string(), + RuntimeConfig::Simple("node".to_string()), + ); + runtimes.insert( + "javascript".to_string(), + RuntimeConfig::Simple("node".to_string()), + ); runtimes.insert("js".to_string(), RuntimeConfig::Simple("node".to_string())); runtimes.insert("php".to_string(), RuntimeConfig::Simple("php".to_string())); - runtimes.insert("perl".to_string(), RuntimeConfig::Simple("perl".to_string())); + runtimes.insert( + "perl".to_string(), + RuntimeConfig::Simple("perl".to_string()), + ); runtimes.insert("jq".to_string(), RuntimeConfig::Simple("jq".to_string())); // Go requires file-based execution - runtimes.insert("go".to_string(), RuntimeConfig::Detailed { - command: "go run".to_string(), - execution_mode: ExecutionMode::File, - }); - runtimes.insert("golang".to_string(), RuntimeConfig::Detailed { - command: "go run".to_string(), - execution_mode: ExecutionMode::File, - }); + runtimes.insert( + "go".to_string(), + RuntimeConfig::Detailed { + command: "go run".to_string(), + execution_mode: ExecutionMode::File, + }, + ); + runtimes.insert( + "golang".to_string(), + RuntimeConfig::Detailed { + command: "go run".to_string(), + execution_mode: ExecutionMode::File, + }, + ); // mq requires argument-based execution - runtimes.insert("mq".to_string(), RuntimeConfig::Detailed { - command: "mq".to_string(), - execution_mode: ExecutionMode::Arg, - }); + runtimes.insert( + "mq".to_string(), + RuntimeConfig::Detailed { + command: "mq".to_string(), + execution_mode: ExecutionMode::Arg, + }, + ); runtimes } diff --git a/src/error.rs b/src/error.rs index a4cfc5f..e1f1cac 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,11 +1,11 @@ -//! Error types for mx +//! Error types for mq_task use thiserror::Error; -/// Result type for mx operations +/// Result type for mq_task operations pub type Result = std::result::Result; -/// Error types for mx operations +/// Error types for mq_task operations #[derive(Error, Debug)] pub enum Error { /// Error reading or parsing Markdown file diff --git a/src/lib.rs b/src/lib.rs index 28cdf34..f808597 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ -//! mx - Markdown-based task runner +//! mq_task - Markdown-based task runner //! -//! mx is a task runner that executes code blocks in Markdown files based on section titles. +//! mq_task is a task runner that executes code blocks in Markdown files based on section titles. //! It uses mq query language to parse and extract sections from Markdown documents. pub mod config; diff --git a/src/main.rs b/src/main.rs index 17f6517..c0159f3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,16 @@ -//! mx - Markdown-based task runner CLI +//! mq_task - Markdown-based task runner CLI use clap::{Parser, Subcommand}; use colored::*; use miette::{IntoDiagnostic, Result}; use std::path::PathBuf; -use mx::{Config, ExecutionMode, Runner}; +use mq_task::{Config, ExecutionMode, Runner}; const DEFAULT_TASKS_FILE: &str = "README.md"; #[derive(Parser)] -#[command(name = "mx")] +#[command(name = "mq_task")] #[command(about = "Markdown-based task runner", long_about = None)] #[command(version)] struct Cli { @@ -38,7 +38,11 @@ struct Cli { #[arg(short, long, value_name = "MODE")] execution_mode: Option, - /// Arguments to pass to the task (use -- to separate: mx task -- arg1 arg2) + /// Filter code blocks by language (e.g., bash, python, go) + #[arg(long, value_name = "LANG")] + lang: Option, + + /// Arguments to pass to the task (use -- to separate: mq_task task -- arg1 arg2) #[arg(last = true)] args: Vec, @@ -73,7 +77,11 @@ enum Commands { #[arg(short, long, value_name = "MODE")] execution_mode: Option, - /// Arguments to pass to the task (use -- to separate: mx run task -- arg1 arg2) + /// Filter code blocks by language (e.g., bash, python, go) + #[arg(long, value_name = "LANG")] + lang: Option, + + /// Arguments to pass to the task (use -- to separate: mq_task run task -- arg1 arg2) #[arg(last = true)] args: Vec, }, @@ -91,12 +99,16 @@ enum Commands { /// Heading level for sections (1-6) #[arg(short, long)] level: Option, + + /// Filter code blocks by language (e.g., bash, python, go) + #[arg(long, value_name = "LANG")] + lang: Option, }, /// Generate a sample configuration file Init { /// Output path for configuration file - #[arg(short, long, default_value = "mx.toml")] + #[arg(short, long, default_value = "mq_task.toml")] output: PathBuf, }, } @@ -112,21 +124,41 @@ fn main() -> Result<()> { level, runtime, execution_mode, + lang, args, - }) => run_task(file, task, config, level, runtime, execution_mode, args)?, + }) => run_task( + file, + task, + config, + level, + runtime, + execution_mode, + lang, + args, + )?, Some(Commands::List { file, config, level, - }) => list_tasks(file, config, level)?, + lang, + }) => list_tasks(file, config, level, lang)?, Some(Commands::Init { output }) => init_config(output)?, None => { // If no subcommand, check if task is provided if let Some(task) = cli.task { - run_task(cli.file, task, cli.config, cli.level, cli.runtime, cli.execution_mode, cli.args)?; + run_task( + cli.file, + task, + cli.config, + cli.level, + cli.runtime, + cli.execution_mode, + cli.lang, + cli.args, + )?; } else { // No task provided, list available tasks - list_tasks(cli.file, cli.config, cli.level)?; + list_tasks(cli.file, cli.config, cli.level, cli.lang)?; } } } @@ -135,6 +167,7 @@ fn main() -> Result<()> { } /// Run a specific task +#[allow(clippy::too_many_arguments)] fn run_task( markdown_path: PathBuf, task_name: String, @@ -142,6 +175,7 @@ fn run_task( level: Option, runtime_overrides: Vec, execution_mode: Option, + lang_filter: Option, args: Vec, ) -> Result<()> { let mut config = load_config(config_path)?; @@ -171,7 +205,7 @@ fn run_task( println!(); runner - .run_task_with_args(&markdown_path, &task_name, &args) + .run_task_with_lang_filter(&markdown_path, &task_name, &args, lang_filter.as_deref()) .into_diagnostic()?; Ok(()) @@ -182,6 +216,7 @@ fn list_tasks( markdown_path: PathBuf, config_path: Option, level: Option, + lang_filter: Option, ) -> Result<()> { let mut config = load_config(config_path)?; @@ -192,37 +227,102 @@ fn list_tasks( let mut runner = Runner::new(config); - let sections = runner.list_task_sections(&markdown_path).into_diagnostic()?; + let sections = runner + .list_task_sections(&markdown_path) + .into_diagnostic()?; - if sections.is_empty() { - println!( - "{}", - format!("No tasks found in {}", markdown_path.display()).yellow() - ); + // Filter sections by language if specified + let filtered_sections: Vec<_> = if let Some(ref lang) = lang_filter { + sections + .into_iter() + .filter(|section| section.codes.iter().any(|code| code.lang == *lang)) + .collect() + } else { + sections + }; + + if filtered_sections.is_empty() { + if lang_filter.is_some() { + println!( + "{}", + format!( + "No tasks found with language '{}' in {}", + lang_filter.unwrap(), + markdown_path.display() + ) + .yellow() + ); + } else { + println!( + "{}", + format!("No tasks found in {}", markdown_path.display()).yellow() + ); + } return Ok(()); } let mut output = String::new(); output.push_str(&format!( - "{} {}\n\n", + "{} {}{}\n\n", "Available tasks in".bold(), - markdown_path.display().to_string().cyan() + markdown_path.display().to_string().cyan(), + if let Some(ref lang) = lang_filter { + format!(" {}", format!("(language: {})", lang).bright_black()) + } else { + String::new() + } )); - for section in sections { + for section in filtered_sections { + // Show language information if filtering is active + let lang_info = if lang_filter.is_some() { + let langs: Vec = section + .codes + .iter() + .filter_map(|code| { + if let Some(ref filter) = lang_filter { + if code.lang == *filter { + Some(code.lang.clone()) + } else { + None + } + } else { + Some(code.lang.clone()) + } + }) + .collect(); + + if !langs.is_empty() { + format!(" {}", format!("[{}]", langs.join(", ")).bright_black()) + } else { + String::new() + } + } else { + String::new() + }; + if let Some(desc) = section.description { let trimmed = desc.trim(); if !trimmed.is_empty() { output.push_str(&format!( - " {} {}\n", + " {}{} {}\n", section.title.green().bold(), + lang_info, format!("- {}", trimmed).bright_black() )); } else { - output.push_str(&format!(" {}\n", section.title.green().bold())); + output.push_str(&format!( + " {}{}\n", + section.title.green().bold(), + lang_info + )); } } else { - output.push_str(&format!(" {}\n", section.title.green().bold())); + output.push_str(&format!( + " {}{}\n", + section.title.green().bold(), + lang_info + )); } } diff --git a/src/runner.rs b/src/runner.rs index 68cb419..46163cc 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -167,11 +167,27 @@ impl Runner { } pub fn execute_section_with_args(&self, section: &Section, args: &[String]) -> Result<()> { + self.execute_section_with_lang_filter(section, args, None) + } + + pub fn execute_section_with_lang_filter( + &self, + section: &Section, + args: &[String], + lang_filter: Option<&str>, + ) -> Result<()> { for code_block in §ion.codes { if code_block.lang.is_empty() { continue; } + // Apply language filter if specified + if let Some(filter) = lang_filter { + if code_block.lang != filter { + continue; + } + } + self.execute_code_with_args(&code_block.lang, &code_block.code, args)?; } @@ -364,6 +380,17 @@ impl Runner { markdown_path: P, task_name: &str, args: &[String], + ) -> Result<()> { + self.run_task_with_lang_filter(markdown_path, task_name, args, None) + } + + /// Run a specific task with arguments and language filter + pub fn run_task_with_lang_filter>( + &mut self, + markdown_path: P, + task_name: &str, + args: &[String], + lang_filter: Option<&str>, ) -> Result<()> { let markdown = self.load_markdown(markdown_path)?; let sections = self.extract_sections(&markdown)?; @@ -372,7 +399,7 @@ impl Runner { .find_section(§ions, task_name) .ok_or_else(|| Error::SectionNotFound(task_name.to_string()))?; - self.execute_section_with_args(section, args) + self.execute_section_with_lang_filter(section, args, lang_filter) } /// List all available tasks (sections) in a Markdown file @@ -452,4 +479,66 @@ print("world") let not_found = runner.find_section(§ions, "Task 3"); assert!(not_found.is_none()); } + + #[test] + fn test_language_filter() { + let section = Section { + title: "Mixed Task".to_string(), + level: 2, + codes: vec![ + CodeBlock { + lang: "bash".to_string(), + code: "echo 'bash code'".to_string(), + }, + CodeBlock { + lang: "python".to_string(), + code: "print('python code')".to_string(), + }, + CodeBlock { + lang: "bash".to_string(), + code: "echo 'more bash'".to_string(), + }, + ], + description: None, + }; + + let runner = Runner::with_default_config(); + + // Test filtering for bash only - this will fail if bash is not available, + // but demonstrates the filtering logic + let result = runner.execute_section_with_lang_filter(§ion, &[], Some("bash")); + // We can't guarantee bash is available in test environment, so we just check + // that the method runs without panicking + let _ = result; + } + + #[test] + fn test_extract_sections_with_multiple_languages() { + let markdown = r#"# Title + +## Mixed Task + +```bash +echo "bash code" +``` + +```python +print("python code") +``` + +```bash +echo "more bash" +``` +"#; + + let mut runner = Runner::with_default_config(); + let sections = runner.extract_sections(markdown).unwrap(); + + assert_eq!(sections.len(), 1); + assert_eq!(sections[0].title, "Mixed Task"); + assert_eq!(sections[0].codes.len(), 3); + assert_eq!(sections[0].codes[0].lang, "bash"); + assert_eq!(sections[0].codes[1].lang, "python"); + assert_eq!(sections[0].codes[2].lang, "bash"); + } } diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 61bad06..782081c 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,4 +1,4 @@ -use mx::{runner::CodeBlock, Config, Runner}; +use mq_task::{runner::CodeBlock, Config, Runner}; use std::fs; #[test] @@ -88,7 +88,7 @@ print("world") ``` "#; - let config = mx::Config { + let config = mq_task::Config { heading_level: 3, ..Default::default() };