Skip to content

Lorenzo-SF/alaja

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Alaja — Declarative CLI framework & terminal rendering kit for Elixir

Hex version License Version

Alaja is a declarative CLI framework and terminal rendering kit for Elixir. Define commands with a DSL, validate flags, auto-generate help, and render rich terminal output — tables, headers, boxes, bars, breadcrumbs, JSON syntax highlighting, gradients, and interactive prompts — all powered by true-color ANSI escape sequences.

Alaja is the rendering and I/O layer for the Zaguan toolchain. It depends on Pote for colour management, theme resolution, and format conversions.


Quick Start

Add alaja and pote to your mix.exs:

def deps do
  [
    {:alaja, path: "../alaja"},
    {:pote, path: "../pote"}
  ]
end

Define a CLI in 5 minutes

defmodule MyApp.CLI do
  use Alaja.CLI.Definition, otp_app: :my_app

  command "deploy", "Deploy to production" do
    flag :env, :string, default: "staging", values: ~w(staging production)
    flag :force, :boolean, default: false
    argument :version, :string, required: true

    run fn opts ->
      Alaja.print_success("Deploying v#{opts.version} to #{opts.env}...")
      if opts.force, do: Alaja.print_warning("Force mode enabled!")
    end
  end

  command "status", "Show system status" do
    run fn _opts ->
      Alaja.Components.Table.print(
        headers: ["Service", "Status", "Uptime"],
        rows: [
          ["api",     "OK",    "12d 4h"],
          ["db",      "OK",    "30d 2h"],
          ["cache",   "WARN",  "2h 15m"]
        ],
        table_border: :rounded,
        rows_2_color: [:white, :yellow, :white]
      )
    end
  end
end

Run it:

mix run -e 'MyApp.CLI.main(["deploy", "1.2.3"])'
mix run -e 'MyApp.CLI.main(["deploy", "1.2.3", "--env", "production", "--force"])'
mix run -e 'MyApp.CLI.main(["status"])'

Rendering Layer

Message printing (12 severity levels)

Alaja.print_success("Deploy completed!")      # ✓ green
Alaja.print_error("Connection refused")        # ✗ red bold
Alaja.print_warning("Disk usage above 80%")    # ⚠ yellow
Alaja.print_info("Processing 12 files...")     # ℹ cyan
Alaja.print_debug("PID: 0.1234.5")             # ⚙ purple
Alaja.print_notice("Maintenance at 02:00")     # 📢 blue
Alaja.print_alert("CPU spike detected!")       # 🔔 inverted warning
Alaja.print_critical("Database unreachable!")  # 🔥 inverted error
Alaja.print_emergency("System crash!")         # 🆘 blinking
Alaja.print_happy("All tests passed!")         # ✨
Alaja.print_sad("Build failed again...")       # ❄

# Dynamic dispatch
Alaja.Printer.print_message(:success, "Done!")
Alaja.Printer.print_message(:error, "Oops!")
Function Icon Style
print_success/1,2 Green
print_error/1,2 Red bold
print_warning/1,2 Yellow
print_info/1,2 Cyan
print_debug/1,2 Purple
print_notice/1,2 📢 Blue
print_alert/1,2 🔔 Inverted warn
print_critical/1,2 🔥 Inverted error
print_emergency/1,2 🆘 Blinking
print_happy/1,2 Happy theme
print_sad/1,2 Sad theme
print_message/2 Dynamic level

All functions accept printer options: raw: true, x:, y:, align:, verbose:, padding:.

Interactive input

alias Alaja.Printer.Interactive

name   = Interactive.question("What's your name?")
answer = Interactive.yesno("Continue?", default: :no)
result = Interactive.question_with_options("Pick:", [{"Yes", :yes}, {"No", :no}])
Interactive.menu("Select action:", [{"Deploy", :deploy}, {"Rollback", :rollback}])

Printer API (low-level)

# Structured message printing with chunks
chunks = [
  Alaja.Structures.ChunkText.new(" Error: ", color: :error, effects: [:bold]),
  Alaja.Structures.ChunkText.new("File not found", color: :white)
]
msg = Alaja.Structures.MessageInfo.new(chunks, align: :center, padding: 2)
Alaja.Printer.print(msg)

# Raw positioning
Alaja.Printer.print("Loading...", raw: true, x: 10, y: 5)

# Verbose mode returns ANSI string
ansi = Alaja.Printer.print("Hello", verbose: true)

Structures

Structure Module Purpose
ChunkText Alaja.Structures.ChunkText Text fragment + color + effects
EffectInfo Alaja.Structures.EffectInfo Bold, italic, blink, etc.
MessageInfo Alaja.Structures.MessageInfo Compound message + layout opts
chunk = Alaja.Structures.ChunkText.new("Hello", color: "#FF0000", effects: [:bold, :underline])
effects = Alaja.Structures.EffectInfo.new([:bold, :italic, :blink])
msg = Alaja.Structures.MessageInfo.new([chunk], align: :center, padding: 4)

CLI Framework

DSL (Alaja.CLI.Definition)

The declarative DSL provides command, subcommand, flag, argument, and run macros:

defmodule MyApp.CLI do
  use Alaja.CLI.Definition, otp_app: :my_app

  command "build", "Build the project" do
    flag :release, :boolean, default: false
    flag :arch, :string, default: "amd64", values: ~w(amd64 arm64)
    argument :target, :string, required: true

    run fn opts ->
      IO.puts("Building #{opts.target} for #{opts.arch}...")
    end
  end

  subcommand "config", "Manage configuration" do
    command "get", "Read a value" do
      argument :key, :string, required: true

      run fn opts ->
        value = Alaja.Config.get(String.to_atom(opts.key))
        IO.puts("#{opts.key}: #{inspect(value)}")
      end
    end

    command "set", "Write a value" do
      argument :key, :string, required: true
      argument :value, :string, required: true

      run fn opts ->
        Alaja.Config.set(String.to_atom(opts.key), opts.value)
        Alaja.print_success("#{opts.key} = #{opts.value}")
      end
    end
  end
end

Flag types: :string, :integer, :float, :boolean, :atom.

Global options (Alaja.CLI.GlobalOpts)

12 flags shared by all commands, extracted automatically before command dispatch:

Flag Short Type Description
--help -h boolean Show help
--raw -r boolean Raw ANSI positioning
--pos-x integer X coordinate (with --raw)
--pos-y integer Y coordinate (with --raw)
--align -a left/center/right Text alignment
--verbose -v boolean Return ANSI string
--box boolean Wrap output in a bordered box
--box-title string Box title
--box-border atom Border style: rounded, double...
--box-color color Border color
--quiet -q boolean Suppress output
--stdin -s boolean Read JSON from stdin

Help system (Alaja.CLI.Help)

Auto-generated help with summary, full reference, and per-command help — all rendered with Alaja's own table and header components.

Validation (Alaja.CLI.Validator)

# Flag type checking
Alaja.CLI.Validator.validate_flags([%{name: :port, type: :integer, required: true}],
                                    [port: "abc"])
# => {:error, ["--port: expected integer, got 'abc'"]}

# Allowed values
Alaja.CLI.Validator.validate_flags([%{name: :env, values: ~w(staging prod)}],
                                    [env: "dev"])
# => {:error, ["--env: 'dev' is not valid. Allowed: staging, prod"]}

# Missing required args
Alaja.CLI.Validator.validate_args([%{name: :version, required: true}], [])
# => {:error, ["Missing required argument: version"]}

# Dangerous command detection
Alaja.CLI.Validator.dangerous?("rm -rf /")
# => true

Error handling (Alaja.CLI.ErrorHandler)

Formatted error messages with "did you mean?" suggestions using Jaro distance, plus proper exit codes:

$ mycli deploi
Error: unknown command 'deploi'

Did you mean?
  deploy

Available commands:
  deploy              Deploy to production
  status              Show system status

Parser utilities (Alaja.CLI.Parser)

# Collect repeated flags
Alaja.CLI.Parser.collect_repeated(~w(--cmd ls --cmd pwd), "--cmd")
# => ["ls", "pwd"]

# Parse colors
Alaja.CLI.Parser.parse_color("#FF0000")
# => {:ok, {255, 0, 0}}

# Parse color lists
Alaja.CLI.Parser.parse_color_list("#FF0000; #00FF00; #0000FF")
# => {:ok, [{255, 0, 0}, {0, 255, 0}, {0, 0, 255}]}

# Parse KEY=VALUE pairs
Alaja.CLI.Parser.parse_env_pair("PATH=/usr/bin")
# => {:PATH, "/usr/bin"}

# Parse alignment
Alaja.CLI.Parser.parse_align("center")
# => :center

Built-in commands reference

Alaja.CLI.Commands.Show — 16 output subcommands:

Subcommand Description
success Success message with green checkmark
error Error message with red cross
warning Warning message with yellow triangle
info Info message with cyan indicator
debug Debug message with purple indicator
notice Notice message with blue indicator
critical Critical message with magenta indicator
alert Alert message with red indicator
emergency Emergency message with blinking indicator
happy Happy message with green indicator
sad Sad message with blue indicator
message Custom formatted message (chunks, colors, effects)
table Rich tables with borders, per-cell styling
json Pretty-printed JSON with syntax highlighting
bar Progress bar with customizable appearance
animated-bar Animated progress bar
header Styled header with optional subtitle
separator Horizontal divider line with optional text
gradient Gradient-colored text (multi-color support)
breadcrumbs Navigation path display
box Bordered container with optional title
animate Animated spinners and indicators
image Render images (kitty/iterm2/sixel/ASCII)
list Styled list with optional header
ask Interactive text input
menu Interactive selection menu
yesno Interactive yes/no question

Alaja.CLI.Commands.Config — Configuration management:

Action Description
init Initialize ~/.config/alaja
get KEY Read a configuration value
set KEY VALUE Write a configuration value
theme list List available themes
theme set NAME Activate a theme
--show Print current configuration

Visual Components

Module Description
Alaja.Components.Table Bordered tables, per-cell/col/row formatting
Alaja.Components.Header Centered title + subtitle, 3 sizes
Alaja.Components.Separator Horizontal rules with optional centered label
Alaja.Components.Bar Static progress bars, RGB gradients
Alaja.Components.AnimatedBar GenServer-based animated bars (8 styles)
Alaja.Components.Breadcrumbs Path navigation with customizable separator
Alaja.Components.Box Bordered containers (5 border styles)
Alaja.Components.Json Pretty-printed JSON with syntax highlighting
Alaja.Components.ColorWheel HSL wheel, harmony rings, swatches, gradients
Alaja.Components.Gradient Horizontal colour ramps via ColorWheel

Examples

Table — per-column formatting, specific row styling, centered:

Alaja.Components.Table.print(
  headers: ["Service", "Status", "Uptime"],
  rows: [
    ["api",    "OK",     "12d"],
    ["db",     "OK",     "30d"],
    ["cache",  "WARN",   "2h"]
  ],
  headers_color: :cyan,
  headers_effects: [:bold],
  rows_2_color: [:white, :yellow, :white],
  table_border: :rounded,
  table_align: :center
)

Box:

Alaja.Components.Box.print("Hello, world!", title: "Greeting", border: :rounded)
# ╭─ Greeting ──────╮
# │ Hello, world!   │
# ╰─────────────────╯

Bar:

Alaja.Components.Bar.print(75, 100, label: "Upload", width: 40)
Alaja.Components.Bar.print(60, 100, filled_color: {72, 187, 120}, empty_color: {40, 40, 40})

AnimatedBar (8 styles):

{:ok, pid} = Alaja.Components.AnimatedBar.start_link(animation: "moon", length: 30)

# Styles: spinner, kitt, dots, bar, moon, clock, pulse, pulsing_bar

Breadcrumbs:

Alaja.Components.Breadcrumbs.print(["Home", "Projects", "Zaguan"])
# Home › Projects › Zaguan

JSON:

Alaja.Components.Json.print(%{name: "Zaguan", version: "1.0.0", deps: ["pote", "jason"]})

ColorWheel:

Alaja.Components.ColorWheel.show_color_info({255, 87, 51})
Alaja.Components.ColorWheel.show_harmony_ring({255, 0, 0}, :triad)
Alaja.Components.ColorWheel.show_swatches([{255, 0, 0}, {0, 255, 0}, {0, 0, 255}])

Available harmonies: triad, complementary, analogous, square, monochromatic, compound, split-complementary.

Image rendering — Kitty, iTerm2, Sixel, or ASCII fallback:

Alaja.ImageRenderer.render_file("logo.png", width: 40, height: 20)
protocol = Alaja.ImageRenderer.detect_protocol()

Raw mode

Print at exact terminal positions:

Alaja.Printer.print("Header", raw: true, x: 0, y: 0, color: :cyan, effects: [:bold])
Alaja.Printer.print("Body text", raw: true, x: 0, y: 2)

# Globally via the command line
# mycli status --raw --pos-x 10 --pos-y 5

Gradients

Alaja.Helpers.progress_bar(75, 20, {80, 140, 255}, {200, 100, 255})
Alaja.Helpers.lerp({255, 0, 0}, {0, 0, 255}, 0.5)  # => {127, 0, 127}

Alaja.Components.ColorWheel.show_gradient(["#FF0000", "#00FF00", "#0000FF"])

Syntax highlighting

# Highlight a file (auto-detects language)
cells = Alaja.Syntax.highlight_file("lib/my_app.ex")

# Highlight content directly
cells = Alaja.Syntax.highlight_content(code, :elixir)

# Tokenize a line
tokens = Alaja.Syntax.tokenize("defmodule Foo do", :elixir)

Supported languages: :elixir, :json, :markdown, :text.


Low-level Modules

Module Purpose
Alaja.ANSI Pure ANSI escape generators (fg, bg, cursor, mouse)
Alaja.Terminal Terminal size detection ({cols, rows})
Alaja.Buffer 2D cell grid with flat tuple, O(1) access
Alaja.Cell Atomic unit: char + fg/bg RGB + effects list
Alaja.Helpers Sparklines, progress bars, boxes, color lerp
Alaja.Syntax Syntax highlighting for Elixir, JSON, Markdown
Alaja.ImageRenderer Terminal image rendering (Kitty/iTerm2/Sixel/ASCII)
Alaja.ImageTerminal Image protocol detection

ANSI escapes:

Alaja.ANSI.fg(0, 180, 216)           # true-color foreground
Alaja.ANSI.bg(40, 44, 52)            # true-color background
Alaja.ANSI.move_to(10, 5)            # cursor to (col, row)
Alaja.ANSI.hide_cursor()
Alaja.ANSI.alt_screen_on()           # alternate buffer
Alaja.ANSI.mouse_on()                # SGR mouse tracking

Buffer + Cell engine:

buffer = Alaja.Buffer.new(80, 24)
buffer = Alaja.Buffer.put(buffer, 10, 5, "X", {255, 0, 0})
cell = Alaja.Buffer.get(buffer, 10, 5)
Alaja.Buffer.write(buffer)  # flush to stdout

Helpers:

Alaja.Helpers.braille_spark([10, 50, 90, 30, 70], 5)
Alaja.Helpers.box(1, 1, 40, 10, "Workers", {100, 140, 200})
Alaja.Helpers.double_box(1, 1, 40, 10, "Stats", {180, 130, 80})

Configuration

# Key-value store backed by Application env
Alaja.Config.get(:color_depth)           # => :truecolor
Alaja.Config.set(:color_depth, :xterm256)
Alaja.Config.all()                       # all current values

# Theme management
Alaja.Config.list_themes()               # => ["default", "dracula", "monokai", ...]
{:ok, data} = Alaja.Config.load_theme("dracula")

# Built-in themes: default, dracula, monokai, nord, light

Configurable keys: color_depth, theme_active, refresh_rate, double_buffer, max_workers, default_policy.


Dependencies

Package Purpose
Pote Colour management, theme resolution, format conversions
Jason JSON serialization

Dev/tooling:

Package Purpose
Credo Code linting
Dialyxir Static type analysis
ExDoc Documentation generation
ExCoveralls Test coverage
Batamanta Release packaging
Benchee Benchmarking

Installation

Add alaja and pote to your mix.exs:

def deps do
  [
    {:alaja, path: "../alaja"},
    {:pote, path: "../pote"}
  ]
end

Then run mix deps.get.


License

MIT — see LICENSE for details.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages