Automation

User-defined workflows over host task primitives.

← Back to use cases

Full source

This is a self-contained C++ program. Copy it, compile against the mino library, and run it.

/*
 * automation.cpp - user-defined workflows over host APIs.
 *
 * A task automation system where C++ provides task primitives
 * (create, run, check status) and mino scripts define workflows
 * that compose those primitives. Workflows are data: lists of
 * steps that the host interprets. Higher-order functions and
 * threading macros keep workflow definitions concise.
 *
 * Build:
 *   make
 *   c++ -std=c++17 -Isrc -o examples/use-cases/automation \
 *       examples/use-cases/automation.cpp src/[a-z]*.o -lm
 */

#include "mino.h"
#include <cstdio>
#include <cstring>
#include <vector>
#include <string>

/* ── Expose ────────────────────────────────────────────────────────── */

/* Tasks have a name, status, and result. The host simulates
 * execution: each task transitions from `:pending` to `:done`. */

struct Task {
    std::string name;
    std::string status;  /* "pending", "running", "done", "failed" */
    std::string result;
};

static std::vector<Task> tasks;

/* Host function: create a task, return its index. */
static mino_val_t *host_create_task(mino_state_t *S, mino_val_t *args,
                                    mino_env_t *)
{
    const char *name;
    size_t len;
    if (!mino_is_cons(args) ||
        !mino_to_string(mino_car(args), &name, &len))
        return mino_nil(S);
    tasks.push_back({std::string(name, len), "pending", ""});
    return mino_int(S, (long long)(tasks.size() - 1));
}

/* Host function: run a task (simulated). */
static mino_val_t *host_run_task(mino_state_t *S, mino_val_t *args,
                                 mino_env_t *)
{
    long long id;
    if (!mino_is_cons(args) || !mino_to_int(mino_car(args), &id))
        return mino_nil(S);
    if (id < 0 || (size_t)id >= tasks.size())
        return mino_nil(S);
    tasks[(size_t)id].status = "done";
    tasks[(size_t)id].result = "ok";
    return mino_keyword(S, "done");
}

/* Host function: get task status as a map. */
static mino_val_t *host_task_status(mino_state_t *S, mino_val_t *args,
                                    mino_env_t *)
{
    long long id;
    if (!mino_is_cons(args) || !mino_to_int(mino_car(args), &id))
        return mino_nil(S);
    if (id < 0 || (size_t)id >= tasks.size())
        return mino_nil(S);
    auto &t = tasks[(size_t)id];
    mino_val_t *ks[3], *vs[3];
    ks[0] = mino_keyword(S, "name");   vs[0] = mino_string(S, t.name.c_str());
    ks[1] = mino_keyword(S, "status"); vs[1] = mino_keyword(S, t.status.c_str());
    ks[2] = mino_keyword(S, "result"); vs[2] = mino_string(S, t.result.c_str());
    return mino_map(S, ks, vs, 3);
}

/* ── Script ────────────────────────────────────────────────────────── */

/* Workflows are built by composing task primitives.
 * `step` takes a name, creates and runs a task, returns its status.
 * `pipeline` chains steps and collects results. */

static const char *script =
    ";; Execute one step: create, run, report.\n"
    "(defn step [name]\n"
    "  (let [id (create-task name)]\n"
    "    (run-task id)\n"
    "    (task-status id)))\n"
    "\n"
    ";; Run a sequence of named steps, collect results.\n"
    "(defn pipeline [step-names]\n"
    "  (->> step-names\n"
    "       (map step)\n"
    "       (map (fn [s] [(:name s) (:status s)]))\n"
    "       (into [])))\n"
    "\n"
    ";; Conditional step: only run if predicate holds.\n"
    "(defn when-step [pred name]\n"
    "  (when pred (step name)))\n"
    "\n"
    ";; A deployment workflow.\n"
    "(defn deploy [env-name]\n"
    "  (let [steps [\"lint\" \"test\" \"build\"\n"
    "               (str \"deploy-\" env-name)\n"
    "               \"notify\"]\n"
    "        results (pipeline steps)]\n"
    "    {:environment env-name\n"
    "     :steps       results\n"
    "     :all-done?   (every? (fn [[_ s]] (= s :done)) results)}))\n"
    "\n"
    "(deploy \"staging\")\n";

/* ── Embed ─────────────────────────────────────────────────────────── */

int main()
{
    mino_state_t *S   = mino_state_new();
    mino_env_t   *env = mino_env_new_default(S);

    /* Register task primitives. */
    mino_register_fn(S, env, "create-task",  host_create_task);
    mino_register_fn(S, env, "run-task",     host_run_task);
    mino_register_fn(S, env, "task-status",  host_task_status);

    mino_val_t *result = mino_eval_string(S, script, env);

    if (result) {
        printf("workflow result:\n");
        mino_println(S, result);

        /* Show all tasks the workflow created. */
        printf("\ntask log:\n");
        for (size_t i = 0; i < tasks.size(); i++)
            printf("  [%zu] %-20s %s\n", i,
                   tasks[i].name.c_str(), tasks[i].status.c_str());
    } else {
        fprintf(stderr, "error: %s\n", mino_last_error(S));
    }

    mino_env_free(S, env);
    mino_state_free(S);
}
Build and run:
c++ -std=c++17 -O2 \
  -Imino/src -Imino/src/public -Imino/src/runtime -Imino/src/gc -Imino/src/eval \
  -Imino/src/collections -Imino/src/prim -Imino/src/async -Imino/src/interop \
  -Imino/src/diag -Imino/src/vendor/imath \
  -o use-cases/automation \
  use-cases/automation.cpp \
  mino/src/public/*.c mino/src/runtime/*.c mino/src/gc/*.c mino/src/eval/*.c \
  mino/src/collections/*.c mino/src/prim/*.c mino/src/async/*.c mino/src/interop/*.c \
  mino/src/regex/*.c mino/src/diag/*.c mino/src/vendor/imath/*.c \
  -lm
./use-cases/automation

The mino script

Workflows are built by composing task primitives. step takes a name, creates and runs a task, returns its status. pipeline chains steps and collects results.

;; Execute one step: create, run, report.
(defn step [name]
  (let [id (create-task name)]
    (run-task id)
    (task-status id)))

;; Run a sequence of named steps, collect results.
(defn pipeline [step-names]
  (->> step-names
       (map step)
       (map (fn [s] [(:name s) (:status s)]))
       (into [])))

;; Conditional step: only run if predicate holds.
(defn when-step [pred name]
  (when pred (step name)))

;; A deployment workflow.
(defn deploy [env-name]
  (let [steps [\"lint\" \"test\" \"build\"
               (str \"deploy-\" env-name)
               \"notify\"]
        results (pipeline steps)]
    {:environment env-name
     :steps       results
     :all-done?   (every? (fn [[_ s]] (= s :done)) results)}))

(deploy \"staging\")

← All use cases