Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions experimental/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Experiments

This folder contains experiments of using `terminal.lua` to power the real-world LuaRocks CLI application.

- `luarox` a interactive CLI wrapper for LuaRocks
- `luarocket` a full screen application wrapper for LuaRocks

Both applications are just visual experiments for testing `terminal.lua`, they are not functional.
17 changes: 17 additions & 0 deletions experimental/luarocket/bin/luarocket.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env lua

local terminal = require("terminal")
local copas = require("copas")

-- Run the main application inside the initialized terminal
local main = terminal.initwrap(require("luarocket.main"), {
displaybackup = true,
filehandle = io.stdout,
sleep = copas.pause, -- required for coroutine based multithreading
})

-- run the Copas scheduler
copas(function()
main()
copas.exit() -- signal to other coroutines we're done
end)
51 changes: 51 additions & 0 deletions experimental/luarocket/luarocket-scm-1.rockspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
local package_name = "luarocket"
local package_version = "scm"
local rockspec_revision = "1"
local github_account_name = "Tieske"
local github_repo_name = "luarocket"


package = package_name
version = package_version.."-"..rockspec_revision

source = {
url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git",
branch = (package_version == "scm") and "main" or nil,
tag = (package_version ~= "scm") and package_version or nil,
}

description = {
summary = "Terminal UI for LuaRocks",
detailed = [[
Cross platform Terminal UI for LuaRocks.
]],
license = "MIT",
homepage = "https://github.com/"..github_account_name.."/"..github_repo_name,
}

dependencies = {
"terminal",
"copas-async",
"lua-cjson",
}

build = {
type = "builtin",

install = {
bin = {
luarocket = "bin/luarocket.lua",
}
},

modules = {
["luarocket.main"] = "src/main.lua",
["luarocket.luarocks"] = "src/luarocks.lua",
["luarocket.json-encode"] = "src/json-encode.lua",
},

copy_directories = {
-- can be accessed by `luarocks terminal doc` from the commandline
-- "docs",
},
}
55 changes: 55 additions & 0 deletions experimental/luarocket/src/json-encode.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
-- Code by Aapo Talvensari
-- https://github.com/bungle/lua-resty-prettycjson
local ok, cjson = pcall(require, "cjson.safe")
local enc = ok and cjson.encode or function() return nil, "Lua cJSON encoder not found" end
local cat = table.concat
local sub = string.sub
local rep = string.rep
-- @tparam table dt the data to encode
-- @tparam[opt="\n"] string lf the line feed separator
-- @tparam[opt="\t"] string id the indent characters
-- @tparam[opt=" "] string ac the array continuation characters
-- @tparam function ec the encoder function, defaults to CJSON encoder
-- @return string or nil+error
return function(dt, lf, id, ac, ec)
local s, e = (ec or enc)(dt)
if not s then return s, e end
lf, id, ac = lf or "\n", id or "\t", ac or " "
local i, j, k, n, r, p, q = 1, 0, 0, #s, {}, nil, nil
local al = sub(ac, -1) == "\n"
for x = 1, n do
local c = sub(s, x, x)
if not q and (c == "{" or c == "[") then
r[i] = p == ":" and cat{ c, lf } or cat{ rep(id, j), c, lf }
j = j + 1
elseif not q and (c == "}" or c == "]") then
j = j - 1
if p == "{" or p == "[" then
i = i - 1
r[i] = cat{ rep(id, j), p, c }
else
r[i] = cat{ lf, rep(id, j), c }
end
elseif not q and c == "," then
r[i] = cat{ c, lf }
k = -1
elseif not q and c == ":" then
r[i] = cat{ c, ac }
if al then
i = i + 1
r[i] = rep(id, j)
end
else
if c == '"' and p ~= "\\" then
q = not q and true or nil
end
if j ~= k then
r[i] = rep(id, j)
i, k = i + 1, j
end
r[i] = c
end
p, i = c, i + 1
end
return cat(r)
end
172 changes: 172 additions & 0 deletions experimental/luarocket/src/luarocks.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
local json_encode = require("luarocket.json-encode")
local json_decode = require("cjson.safe").decode
local strwidth = require("terminal.text.width").utf8swidth
local split = require("pl.utils").split
local async = require("copas.async")
local M = {}

local logpanel = nil
local configpanel = nil
local listpanel = nil
local lr_config = nil



function M.set_logpanel(panel)
logpanel = panel
end


function M.set_configpanel(panel)
configpanel = panel
end


function M.set_listpanel(panel)
listpanel = panel
end


--- cache table that returns the display width of common strings.
-- This caches the string, but is more performant than looping over them everytime again
local common_string_width = setmetatable({}, {
__index = function(t, k)
local w = strwidth(k)
t[k] = w
return w
end
})


--- Takes a list of lists, and padds each element to the max width of that column.
-- Returns a list of strings, each string being a row with '|' separated columns.
local function tableize(list)
local col_widths = {}
for _, row in ipairs(list) do
for col_idx, value in ipairs(row) do
local col_len = common_string_width[value]
if not col_widths[col_idx] or col_len > col_widths[col_idx] then
col_widths[col_idx] = col_len
end
end
end

local result = {}
for _, row in ipairs(list) do
local padded_cols = {}
for col_idx, value in ipairs(row) do
padded_cols[col_idx] = value .. string.rep(" ", col_widths[col_idx] - common_string_width[value])
end
result[#result + 1] = table.concat(padded_cols, " │ ")
end

return result
end


-- run a LuaRocks command asynchronously and return the result as a table of lines.
-- Arguments will be tostringed and quoted for the command line.
local function run_luarocks_command(...)
local args = {...}
local qargs = {}
for i, arg in ipairs(args) do
qargs[i] = '"' .. tostring(arg) .. '"'
end
local cmd = "luarocks " .. table.concat(qargs, " ")

-- redirect stderr to stdout
cmd = cmd .. " 2>&1"

logpanel:add_line("> " .. cmd, true)

local f, err = async.io_popen(cmd)
if not f then
logpanel:add_line("Lua error: " .. err, true)
return nil, err
end

local result = {}
while true do
local line = f:read("*l")
if not line then
break
end
logpanel:add_line(line:gsub("\t", " "), true)
result[#result + 1] = line
end

local s, et, ec = f:close()
if not s then
logpanel:add_line("# error: " .. tostring(et) .. " (" .. tostring(ec) .. ")", true)
return nil, et, ec
end

return result
end


-- tests luarocks availability
function M.test_luarocks()
return run_luarocks_command("--version")
end



--- Retrieves the config from LR.
-- Result is stored in lr_config and returned.
function M.refresh_config()
local result = run_luarocks_command("config", "--json")
if not result then
return {
error = "failed to collect LuaRocks config (check logs)"
}
end

local config, err = json_decode(result[1])
if not config then
return {
error = "failed to parse LuaRocks config: " .. tostring(err)
}
end

lr_config = config

local newline = string.char(0)
local indent = " "
local array_continuation = " "
local lines = split(json_encode(config, newline, indent, array_continuation), newline)

configpanel:set_lines(lines)
return config
end



--- returns the LR config from cache, or retrieves it if not cached yet.
function M.get_config()
if lr_config == nil then
M.refresh_config()
end
return lr_config
end


--- lists installed rocks
function M.list_rocks(tree)
if tree then
assert(type(tree) == "string", "tree must be a string")
tree = "--tree=" .. tostring(tree)
end
local list = run_luarocks_command("list", "--porcelain", tree)
if not list then
return {}
end
for i, line in ipairs(list) do
list[i] = split(line, "\t")
end
listpanel:set_lines(tableize(list))
end


return M

Loading
Loading