From c69f297dd59ef8aa2b3dc4a4aea72da996a709e7 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Fri, 13 Mar 2026 17:58:49 -0400 Subject: [PATCH] fix: revert dev --- lua/pending/health.lua | 8 +++- lua/pending/init.lua | 58 +++++++++++++++++++++++--- plugin/pending.lua | 12 +++--- spec/sync_spec.lua | 94 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+), 12 deletions(-) diff --git a/lua/pending/health.lua b/lua/pending/health.lua index d00031b..7d95b5d 100644 --- a/lua/pending/health.lua +++ b/lua/pending/health.lua @@ -59,7 +59,7 @@ function M.check() end local sync_paths = vim.fn.globpath(vim.o.runtimepath, 'lua/pending/sync/*.lua', false, true) - if #sync_paths == 0 then + if #sync_paths == 0 and vim.tbl_isempty(require('pending').registered_backends()) then vim.health.info('No sync backends found') else for _, path in ipairs(sync_paths) do @@ -70,6 +70,12 @@ function M.check() backend.health() end end + for rname, rbackend in pairs(require('pending').registered_backends()) do + if type(rbackend.health) == 'function' then + vim.health.start('pending.nvim: sync/' .. rname) + rbackend.health() + end + end end end diff --git a/lua/pending/init.lua b/lua/pending/init.lua index 5c28998..013533c 100644 --- a/lua/pending/init.lua +++ b/lua/pending/init.lua @@ -1141,18 +1141,60 @@ end ---@class pending.SyncBackend ---@field name string ----@field auth fun(): nil +---@field auth? fun(sub_action?: string): nil ---@field push? fun(): nil ---@field pull? fun(): nil ---@field sync? fun(): nil ---@field health? fun(): nil +---@type table +local _registered_backends = {} + ---@type string[]? local _sync_backends = nil ---@type table? local _sync_backend_set = nil +---@param name string +---@return pending.SyncBackend? +function M.resolve_backend(name) + if _registered_backends[name] then + return _registered_backends[name] + end + local ok, mod = pcall(require, 'pending.sync.' .. name) + if ok and type(mod) == 'table' and mod.name then + return mod + end + return nil +end + +---@param backend pending.SyncBackend +---@return nil +function M.register_backend(backend) + if type(backend) ~= 'table' or type(backend.name) ~= 'string' or backend.name == '' then + log.error('register_backend: backend must have a non-empty `name` field') + return + end + local builtin_ok, builtin = pcall(require, 'pending.sync.' .. backend.name) + if builtin_ok and type(builtin) == 'table' and builtin.name then + log.error('register_backend: backend `' .. backend.name .. '` already exists as a built-in') + return + end + if _registered_backends[backend.name] then + log.error('register_backend: backend `' .. backend.name .. '` is already registered') + return + end + _registered_backends[backend.name] = backend + _sync_backends = nil + _sync_backend_set = nil +end + +---@return table +function M.registered_backends() + return _registered_backends +end + ---@return string[], table local function discover_backends() if _sync_backends then @@ -1169,6 +1211,12 @@ local function discover_backends() _sync_backend_set[mod.name] = true end end + for name, _ in pairs(_registered_backends) do + if not _sync_backend_set[name] then + table.insert(_sync_backends, name) + _sync_backend_set[name] = true + end + end table.sort(_sync_backends) return _sync_backends, _sync_backend_set end @@ -1177,8 +1225,8 @@ end ---@param action? string ---@return nil local function run_sync(backend_name, action) - local ok, backend = pcall(require, 'pending.sync.' .. backend_name) - if not ok then + local backend = M.resolve_backend(backend_name) + if not backend then log.error('Unknown sync backend: ' .. backend_name) return end @@ -1543,8 +1591,8 @@ function M.auth(args) local backends_list = discover_backends() local auth_backends = {} for _, name in ipairs(backends_list) do - local ok, mod = pcall(require, 'pending.sync.' .. name) - if ok and type(mod.auth) == 'function' then + local mod = M.resolve_backend(name) + if mod and type(mod.auth) == 'function' then table.insert(auth_backends, { name = name, mod = mod }) end end diff --git a/plugin/pending.lua b/plugin/pending.lua index 9f25dd5..48ade42 100644 --- a/plugin/pending.lua +++ b/plugin/pending.lua @@ -304,8 +304,8 @@ end, { if #parts == 0 or (#parts == 1 and not trailing) then local auth_names = {} for _, b in ipairs(pending.sync_backends()) do - local ok, mod = pcall(require, 'pending.sync.' .. b) - if ok and type(mod.auth) == 'function' then + local mod = pending.resolve_backend(b) + if mod and type(mod.auth) == 'function' then table.insert(auth_names, b) end end @@ -313,8 +313,8 @@ end, { end local backend_name = parts[1] if #parts == 1 or (#parts == 2 and not trailing) then - local ok, mod = pcall(require, 'pending.sync.' .. backend_name) - if ok and type(mod.auth_complete) == 'function' then + local mod = pending.resolve_backend(backend_name) + if mod and type(mod.auth_complete) == 'function' then return filter_candidates(arg_lead, mod.auth_complete()) end return {} @@ -328,8 +328,8 @@ end, { if not after_backend then return {} end - local ok, mod = pcall(require, 'pending.sync.' .. matched_backend) - if not ok then + local mod = pending.resolve_backend(matched_backend) + if not mod then return {} end local actions = {} diff --git a/spec/sync_spec.lua b/spec/sync_spec.lua index 51156bf..b7dfe8d 100644 --- a/spec/sync_spec.lua +++ b/spec/sync_spec.lua @@ -124,6 +124,100 @@ describe('sync', function() end) end) + describe('register_backend', function() + it('registers a custom backend', function() + pending.register_backend({ name = 'custom', pull = function() end }) + local set = pending.sync_backend_set() + assert.is_true(set['custom'] == true) + assert.is_true(vim.tbl_contains(pending.sync_backends(), 'custom')) + end) + + it('rejects backend without name', function() + local msg + local orig = vim.notify + vim.notify = function(m, level) + if level == vim.log.levels.ERROR then + msg = m + end + end + pending.register_backend({}) + vim.notify = orig + assert.truthy(msg and msg:find('non%-empty')) + end) + + it('rejects backend with empty name', function() + local msg + local orig = vim.notify + vim.notify = function(m, level) + if level == vim.log.levels.ERROR then + msg = m + end + end + pending.register_backend({ name = '' }) + vim.notify = orig + assert.truthy(msg and msg:find('non%-empty')) + end) + + it('rejects duplicate of built-in backend', function() + local msg + local orig = vim.notify + vim.notify = function(m, level) + if level == vim.log.levels.ERROR then + msg = m + end + end + pending.register_backend({ name = 'gcal' }) + vim.notify = orig + assert.truthy(msg and msg:find('already exists')) + end) + + it('rejects duplicate registered backend', function() + pending.register_backend({ name = 'dup_test', pull = function() end }) + local msg + local orig = vim.notify + vim.notify = function(m, level) + if level == vim.log.levels.ERROR then + msg = m + end + end + pending.register_backend({ name = 'dup_test' }) + vim.notify = orig + assert.truthy(msg and msg:find('already registered')) + end) + end) + + describe('resolve_backend', function() + it('resolves built-in backend', function() + local mod = pending.resolve_backend('gcal') + assert.is_not_nil(mod) + assert.are.equal('gcal', mod.name) + end) + + it('resolves registered backend', function() + local custom = { name = 'resolve_test', pull = function() end } + pending.register_backend(custom) + local mod = pending.resolve_backend('resolve_test') + assert.is_not_nil(mod) + assert.are.equal('resolve_test', mod.name) + end) + + it('returns nil for unknown backend', function() + assert.is_nil(pending.resolve_backend('nonexistent_xyz')) + end) + + it('dispatches command to registered backend', function() + local called = false + pending.register_backend({ + name = 'cmd_test', + pull = function() + called = true + end, + }) + pending.command('cmd_test pull') + assert.is_true(called) + end) + end) + describe('auto-discovery', function() it('discovers gcal and gtasks backends', function() local backends = pending.sync_backends()