diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..4161bc9 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,12 @@ +#!/bin/sh +set -e + +echo "Running pre-commit checks..." + +# Run code quality checks via just +if command -v just >/dev/null 2>&1; then + just check +else + echo "Error: 'just' command not found. Please install just (https://github.com/casey/just)" + exit 1 +fi \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b1dbd9f..642fcf2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,9 +23,16 @@ jobs: # because it doesn't run tests (no busted dependency) version: 0.18.8 - - name: Run tests + - name: Install just run: | - lx --lua-version 5.4 test + mkdir -p /tmp/just + curl -L https://github.com/casey/just/releases/download/1.48.1/just-1.48.1-x86_64-unknown-linux-musl.tar.gz -o /tmp/just/just.tar.gz + tar -xzf /tmp/just/just.tar.gz -C /tmp/just + sudo mv /tmp/just/just /usr/local/bin/ + rm -rf /tmp/just + + - name: Run tests + run: just test-ci lint: name: Lint & Format @@ -40,10 +47,18 @@ jobs: # See comment in test job above for version pinning rationale version: 0.18.8 + - name: Install just + run: | + mkdir -p /tmp/just + curl -L https://github.com/casey/just/releases/download/1.48.1/just-1.48.1-x86_64-unknown-linux-musl.tar.gz -o /tmp/just/just.tar.gz + tar -xzf /tmp/just/just.tar.gz -C /tmp/just + sudo mv /tmp/just/just /usr/local/bin/ + rm -rf /tmp/just + - name: Check formatting run: | lx fmt git diff --exit-code || (echo "::error::Code is not formatted. Run 'lx fmt' locally and commit the changes." && exit 1) - name: Run linter - run: lx --lua-version 5.4 lint + run: just lint-ci diff --git a/.gitignore b/.gitignore index 9ce6948..89d4287 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,11 @@ luacov.report.out # Demo recording *.cast + +# Build artifacts +.build/ +/roda +bin_spin.luastatic.c +spin.luastatic.c +lua/spin.luastatic.c +*.luastatic.c diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..6270562 --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,95 @@ +-- luacheck configuration for roda.lua +-- Treat all warnings as errors by default +-- but ignore some stylistic warnings + +ignore = { + "212", -- deep nesting + "213", -- long line (over 120 chars) + "211", -- line contains only whitespace + "111", -- setting non-standard global variable + "112", -- mutating non-standard global variable + "113", -- accessing undefined variable + "421", -- variable defined but not used (handled by unused argument detection) +} + +-- Files to check +files = { + "**/*.lua", + "!**/_spec.lua", -- exclude test files from some checks +} + +-- Global variables that are allowed +globals = { + "arg", -- command line arguments + "io", + "package", + "string", + "table", + "os", + "debug", + "math", + "coroutine", + "utf8", + "jit", + "bit", + "bit32", + "_G", + "_VERSION", + "require", + "setmetatable", + "getmetatable", + "pairs", + "ipairs", + "next", + "type", + "tostring", + "tonumber", + "assert", + "error", + "pcall", + "xpcall", + "rawget", + "rawset", + "rawequal", + "select", + "unpack", + "print", + "collectgarbage", + "dofile", + "load", + "loadfile", + "loadstring", + "module", + "setfenv", + "getfenv", + "newproxy", + "spawn", + "uv", -- luv library + "busted", -- test framework + "describe", + "it", + "before_each", + "after_each", + "setup", + "teardown", + "pending", + "assert", + "mock", + "stub", + "spy", +} + +-- Maximum line length +max_line_length = 120 + +-- Maximum number of consecutive empty lines +max_empty_lines = 2 + +-- Allow unused arguments that start with underscore +allow_unused = {"^_"} -- matches `_` and `_err` etc. + +-- Allow unused loop variables +unused_args = false + +-- Check globals defined in other modules +check_globals = false diff --git a/justfile b/justfile new file mode 100644 index 0000000..ec370e1 --- /dev/null +++ b/justfile @@ -0,0 +1,253 @@ +# Roda Project Hub +# Pure Lua terminal spinner library with CLI tool +# Usage: just [args...] +# +# Expected .env variables (optional, override defaults): +# LUA_PREFIX - Path to Lua installation (default: brew --prefix lua) +# BUILD_DIR - Build output directory (default: .build) +# LOG_LEVEL - Logging verbosity (default: info) +# --- Global Settings --- + +set unstable := true + +# Enable latest Just features + +set dotenv-load := true + +# Auto-load .env files + +set export := true + +# Export variables to recipe environment + +# Shell configuration (bash works on macOS & Linux, Windows uses PowerShell) +set shell := ["bash", "-euo", "pipefail", "-c"] +set windows-shell := ["powershell.exe", "-NoLogo", "-Command"] + +# Consistent shell with strict mode + +set positional-arguments := true + +# Enable $@ for recipe arguments + +# Cross-platform support +# --- Variables --- + +# Lua installation prefix (default: brew --prefix lua on macOS, /usr on other platforms) +lua_prefix := env('LUA_PREFIX', if os() == "macos" { `brew --prefix lua` } else { "/usr" }) +# Lua version for development headers (default: 5.5) +lua_version := "5.5" +# Include path for Lua development headers (override with LUA_INCLUDE env var) +lua_include := env('LUA_INCLUDE', lua_prefix / ("include/lua" + lua_version)) +# Static Lua library (adjust path if using shared library) +lua_lib := lua_prefix / "lib/liblua.a" +macos_version := if os() == "macos" { `sw_vers -productVersion | cut -d. -f1-2` } else { "" } +build_dir := absolute_path(clean(env('BUILD_DIR', '.build'))) +package_name := "roda" + +# --- Default --- + +[doc("Show all available recipes grouped by category")] +default: + @just --list + +# --- Validation --- + +[doc("Validate environment and tooling")] +[group('workflow')] +check-env: + @echo {{ assert(lua_prefix != '', "Lua not found! Set LUA_PREFIX env var or install via brew") }} + @echo "Lua prefix: {{ lua_prefix }}" + @echo "Build dir: {{ build_dir }}" + @echo "Environment validated." + +# --- Core Development --- + +[doc("Format Lua files")] +[group('dev')] +fmt: + lx --lua-version 5.5 fmt + +[doc("Lint Lua files")] +[group('dev')] +lint: + lx --lua-version 5.5 check + +[doc("Lint for CI (Lua 5.4)")] +[group('ci')] +lint-ci: + lx --lua-version 5.4 --variables "WITH_SHARED_LIBUV=OFF" lint + +[doc("Run code quality checks")] +[group('dev')] +check: lint fmt + +# --- Unit Tests --- + +[doc("Run unit tests (all spec/*_spec.lua files via lux/busted)")] +[group('test')] +test-unit: + @echo "Running unit tests..." + lx --lua-version 5.5 --lua-dir {{ lua_prefix }} --variables "WITH_SHARED_LIBUV=OFF" test + +[doc("Alias for test-unit")] +[group('test')] +test: test-unit + +[doc("Run unit tests for CI (Lua 5.4)")] +[group('ci')] +test-ci: + @echo "Running unit tests for CI..." + lx --lua-version 5.4 --variables "WITH_SHARED_LIBUV=OFF" test + +# --- Build --- + +[doc("Ensure all lux dependencies are installed")] +[private] +ensure-deps: + @echo "Ensuring dependencies are installed..." + CFLAGS="-I{{ lua_include }} {{ if os() == 'macos' { '-mmacosx-version-min=' + macos_version } else { '' } }}" \ + {{ if os() == 'macos' { 'MACOSX_DEPLOYMENT_TARGET=' + macos_version } else { '' } }} \ + lx --lua-version 5.5 --lua-dir {{ lua_prefix }} --variables "WITH_SHARED_LIBUV=OFF" build --only-deps --no-lock + +[doc("Build the standalone executable")] +[group('build')] +build: ensure-deps prep build-luv build-system compile + +[doc("Prepare the build directory")] +[group('build')] +[private] +prep: + mkdir -p {{ build_dir }} + +[doc("Statically compile luv (CMake)")] +[group('build')] +[private] +build-luv: prep + @echo "Building static luv..." + {{ if path_exists(build_dir / "luv") == "true" { "" } else { "git clone --recursive https://github.com/luvit/luv.git " + (build_dir / "luv") } }} + cd {{ build_dir / 'luv' }} && cmake -DBUILD_STATIC_LIBS=ON -DBUILD_MODULE=OFF -DWITH_LUA_ENGINE=Lua -DLUA_BUILD_TYPE=System -DLUA_INCLUDE_DIR={{ lua_include }} -DLUA_LIBRARIES={{ lua_lib }} . + cd {{ build_dir / 'luv' }} && make + cp {{ build_dir / 'luv' / 'libluv.a' }} {{ build_dir }}/ + cp {{ build_dir / 'luv' / 'deps' / 'libuv' / 'libuv.a' }} {{ build_dir }}/ + +[doc("Statically compile luasystem (GCC/AR)")] +[group('build')] +[private] +build-system: prep + @echo "Building static luasystem..." + {{ if path_exists(build_dir / "luasystem") == "true" { "" } else { "git clone https://github.com/o-lim/luasystem.git " + (build_dir / "luasystem") } }} + cd {{ build_dir / 'luasystem' }} && gcc -c src/core.c src/compat.c src/time.c -I{{ lua_include }} + cd {{ build_dir / 'luasystem' }} && ar rcs libsystem.a core.o compat.o time.o + cp {{ build_dir / 'luasystem' / 'libsystem.a' }} {{ build_dir }}/ + +[doc("Compile the final binary using luastatic")] +[group('build')] +[private] +compile: + @echo "Compiling standalone binary..." + cd lua && luastatic ../bin/spin.lua \ + roda/init.lua roda/spinners.lua roda/ansi.lua roda/symbols.lua roda/util.lua roda/argp.lua \ + ../{{ build_dir / 'libluv.a' }} ../{{ build_dir / 'libuv.a' }} ../{{ build_dir / 'libsystem.a' }} {{ lua_lib }} \ + -I{{ lua_include }} && \ + mv spin.luastatic.c ../{{ build_dir }}/ && \ + cd .. && \ + mv lua/spin roda + +[doc("Test the standalone executable")] +[group('test')] +test-cli: build + @echo "=== Test 1: Normal execution (sleep 2) ===" + ./roda --title "Sleeping..." -- sleep 2 + @echo "=== Test 2: Custom spinner (sleep 1) ===" + ./roda --title "Sleeping..." --spinner "line" -- sleep 1 + @echo "=== Test 3: Error case (nonexistent command) ===" + ./roda --title "Missing command" -- nonexistentcommand || true + @echo "=== Test 4: Command returns false (exit code 1) ===" + ./roda --title "Failing" -- false || true + @echo "=== Test 5: Show output flag ===" + ./roda --show-output -- echo "hello" + @echo "=== Test 6: No command (should exit 0) ===" + ./roda + @echo "=== Test 7: Invalid spinner name ===" + ./roda --spinner invalid_spinner -- sleep 1 || true + @echo "All tests completed!" + +[doc("Run all tests (unit + CLI)")] +[group('test')] +test-all: test-unit test-cli + +[doc("Performance benchmark: verify roda adds minimal overhead to wrapped commands")] +[group('test')] +test-perf: build + @echo "Running performance benchmark..." + @echo "Benchmarking: roda --title 'test' -- sleep 1" + @hyperfine --warmup 1 --runs 5 \ + --min-runs 3 \ + --export-json .build/benchmark-results.json \ + --export-markdown .build/benchmark-results.md \ + "./roda --title 'perf-test' -- sleep 1" + @echo "" + @echo "Results saved to .build/benchmark-results.json and .build/benchmark-results.md" + @# Validate: sleep 1 should complete in < 1.3s (1s sleep + 0.3s overhead budget) + @MEAN=$(jq -r '.results[0].mean' .build/benchmark-results.json) && \ + echo "Mean execution time: $${MEAN}s" && \ + PASS=$(echo "$$MEAN < 1.3" | bc -l) && \ + if [ "$$PASS" -eq 1 ]; then \ + echo "✅ Performance check passed (threshold: 1.3s)"; \ + else \ + echo "❌ Performance check FAILED: mean $${MEAN}s exceeds 1.3s threshold"; \ + exit 1; \ + fi + +# --- Workflow --- + +[doc("Install dependencies")] +[group('workflow')] +install: + CFLAGS="-I{{ lua_include }} {{ if os() == 'macos' { '-mmacosx-version-min=' + macos_version } else { '' } }}" \ + {{ if os() == 'macos' { 'MACOSX_DEPLOYMENT_TARGET=' + macos_version } else { '' } }} \ + lx --lua-version 5.5 --lua-dir {{ lua_prefix }} --variables "WITH_SHARED_LIBUV=OFF" build --only-deps --no-lock + +[doc("Run spinner directly without building (development mode)")] +[group('dev')] +dev *args: + @lx lua --no-lock -- bin/spin.lua {{ args }} + +[doc("Full pre-commit validation (code quality + unit tests)")] +[group('workflow')] +validate: check test + +[doc("Full CI pipeline (validate + build + integration tests)")] +[group('workflow')] +all: validate build test-cli + +# --- Release & Publishing --- + +[doc("Prepare release (requires full pipeline to pass)")] +[group('release')] +release: all + @echo "Preparing release..." + @echo "Release artifacts ready." + +[doc("Publish to LuaRocks via lux")] +[confirm("Publish to LuaRocks? This action cannot be undone.")] +[group('release')] +publish: release + @echo "Publishing to LuaRocks..." + lx --lua-version 5.5 publish + +# --- Maintenance --- + +[doc("Clean build artifacts")] +[confirm("Remove all build artifacts and binaries?")] +[group('maintenance')] +clean: + rm -rf {{ build_dir }} + rm -f roda + rm -f *.luastatic.c lua/*.luastatic.c || true + +[doc("Update lux dependencies")] +[group('maintenance')] +update: + lx --lua-version 5.5 update diff --git a/lua/roda/ansi.lua b/lua/roda/ansi.lua index 9ba5b7a..1bbab36 100644 --- a/lua/roda/ansi.lua +++ b/lua/roda/ansi.lua @@ -1,5 +1,4 @@ --- roda/ansi.lua - ANSI escape code ---- @module roda.ansi --- @author TJ Kolleh --- @license EUPL-1.2 @@ -11,7 +10,7 @@ local M = {} M.hide_cursor = "\27[?25l" M.show_cursor = "\27[?25h" M.clear_line = "\27[2K" -M.move_to_col_1 = "\27[G" +M.move_to_col_1 = "\r\27[1G" M.move_up = "\27[A" ------------------------------------------------------------------------------- @@ -52,10 +51,18 @@ end --- Check if the given stream supports ANSI colors ---@param stream file*|nil The stream to check (defaults to io.stderr) ---@return boolean True if colors are supported -function M.supports_color(_stream) - -- Basic check: assume TTY supports color - -- In practice, you'd check isatty() but Lua doesn't have this built-in - -- _stream parameter reserved for future TTY detection +function M.supports_color(stream) + local uv_ok, uv = pcall(require, "luv") + if uv_ok then + local fd = 2 -- stderr + if stream == io.stdout then + fd = 1 + end + if stream == io.stdin then + fd = 0 + end + return uv.guess_handle(fd) == "tty" + end return true end diff --git a/lua/roda/argp.lua b/lua/roda/argp.lua new file mode 100644 index 0000000..3f848d0 --- /dev/null +++ b/lua/roda/argp.lua @@ -0,0 +1,222 @@ +--- Argument parser similar to GNU-style CLI tools +-- @module argp +-- Vendored from https://github.com/uriid1/argp (MIT License) +-- Copyright (c) 2024 uriid1 +local argp = {} + +--- Create a new argument parser instance +-- @param config (table) containing `name`, `description`, `version`, and optional `epilog` +-- @return parser instance +function argp:new(config) + local instance = { + name = config.name, + description = config.description, + version = config.version, + epilog = config.epilog, + _options = {}, + _options_map = {}, + } + + setmetatable(instance, { __index = self }) + return instance +end + +--- Add single option +-- @param[optchain] opt (table) option definition with keys +-- @param opt.short (string) Short form (e.g. `'v'` for `-v`) +-- @param opt.long (string) Long form (e.g. `'verbose'` for `--verbose`) +-- @param opt.description (string) Option description for help text +-- @param opt.type (string) Expected type: `'string'` or `'number'` +-- @param opt.count_params (number|string) Number of expected comma-separated params, or `'*'` for unlimited +-- @param opt.dest (string) Optional key name in parsed table +function argp:add_option(opt) + local option = { + short = opt.short, + long = opt.long, + description = opt.description, + type = opt.type or "string", + count_params = opt.count_params or 0, + dest = opt.dest or opt.long or opt.short, + } + + if option.short then + self._options_map[option.short] = option + end + if option.long then + self._options_map[option.long] = option + end + table.insert(self._options, option) +end + +--- Add multiple options +-- @param opts (table) List of options +function argp:options(opts) + for i = 1, #opts do + self:add_option(opts[i]) + end +end + +--- Internal +-- +local function typeConverter(__type, value) + if __type == "number" then + return tonumber(value) + elseif __type == "boolean" then + return value == "true" + elseif __type == "string" then + return tostring(value) + end +end + +--- Internal +-- +function argp:parse_comma_values(values_str, opt) + local max_count = opt.count_params == "*" and math.huge or opt.count_params + local count = 0 + local values = {} + + for part in values_str:gmatch("[^,]+") do + local convertedValue = typeConverter(opt.type, part) + if convertedValue then + table.insert(values, convertedValue) + else + error( + ("%s: option ‘--%s’: %s value expected, got “%s”"):format(self.name, opt.dest, opt.type, part) + ) + end + + count = count + 1 + if count > max_count then + error(("%s: option ‘--%s’: too many arguments"):format(self.name, opt.dest)) + end + end + + return values +end + +--- Internal +-- +function argp:parse_short_or_long(arg, type) + local re + if type == "long" then + re = "^%-%-([^=]+)=?(.*)" + elseif type == "short" then + re = "^%-([^=]+)=?(.*)" + end + + local name, value = arg:match(re) + local opt = self._options_map[name] + + if opt == nil then + error(("%s: unrecognized option ‘-%s’"):format(self.name, name)) + end + + if opt.count_params == 0 then + if value ~= "" or value == nil then + error(("%s: option ‘-%s’ does not take a value"):format(self.name, name)) + end + + return opt.dest, true + elseif opt.count_params == 1 then + if value ~= nil and value == "" then + -- Return nil so parse() can look ahead for the next argument + return opt.dest, nil + end + + local convertedValue = typeConverter(opt.type, value) + if convertedValue == nil then + error( + string.format("%s: option '--%s': %s value expected, got \"%s\"", self.name, opt.dest, opt.type, value) + ) + end + + return opt.dest, convertedValue + else + if value == nil or value == "" then + error(("%s: option ‘-%s’ requires an arguments"):format(self.name, name)) + end + + return opt.dest, self:parse_comma_values(value, opt) + end +end + +--- Internal +-- +function argp:parse(args) + args = args or arg or {} + + local result = {} + local i = 1 + + while i <= #args do + local current = args[i] + + -- End of options marker + if current == "--" then + break + end + + -- Long options like --option or --option=value,... + if current:find("^%-%-") then + local dest, value = self:parse_short_or_long(current, "long") + -- If option expects a value but didn't get one inline, look at next arg + if value == nil or (type(value) == "string" and value == "") then + local opt = self._options_map[current:match("^%-%-(.+)")] + if opt and opt.count_params >= 1 and i + 1 <= #args and not args[i + 1]:find("^%-") then + i = i + 1 + value = typeConverter(opt.type, args[i]) + end + end + result[dest] = value + + -- Short options like -o or -o=value,... + elseif current:find("^%-") then + local dest, value = self:parse_short_or_long(current, "short") + -- If option expects a value but didn't get one inline, look at next arg + if value == nil or (type(value) == "string" and value == "") then + local opt = self._options_map[current:match("^%-(.+)")] + if opt and opt.count_params >= 1 and i + 1 <= #args and not args[i + 1]:find("^%-") then + i = i + 1 + value = typeConverter(opt.type, args[i]) + end + end + result[dest] = value + end + + i = i + 1 + end + + return result +end + +--- Print program help text in GNU style +-- Displays usage, description, options, and epilog +function argp:print_system_help() + io.write(("Usage: %s [OPTION...]\n"):format(self.name)) + + if self.description and #self.description > 0 then + io.write("\n" .. self.description .. "\n") + end + + io.write("\nOptions:\n") + for _, opt in ipairs(self._options) do + local forms = {} + if opt.short then + table.insert(forms, "-" .. opt.short) + end + if opt.long then + table.insert(forms, "--" .. opt.long) + end + + local left = table.concat(forms, ", ") + io.write((" %-20s %s\n"):format(left, opt.description)) + end + + if self.epilog and #self.epilog > 0 then + io.write("\n" .. self.epilog .. "\n") + end + + os.exit(0) +end + +return argp diff --git a/lua/roda/init.lua b/lua/roda/init.lua index dedd750..c0dae61 100644 --- a/lua/roda/init.lua +++ b/lua/roda/init.lua @@ -1,7 +1,6 @@ --- roda.lua - Elegant terminal spinner for Lua --- Roda (Portuguese for "wheel") --- ---- @module roda --- @author TJ Kolleh --- @license EUPL-1.2 @@ -118,25 +117,70 @@ function Spinner:execute(command, args) local with_cmd = make_process_bracket(command, args) local run_cmd = function(rslt, done_process) local output = {} + local process_exited = false + local stdout_eof = false + local stderr_eof = false + local exit_code_saved = 0 + + local function check_done() + if process_exited and stdout_eof and stderr_eof then + if exit_code_saved == 0 then + self:succeed() + else + self:fail() + end + done_process(exit_code_saved, table.concat(output)) + end + end + + -- evaluate the non-blocking process + local handle, spawn_err + handle, spawn_err = uv.spawn(rslt.command, { + args = rslt.args, + stdio = { nil, rslt.stdout, rslt.stderr }, + }, function(exit_code) + process_exited = true + exit_code_saved = exit_code + if handle and not handle:is_closing() then + uv.close(handle) + end + check_done() + end) + + if not handle then + -- Immediate failure path + stdout_eof = true + stderr_eof = true + process_exited = true + exit_code_saved = 127 -- Command not found + -- Print spawn error to stderr + if spawn_err then + self._stream:write("error: " .. tostring(spawn_err) .. "\n") + self._stream:flush() + end + check_done() + return + end + rslt.handle = handle -- read the stdout stream (async) - uv.read_start(rslt.stdout, function(_, data) + uv.read_start(rslt.stdout, function(_err, data) if data then table.insert(output, data) + else + stdout_eof = true + check_done() end end) - -- evaluate the non-blocking process - rslt.handle = uv.spawn(rslt.command, { - args = rslt.args, - stdio = { nil, rslt.stdout, rslt.stderr }, - }, function(exit_code) - if exit_code == 0 then - self:succeed() + -- read the stderr stream (async) + uv.read_start(rslt.stderr, function(_err, data) + if data then + table.insert(output, data) else - self:fail() + stderr_eof = true + check_done() end - done_process(exit_code, table.concat(output)) end) end diff --git a/lua/roda/spinners.lua b/lua/roda/spinners.lua index 8443ccd..7231ced 100644 --- a/lua/roda/spinners.lua +++ b/lua/roda/spinners.lua @@ -1,6 +1,5 @@ --- roda/spinners.lua - Spinner frame definitions --- Inspired by cli-spinners (https://github.com/sindresorhus/cli-spinners) ---- @module roda.spinners --- @author TJ Kolleh --- @license EUPL-1.2 diff --git a/lua/roda/symbols.lua b/lua/roda/symbols.lua index 926a583..04d7312 100644 --- a/lua/roda/symbols.lua +++ b/lua/roda/symbols.lua @@ -1,5 +1,4 @@ --- roda/symbols.lua - Terminal symbols for final states ---- @module roda.symbols --- @author TJ Kolleh --- @license EUPL-1.2 diff --git a/lua/roda/util.lua b/lua/roda/util.lua index 5b35fa1..e6d213e 100644 --- a/lua/roda/util.lua +++ b/lua/roda/util.lua @@ -1,5 +1,4 @@ --- roda/util.lua ---- @module roda.util --- @author TJ Kolleh --- @license EUPL-1.2 diff --git a/lux.toml b/lux.toml index cd600f8..aef8050 100644 --- a/lux.toml +++ b/lux.toml @@ -15,22 +15,22 @@ labels = ["terminal", "spinner", "cli", "ansi", "progress", "neovim"] url = "https://github.com/tkolleh/roda.lua/archive/refs/tags/v$(VERSION).zip" dev = "git+https://github.com/tkolleh/roda.lua.git" + + [dependencies] luasystem = ">=0.4.0" luv = ">= 1.44.2" +# argp is vendored in lua/roda/argp.lua (git rockspec doesn't install source files) [test_dependencies] -busted = ">=2.0" -nlua = ">=0.3" +busted = "2.3.0-1" [test] type = "busted" -[build] -type = "builtin" +[build_dependencies] +luastatic = ">=0.0.12" # Standalone compiler -[build.install.lua] -"roda" = "lua/roda/init.lua" -"roda.spinners" = "lua/roda/spinners.lua" -"roda.ansi" = "lua/roda/ansi.lua" -"roda.symbols" = "lua/roda/symbols.lua" +[build] +type = "none" +copy_directories = ["bin"] diff --git a/spec/ansi_spec.lua b/spec/ansi_spec.lua index 15ecc9e..e46c376 100644 --- a/spec/ansi_spec.lua +++ b/spec/ansi_spec.lua @@ -20,7 +20,7 @@ describe("ansi module", function() it("should have move_to_col_1 code", function() assert.is_string(ansi.move_to_col_1) - assert.equals("\27[G", ansi.move_to_col_1) + assert.equals("\r\27[1G", ansi.move_to_col_1) end) it("should have move_up code", function()