cliclack_yaml 0.1.1

A YAML-based configuration layer for cliclack interactive CLI prompts
Documentation
# cliclack-yaml

A Rust crate that lets you configure [cliclack](https://crates.io/crates/cliclack) interactive CLI prompts using YAML files instead of code.

## Features

- Define CLI prompts entirely in YAML — no Rust code needed per prompt
- 11 step types: `intro`, `outro`, `input`, `password`, `confirm`, `select`, `multiselect`, `spinner`, `print`, `progress`, `clearscreen`
- Variable interpolation with `{variable_name}` syntax
- Conditional step execution based on previous step results
- Text styling (colors, backgrounds) for intro/outro steps
- Input validation (not-empty, regex, min-length)

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
cliclack_yaml = "0.1.1"
```

## Quick Start

### 1. Set up a prompts folder

Create a folder in your project to hold YAML prompt files, for example:

```
src/
  prompts/
    auth/
      login.yaml
    init/
      setup.yaml
    greeting.yaml
```

### 2. Configure cliclack_yaml in `main.rs`

```rust
use std::env;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Set the application root (typically the current working directory)
    cliclack_yaml::set_app_root(env::current_dir()?)?;

    // Set the prompt folder relative to app root
    cliclack_yaml::set_prompt_folder("src/prompts")?;

    // Run a prompt defined in src/prompts/greeting.yaml
    let results = cliclack_yaml::render_prompt_interaction("greeting")?;
    println!("User said: {:?}", results);

    Ok(())
}
```

### 3. Write YAML prompt files

**Simple greeting (`src/prompts/greeting.yaml`):**

```yaml
- type: intro
  text: " Welcome to My CLI "
  style:
    color: black
    background: cyan

- type: input
  prompt: "What is your name?"
  output: "name"

- type: outro
  text: " Hello, {name}! "
  style:
    color: black
    background: green
```

**Login flow with password (`src/prompts/auth/login.yaml`):**

```yaml
- type: intro
  text: " 🔐 Login "
  style:
    color: black
    background: cyan

- type: input
  prompt: "Email address"
  output: "email"

- type: password
  prompt: "Password"
  output: "password"

- type: outro
  text: " Authenticating... "
  style:
    color: black
    background: blue
```

Run nested prompts using the folder path:

```rust
let results = cliclack_yaml::render_prompt_interaction("auth/login")?;
let email = results.get("email").unwrap();
```

## Passing Variables

You can pass variables into prompts and reference them with `{variable_name}`:

```rust
let results = cliclack_yaml::render_prompt_interaction_with_vars(
    "init/setup",
    vec![("config_path", "/home/user/.config/myapp")],
)?;
```

```yaml
- type: print
  text: "Config will be created at: {config_path}"
```

## YAML Step Reference

### clearscreen

Clears the terminal screen.

```yaml
- type: clearscreen
```

### intro

Displays a styled introduction banner.

```yaml
- type: intro
  text: " My Application "
  style:
    color: black        # black, white, red, green, blue
    background: cyan    # cyan, green, red, yellow
```

### input

Prompts for text input with optional validation.

```yaml
- type: input
  prompt: "Project name"
  placeholder: "my-project"
  output: "project_name"
  validate:
    - type: not-empty
      message: "Project name is required"
    - type: must-start-with-letter
      message: "Must start with a letter"
    - type: regex
      pattern: "^[a-z][a-z0-9-]*$"
      message: "Only lowercase letters, numbers, and hyphens"
```

### password

Prompts for password input with masking.

```yaml
- type: password
  prompt: "Enter password"
  output: "password"
  mask: '*'
  confirm_password: true
  confirm_prompt: "Confirm password"
  validate:
    - type: not-empty
      message: "Password is required"
    - type: min-length
      length: 8
      message: "Must be at least 8 characters"
```

### confirm

Prompts for yes/no confirmation. Returns `true`/`false` in the output.

```yaml
- type: confirm
  prompt: "Continue?"
  output: "confirmed"
  default: true
```

### select

Prompts the user to select one option from a list.

```yaml
- type: select
  prompt: "Choose a framework"
  output: "framework"
  items:
    - label: "React"
      value: "react"
    - label: "Vue"
      value: "vue"
      hint: "Recommended"
    - label: "Angular"
      value: "angular"
```

### multiselect

Prompts the user to select multiple options.

```yaml
- type: multiselect
  prompt: "Select features"
  output: "features"
  required: true
  items:
    - label: "Authentication"
      value: "auth"
    - label: "Database"
      value: "db"
      is_selected: true
    - label: "API"
      value: "api"
```

### spinner

Displays a spinner while executing a named function.

```yaml
- type: spinner
  start_text: "Installing dependencies..."
  stop_text: "Dependencies installed!"
  run_fn: "install_deps"
  output: "install_result"
```

> **Note:** The `run_fn` value must match a function name registered in your [`execute_function_by_name`] handler.

### print

Prints a message within the CLI frame. The `loglevel` field controls the visual style:

| `loglevel`       | Symbol | Description                                     |
|------------------|--------|-------------------------------------------------|
| *(omitted)*      | `│`    | Bar continuation — blends into the frame        |
| `none`           | `├`    | Remark — connect-left, no marker icon           |
| `info`           | `●`    | Info marker (blue)                              |
| `warning`/`warn` | `▲`    | Warning marker (yellow)                         |
| `error`          | `■`    | Error marker (red)                              |

**Plain text (no loglevel) — blends into the frame:**

```yaml
- type: print
  text: "This text appears as part of the frame"
```

**With log level marker:**

```yaml
- type: print
  loglevel: info
  text: "Config path: {config_path}"
  input: config_path  # optional: variable to read
```

**Remark style (connect-left, no icon):**

```yaml
- type: print
  loglevel: none
  text: "A remark without a marker icon"
```

> **Tip:** Use no `loglevel` for content like tables or formatted output that should blend seamlessly into the cliclack frame. Use `loglevel: none` when you want a visual separator (``) without an icon. Use `info`/`warning`/`error` for messages that need attention markers.

### progress

Displays a progress bar or spinner for long-running tasks.

```yaml
- type: progress
  progress_type: Bar
  start_message: "Downloading..."
  stop_message: "Download complete!"
  items:
    - name: "file1.zip"
      duration_ms: 2000
    - name: "file2.zip"
      duration_ms: 1500
```

### multi-progress

Displays multiple progress bars simultaneously.

```yaml
- type: multi-progress
  title: "Installing components"
  progress_bars:
    - label: "Core"
      progress_type: Bar
      duration_ms: 3000
    - label: "Plugins"
      progress_type: Spinner
      duration_ms: 2000
```

### outro

Displays a styled closing message.

```yaml
- type: outro
  text: " All done! "
  input: result_var   # optional: variable to display
  style:
    color: black
    background: green
```

## Conditional Steps

Steps can be conditionally executed based on previous step results:

```yaml
- type: confirm
  prompt: "Enable advanced mode?"
  output: "advanced"
  step_name: "advanced_check"

- type: input
  prompt: "Enter API key"
  output: "api_key"
  condition:
    parent: "advanced_check"
    value: true
```

The `input` step only runs if the user confirmed "advanced_check".

## Advanced: Embedded Prompts with Fallback

For distributing your CLI as a single binary, you can embed YAML files at compile time using [`include_dir`](https://crates.io/crates/include_dir) and fall back to them when no external prompt folder is found:

```rust
use std::collections::HashMap;
use anyhow::Result;
use include_dir::{include_dir, Dir};

// Embed all YAML files from src/prompts/ into the binary
static EMBEDDED_PROMPTS: Dir = include_dir!("src/prompts");

fn get_embedded_yaml(prompt_name: &str) -> Result<String> {
    let file_path = format!("{}.yaml", prompt_name);
    EMBEDDED_PROMPTS
        .get_file(&file_path)
        .and_then(|f| f.contents_utf8())
        .map(|s| s.to_string())
        .ok_or_else(|| anyhow::anyhow!("Prompt '{}' not found", prompt_name))
}

/// Try external folder first, fall back to embedded prompts
pub fn run_prompt(
    prompt_name: &str,
    vars: Option<HashMap<String, String>>,
) -> Result<HashMap<String, String>> {
    let yaml_str = match cliclack_yaml::get_yaml_content(prompt_name) {
        Ok(content) => content,
        Err(_) => get_embedded_yaml(prompt_name)?,
    };

    let steps: Vec<cliclack_yaml::PromptStep> = serde_yml::from_str(&yaml_str)?;
    cliclack_yaml::create_prompt_with_vars_custom(steps, vars)
}
```

This pattern lets users override prompts by placing YAML files in the configured prompt folder, while keeping defaults baked into the binary.

## License

MIT

## Examples

See the [`examples/config/`](examples/config/) folder for a complete set of white-label YAML configurations covering auth flows, initialization, config management, CRUD operations, installation progress, system checks, and showcases for every feature (conditionals, variables, validation, styling).

For a full working CLI application that integrates `cliclack_yaml` with [clap](https://crates.io/crates/clap), see the **[cliclack-yaml-cli-example](https://github.com/mibaatwork/cliclack-yaml-cli-example)** repository. It demonstrates embedded prompts, configuration management, and how to structure a white-label CLI project using this crate.