Skip to content

Documentation and the command line ​

Pure. Doc comments end to end — summary, description, @param, @return, @example, @complete in all four forms, @hidden — and what the CLI does with a signature.

bash
lask run --help              # the listing, from the first line of each comment
lask run publish --help      # one task, from the same comment
lask eval publish --tag stable
lask eval --stdout-encode pretty-json matrix

The thing to take away: - maps to _, so publish(--dry_run = false) is --dry-run on the command line, and the comment above a task is the only place its help is written.

To try the commands above, paste the source below into main.lask in an empty directory, or clone the repository:

bash
git clone https://github.com/lask-task-runner/lask.git
cd lask/example/02-language/14-docs-and-cli

Source ​

This is example/02-language/14-docs-and-cli in the lask repository.

lask
// Documentation and the command line
//
// A task's signature is its command line, and the comment above it is
// its help. Neither is written twice.
//
//   lask run --help                  # every task in this module
//   lask run publish --help          # one task, from the comment below
//   lask eval publish --tag v1.2.0 --dry-run true
//   lask eval --stdout-encode pretty-json matrix

known_tags: Map<String> = {
  "latest": "the moving tag",
  "stable": "the last release",
  "edge": "built from main"
}

// Publish the image under a tag.
//
// The unbroken run of comments directly above a declaration is its
// documentation. The first paragraph is the summary that listings show;
// everything up to the first `@` tag is the long description that
// `--help` and the editor's hover print underneath it.
//
// @param tag      Which tag to publish under.
// @param dry_run  Report what would happen instead of doing it.
// @return         A line describing what was published.
// @example        lask eval publish --tag stable
// @complete tag   @keys known_tags
publish(--tag: String = "latest", --dry_run: Bool = false): String = do {
  // `dry_run` is `--dry-run` on the command line: `-` maps to `_`, in
  // the task name as well as in every parameter name. `@param` is
  // matched after that mapping, so `dry_run` and `dry-run` name the
  // same parameter.
  dry_run
    ? "would publish app:#{tag}"
    : "published app:#{tag} (#{get_or(known_tags, tag, "unknown tag")})"
}

// Show the tags this module knows about.
//
// `@complete <param> @keys <map>` tells the shell to offer that map's
// keys; the other forms are `@file [*.ext]`, `@dir`, and a literal list
// of choices, as on `format` below.
//
// @param tag     A tag to look up.
// @complete tag  @keys known_tags
describe_tag(--tag: String = "latest"): String = get_or(known_tags, tag, "unknown tag")

// Render a report in one of three formats.
//
// @param format    Output format.
// @param out_dir   Where the report would be written.
// @complete format text json yaml
// @complete out_dir @dir
// @example lask eval report --format json --out-dir ./build
report(--format: String = "text", --out_dir: String = "."): String =
  case (format) {
    "text" -> "report -> #{out_dir}/report.txt"
    "json" -> "report -> #{out_dir}/report.json"
    "yaml" -> "report -> #{out_dir}/report.yaml"
    else   -> fail(error(4, "unknown format: #{format}"))
  }

// Check a file the caller names.
//
// @param path       The file to check.
// @complete path    @file *.lask
check_file(path: String): String = "would check #{path}"

// Greet someone, positionally.
//
// Positional arguments are bound by position and keyword arguments by
// name, on the command line exactly as in the language: this one is
// `lask eval greet alice --loudly true`.
//
// @param name    Who to greet.
// @param loudly  Shout it.
greet(name: String, --loudly: Bool = false): String =
  if (loudly) { to_upper("hello, #{name}") } else { "hello, #{name}" }

// Return something structured, to show the output encodings.
//
// `eval` writes the return value to stdout as JSON by default;
// `--stdout-encode pretty-json` indents it and `text` writes a string
// value unquoted. `run` writes nothing at all.
//
// Note where that option goes: everything after the function name
// belongs to the *function*, so Lask's own options come before it.
//
// @example lask eval --stdout-encode pretty-json matrix
matrix(): Array<Record<name: String, image: String>> = [
  { name: "api", image: "python:3.12.14-alpine3.24" },
  { name: "web", image: "node:20.20.2-alpine3.23" }
]

// Scale the service, to show how arguments are decoded.
//
// An argument's type decides how the CLI reads it: `--arg-decode auto`,
// the default, reads `42` as a Number and `true` as a Bool where the
// parameter asks for one, and leaves text alone where it asks for a
// String.
//
// @example lask eval scale --replicas 4
scale(--replicas: Number = 1, --labels: Array<String> = []): String =
  "#{replicas} replicas, labels: #{join(labels, ",")}"

// Kept out of listings, still reachable by name.
//
// `@hidden` is for a task that exists for another task, or for a
// maintenance escape hatch: `lask run internals --help` still works.
//
// @hidden
internals(): String = "not in the listing"

// `internal`, unlike `@hidden`, is about the language: the declaration
// is not a public symbol, so no other module can import it and the CLI
// cannot invoke it at all.
internal helper(): String = "unreachable from outside"

Released under the MIT License.