ppq: Display Opencode releases

This commit is contained in:
Jiri Jakes 2026-03-15 11:53:01 -03:00
parent 81fbe9ed45
commit a88e8fe7d6

View file

@ -77,24 +77,143 @@
(when secret
(funcall (plist-get (car secret) :secret)))))
;; --- Table 1: Fruits ---
;; --- Table 1: Releases ---
(defvar ppq-fruits-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map tabulated-list-mode-map)
(defvar ppq--releases-data nil
"Cached releases data from GitHub.")
(defvar ppq--releases-projects
'(("Opencode" . "anomalyco/opencode"))
"List of projects to fetch releases for.
Each element is a cons cell (DISPLAY-NAME . REPO-STRING).")
(defun ppq--releases-get-buffer-name ()
"Get the releases buffer name."
"*Dashboard:Releases*")
(defvar ppq--releases-active-processes nil
"List of active release fetch processes.")
(defun ppq--releases-process-sentinel (process _event)
"Process sentinel for gh release list.
PROCESS is the process object, _EVENT is the status change string."
(setq ppq--releases-active-processes
(delq process ppq--releases-active-processes))
(when (memq (process-status process) '(exit signal))
(let ((stdout-buf (process-buffer process))
(stderr-buf (process-get process 'ppq-stderr-buffer))
(exit-code (process-exit-status process))
(project-name (process-get process 'ppq-project-name)))
(if (zerop exit-code)
(let ((output (with-current-buffer stdout-buf
(buffer-string))))
(condition-case nil
(let* ((json (json-parse-string (string-trim output) :object-type 'alist))
(releases (if (vectorp json) (append json nil) json)))
(push (cons project-name releases) ppq--releases-data)
(ppq--releases-refresh-buffer))
(error nil)))
(let ((stderr-output (with-current-buffer stderr-buf
(buffer-string))))
(message "ppq releases: failed for %s: %s"
project-name (string-trim stderr-output))))
(when (buffer-live-p stdout-buf)
(kill-buffer stdout-buf))
(when (buffer-live-p stderr-buf)
(kill-buffer stderr-buf)))))
(defun ppq--releases-fetch-async (project-name repo)
"Fetch releases for REPO asynchronously using gh CLI.
PROJECT-NAME is the display name for the project."
(let* ((stdout-buf (generate-new-buffer " *gh-releases-stdout*"))
(stderr-buf (generate-new-buffer " *gh-releases-stderr*"))
(process-environment (append '("PAGER=cat" "GH_PAGER=cat" "GIT_PAGER=cat" "NO_COLOR=1")
process-environment))
(process (make-process
:name "gh-releases"
:buffer stdout-buf
:command (list "gh" "api"
(format "repos/%s/releases?per_page=5" repo))
:stderr stderr-buf
:sentinel #'ppq--releases-process-sentinel
:noquery t)))
(process-put process 'ppq-project-name project-name)
(process-put process 'ppq-stderr-buffer stderr-buf)
(push process ppq--releases-active-processes)))
(defun ppq--releases-fetch-all-async ()
"Fetch releases for all configured projects."
(setq ppq--releases-data nil)
(dolist (project ppq--releases-projects)
(ppq--releases-fetch-async (car project) (cdr project))))
(defun ppq--releases-format-date (iso-date)
"Format ISO-DATE string to human-readable date."
(when iso-date
(condition-case nil
(format-time-string
"%Y-%m-%d"
(encode-time (parse-time-string iso-date)))
(error iso-date))))
(defun ppq--releases-insert-content ()
"Insert releases content into current buffer."
(let ((inhibit-read-only t))
(erase-buffer)
(insert "* Software Releases\n\n")
(if ppq--releases-data
(dolist (project-data ppq--releases-data)
(let ((project-name (car project-data))
(releases (cdr project-data)))
(insert (format "** %s\n" project-name))
(if releases
(dolist (release releases)
(let* ((tag (cdr (assq 'tag_name release)))
(name (cdr (assq 'name release)))
(published (cdr (assq 'published_at release)))
(body (cdr (assq 'body release)))
(display-name (if (and name (not (string= name "")))
name
tag)))
(insert (format "*** %s (%s)\n"
display-name
(ppq--releases-format-date published)))
;; Insert changelog body if present
(when (and body (not (eq body :null)) (not (string= body "")))
(insert "\n#+begin_src markdown\n")
(insert body)
(insert "\n#+end_src\n"))))
(insert "- No releases found\n"))
(insert "\n")))
(insert "Loading releases...\n"))
(goto-char (point-min))
;; Fold to show release versions (level 3) but hide changelog content
(org-content 3)))
(defun ppq--releases-refresh-buffer ()
"Refresh the releases buffer if it exists."
(when-let ((buf (get-buffer (ppq--releases-get-buffer-name))))
(with-current-buffer buf
(ppq--releases-insert-content))))
(defun ppq-releases-mode ()
"Major mode for displaying GitHub releases."
(interactive)
(kill-all-local-variables)
(org-mode)
(org-indent-mode 1)
(setq major-mode 'ppq-releases-mode
mode-name "Releases")
(use-local-map (let ((map (copy-keymap org-mode-map)))
(define-key map "q" #'ppq-quit)
map))
(setq buffer-read-only t)
(ppq--releases-insert-content))
(define-derived-mode ppq-fruits-mode tabulated-list-mode "Fruits"
"A table of fruits."
(setq tabulated-list-format [("Fruit" 15 t) ("Color" 10 t) ("Taste" 10 t)])
(setq tabulated-list-entries
'((1 ["Apple" "Red" "Sweet"])
(2 ["Banana" "Yellow" "Sweet"])
(3 ["Lemon" "Yellow" "Sour"])
(4 ["Grape" "Purple" "Sweet"])))
(tabulated-list-init-header)
(tabulated-list-print))
(defvar ppq-releases-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "q" #'ppq-quit)
map))
;; --- Table 2: Models ---
@ -301,11 +420,18 @@
(defun ppq-quit ()
"Quit the dashboard, restore previous window configuration, and kill buffers."
(interactive)
(dolist (buf (list (get-buffer "*Dashboard:Fruits*")
;; Kill any active release fetch processes
(dolist (proc ppq--releases-active-processes)
(when (process-live-p proc)
(delete-process proc)))
(setq ppq--releases-active-processes nil)
;; Kill buffers
(dolist (buf (list (get-buffer "*Dashboard:Releases*")
(get-buffer "*Dashboard:Models*")
(get-buffer "*Dashboard:Info*")))
(when buf (kill-buffer buf)))
(setq ppq--models-data nil)
(setq ppq--releases-data nil)
(when ppq--window-config
(set-window-configuration ppq--window-config)
(setq ppq--window-config nil)))
@ -318,12 +444,12 @@
(interactive)
(setq ppq--window-config (current-window-configuration))
(let ((fruits-buf (get-buffer-create "*Dashboard:Fruits*"))
(let ((releases-buf (get-buffer-create "*Dashboard:Releases*"))
(models-buf (get-buffer-create "*Dashboard:Models*"))
(org-buf (get-buffer-create "*Dashboard:Info*")))
(with-current-buffer fruits-buf
(ppq-fruits-mode))
(with-current-buffer releases-buf
(ppq-releases-mode))
(with-current-buffer models-buf
(ppq-models-mode))
@ -337,7 +463,7 @@
(insert "Welcome to the *PayPerQ* dashboard!\n\n")
(insert "** Notes\n")
(insert "- Press ~q~ in any table to quit\n")
(insert "- Table 1 :: Fruits of the world\n")
(insert "- Table 1 :: Recent Releases (loading...)\n")
(insert "- Table 2 :: PayPerQ Models (loading...)\n\n")
(insert "** Balance\n\n")
(insert "** Topup (BTC-LN)\n")
@ -352,12 +478,13 @@
(let* ((top-left (selected-window))
(top-right (split-window-right))
(bottom (split-window-below (/ (window-height) 2))))
(set-window-buffer top-left fruits-buf)
(set-window-buffer top-left releases-buf)
(set-window-buffer top-right models-buf)
(set-window-buffer bottom org-buf))
(select-window (get-buffer-window fruits-buf)))
(select-window (get-buffer-window releases-buf)))
(ppq--releases-fetch-all-async)
(ppq--fetch-models-async)
(ppq--fetch-balance-async))