automate/020_yazi-plugin_httm-nicoti...

239 lines
7.2 KiB
Bash

#!/usr/bin/env bash
: <<!
# ------------------------------------------------------------------------------
# Yazi nicotine plugin — pure Lua reimplementation (no external nicotine script)
# Requires: httm, git, tar, find, stat, realpath (or readlink -f), mktemp
# ------------------------------------------------------------------------------
!
set -euo pipefail
DEST=${1:-/etc/skel/}
YAZI_HOME="${DEST}/.config/yazi"
YAZI_PLUGIN_HOME="${YAZI_HOME}/plugins"
NICOTINE_PLUGIN_HOME="${YAZI_PLUGIN_HOME}/httm-nicotine.yazi"
mkdir -p "${NICOTINE_PLUGIN_HOME}"
conf_print_nicotine_main() {
cat <<'EOF'
local function log(msg)
-- ya.err("[nicotine] " .. msg) -- uncomment for debug logging to yazi log
end
-- Safe git identity (prevents commit failures when git config is missing)
local GIT_ENV = {
GIT_AUTHOR_NAME = "yazi-nicotine",
GIT_AUTHOR_EMAIL = "yazi-nicotine@localhost",
GIT_COMMITTER_NAME = "yazi-nicotine",
GIT_COMMITTER_EMAIL = "yazi-nicotine@localhost",
}
-- Grab selected files / hovered file + cwd (sync context only)
local get_state = ya.sync(function()
local targets = {}
local selected = cx.active.selected
if #selected == 0 then
local hovered = cx.active.current.hovered
if hovered then
table.insert(targets, tostring(hovered.url))
end
else
for _, item in pairs(selected) do
table.insert(targets, tostring(item.url))
end
end
return {
targets = targets,
cwd = tostring(cx.active.current.cwd),
}
end)
local function run(cmd_builder)
local child, err = cmd_builder:spawn()
if not child then
ya.notify({ title = "Nicotine", content = "Spawn failed: " .. tostring(err), level = "error" })
return nil
end
local status = child:wait()
if not status.success then
ya.notify({ title = "Nicotine", content = cmd_builder._program .. " failed (code " .. tostring(status.code) .. ")", level = "error" })
return nil
end
return status
end
local function get_modification_time(path)
local st = run(Command("stat"):arg("-c"):arg("%Y"):arg(path))
if not st then return os.time() end -- fallback
local code = st.output and tonumber(st.output:match("^%d+")) or os.time()
return code
end
local function copy_add_commit(debug, src_path, repo_dir, commit_msg_suffix)
local cp = Command("cp"):arg("-a"):arg(src_path):arg(repo_dir .. "/")
run(cp)
local date_unix = get_modification_time(src_path)
local date_str = os.date("!%Y-%m-%dT%H:%M:%S", date_unix)
local git = Command("git"):cwd(repo_dir)
:env(GIT_ENV.GIT_AUTHOR_NAME, GIT_ENV.GIT_AUTHOR_NAME)
:env(GIT_ENV.GIT_AUTHOR_EMAIL, GIT_ENV.GIT_AUTHOR_EMAIL)
:env(GIT_ENV.GIT_COMMITTER_NAME, GIT_ENV.GIT_COMMITTER_NAME)
:env(GIT_ENV.GIT_COMMITTER_EMAIL,GIT_ENV.GIT_COMMITTER_EMAIL)
run(git:arg("add"):arg("--all"))
local commit = git:arg("commit")
:arg("--quiet")
:arg("--allow-empty")
:arg("--message"):arg("httm snapshot: " .. commit_msg_suffix)
:arg("--date"):arg(date_str)
if debug then commit = commit:arg("--verbose") end
run(commit)
end
-- Recursive collection of unique versions (mimics original traverse + get_unique_versions)
local function process_path(debug, path, repo_dir)
local httm_out = ""
local httm_cmd = Command("httm"):arg("-n"):arg("--omit-ditto"):arg(path)
:stdout(Command.PIPED):stderr(Command.PIPED)
local child, _ = httm_cmd:spawn()
if child then
local status = child:wait()
if status.success then
httm_out = child:read_lines() or ""
end
end
local versions = {}
for line in httm_out:gmatch("[^\n]+") do
if line ~= "" then table.insert(versions, line) end
end
if #versions <= 1 then
-- no snapshots or single version → just current file/dir
copy_add_commit(debug, path, repo_dir, "current version of " .. path)
else
for _, ver in ipairs(versions) do
copy_add_commit(debug, ver, repo_dir, "snapshot: " .. ver)
end
end
-- If directory, recurse into children
local is_dir = run(Command("test"):arg("-d"):arg(path))
if is_dir then
local find_cmd = Command("find"):arg(path):arg("-mindepth"):arg("1"):arg("-maxdepth"):arg("1")
:stdout(Command.PIPED)
local child = find_cmd:spawn()
if child then
local status = child:wait()
if status.success then
for line in child:read_lines() do
if line ~= "" then
local sub = line
local base = sub:match("^.*/([^/]+)$") or "unknown"
local sub_repo = repo_dir .. "/" .. base
run(Command("mkdir"):arg("-p"):arg(sub_repo))
process_path(debug, sub, sub_repo)
end
end
end
end
end
end
return {
entry = function(_, args)
local state = get_state()
local targets = state.targets
local cwd = state.cwd
if #targets == 0 then
return ya.notify({ title = "Nicotine", content = "No files selected or hovered", level = "warn" })
end
local is_archive = args[1] == "--archive"
local debug = false -- set to true for verbose git output
local tmp_base = os.getenv("TMPDIR") or "/tmp"
local rand = math.random(10000000, 99999999)
local tmp_dir = string.format("%s/yazi-nicotine-%d", tmp_base, rand)
local repo_dir = tmp_dir .. "/nicotine-combined-git"
run(Command("mkdir"):arg("-p"):arg(repo_dir))
ya.notify({
title = "Nicotine",
content = is_archive and "Creating archive..." or "Building git history...",
timeout = 3,
})
run(Command("git"):cwd(repo_dir):arg("init"):arg("--quiet"))
for _, p in ipairs(targets) do
local real = run(Command("realpath"):arg(p))
if real then
process_path(debug, real.output:match("^%S+"), repo_dir)
end
end
if is_archive then
local archive_path = cwd .. "/nicotine-combined-git.tar.gz"
run(Command("tar"):cwd(tmp_dir):arg("-zcf"):arg(archive_path):arg("nicotine-combined-git"))
run(Command("rm"):arg("-rf"):arg(tmp_dir))
ya.notify({ title = "Nicotine", content = "Archive created:\n" .. archive_path, timeout = 5 })
else
-- Open lazygit
local permit = ui.hide()
local lg = Command("lazygit")
:cwd(repo_dir)
:stdin(Command.INHERIT)
:stdout(Command.INHERIT)
:stderr(Command.INHERIT)
local child, err = lg:spawn()
if not child then
permit:drop()
run(Command("rm"):arg("-rf"):arg(tmp_dir))
return ya.notify({ title = "Nicotine", content = "lazygit failed: " .. tostring(err), level = "error" })
end
child:wait()
permit:drop()
run(Command("rm"):arg("-rf"):arg(tmp_dir))
ya.notify({ title = "Nicotine", content = "Session ended.", timeout = 2 })
end
end,
}
EOF
}
conf_print_nicotine_main | tee "${NICOTINE_PLUGIN_HOME}/main.lua"
conf_print_nicotine_keymap() {
cat <<'EOF'
[[manager.prepend_keymap]]
on = [ "g", "n" ]
run = "plugin nicotine"
desc = "Nicotine: view git history of selection in lazygit"
[[manager.prepend_keymap]]
on = [ "g", "z" ]
run = "plugin nicotine --args='--archive'"
desc = "Nicotine: create .tar.gz snapshot archive of selection"
EOF
}
# conf_print_nicotine_keymap | tee -a "${NICOTINE_PLUGIN_HOME}/keymap.toml"
echo "Plugin location: ${NICOTINE_PLUGIN_HOME}/main.lua"
echo "Remember to restart yazi or run :plugin --reload nicotine"