← Volver al listado de tecnologías

Apéndice B: Ecosistema y Herramientas

Por: Artiko
lualuarocksbustedtestingtoolsecosystem

Apéndice B: Ecosistema y Herramientas

“Un buen programador es aquel que siempre mira a ambos lados antes de cruzar una calle de un solo sentido.” — Doug Linder

El ecosistema de Lua es pequeño pero poderoso. Aunque no tiene tantas librerías como Python o JavaScript, las que existen son de alta calidad y bien mantenidas.

LuaRocks: El Package Manager

LuaRocks es el equivalente de npm (Node.js) o pip (Python) para Lua.

Instalación

# macOS
brew install luarocks

# Ubuntu/Debian
sudo apt-get install luarocks

# Desde source
wget https://luarocks.org/releases/luarocks-3.9.2.tar.gz
tar zxpf luarocks-3.9.2.tar.gz
cd luarocks-3.9.2
./configure && make && sudo make install

Verificar:

luarocks --version
# /usr/local/bin/luarocks 3.9.2

Comandos Básicos

# Buscar paquetes
luarocks search json

# Instalar paquete
luarocks install luasocket

# Instalar versión específica
luarocks install lua-cjson 2.1.0

# Listar paquetes instalados
luarocks list

# Actualizar paquete
luarocks install --server=https://luarocks.org/dev lua-cjson

# Remover paquete
luarocks remove lua-cjson

# Ver información de paquete
luarocks show luasocket

Instalar Localmente (sin sudo)

# Instalar en directorio local
luarocks install --local luasocket

# Configurar path
eval $(luarocks path)
# O agregar a .bashrc/.zshrc:
# eval $(luarocks path --bin)

Crear Tu Propio Rock

1. Estructura del Proyecto

mi-libreria/
├── rockspec/
│   └── mi-libreria-1.0-1.rockspec
├── src/
│   └── mi-libreria.lua
├── spec/
│   └── mi-libreria_spec.lua
└── README.md

2. Rockspec File

-- mi-libreria-1.0-1.rockspec
package = "mi-libreria"
version = "1.0-1"
source = {
   url = "git://github.com/usuario/mi-libreria.git",
   tag = "v1.0"
}
description = {
   summary = "Una librería útil para X",
   detailed = [[
      Descripción detallada de lo que hace tu librería.
   ]],
   homepage = "https://github.com/usuario/mi-libreria",
   license = "MIT"
}
dependencies = {
   "lua >= 5.1, < 5.5"
}
build = {
   type = "builtin",
   modules = {
      ["mi-libreria"] = "src/mi-libreria.lua"
   }
}

3. Publicar

# Crear cuenta en luarocks.org

# Subir tu rock
luarocks upload mi-libreria-1.0-1.rockspec --api-key=YOUR_API_KEY

Testing: Busted

Busted es el framework de testing más popular para Lua, inspirado en RSpec (Ruby).

Instalación

luarocks install busted

Ejemplo Básico

-- spec/calculator_spec.lua
describe("Calculator", function()
    local Calculator = require("calculator")
    local calc

    before_each(function()
        calc = Calculator.new()
    end)

    describe("addition", function()
        it("adds two numbers", function()
            assert.are.equal(5, calc:add(2, 3))
        end)

        it("handles negative numbers", function()
            assert.are.equal(-1, calc:add(-3, 2))
        end)
    end)

    describe("division", function()
        it("divides two numbers", function()
            assert.are.equal(2, calc:divide(6, 3))
        end)

        it("throws error on division by zero", function()
            assert.has.errors(function()
                calc:divide(5, 0)
            end)
        end)
    end)
end)

Ejecutar Tests

# Todos los tests
busted

# Test específico
busted spec/calculator_spec.lua

# Con verbose
busted -v

# Con coverage (requiere luacov)
busted --coverage

Aserciones Comunes

-- Igualdad
assert.are.equal(expected, actual)
assert.are.same(expected, actual)  -- Deep equality para tablas

-- Booleanos
assert.is_true(value)
assert.is_false(value)
assert.is_nil(value)

-- Tipos
assert.is_number(value)
assert.is_string(value)
assert.is_table(value)
assert.is_function(value)

-- Errores
assert.has.errors(function() error("fail") end)
assert.has_no.errors(function() return 42 end)

-- Tablas
assert.are.same({1, 2, 3}, actual)

-- Strings
assert.matches("^hello", "hello world")

Mocking y Stubbing

describe("User Service", function()
    local UserService = require("user_service")
    local mock_db

    before_each(function()
        -- Crear mock de database
        mock_db = {
            query = function() end
        }

        -- Stub del método query
        stub(mock_db, "query").returns({
            {id = 1, name = "Alice"},
            {id = 2, name = "Bob"}
        })
    end)

    it("fetches users from database", function()
        local service = UserService.new(mock_db)
        local users = service:get_all_users()

        assert.are.equal(2, #users)
        assert.stub(mock_db.query).was.called()
    end)

    after_each(function()
        mock_db.query:revert()
    end)
end)

Spies

describe("Event System", function()
    it("calls callback when event is emitted", function()
        local EventEmitter = require("event_emitter")
        local emitter = EventEmitter.new()

        local callback = spy.new(function() end)
        emitter:on("test", callback)
        emitter:emit("test")

        assert.spy(callback).was.called()
        assert.spy(callback).was.called(1)
    end)
end)

Linting: Luacheck

Luacheck es un linter y analizador estático para Lua.

Instalación

luarocks install luacheck

Uso Básico

# Analizar archivo
luacheck myfile.lua

# Analizar directorio
luacheck src/

# Con configuración
luacheck . --config .luacheckrc

Configuración (.luacheckrc)

-- .luacheckrc
std = "luajit"
codes = true
ignore = {
    "212",  -- Unused argument
    "213",  -- Unused loop variable
}
globals = {
    "ngx",  -- Para OpenResty
    "love"  -- Para Love2D
}
max_line_length = 120

Ejemplos de Warnings

-- ❌ Warning: unused variable
local x = 10

-- ❌ Warning: global variable
function test()
    y = 20  -- Debería ser: local y = 20
end

-- ❌ Warning: line too long
local very_long_variable_name = "This is a very long string that exceeds the maximum line length configured in luacheck"

-- ✅ OK
local function test()
    local y = 20
    return y
end

IDEs y Editores

VS Code (Recomendado)

Extensiones esenciales:

  1. Lua (sumneko.lua)

    • Language server con autocompletado
    • Type checking
    • Goto definition
    • Refactoring
  2. Lua Debug (actboy168.lua-debug)

    • Debugging integrado
    • Breakpoints
    • Watch variables
  3. vscode-lua (trixnz.vscode-lua)

    • Snippets
    • Formatting

Configuración (settings.json):

{
    "Lua.runtime.version": "LuaJIT",
    "Lua.diagnostics.globals": ["ngx", "love"],
    "Lua.workspace.library": [
        "/usr/local/share/lua/5.1"
    ],
    "Lua.workspace.checkThirdParty": false
}

Neovim

Configuración con nvim-lspconfig:

-- ~/.config/nvim/lua/lsp.lua
local lspconfig = require('lspconfig')

lspconfig.lua_ls.setup {
    settings = {
        Lua = {
            runtime = {
                version = 'LuaJIT',
            },
            diagnostics = {
                globals = {'vim', 'ngx'},
            },
            workspace = {
                library = vim.api.nvim_get_runtime_file("", true),
            },
        },
    },
}

IntelliJ IDEA / CLion

Plugin: EmmyLua

ZeroBrane Studio

IDE dedicado para Lua con debugging integrado.

# macOS
brew install --cask zerobranestudio

# Ubuntu (desde .deb)
wget https://download.zerobrane.com/ZeroBraneStudioEduPack-1.90-linux.sh
sh ZeroBraneStudioEduPack-1.90-linux.sh

Características:

Librerías Populares

Web Frameworks

1. Lapis (OpenResty)

luarocks install lapis
-- app.lua
local lapis = require("lapis")
local app = lapis.Application()

app:get("/", function()
    return "Hello World!"
end)

app:get("/users/:id", function(self)
    return "User ID: " .. self.params.id
end)

return app

2. Sailor

luarocks install sailor

MVC framework completo.

HTTP Clients

1. LuaSocket

luarocks install luasocket
local http = require("socket.http")
local body, code = http.request("https://api.github.com")
print(code, #body)

2. lua-http (HTTP/2)

luarocks install http

JSON

1. lua-cjson (Más rápido)

luarocks install lua-cjson
local cjson = require("cjson")

local data = {name = "Alice", age = 30}
local json = cjson.encode(data)
print(json)  -- {"name":"Alice","age":30}

local decoded = cjson.decode(json)
print(decoded.name)  -- Alice

2. dkjson (Pure Lua)

luarocks install dkjson

Database

1. LuaSQL

# MySQL
luarocks install luasql-mysql

# PostgreSQL
luarocks install luasql-postgres

# SQLite
luarocks install luasql-sqlite3
local luasql = require("luasql.sqlite3")
local env = luasql.sqlite3()
local conn = env:connect("test.db")

conn:execute("CREATE TABLE users (id INTEGER, name TEXT)")
conn:execute("INSERT INTO users VALUES (1, 'Alice')")

local cursor = conn:execute("SELECT * FROM users")
local row = cursor:fetch({}, "a")
while row do
    print(row.id, row.name)
    row = cursor:fetch(row, "a")
end

2. pgmoon (PostgreSQL async para OpenResty)

luarocks install pgmoon

Redis

luarocks install lua-resty-redis
local redis = require("resty.redis")
local red = redis:new()

red:connect("127.0.0.1", 6379)
red:set("key", "value")
local val = red:get("key")

Debugging

Built-in Debug Library

local debug = require("debug")

function foo(x)
    print(debug.getinfo(1).name)  -- Nombre de función
    print(debug.getinfo(1).source)  -- Archivo
    print(debug.getinfo(1).currentline)  -- Línea

    -- Ver todas las locales
    local i = 1
    while true do
        local name, value = debug.getlocal(1, i)
        if not name then break end
        print(name, value)
        i = i + 1
    end
end

foo(42)

MobDebug (Remote Debugging)

luarocks install mobdebug
-- En tu código
require("mobdebug").start()

-- Ejecutar con ZeroBrane o otro debugger

Formatters

StyLua

Formatter automático para Lua.

# Instalación
cargo install stylua

# Uso
stylua myfile.lua

# Todo el proyecto
stylua .

Configuración (stylua.toml):

column_width = 120
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 4
quote_style = "AutoPreferDouble"

Profiling

ProFi

luarocks install profi
local ProFi = require("ProFi")

ProFi:start()

-- Tu código aquí
for i = 1, 1000000 do
    math.sqrt(i)
end

ProFi:stop()
ProFi:writeReport("profile.txt")

Generadores de Documentación

LDoc

luarocks install ldoc
--- Calculator module
-- @module calculator

local Calculator = {}

--- Create a new calculator instance
-- @treturn Calculator new calculator
function Calculator.new()
    return setmetatable({}, {__index = Calculator})
end

--- Add two numbers
-- @tparam number a first number
-- @tparam number b second number
-- @treturn number sum of a and b
function Calculator:add(a, b)
    return a + b
end

return Calculator

Generar docs:

ldoc .

Resumen

El ecosistema de Lua es:

Próximo: Apéndice C: Lua en el Mundo Real