Skip to content

Commit

Permalink
feat!: move code to nio asynchronous logic, implement sync command
Browse files Browse the repository at this point in the history
  • Loading branch information
vhyrro committed Aug 21, 2023
1 parent 3f1ac14 commit 30d8708
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 96 deletions.
1 change: 0 additions & 1 deletion lua/rocks/constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ constants.DEFAULT_CONFIG = [[
[rocks]
toml = "0.3.0-0" # rocks.nvim can manage its own runtime dependencies too, goated!
# magick = "1.6.0-1" # I am using magick for testing stuff sometimes, so lets keep it here
[plugins]
# If the plugin name contains a dot then you must add quotes to the key name!
Expand Down
146 changes: 79 additions & 67 deletions lua/rocks/operations.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
--- Commentary:
--
-- This module handles all the operations that has something to do with
-- luarocks. Installing, uninstalling, updating, etc
-- luarocks. Installing, uninstalling, updating, etc.
--
-- NOTE: The code is not purely idiomatic lua, as a lot of async is involved.
-- A future rewrite would include pulling in plenary.nvim for utility functions
-- related to async.
--
-------------------------------------------------------------------------------
--
Expand All @@ -24,89 +28,97 @@ local constants = require("rocks.constants")
local fs = require("rocks.fs")
local config = require("rocks.config")
local state = require("rocks.state")
local nio = require("nio")

local operations = {}

function operations.install(name, version, callback)
---@alias Rock {name: string, version: string}
---
operations.install = function(name, version)
-- TODO(vhyrro): Input checking on name and version
vim.system({ "luarocks", "--lua-version=" .. constants.LUA_VERSION, "--tree=" .. config.rocks_path, "install", name, version }, function(obj)
callback(obj.code, obj.stderr)
local future = nio.control.future()
vim.system({ "luarocks", "--lua-version=" .. constants.LUA_VERSION, "--tree=" .. config.rocks_path, "install", name, version }, {}, function(...)
-- TODO: Raise an error with set_error on the future if something goes wrong
future.set(...)
end)
return future
end

function operations.sync(location)
-- Read or create a new config file and decode it
local user_config = require("toml").decode(fs.read_or_create(location or config.config_path, constants.DEFAULT_CONFIG))
operations.remove = function(name)
local future = nio.control.future()
vim.system({ "luarocks", "--lua-version=" .. constants.LUA_VERSION, "--tree=" .. config.rocks_path, "remove", name }, {}, function(...)
-- TODO: Raise an error with set_error on the future if something goes wrong
future.set(...)
end)
return future
end

-- Merge `rocks` and `plugins` fields as they are just an eye-candy separator for clarity purposes
local user_rocks = vim.tbl_deep_extend("force", user_config.rocks, user_config.plugins)
--- Synchronizes the state inside of rocks.toml with the physical state on the current
--- machine.
---@param user_rocks? { [string]: Rock|string }
---@type fun(user_rocks: { [string]: Rock|string })
operations.sync = function(user_rocks)
nio.run(function()
if user_rocks == nil then
-- Read or create a new config file and decode it
local user_config = require("toml").decode(fs.read_or_create(config.config_path, constants.DEFAULT_CONFIG))

-- Merge `rocks` and `plugins` fields as they are just an eye-candy separator for clarity purposes
user_rocks = vim.tbl_deep_extend("force", user_config.rocks, user_config.plugins)
end

-- TODO: change this to look for plugins that are not installed yet, also
-- invoke the update command at the end
state.installed_rocks(function(rocks)
local counter = #vim.tbl_keys(rocks)
for name, data in pairs(user_rocks) do
-- TODO(vhyrro): Good error checking
if type(data) == "string" then

if counter == 0 then
vim.print("Nothing new to install!")
return
user_rocks[name] = {
name = name,
version = data,
}
end
end

for name, data in pairs(rocks) do
operations.install(name, data.version, function(code, err)
counter = counter - 1

if code == 0 then
vim.print("Successfully updated '" .. name .. "'!")
else
vim.print("Failed to update '" .. name .. "'!")
vim.print("Error trace below:")
vim.print(err)
vim.print("Run :messages for full stacktrace.")
return
end

if counter == 0 then
vim.print("Everything is now in-sync!")
end
end)
local rocks = state.installed_rocks()

---@type string[]
local key_list = nio.fn.uniq(vim.list_extend(vim.tbl_keys(rocks), vim.tbl_keys(user_rocks)))

local actions = {}

for _, key in ipairs(key_list) do
if user_rocks[key] and not rocks[key] then
table.insert(actions, function()
return operations.install(user_rocks[key].name, user_rocks[key].version).wait()
-- TODO: After the operation is complete update a UI
end)
elseif not user_rocks[key] and rocks[key] then
table.insert(actions, function()
-- NOTE: This will fail if it breaks dependencies.
-- That is generall good, although we definitely want a handler
-- that ignores this.
-- To my knowledge there is no way to query all rocks that are *not*
-- dependencies.
return operations.remove(rocks[key].name).wait()
end)
elseif user_rocks[key].version ~= rocks[key].version then
table.insert(actions, function()
-- TODO: Clean this up?
-- `nio.first` seems to cause luarocks to throw some error, look into that.
local removed = operations.remove(rocks[key].name).wait()
local installed = operations.install(user_rocks[key].name, user_rocks[key].version).wait()
return { removed, installed }
end)
end
end
end)

operations.update()
end

function operations.update()
vim.api.nvim_echo({{"Checking for updates..."}}, false, {})
if not vim.tbl_isempty(actions) then
-- TODO: Error handling
local tasks = nio.gather(actions)

state.outdated_rocks(function(rocks)
local counter = #vim.tbl_keys(rocks)

if counter == 0 then
vim.print("Nothing to update!")
return
vim.schedule(function() vim.print(tasks) end)
end

for name, data in pairs(rocks) do
vim.print("New version for '" .. name .. "': " .. data.version .. " -> " .. data.target_version)

operations.install(name, data.target_version, function(code, err)
counter = counter - 1

if code == 0 then
vim.print("Successfully updated '" .. name .. "'!")
else
vim.print("Failed to update '" .. name .. "'!")
vim.print("Error trace below:")
vim.print(err)
vim.print("Run :messages for full stacktrace.")
return
end

if counter == 0 then
vim.print("Everything is now up-to-date!")
end
end)
end
vim.print("Complete!")
end)
end

Expand Down
16 changes: 10 additions & 6 deletions lua/rocks/setup.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@

local setup = {}

-- local constants = require("rocks.constants")
-- local operations = require("rocks.operations")
local constants = require("rocks.constants")
local config = require("rocks.config")

local function bootstrap_install(name, version)
vim.system({ "luarocks", "--lua-version=" .. constants.LUA_VERSION, "--tree=" .. config.rocks_path, "install", name, version }):wait()
end

--- Initialize rocks.nvim
--- Add luarocks Neovim tree paths to LUA_PATH and LUA_CPATH and download required rocks to work
function setup.init()
Expand All @@ -46,10 +49,11 @@ function setup.init()
local is_toml_installed, _ = pcall(require, "toml")

if not is_toml_installed then
vim.notify("Installing 'toml' dependency by using luarocks. This requires compiling C++ code so it may take a while, please wait ...")

-- operations.install("toml", "0.3.0-0")
-- operations.install("nui.nvim", "0.1.0-0")
vim.ui.select({ "Ok" }, { prompt = "Installing 'toml' dependency by using luarocks. This requires compiling C++ code so it may take a while, please wait ..." }, function()
vim.schedule(function()
bootstrap_install("toml", "0.3.0-0")
end)
end)
end
end

Expand Down
39 changes: 17 additions & 22 deletions lua/rocks/state.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,29 @@ local state = {}

local constants = require("rocks.constants")
local config = require("rocks.config")
local nio = require("nio")

---@alias Rock { name: string, version: string, target_version?: string }
---@type fun(): {[string]: Rock}
---@async
state.installed_rocks = nio.create(function()
---@type {[string]: Rock}
local rocks = {}

---
---@param callback fun(installed: {[string]: Rock})
function state.installed_rocks(callback)
local _callback = function(obj)
---@type {[string]: Rock}
local rocks = {}
local future = nio.control.future()

for name, version in obj.stdout:gmatch("([^%s]+)%s+(%d+%.%d+%.%d+%-%d+)%s+installed%s+[^%s]+") do
rocks[name] = { name = name, version = version }
end
vim.system({"luarocks", "--lua-version=" .. constants.LUA_VERSION, "--tree=" .. config.rocks_path, "list", "--porcelain"}, {text = true}, function(obj)
-- TODO: Error handling
future.set(obj.stdout)
end)

if callback then
callback(rocks)
else
return rocks
end
end
local installed_rock_list = future.wait()

if callback then
vim.system({"luarocks", "--lua-version=" .. constants.LUA_VERSION, "--tree=" .. config.rocks_path, "list", "--porcelain"}, _callback)
else
local rocklist = vim.system({"luarocks", "--lua-version=" .. constants.LUA_VERSION, "--tree=" .. config.rocks_path, "list", "--porcelain"}):wait()
return _callback(rocklist)
for name, version in installed_rock_list:gmatch("([^%s]+)%s+(%d+%.%d+%.%d+%-%d+)%s+installed%s+[^%s]+") do
rocks[name] = { name = name, version = version }
end
end

return rocks
end)

---@param callback fun(installed: {[string]: Rock})
function state.outdated_rocks(callback)
Expand Down

0 comments on commit 30d8708

Please sign in to comment.