rename
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
local curl = require("plenary.curl")
|
||||
local helpers = require("eta.helpers")
|
||||
local M = {}
|
||||
|
||||
---@class eta.clickup.Session: eta.Session
|
||||
---@field user string
|
||||
---@field workspace string
|
||||
|
||||
---@class eta.clickup.Ref
|
||||
---@field name string
|
||||
---@field id string
|
||||
|
||||
---@class eta.clickup.Dep
|
||||
---@field task_id string
|
||||
---@field depends_on string
|
||||
|
||||
---@class eta.clickup.Task
|
||||
---@field id string
|
||||
---@field name string
|
||||
---@field tags? table[]
|
||||
---@field locations? table[]
|
||||
---@field list? eta.clickup.Ref
|
||||
---@field parent? string | nil
|
||||
---@field dependencies? eta.clickup.Dep[]
|
||||
---@field [string] string
|
||||
|
||||
---@param self eta.clickup.Session
|
||||
---@return eta.clickup.Task[]
|
||||
M.latest_tasks = function(self)
|
||||
local ret = helpers.request("get", self, "/team/" .. self.workspace .. "/task",{subtasks="true", include_markdown_description="true", ['assignees[]']= self.user})
|
||||
if ret then
|
||||
return ret.tasks
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
---@param self eta.clickup.Session
|
||||
---@param id string
|
||||
---@return eta.clickup.Task
|
||||
M.task = function(self, id)
|
||||
local ret = helpers.request("get", self, "/task/" .. id,{include_markdown_description="true"}) or {}
|
||||
return ret
|
||||
end
|
||||
|
||||
---@param self eta.clickup.Session
|
||||
---@param id string
|
||||
M.task_relations = function(self, id)
|
||||
local ret = helpers.request("get", self, "/task/" .. id .. "/dependency", {}) or {}
|
||||
return ret
|
||||
end
|
||||
|
||||
M.insert_ref = function()
|
||||
local pos = vim.api.nvim_win_get_cursor(0)
|
||||
local row = pos[1] - 1
|
||||
local col = pos[2]
|
||||
local tasks = M.latest_tasks(require("plugin.eta").clickup_session)
|
||||
|
||||
---@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 = require("plugin.eta").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.picker").pick({
|
||||
title = "Select Task",
|
||||
format = require("plugin.eta")._item_format,
|
||||
preview = "preview",
|
||||
confirm = function(picker, item)
|
||||
picker:close()
|
||||
vim.api.nvim_buf_set_text(0, row, col, row, col,
|
||||
{ "[#" .. item.id .. "](https://app.clickup.com/t/" .. item.id .. ")" })
|
||||
end,
|
||||
items = items
|
||||
})
|
||||
end
|
||||
return M
|
||||
@@ -0,0 +1,149 @@
|
||||
local notify = require("snacks.notifier").notify
|
||||
local helpers = require("eta.helpers")
|
||||
local M = {}
|
||||
|
||||
---@class eta.gitlab.Session: eta.Session
|
||||
---@field auth string personal access token `PRIVATE_TOKEN: <auth_token>`
|
||||
|
||||
---@class eta.gitlab.Namespace
|
||||
---@field full_path string
|
||||
|
||||
---@class eta.gitlab.Project
|
||||
---@field id number
|
||||
---@field name string
|
||||
---@field path_with_namespace string
|
||||
---@field tag_list string[]
|
||||
---@field text? string
|
||||
---@field namespace eta.gitlab.Namespace
|
||||
---@field preview? {ft: string, text: string}
|
||||
|
||||
---@class eta.gitlab.Milestone
|
||||
---@field id number
|
||||
---@field title string
|
||||
|
||||
---@class eta.gitlab.Assignee
|
||||
---@field username string
|
||||
---@field id number
|
||||
|
||||
---@class eta.gitlab.Issue
|
||||
---@field id number
|
||||
---@field milestone eta.gitlab.Milestone
|
||||
---@field title string
|
||||
---@field assignees eta.gitlab.Assignee[]
|
||||
---@field description string
|
||||
---@field labels string[]
|
||||
|
||||
|
||||
---@param session eta.gitlab.Session
|
||||
---@return eta.gitlab.Project[]
|
||||
M.possible_projects = function(session)
|
||||
return helpers.request("get", session, "/projects", {simple="true", per_page="100"}) or {}
|
||||
end
|
||||
|
||||
---@param prj eta.gitlab.Project
|
||||
M._project_format = function(prj)
|
||||
local ret = {}
|
||||
|
||||
ret[#ret + 1] = { prj.namespace.full_path .. "/", "SnacksPickerComment" }
|
||||
ret[#ret + 1] = { prj.name }
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
---@param self eta.gitlab.Session
|
||||
---@return eta.gitlab.Project | nil
|
||||
M.update_current_project = function(self)
|
||||
local cmd = "git remote -v | grep fetch | cut -f2 | cut -d' ' -f1 | cut -d':' -f2"
|
||||
local handle, _ = io.popen(cmd, 'r')
|
||||
if not handle then return nil end
|
||||
local repo = handle:read("*a")
|
||||
handle:close()
|
||||
|
||||
local prjs = M.possible_projects(self)
|
||||
for _, prj in ipairs(prjs) do
|
||||
if prj.path_with_namespace == repo then
|
||||
M.active_project = prj
|
||||
notify("selected " .. prj.path_with_namespace, "info", { title = "ETA", style = 'fancy' })
|
||||
return prj
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@type eta.gitlab.Project
|
||||
M.active_project = nil
|
||||
|
||||
---@param picker snacks.Picker
|
||||
---@param item eta.gitlab.Project
|
||||
M._on_project_select = function(picker, item)
|
||||
M.active_project = item
|
||||
picker:close()
|
||||
notify("selected " .. item.path_with_namespace, "info", { title = "ETA", style = 'fancy' })
|
||||
end
|
||||
|
||||
---@param self eta.gitlab.Session
|
||||
M.select_project = function(self)
|
||||
local prjs = M.possible_projects(self)
|
||||
require("snacks.picker").pick({
|
||||
title = "Select Project",
|
||||
format = M._project_format,
|
||||
preview = "preview",
|
||||
confirm = M._on_project_select,
|
||||
items = prjs
|
||||
})
|
||||
end
|
||||
|
||||
---@param self eta.gitlab.Session
|
||||
---@return eta.gitlab.Issue[]
|
||||
M.active_issues = function(self)
|
||||
if not M.active_project then
|
||||
if not M.update_current_project(self) then
|
||||
M.select_project(self)
|
||||
end
|
||||
end
|
||||
|
||||
return request("get", self, "/projects/" .. tostring(M.active_project.id) .. "/issues", {})
|
||||
end
|
||||
|
||||
---@param self eta.gitlab.Session
|
||||
---@param name string
|
||||
---@param clickup_task_id string
|
||||
---@param tags string[]
|
||||
M.new_issue = function(self, name, clickup_task_id, tags)
|
||||
if not M.active_project then
|
||||
if not M.update_current_project(self) then
|
||||
M.select_project(self)
|
||||
end
|
||||
end
|
||||
|
||||
local description = "%23" .. clickup_task_id
|
||||
local title = name:gsub("%s", "%20")
|
||||
|
||||
return request("post", self, "/projects/" ..
|
||||
tostring(M.active_project.id) ..
|
||||
"/issues", {
|
||||
title=title, description=description, labels=table.concat(tags, ",")
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
---@param self eta.gitlab.Session
|
||||
---@param name string
|
||||
---@param issue number
|
||||
---@param clickup_task_id string
|
||||
---@param tags string[]
|
||||
M.new_mr = function(self, name, issue, clickup_task_id, tags)
|
||||
if not M.active_project then
|
||||
if not M.update_current_project(self) then
|
||||
M.select_project(self)
|
||||
end
|
||||
end
|
||||
|
||||
local description = "Closes %23" .. tostring(issue) .. " | %23" .. clickup_task_id
|
||||
local title = name:gsub("%s", "%20")
|
||||
|
||||
return request("post", self, "/projects/" .. tostring(M.active_project.id) .. "/merge_requests", {
|
||||
title=title, description=description, labels=table.concat(tags, ",")
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,63 @@
|
||||
local curl = require("plenary.curl")
|
||||
local M = {}
|
||||
|
||||
---@class eta.Session
|
||||
---@field auth string
|
||||
---@field base_url string
|
||||
|
||||
---@param method `get` | `post`
|
||||
---@param session eta.Session
|
||||
---@param endpoint string
|
||||
---@param params {[string]: string}
|
||||
---@returns table | nil
|
||||
M.request = function(method, session, endpoint, params)
|
||||
local param_list = {}
|
||||
for param_name, param_value in pairs(params) do
|
||||
table.insert(param_list, param_name .. "=" .. param_value)
|
||||
end
|
||||
local url = session.base_url .. endpoint
|
||||
if #param_list then
|
||||
url = url .. "?" .. table.concat(param_list, "&")
|
||||
end
|
||||
local resp = nil
|
||||
if method == "get" then
|
||||
resp = curl.get(url, {headers = {
|
||||
-- ['PRIVATE-TOKEN'] = session.auth,
|
||||
['Authorization'] = session.auth,
|
||||
["content-type"] = "application/json",
|
||||
}})
|
||||
elseif method == "post" then
|
||||
resp = curl.post(url, {headers = {
|
||||
-- ['PRIVATE-TOKEN'] = session.auth,
|
||||
['Authorization'] = session.auth,
|
||||
["content-type"] = "application/json",
|
||||
}})
|
||||
else
|
||||
return nil
|
||||
end
|
||||
|
||||
if resp.status == 200 then
|
||||
local prjs = vim.json.decode(resp.body)
|
||||
for _, prj in ipairs(prjs) do
|
||||
local fcmd = "echo '" .. vim.json.encode(prj) .. "' | jq"
|
||||
local fhandle, _ = io.popen(fcmd, 'r')
|
||||
if not fhandle then goto continue end
|
||||
local formatted = fhandle:read("*a")
|
||||
fhandle:close()
|
||||
|
||||
prj.text = prj.path_with_namespace
|
||||
prj.preview = {
|
||||
ft = "json",
|
||||
text = formatted
|
||||
}
|
||||
|
||||
::continue::
|
||||
end
|
||||
return prjs
|
||||
end
|
||||
|
||||
print("failed http request: " .. tostring(resp.status) .. " (" .. resp.body .. ", " .. url .. ")")
|
||||
return nil
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,319 @@
|
||||
local curl = require("plenary.curl")
|
||||
local notify = require("snacks.notifier").notify
|
||||
local gitlab = require("eta.gitlab")
|
||||
local clickup = require("eta.clickup")
|
||||
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 -y")
|
||||
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 = "cat " .. temp_file .. " | yq"
|
||||
local handle, errres = io.popen(cmd, 'r')
|
||||
local json_result = handle:read("*a")
|
||||
handle:close()
|
||||
|
||||
|
||||
-- 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
|
||||
|
||||
os.remove(temp_file)
|
||||
return table_result
|
||||
end
|
||||
|
||||
---@type eta.clickup.Session
|
||||
M.clickup_session = nil
|
||||
|
||||
---@type eta.gitlab.Session
|
||||
M.gitlab_session = nil
|
||||
|
||||
|
||||
---@param task eta.clickup.Task
|
||||
---@return eta.clickup.Task
|
||||
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.clickup_session.base_url .. "/task/" .. task.id, {
|
||||
headers = {
|
||||
['Authorization'] = M.clickup_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 eta.clickup.Task
|
||||
local data = M.yaml_to_table(parts[2]) or {}
|
||||
|
||||
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 = "ETA", 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 picker snacks.Picker | nil
|
||||
---@param item SelectionItem
|
||||
M._on_select_task = function(picker, item)
|
||||
local notif_id = notify("opening task #" .. item.id, "info", { title = "ETA", 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 = { "---"}
|
||||
local to_dump = {}
|
||||
for _, k in ipairs({ "id", "name", "status", "tags", "list", "parent", "dependencies" }) do
|
||||
to_dump[k] = item[k]
|
||||
end
|
||||
local yaml_string = M.table_to_yaml(to_dump) or ""
|
||||
for line in yaml_string:gmatch("([^\n]*)\n?") do
|
||||
table.insert(content, line)
|
||||
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, {})
|
||||
if picker then
|
||||
picker:close()
|
||||
end
|
||||
local to_open = vim.fn.bufnr(new_name)
|
||||
vim.api.nvim_win_set_buf(0, to_open)
|
||||
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 id string
|
||||
M.open_task = function(id)
|
||||
if id:match("[a-z0-9]+") then
|
||||
local notif_id = notify("opening #".. id, "info", { timeout = 1000, title = "ETA", style = 'fancy' })
|
||||
local t = clickup.task(M.clickup_session, id)
|
||||
---@type SelectionItem
|
||||
local item = {
|
||||
idx = nil,
|
||||
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 = nil
|
||||
}
|
||||
pcall(M._on_select_task,nil, item)
|
||||
else
|
||||
error("invalid id format")
|
||||
end
|
||||
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 = "ETA", style = 'fancy' })
|
||||
if not pcall(M.open_task,vim.fn.expand("<cword>")) then
|
||||
local tasks = clickup.latest_tasks(M.clickup_session)
|
||||
|
||||
---@type SelectionItem[]
|
||||
local items = {}
|
||||
|
||||
for tix, t in ipairs(tasks) do
|
||||
if string.sub(t.name, -7, -1) ~= "Absence" then
|
||||
local preview_frontmatter = ""
|
||||
|
||||
local deps = {}
|
||||
for _, dependency in ipairs(t.dependencies) do
|
||||
table.insert(deps, dependency.depends_on)
|
||||
end
|
||||
|
||||
---@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,
|
||||
dependencies = deps,
|
||||
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
|
||||
|
||||
---@param data vim.api.keyset.create_user_command.command_args
|
||||
M.mr_from_task = function(data)
|
||||
if data.args then
|
||||
notify(data.args, "info", { title = "ETA" })
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user