From 34ff1b92acacf75a588bf1075aee03c52e86088c Mon Sep 17 00:00:00 2001 From: Saecki Date: Mon, 15 Jan 2024 18:49:29 +0100 Subject: [PATCH] WIP --- lua/crates/{api.tl => api.lua} | 280 +++++++++++++++++++-------------- 1 file changed, 165 insertions(+), 115 deletions(-) rename lua/crates/{api.tl => api.lua} (53%) diff --git a/lua/crates/api.tl b/lua/crates/api.lua similarity index 53% rename from lua/crates/api.tl rename to lua/crates/api.lua index 5d81a270..7c4566f5 100644 --- a/lua/crates/api.tl +++ b/lua/crates/api.lua @@ -1,64 +1,65 @@ -local record M - run_queued_jobs: function() - - crate_jobs: {string:CrateJob} - deps_jobs: {string:DepsJob} - queued_jobs: {QueuedJob} - num_requests: integer - - record CrateJob - job: Job - callbacks: {function(Crate, boolean)} - end - - record DepsJob - job: Job - callbacks: {function({Dependency}, boolean)} - end - - record QueuedJob - kind: JobKind - name: string - - crate_callbacks: {function(Crate|nil, boolean)} - - version: string - deps_callbacks: {function({Dependency}|nil, boolean)} - end - - enum JobKind - "crate" - "deps" - end -end - local semver = require("crates.semver") local state = require("crates.state") local time = require("crates.time") local DateTime = time.DateTime -local types = require("crates.types") -local Dependency = types.Dependency -local Crate = types.Crate -local Features = types.Features -local Version = types.Version local Job = require("plenary.job") +local types = require("crates.types") +local ApiFeatures = types.ApiFeatures + +local M = { + ---@type table + crate_jobs = {}, + ---@type table + deps_jobs = {}, + ---@type QueuedJob[] + queued_jobs = {}, + ---@type integer + num_requests = 0, +} + +---@class CrateJob +---@field job Job +---@field callbacks fun(crate: ApiCrate|nil, cancelled: boolean)[] + +---@class DepsJob +---@field job Job +---@field callbacks fun(deps: ApiDependency[]|nil, cancelled: boolean)[] + +---@class QueuedJob +---@field kind JobKind +---@field name string +---@field crate_callbacks fun(crate: ApiCrate|nil, cancelled: boolean)[] +---@field version string +---@field deps_callbacks fun(deps: ApiDependency[]|nil, cancelled: boolean)[] + +---@enum JobKind +local JobKind = { + crate = "crate", + deps = "deps", +} local ENDPOINT = "https://crates.io/api/v1" -local USERAGENT = vim.fn.shellescape("crates.nvim (https://github.com/saecki/crates.nvim)") as string -local JSON_DECODE_OPTS: vim.json.DecodeOpts = { luanil = { object = true, array = true } } +---@type string +local USERAGENT = vim.fn.shellescape("crates.nvim (https://github.com/saecki/crates.nvim)") -M.crate_jobs = {} -M.deps_jobs = {} -M.queued_jobs = {} -M.num_requests = 0 +---@class vim.json.DecodeOpts +---@class DecodeOpts +---@field luanil Luanil +---@class Luanil +---@field object boolean +---@field array boolean + +---@type vim.json.DecodeOpts +local JSON_DECODE_OPTS = { luanil = { object = true, array = true } } -local function parse_json(json_str: string): table - if not json_str then - return - end - local success, json = pcall(vim.json.decode, json_str, JSON_DECODE_OPTS) as (boolean, {string:{table}}) +---comment +---@param json_str string +---@return table|nil +local function parse_json(json_str) + ---@type boolean, any + local success, json = pcall(vim.json.decode, json_str, JSON_DECODE_OPTS) if not success then return end @@ -68,15 +69,20 @@ local function parse_json(json_str: string): table end end -local function request_job(url: string, on_exit: function(j: Job, code: integer, signal: integer)): Job +---@param url string +---@param on_exit fun(job: Job, code: integer, signal: integer) +---@return Job +local function request_job(url, on_exit) return Job:new { command = "curl", args = { unpack(state.cfg.curl_args), "-A", USERAGENT, url }, - on_exit = vim.schedule_wrap(on_exit) as function(Job, integer, integer), + on_exit = vim.schedule_wrap(on_exit), } end -local function enqueue_crate_job(name: string, callbacks: {function(Crate|nil, boolean)}) +---@param name string +---@param callbacks fun(crate: ApiCrate|nil, cancelled: boolean)[] +local function enqueue_crate_job(name, callbacks) for _,j in ipairs(M.queued_jobs) do if j.kind == "crate" and j.name == name then vim.list_extend(j.crate_callbacks, callbacks) @@ -91,7 +97,10 @@ local function enqueue_crate_job(name: string, callbacks: {function(Crate|nil, b }) end -local function enqueue_deps_job(name: string, version: string, callbacks: {function({Dependency}|nil, boolean)}) +---@param name string +---@param version string +---@param callbacks fun(deps: ApiDependency[]|nil, cancelled: boolean)[] +local function enqueue_deps_job(name, version, callbacks) for _,j in ipairs(M.queued_jobs) do if j.kind == "deps" and j.name == name and j.version == version then vim.list_extend(j.deps_callbacks, callbacks) @@ -108,54 +117,59 @@ local function enqueue_deps_job(name: string, version: string, callbacks: {funct end -function M.parse_crate(json_str: string): Crate|nil +---@param json_str string +---@return ApiCrate|nil +function M.parse_crate(json_str) local json = parse_json(json_str) if not (json and json.crate) then - return + return nil end - local c = json.crate as {string:any} - local crate: Crate = { - name = c.id as string, - description = c.description as string, - created = DateTime.parse_rfc_3339(c.created_at as string), - updated = DateTime.parse_rfc_3339(c.updated_at as string), - downloads = c.downloads as integer, - homepage = c.homepage as string, - documentation = c.documentation as string, - repository = c.repository as string, + ---@type table + local c = json.crate + ---@type ApiCrate + local crate = { + name = c.id, + description = c.description, + created = DateTime.parse_rfc_3339(c.created_at), + updated = DateTime.parse_rfc_3339(c.updated_at), + downloads = c.downloads, + homepage = c.homepage, + documentation = c.documentation, + repository = c.repository, categories = {}, keywords = {}, versions = {}, } - for _,ct_id in ipairs(c.categories as {{string:any}}) do - for _,ct in ipairs(json.categories as {{string:any}}) do + for _,ct_id in ipairs(c.categories) do + for _,ct in ipairs(json.categories) do if ct.id == ct_id then - table.insert(crate.categories, ct.category as string) + table.insert(crate.categories, ct.category) end end end - for _,kw_id in ipairs(c.keywords as {{string:any}}) do - for _,kw in ipairs(json.keywords as {{string:any}}) do + for _,kw_id in ipairs(c.keywords) do + for _,kw in ipairs(json.keywords) do if kw.id == kw_id then - table.insert(crate.keywords, kw.keyword as string) + table.insert(crate.keywords, kw.keyword) end end end - for _,v in ipairs(json.versions as {table}) do + for _,v in ipairs(json.versions) do if v.num then - local version: Version = { - num = v.num as string, - features = Features.new({}), - yanked = v.yanked as boolean, - parsed = semver.parse_version(v.num as string), - created = DateTime.parse_rfc_3339(v.created_at as string) + ---@type ApiVersion + local version = { + num = v.num, + features = ApiFeatures.new({}), + yanked = v.yanked, + parsed = semver.parse_version(v.num), + created = DateTime.parse_rfc_3339(v.created_at) } - for n,m in pairs(v.features as {string:{string}}) do + for n,m in pairs(v.features) do table.sort(m) version.features:insert({ name = n, @@ -195,7 +209,9 @@ function M.parse_crate(json_str: string): Crate|nil return crate end -local function fetch_crate(name: string, callbacks: {function(Crate|nil, boolean)}) +---@param name string +---@param callbacks fun(crate: ApiCrate|nil, cancelled: boolean)[] +local function fetch_crate(name, callbacks) local existing = M.crate_jobs[name] if existing then vim.list_extend(existing.callbacks, callbacks) @@ -209,17 +225,22 @@ local function fetch_crate(name: string, callbacks: {function(Crate|nil, boolean local url = string.format("%s/crates/%s", ENDPOINT, name) - local function on_exit(j: Job, code: integer, signal: integer) + ---@param j Job + ---@param code integer + ---@param signal integer + local function on_exit(j, code, signal) local cancelled = signal ~= 0 - local json: string = nil + ---@type string|nil + local json_str if code == 0 then - json = table.concat(j:result(), "\n") + json_str = table.concat(j:result(), "\n") end - local crate: Crate = nil - if not cancelled then - crate = M.parse_crate(json) + ---@type ApiCrate|nil + local crate + if not cancelled and json_str then + crate = M.parse_crate(json_str) end for _,c in ipairs(callbacks) do c(crate, cancelled) @@ -240,29 +261,36 @@ local function fetch_crate(name: string, callbacks: {function(Crate|nil, boolean job:start() end -function M.fetch_crate(name: string): Crate, boolean - return coroutine.yield(function(resolve: function(Crate, boolean)) +---@param name string +---@return ApiCrate|nil, boolean +function M.fetch_crate(name) + ---@param resolve fun(crate: ApiCrate|nil, cancelled: boolean) + return coroutine.yield(function(resolve) fetch_crate(name, {resolve}) - end) as (Crate, boolean) + end) end -function M.parse_deps(json_str: string): {Dependency}|nil +---@param json_str string +---@return ApiDependency[]|nil +function M.parse_deps(json_str) local json = parse_json(json_str) if not (json and json.dependencies) then return end - local dependencies: {Dependency} = {} - for _,d in ipairs(json.dependencies as {table}) do + ---@type ApiDependency[] + local dependencies = {} + for _,d in ipairs(json.dependencies) do if d.crate_id then - local dependency: Dependency = { - name = d.crate_id as string, - opt = d.optional as boolean or false, - kind = d.kind as Dependency.Kind or "normal", + ---@type ApiDependency + local dependency = { + name = d.crate_id, + opt = d.optional or false, + kind = d.kind or "normal", vers = { - text = d.req as string, - reqs = semver.parse_requirements(d.req as string), + text = d.req, + reqs = semver.parse_requirements(d.req), }, } table.insert(dependencies, dependency) @@ -272,7 +300,10 @@ function M.parse_deps(json_str: string): {Dependency}|nil return dependencies end -local function fetch_deps(name: string, version: string, callbacks: {function({Dependency}, boolean)}) +---@param name string +---@param version string +---@param callbacks fun(deps: ApiDependency[]|nil, cancelled: boolean)[] +local function fetch_deps(name, version, callbacks) local jobname = name .. ":" .. version local existing = M.deps_jobs[jobname] if existing then @@ -287,17 +318,22 @@ local function fetch_deps(name: string, version: string, callbacks: {function({D local url = string.format("%s/crates/%s/%s/dependencies", ENDPOINT, name, version) - local function on_exit(j: Job, code: integer, signal: integer) + ---@param j Job + ---@param code integer + ---@param signal integer + local function on_exit(j, code, signal) local cancelled = signal ~= 0 - local json: string = nil + ---@type string|nil + local json_str if code == 0 then - json = table.concat(j:result(), "\n") + json_str = table.concat(j:result(), "\n") end - local deps: {Dependency} = nil - if not cancelled then - deps = M.parse_deps(json) + ---@type ApiDependency[]|nil + local deps + if not cancelled and json_str then + deps = M.parse_deps(json_str) end for _,c in ipairs(callbacks) do c(deps, cancelled) @@ -318,32 +354,46 @@ local function fetch_deps(name: string, version: string, callbacks: {function({D job:start() end -function M.fetch_deps(name: string, version: string): {Dependency}, boolean - return coroutine.yield(function(resolve: function({Dependency}, boolean)) +---@param name string +---@param version string +---@return ApiDependency[]|nil, boolean +function M.fetch_deps(name, version) + ---@param resolve fun(deps: ApiDependency[]|nil, cancelled: boolean) + return coroutine.yield(function(resolve) fetch_deps(name, version, {resolve}) - end) as ({Dependency}, boolean) + end) end -function M.is_fetching_crate(name: string): boolean +---@param name string +---@return boolean +function M.is_fetching_crate(name) return M.crate_jobs[name] ~= nil end -function M.is_fetching_deps(name: string, version: string): boolean +---@param name string +---@param version string +---@return boolean +function M.is_fetching_deps(name, version) return M.deps_jobs[name .. ":" .. version] ~= nil end -local function add_crate_callback(name: string, callback: function(Crate, boolean)) +---@param name string +---@param callback fun(crate: ApiCrate|nil, cancelled: boolean) +local function add_crate_callback(name, callback) table.insert( M.crate_jobs[name].callbacks, callback ) end -function M.await_crate(name: string): Crate, boolean - return coroutine.yield(function(resolve: function(Crate, boolean)) +---@param name string +---@return ApiCrate|nil, boolean +function M.await_crate(name) + ---@param resolve fun(crate: ApiCrate|nil, cancelled: boolean) + return coroutine.yield(function(resolve) add_crate_callback(name, resolve) - end) as (Crate, boolean) + end) end local function add_deps_callback(name: string, version: string, callback: function({Dependency}, boolean))