Aplicaciones CLI

Crea un proceso de línea de comandos que escribe en el terminal y amplíalo con entrada, color, información del sistema y comandos con nombre.

Clasificación: tutorial ejecutable. La aplicación de saludo está completa. Las secciones posteriores son sustituciones opcionales de src/cli.lua o de la entrada app:cli, según se indica en cada sección.

Qué Estamos Construyendo

Un CLI simple que saluda al usuario:

$ wippy run -x app:cli
Hello from CLI!

Requisitos previos

  • Entorno de ejecución Wippy v0.3.32a disponible como wippy. Confírmalo con wippy version --short.
  • Un terminal interactivo. Los ejemplos de entrada requieren stdin y los de color, un terminal que muestre secuencias de escape ANSI.

Estructura del Proyecto

cli-app/
├── wippy.lock
└── src/
    ├── _index.yaml
    └── cli.lua

Paso 1: Crear Proyecto

mkdir cli-app && cd cli-app
mkdir src

Paso 2: Definiciones de Entradas

Cree src/_index.yaml:

version: "1.0"
namespace: app

entries:
  # Terminal host connects processes to stdin/stdout
  - name: terminal
    kind: terminal.host
    lifecycle:
      auto_start: true

  # CLI process
  - name: cli
    kind: process.lua
    source: file://cli.lua
    method: main
    modules:
      - io
El terminal.host conecta su proceso Lua a la terminal. Sin él, io.print() no tiene donde escribir.

Paso 3: Código CLI

Cree src/cli.lua:

local io = require("io")

local function main()
    io.print("Hello from CLI!")
    return 0
end

return { main = main }

Paso 4: Ejecutarlo

wippy init
wippy run -x app:cli

Salida esperada:

Hello from CLI!
El indicador -x ejecuta el proceso como comando. Detecta automáticamente el único terminal.host del registro; utiliza --host cuando exista más de uno. Sin un indicador de logging, el modo comando suprime los logs del entorno de ejecución para mantener legible la salida del proceso.

Leer entrada del usuario

Sustituye src/cli.lua por esta versión. Informa de los errores de lectura y escritura del terminal en lugar de tratarlos como entrada vacía:

local io = require("io")

local function main()
    local _, write_err = io.write("Enter your name: ")
    if write_err then
        io.eprint("Cannot write prompt:", write_err)
        return 1
    end

    local _, flush_err = io.flush()
    if flush_err then
        io.eprint("Cannot flush prompt:", flush_err)
        return 1
    end

    local name, read_err = io.readline()
    if read_err then
        io.eprint("Cannot read input:", read_err)
        return 1
    end

    if name and #name > 0 then
        io.print("Hello, " .. name .. "!")
    else
        io.print("Hello, stranger!")
    end

    return 0
end

return { main = main }

Salida en color

Sustituye src/cli.lua por esta versión para utilizar códigos de escape ANSI:

local io = require("io")

local reset = "\027"
local function red(s) return "\027" .. s .. reset end
local function green(s) return "\027" .. s .. reset end
local function yellow(s) return "\027" .. s .. reset end
local function cyan(s) return "\027" .. s .. reset end
local function bold(s) return "\027" .. s .. reset end

local function main()
    io.print(bold(cyan("Welcome!")))
    local _, write_err = io.write(yellow("Enter a number: "))
    if write_err then
        io.eprint("Cannot write prompt:", write_err)
        return 1
    end

    local _, flush_err = io.flush()
    if flush_err then
        io.eprint("Cannot flush prompt:", flush_err)
        return 1
    end

    local input, read_err = io.readline()
    if read_err then
        io.eprint("Cannot read input:", read_err)
        return 1
    end
    local n = tonumber(input)

    if n then
        io.print("Squared: " .. green(tostring(n * n)))
        return 0
    else
        io.print(red("Error: ") .. "not a number")
        return 1
    end
end

return { main = main }

Información del Sistema

Acceda a estadísticas del runtime con el módulo system. Cada lectura está protegida por la acción system.read, así que el proceso también necesita una política que la permita:

  # Agregar a las entradas
  - name: system_read
    kind: security.policy
    policy:
      actions:
        - system.read
      resources: "*"
      effect: allow

  # Actualizar la entrada cli
  - name: cli
    kind: process.lua
    source: file://cli.lua
    method: main
    security:
      policies:
        - app:system_read
    modules:
      - io
      - system

Después sustituye src/cli.lua:

local io = require("io")
local system = require("system")

local function main()
    local hostname, hostname_err = system.process.hostname()
    if hostname_err then
        io.eprint("Cannot read hostname:", hostname_err)
        return 1
    end

    local cpu_count, cpu_err = system.runtime.cpu_count()
    if cpu_err then
        io.eprint("Cannot read CPU count:", cpu_err)
        return 1
    end

    local goroutines, goroutine_err = system.runtime.goroutines()
    if goroutine_err then
        io.eprint("Cannot read goroutine count:", goroutine_err)
        return 1
    end

    local mem, memory_err = system.memory.stats()
    if memory_err then
        io.eprint("Cannot read memory stats:", memory_err)
        return 1
    end

    io.print("Host: " .. hostname)
    io.print("CPUs: " .. cpu_count)
    io.print("Goroutines: " .. goroutines)
    io.print("Memory: " .. string.format("%.1f MB", mem.heap_alloc / 1024 / 1024))

    return 0
end

return { main = main }

Comandos con Nombre

Para invocar el proceso por nombre en lugar de utilizar -x app:cli, añade metadatos de comando.

Sustituye la entrada app:cli por esta versión. Conserva la entrada terminal.host del proyecto base.

  - name: cli
    kind: process.lua
    meta:
      command:
        name: greet
        short: Greet the user
    source: file://cli.lua
    method: main
    modules:
      - io

Ahora ejecútalo por nombre:

wippy run greet

Lista todos los comandos disponibles:

wippy run list
Available commands:

  greet  Greet the user  (app:cli)

Run with: wippy run <command>

Códigos de Salida

Retorne desde main() para establecer el código de salida:

local function main()
    if error_occurred then
        return 1  -- Error
    end
    return 0      -- Success
end

Referencia de I/O

Función Devuelve Descripción
io.print(...) boolean o nil, error sin contexto de terminal Escribe en stdout con tabulaciones y una nueva línea final
io.write(...) boolean, error Escribe en stdout sin separadores ni nueva línea
io.eprint(...) boolean o nil, error sin contexto de terminal Escribe en stderr con tabulaciones y una nueva línea final
io.readline() string, error Lee una línea sin la nueva línea final; EOF sin datos es un error
io.flush() boolean, error Vacía stdout cuando el stream lo admite

Flags CLI

Flag Descripción
wippy run -x app:cli Ejecutar proceso CLI (auto-detecta terminal.host)
wippy run -x app:cli --host app:terminal Host de terminal explícito
wippy run -x app:cli -v Con logging verboso

Solución de problemas y limpieza

  • no terminal host found significa que el registro no contiene ningún terminal.host; utiliza la entrada del paso 2. Si existen varios hosts, pasa --host app:terminal.
  • no terminal context significa que el proceso no se inició mediante un host de terminal. Utiliza wippy run -x app:cli, no un process.service en segundo plano.
  • Los errores de entrada en EOF son previsibles cuando stdin está cerrado. Ejecuta el comando en un terminal interactivo para los ejemplos de entrada.
  • Si las secuencias ANSI aparecen como caracteres literales, utiliza el ejemplo sin color o un terminal compatible con ANSI.
  • El comando termina cuando main() devuelve el control. Después de salir del directorio, elimina cli-app/ si solo era un ejercicio desechable.

Siguientes Pasos