303 lines
8.1 KiB
Lua
303 lines
8.1 KiB
Lua
local curl = require("plenary.curl")
|
|
local notify = require("snacks.notifier").notify
|
|
local M = {}
|
|
|
|
---@param data table
|
|
---@return string | nil
|
|
M.table_to_yaml = function(data)
|
|
local json = vim.json.encode(data)
|
|
|
|
-- Nutzt 'yq' um JSON zu YAML zu konvertieren
|
|
local handle = io.popen("echo '" .. json .. "' | yq -P e '. ' -")
|
|
local result = handle:read("*a")
|
|
handle:close()
|
|
|
|
if not result then
|
|
print("Error: yq not installed or got invalid json data")
|
|
return nil
|
|
end
|
|
return result
|
|
end
|
|
|
|
---@param yaml_string string
|
|
---@return table | nil
|
|
M.yaml_to_table = function(yaml_string)
|
|
-- 1. YAML String in yq einspeisen und JSON ausgeben lassen
|
|
-- Das Flag -o=json sorgt für die Konvertierung
|
|
local temp_file = os.tmpname()
|
|
local temp_file_handle = io.open(temp_file, "w+b")
|
|
if not temp_file_handle then
|
|
print("Error: could not open temp file")
|
|
print(" temp_file: " .. temp_file)
|
|
return nil
|
|
end
|
|
temp_file_handle:write(yaml_string)
|
|
temp_file_handle:flush()
|
|
temp_file_handle:close()
|
|
|
|
local cmd = "yq '.' " .. temp_file
|
|
local handle, errres = io.popen(cmd, 'r')
|
|
local json_result = handle:read("*a")
|
|
handle:close()
|
|
|
|
os.remove(temp_file)
|
|
|
|
-- 2. Fehlerprüfung
|
|
if json_result == "" then
|
|
print("Error: yq not installed or got invalid yaml data")
|
|
print(" cmd: " .. cmd)
|
|
print(" err: " .. errres)
|
|
return nil
|
|
end
|
|
|
|
-- 3. JSON in Lua-Table umwandeln
|
|
local ok, table_result = pcall(vim.json.decode, json_result)
|
|
if not ok then
|
|
print("Error: failed to decode json")
|
|
print(" cmd: " .. cmd)
|
|
print(" json: " .. json_result)
|
|
return nil
|
|
end
|
|
|
|
return table_result
|
|
end
|
|
|
|
---@class ClickupSession
|
|
---@field auth string
|
|
---@field user string
|
|
---@field workspace string
|
|
---@field base_url string
|
|
|
|
---@type ClickupSession
|
|
M.session = nil
|
|
|
|
---@class ClickupRef
|
|
---@field name string
|
|
---@field id string
|
|
|
|
---@class ClickupTask
|
|
---@field id string
|
|
---@field name string
|
|
---@field tags? table[]
|
|
---@field locations? table[]
|
|
---@field list? ClickupRef
|
|
---@field parent? string | nil
|
|
---@field [string] string
|
|
|
|
---@return ClickupTask[]
|
|
M.latest_tasks = function()
|
|
local resp = curl.get(M.session.base_url ..
|
|
"/team/" .. M.session.workspace .. "/task?subtasks=true&include_markdown_description=true&assignees[]=" ..
|
|
M.session.user, {
|
|
headers = {
|
|
['Authorization'] = M.session.auth,
|
|
["accept"] = "application/json",
|
|
["content-type"] = "application/json",
|
|
}
|
|
})
|
|
|
|
if resp.status == 200 then
|
|
return vim.json.decode(resp.body).tasks
|
|
end
|
|
|
|
print("failed http request: " .. tostring(resp.status) .. " (" .. resp.body .. ")")
|
|
return {}
|
|
end
|
|
|
|
---@param task ClickupTask
|
|
---@return ClickupTask
|
|
M._update_task = function(task)
|
|
local update_table = {}
|
|
|
|
for _, k in ipairs({ "name", "markdown_content", "status" }) do
|
|
if task[k] then
|
|
update_table[k] = task[k]
|
|
end
|
|
end
|
|
|
|
local resp = curl.put(M.session.base_url .. "/task/" .. task.id, {
|
|
headers = {
|
|
['Authorization'] = M.session.auth,
|
|
["accept"] = "application/json",
|
|
["content-type"] = "application/json",
|
|
},
|
|
body = vim.json.encode(update_table)
|
|
})
|
|
|
|
if resp.status ~= 200 then
|
|
error("failed to update " .. task.id .. "\n(" .. resp.body .. ")")
|
|
end
|
|
|
|
return vim.json.decode(resp.body)
|
|
end
|
|
|
|
---@param args vim.api.keyset.create_autocmd.callback_args
|
|
M._on_tempbuf_write = function(args)
|
|
local lines = vim.api.nvim_buf_get_lines(args.buf, 0, -1, false)
|
|
---@type string[]
|
|
local parts = {}
|
|
---@type string[]
|
|
local current = {}
|
|
for _, line in ipairs(lines) do
|
|
if line == "---" then
|
|
if #current then
|
|
table.insert(parts, table.concat(current, "\n"))
|
|
current = {}
|
|
end
|
|
else
|
|
table.insert(current, line)
|
|
end
|
|
end
|
|
table.insert(parts, table.concat(current, "\n"))
|
|
---@type ClickupTask
|
|
local data = vim.json.decode(parts[2])
|
|
data['markdown_content'] = string.gsub(parts[3], "%-%-%-\n", "")
|
|
|
|
M._update_task(data)
|
|
|
|
vim.api.nvim_set_option_value("modified", false, { buf = args.buf })
|
|
local notif_id = notify("updated task #" .. data.id, "info", {title="MrPy", style='fancy'})
|
|
end
|
|
|
|
---@class SelectionItem
|
|
---@field status string
|
|
---@field parent string | nil
|
|
---@field list string
|
|
---@field name string
|
|
---@field description string
|
|
---@field tags string[]
|
|
---@field id string
|
|
|
|
---@param item SelectionItem
|
|
M._on_select_task = function(picker, item)
|
|
local notif_id = notify("opening task #" .. item.id, "info", {title="MrPy", style='fancy'})
|
|
local new_name = "[ClickUp] " .. item.name
|
|
local new_buf_no = vim.api.nvim_create_buf(true, false)
|
|
|
|
local status, _ = pcall(vim.api.nvim_buf_set_name, new_buf_no, new_name)
|
|
if status then
|
|
-- vim.api.nvim_set_option_value("buftype", "nofile", { buf = new_buf_no })
|
|
vim.api.nvim_set_option_value("filetype", "markdown", { buf = new_buf_no })
|
|
vim.api.nvim_create_autocmd({ "BufWriteCmd" }, { buffer = new_buf_no, callback = M._on_tempbuf_write })
|
|
local content = { "---", "{" }
|
|
for _, k in ipairs({ "id", "name", "status", "tags", "list", "parent" }) do
|
|
if content[#content] ~= "{" then
|
|
content[#content] = content[#content] .. ","
|
|
end
|
|
table.insert(content, ' "' .. k .. '": ' .. vim.json.encode(item[k]))
|
|
end
|
|
table.insert(content, "}")
|
|
table.insert(content, "---")
|
|
for line in string.gmatch(item.description, "[^\r\n]+") do
|
|
table.insert(content, line)
|
|
end
|
|
vim.api.nvim_buf_set_lines(new_buf_no, 0, 0, false, content)
|
|
vim.api.nvim_set_option_value("modified", false, { buf = new_buf_no })
|
|
vim.api.nvim_win_set_buf(0, new_buf_no)
|
|
else
|
|
vim.api.nvim_buf_delete(new_buf_no, {})
|
|
picker:close()
|
|
end
|
|
end
|
|
|
|
---@param item SelectionItem
|
|
M._item_format = function(item, _)
|
|
local ret = {}
|
|
|
|
local hl = "SnacksPickerComment"
|
|
if item.status == "in progress" then
|
|
hl = "@method"
|
|
elseif item.status == "selected for development" then
|
|
hl = "@constant.builtin"
|
|
elseif item.status == "in review" then
|
|
hl = "@keyword"
|
|
elseif item.status == "done" then
|
|
hl = "@variable.builtin"
|
|
end
|
|
|
|
if item.parent ~= vim.NIL then
|
|
ret[#ret + 1] = { " ", "SnacksPickerComment" }
|
|
end
|
|
|
|
ret[#ret + 1] = { item.name, hl }
|
|
|
|
for _, v in ipairs(item.tags) do
|
|
ret[#ret + 1] = { " #" .. v, "SnacksPickerComment" }
|
|
end
|
|
|
|
ret[#ret + 1] = {
|
|
" " .. item.id,
|
|
"SnacksPickerComment"
|
|
}
|
|
return ret
|
|
end
|
|
|
|
---@param list table[]
|
|
---@param keys string[]
|
|
M.retrieve_subkeys = function(list, keys)
|
|
local ret = {}
|
|
local interm = list
|
|
|
|
for _, key in ipairs(keys) do
|
|
ret = {}
|
|
for _, item in ipairs(interm) do
|
|
table.insert(ret, item[key])
|
|
end
|
|
interm = ret
|
|
end
|
|
|
|
return ret
|
|
end
|
|
|
|
---@param data vim.api.keyset.create_user_command.command_args
|
|
M.select_task = function(data)
|
|
local notif_id = notify("fetching tasks", "warn", {timeout = 10000, title="MrPy", style='fancy'})
|
|
vim.schedule(function()
|
|
local tasks = M.latest_tasks()
|
|
|
|
---@type SelectionItem[]
|
|
local items = {}
|
|
|
|
for tix, t in ipairs(tasks) do
|
|
if string.sub(t.name, -7, -1) ~= "Absence" then
|
|
local preview_frontmatter = ""
|
|
|
|
---@type SelectionItem
|
|
local prepared = {
|
|
idx = tix,
|
|
id = t.id,
|
|
text = t.name .. t.id .. t.markdown_description,
|
|
name = t.name,
|
|
tags = M.retrieve_subkeys(t.tags, { "name" }),
|
|
status = t.status.status,
|
|
parent = t.parent,
|
|
list = t.list.name,
|
|
description = t.markdown_description,
|
|
preview = {
|
|
ft = "markdown",
|
|
},
|
|
action = M._on_select_task
|
|
}
|
|
|
|
for _, k in ipairs({ "id", "name", "status", "tags", "parent" }) do
|
|
preview_frontmatter = preview_frontmatter .. "\n" .. k .. ": " .. vim.json.encode(prepared[k])
|
|
end
|
|
|
|
prepared.preview.text = "---" .. preview_frontmatter .. "\n---\n" .. t.markdown_description
|
|
table.insert(items, #items + 1, prepared)
|
|
end
|
|
end
|
|
|
|
require("snacks.notifier").hide(notif_id)
|
|
require("snacks.picker").pick({
|
|
title = "Select Task",
|
|
format = M._item_format,
|
|
preview = "preview",
|
|
confirm = M._on_select_task,
|
|
items = items
|
|
})
|
|
end)
|
|
end
|
|
|
|
return M
|