feat(archive): duration syntax and confirmation prompt (#113)
* feat(s3): create bucket interactively during auth when unconfigured Problem: when a user runs `:Pending s3 auth` with no bucket configured, auth succeeds but offers no way to create the bucket. The user must manually run `aws s3api create-bucket` and update their config. Solution: add `util.input()` coroutine-aware prompt wrapper and a `create_bucket()` flow in `s3.lua` that prompts for bucket name and region, handles the `us-east-1` LocationConstraint quirk, and logs a config snippet on success. Called automatically from `auth()` when `sync.s3.bucket` is absent. * ci: typing * feat(parse): add `parse_duration_to_days` for duration string conversion Problem: The archive command accepted only a bare integer for days, inconsistent with the `+Nd`/`+Nw`/`+Nm` duration syntax used elsewhere. Solution: Add `parse_duration_to_days()` supporting `Nd`, `Nw`, `Nm`, and bare integers. Returns nil on invalid input for caller error handling. * feat(archive): duration syntax and confirmation prompt Problem: `:Pending archive` accepted only a bare integer for days and silently deleted tasks with no confirmation, risking accidental data loss. Solution: Accept duration strings (`7d`, `3w`, `2m`) via `parse.parse_duration_to_days()`, show a `vim.ui.input` confirmation prompt before removing tasks, and skip the prompt when zero tasks match.
This commit is contained in:
parent
7640241cf2
commit
dc365e266b
6 changed files with 287 additions and 25 deletions
|
|
@ -992,35 +992,67 @@ local function run_sync(backend_name, action)
|
|||
backend[action]()
|
||||
end
|
||||
|
||||
---@param days? integer
|
||||
---@param msg string
|
||||
---@param callback fun()
|
||||
local function confirm(msg, callback)
|
||||
vim.ui.input({ prompt = msg .. ' [y/N]: ' }, function(input)
|
||||
if input and input:lower() == 'y' then
|
||||
callback()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
---@param arg? string
|
||||
---@return nil
|
||||
function M.archive(days)
|
||||
if days == nil then
|
||||
function M.archive(arg)
|
||||
local days
|
||||
if arg and arg ~= '' then
|
||||
days = parse.parse_duration_to_days(arg)
|
||||
if not days then
|
||||
log.error('Invalid duration: ' .. arg .. '. Use e.g. 7d, 2w, 3m, or a bare number.')
|
||||
return
|
||||
end
|
||||
else
|
||||
days = 30
|
||||
end
|
||||
local cutoff = os.date('!%Y-%m-%dT%H:%M:%SZ', os.time() - (days * 86400)) --[[@as string]]
|
||||
local s = get_store()
|
||||
local tasks = s:tasks()
|
||||
log.debug(('archive: days=%d cutoff=%s total_tasks=%d'):format(days, cutoff, #tasks))
|
||||
local archived = 0
|
||||
local kept = {}
|
||||
local count = 0
|
||||
for _, task in ipairs(tasks) do
|
||||
if (task.status == 'done' or task.status == 'deleted') and task['end'] then
|
||||
if task['end'] < cutoff then
|
||||
archived = archived + 1
|
||||
goto skip
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
table.insert(kept, task)
|
||||
::skip::
|
||||
end
|
||||
s:replace_tasks(kept)
|
||||
_save_and_notify()
|
||||
log.info('Archived ' .. archived .. ' task' .. (archived == 1 and '' or 's') .. '.')
|
||||
local bufnr = buffer.bufnr()
|
||||
if bufnr and vim.api.nvim_buf_is_valid(bufnr) then
|
||||
buffer.render(bufnr)
|
||||
if count == 0 then
|
||||
log.info('No tasks to archive.')
|
||||
return
|
||||
end
|
||||
confirm(
|
||||
'Archive ' .. count .. ' task' .. (count == 1 and '' or 's') .. ' completed/deleted more than ' .. days .. 'd ago?',
|
||||
function()
|
||||
local kept = {}
|
||||
for _, task in ipairs(tasks) do
|
||||
if (task.status == 'done' or task.status == 'deleted') and task['end'] then
|
||||
if task['end'] < cutoff then
|
||||
goto skip
|
||||
end
|
||||
end
|
||||
table.insert(kept, task)
|
||||
::skip::
|
||||
end
|
||||
s:replace_tasks(kept)
|
||||
_save_and_notify()
|
||||
log.info('Archived ' .. count .. ' task' .. (count == 1 and '' or 's') .. '.')
|
||||
local bufnr = buffer.bufnr()
|
||||
if bufnr and vim.api.nvim_buf_is_valid(bufnr) then
|
||||
buffer.render(bufnr)
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
---@return nil
|
||||
|
|
@ -1342,7 +1374,7 @@ function M.command(args)
|
|||
local action = rest:match('^(%S+)')
|
||||
run_sync(cmd, action)
|
||||
elseif cmd == 'archive' then
|
||||
M.archive(tonumber(rest))
|
||||
M.archive(rest ~= '' and rest or nil)
|
||||
elseif cmd == 'due' then
|
||||
M.due()
|
||||
elseif cmd == 'filter' then
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue