<h1 id="rad-✨"><a href="#rad-✨"><img alt="rad ✨" src=""></a></h1>

[![Hex Package](](
[![Hex Docs](](

A flexible task runner companion for the Gleam build manager.

<p align="center" width="100%"><a href="#screenshot" id="screenshot"><img alt="A rad screenshot." src="" width="300"></a></p>

Rad has a variety of builtin features. Some of the more powerful include serving
documentation and watching the file system. With
[`rad docs serve`](,
you can build docs for your project and all of its dependencies, and then serve
them together over `HTTP`, like a small-scale [hexdocs](
service specific to your project. Plus, with
[`rad watch`]( you get
live reloading of any clients connected to the docs server in addition to
automated testing when saving files, for rapid feedback when coding and writing

Try this `rad` one-liner:

$ # Press `Ctrl+Z` and use the `fg` command as needed
$ rad docs serve --all --host= &; rad watch

Then, visit [`localhost:7000`](http://localhost:7000/), or open
`[your machine's LAN IP]:7000` from another device on your network, and watch
what happens when you edit and save one of your project's files!

Rad provides a helpful framework for automating repetitive actions, reducing
mental burden, and lowering the potential for maintenance errors when developing
your Gleam projects.

Make `rad` your own by customizing it to suit your projects and workflows!

## Quick Start

$ rad help       # Print help information
$ rad shell      # Start an Erlang shell
$ rad shell iex  # Start an Elixir shell
$ rad shell deno # Start a JavaScript shell
$ rad shell node # Start a JavaScript shell
$ rad tree       # Print the file structure
$ rad docs serve # Serve HTML documentation
$ rad watch      # Automate project tasks

## Requirements

[Node.js]( is required to run `rad`. Although most `rad`
commands can be executed with the [Erlang]( runtime,
`rad` will always initialize via the [Node.js]( runtime.

Additionally, some `rad` commands require [Node.js](
`>= v17.5.0`, for `fetch` support.

## Installation

### Gleam

$ gleam add rad

## Usage

### Basic Usage

You must run `rad` from your project's base directory (where `gleam.toml`

$ ./build/packages/rad/priv/rad <subcommand> [flags]

For convenience when invoking `rad`, first perform one of the following
operations in a manner consistent with your shell of choice. The goal is to get
`priv/rad` or `priv/rad.ps1` somewhere in your `$PATH`; there are many ways to
accomplish this, these are merely some suggestions.

#### POSIX (Bash-Like)

<summary><strong>Alias <code>priv/rad</code></strong></summary>

$ alias rad='./build/packages/rad/priv/rad'
$ # To persist across sessions, add it to your .bashrc or an analogous file

<summary><strong>Copy <code>priv/rad</code> into your <code>$PATH</code></strong></summary>

$ sudo cp ./build/packages/rad/priv/rad /usr/local/bin/

<summary><strong>Link <code>priv/rad</code> into your <code>$PATH</code></strong></summary>

$ sudo git clone /usr/local/share/rad
$ sudo ln -s ../share/rad/priv/rad /usr/local/bin/

#### PowerShell

<summary><strong>Alias <code>priv/rad.ps1</code></strong></summary>

PS> function rad { ./build/packages/rad/priv/rad.ps1 @Args }
PS> # To persist across sessions, add it to your $profile file

<summary><strong>Copy <code>priv/rad.ps1</code> into your <code>$env:PATH</code></strong></summary>

PS> # Create "${HOME}/bin"
PS> New-Item -Type Directory -Force "${HOME}/bin"

PS> # Add "${HOME}/bin" to $env:PATH
PS> $path = "${HOME}/bin"
PS> $sep = ";" # Use ":" for *nix
PS> $paths = $env:PATH -split $sep
PS> if ($paths -notcontains $path) {
      $env:PATH = (@($path) + $paths | where { $_ }) -join $sep
PS> # To persist across sessions, add the previous lines to your $profile file

PS> # Copy rad.ps1
PS> Copy-Item "./build/packages/rad/priv/rad.ps1" -Destination "${HOME}/bin/"

<summary><strong>Link <code>priv/rad.ps1</code> into your <code>$env:PATH</code></strong></summary>

PS> # Create "${HOME}/bin"
PS> New-Item -Type Directory -Force "${HOME}/bin"

PS> # Add "${HOME}/bin" to $env:PATH
PS> $path = "${HOME}/bin"
PS> $sep = ";" # Use ":" for *nix
PS> $paths = $env:PATH -split $sep
PS> if ($paths -notcontains $path) {
      $env:PATH = (@($path) + $paths | where { $_ }) -join $sep
PS> # To persist across sessions, add the previous lines to your $profile file

PS> # Create "${HOME}/src"
PS> New-Item -Type Directory -Force "${HOME}/src"

PS> # Clone the rad repository
PS> git clone "${HOME}/src/rad"

PS> # Link rad.ps1
PS> New-Item -ItemType SymbolicLink -Target "../src/rad/priv/rad.ps1" -Path "${HOME}/bin/rad.ps1"


After completing one of the previous operations, you should be able to invoke
`rad` as follows.

$ rad <subcommand> [flags]

More information about `rad`'s standard subcommands can be found in
[`rad` hexdocs]( or with
[`rad help`](

### Configuration

You can extend `rad` with your project's `gleam.toml` configuration file.

workbook = "workbook"
targets = ["erlang", "javascript"]
with = "javascript"

name = "javascript"
check = ["rome", "ci", "--indent-style=space", "src", "test"]
run = ["rome", "format", "--indent-style=space", "--write", "src", "test"]

path = ["purple", "heart"]
run = ["echo", "💜 The dream you'll have here is a dream within a dream."]
shortdoc = "💜 The dream you'll have here is a dream within a dream."

path = ["sparkles"]
run = ["echo", "✨ It's been a long road getting here..."]

path = ["sparkling", "heart"]
run = ["sh", "-c", "echo '💖 I was staring out the window and there it was, just fluttering there...' $(rad version)!"]

#### `[rad]`

In the base `rad` table, you can define a custom
[`workbook`]( (see
[Advanced Usage](#advanced-usage)), a default array of compilation `targets`
that `rad` tasks like `build` and `test` will cover, and a default runtime for
`rad` to run all tasks `with` (some tasks, like
[`shell`](, will not
succeed `with` the `erlang` runtime; `javascript` is the default).

#### `[[rad.formatters]]`

The [`rad format`](
task runs the `gleam` formatter along with any formatters defined in your
`gleam.toml` config via the `rad.formatters` table array. The `name`, `check`,
and `run` fields are all mandatory for each formatter you define.

#### `[[rad.tasks]]`

You can define your own basic tasks via the `rad.tasks` table array. Few
assumptions are made about your environment, so `rad` won't run your commands
through any shell interpreter on its own; however, the scope of your commands is
virtually unlimited, and you're free to specify your shell interpreter of
choice. The `path` and `run` fields are mandatory for each task you define,
while the `shortdoc` field is optional. Both `path` and `run` must be formatted
as arrays of strings; the strings will generally be single words corresponding
to command line arguments. If your task has a `shortdoc`, it will appear in `rad
help` information as long as it has a visible parent `path`.

### Advanced Usage

The standard `rad` workbook module exemplifies how to create a custom
`workbook.gleam` module for your own project.

By providing [`main`](
and [`workbook`](
functions in your project's `workbook.gleam` file, you can extend `rad`'s
[`workbook`]( with
your own or write one entirely from scratch, optionally making it and your
[`Runner`]( available for any
dependent projects!

### Examples

// src/workbook.gleam

import gleam/dynamic
import gleam/json
import gleam/result
import glint.{CommandInput}
import glint/flag
import rad
import rad/task.{Result, Task}
import rad/util
import rad/workbook.{Workbook}
import rad/workbook/standard
import snag

pub fn main() -> Nil {
  |> rad.do_main

pub fn workbook() -> Workbook {
  let standard_workbook = standard.workbook()
  assert Ok(root_task) =
    |> workbook.get(from: standard_workbook)
  assert Ok(help_task) =
    |> workbook.get(from: standard_workbook)

  |> workbook.task(
    add: root
    |> task.runner(into: root_task),
  |> workbook.task(
    add: workbook
    |> task.runner(into: help_task),
  |> workbook.task(
    add: ["commit"]
    |> commit)
    |> task.shortdoc("Generate a questionable commit message"),

pub fn root(input: CommandInput, task: Task(Result)) -> Result {
  assert Ok(flag.B(ver)) =
    |> flag.get_value(from: input.flags)
  case ver {
    True -> standard.root(input, task)
    False -> workbook)(input, task)

pub fn commit(_input: CommandInput, _task: Task(Result)) -> Result {
  let script =
      .then(async (response) => [response.status, await response.text()])
        ([status, text]) =>
          console.log(JSON.stringify({ status: status, text: text.trim() }))

  try output =
    ["--eval", script]
    |> util.javascript_run(opt: [])

  let snag ="service unreachable")

  try status =
    |> json.decode(using: dynamic.field(named: "status", of:
    |> result.replace_error(snag)

  case status < 400 {
    True ->
      |> json.decode(using: dynamic.field(named: "text", of: dynamic.string))
      |> result.replace_error(snag)
    False -> Error(snag)

#### In the shell

$ rad commit
Chuck Norris Emailed Me This Patch... I'm Not Going To Question It

## Further Reading

For more information on all things `rad`, read the