;;; elogt.el --- Portage Emerge log browser -*- lexical-binding: t -*-
;; Copyright 2023 Gentoo Authors
;; This file is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 2 of the License, or
;; (at your option) any later version.
;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see .
;; Authors: Maciej Barć
;; Maintainer: Gentoo Emacs project
;; Created: 03 Feb 2023
;; Version: 0.0.0
;; Keywords: convenience
;; Homepage: https://gitweb.gentoo.org/proj/emacs-elogt.git
;; Package-Requires: ((emacs "25"))
;; SPDX-License-Identifier: GPL-2.0-or-later
;;; Commentary:
;; Portage Emerge log browser for GNU Emacs.
;; ElogT displays the Portage logs in a convenient table and allows log file
;; browsing and deletion.
;;; Code:
(defconst elogt-version "0.0.0"
"ElogT version.")
(defconst elogt--portage-log-stars
'((" \e[31;01m*\e[0m" . error)
(" \e[32m*\e[0m" . info)
(" \e[33;01m*\e[0m" . warn)))
(defconst elogt--priority-level-indicators
`((error . ,(propertize "Error" 'font-lock-face '(:foreground "red")))
(info . ,(propertize "Info" 'font-lock-face '(:foreground "green")))
(warn . ,(propertize "Warn" 'font-lock-face '(:foreground "orange")))
(none . ,(propertize "None" 'font-lock-face '(:foreground "purple")))))
;; Customization
(defgroup elogt nil
"Customization for ElogT, Portage Emerge log browser."
:group 'ebuild)
(defcustom elogt-portage-log-dir "/var/log/portage"
"Portage log directory location."
:safe 'stringp
:type 'file
:group 'elogt)
(defcustom elogt-check-priority t
"Check priority of each logfile.
If set to nil entries will have a phony Info level."
:type 'boolean
:group 'elogt)
;; Log file processing
(defun elogt--gather-portage-logs ()
"Gather logs from ‘elogt-log-dir’."
(directory-files elogt-portage-log-dir t "\\.log"))
(defun elogt--logfile-find-stars (file-name)
"Find special info indicators in FILE-NAME."
(let ((buffer (find-file-noselect file-name 'nowarn 'rawfile))
(found-stars nil))
(with-current-buffer buffer
(dolist (star elogt--portage-log-stars)
(save-excursion
(goto-char (point-min))
(let ((found (search-forward (car star) nil 'noerror)))
(when found
(push (cdr star) found-stars))))))
(kill-buffer buffer)
found-stars))
(defun elogt--priority-level-property (key)
"Rerun a value referenced by KEY of ‘elogt--priority-level-indicators’."
(cdr (assoc key elogt--priority-level-indicators)))
(defun elogt--logfile-priority-level (file-name)
"Return a FILE-NAME priority level.
A logfile priority level is one of: None, Info, Warn, Error."
(let ((found-stars (elogt--logfile-find-stars file-name)))
(cond
((not elogt-check-priority) (elogt--priority-level-property 'info))
((null found-stars) (elogt--priority-level-property 'none))
((member 'error found-stars) (elogt--priority-level-property 'error))
((member 'warn found-stars) (elogt--priority-level-property 'warn))
(t (elogt--priority-level-property 'info)))))
(defun elogt--logfile-properties (file-name)
"Extract the properties form given FILE-NAME."
(apply #'vector (cons (elogt--logfile-priority-level file-name)
(split-string (file-name-base file-name) ":"))))
(defun elogt--make-log-table-contents ()
"Make ElogT table contents."
(let ((index 0))
(mapcar (lambda (log-file-path)
(let ((entry (list index
(elogt--logfile-properties log-file-path))))
(setq index (+ index 1))
entry))
(elogt--gather-portage-logs))))
;; Table interaction
(defun elogt--entry-logfile-path (table-entry)
"Return a logfile path of a given ElogT TABLE-ENTRY."
(format "%s/%s:%s:%s.log"
elogt-portage-log-dir
(aref table-entry 1)
(aref table-entry 2)
(aref table-entry 3)))
(defun elogt--get-table-entry-logfile-path ()
"Return a logfile path of current ElogT table entry."
(elogt--entry-logfile-path (tabulated-list-get-entry)))
(defun elogt--open-entry-file ()
"Open a ElogT table entry at point.
Return opened buffer (done via `find-file')."
(find-file (elogt--get-table-entry-logfile-path)))
(defun elogt-open-entry ()
"Open specified ElogT table entry and put it in a mode for viewing only."
(interactive)
(let ((buffer (elogt--open-entry-file)))
(with-current-buffer buffer
(fundamental-mode)
(view-mode))))
(defun elogt--delete-entry-file ()
"Delete a logfile of a entry at point."
(delete-file (elogt--get-table-entry-logfile-path)))
(defun elogt-delete-entry ()
"Delete specified ElogT table entry."
(interactive)
(elogt--delete-entry-file)
(tabulated-list-delete-entry))
(defun elogt-refresh-table ()
"Refresh the ElogT table."
(interactive)
(message "Refreshing the ElogT table, please wait...")
(setq tabulated-list-entries (elogt--make-log-table-contents))
(tabulated-list-init-header)
(tabulated-list-print t)
(message "The ElogT table is ready."))
;; Major mode
(defvar elogt-mode-hook nil
"Hook for `elogt' major mode.")
(defvar elogt-mode-map
(let ((elogt-mode-map (make-keymap)))
(define-key elogt-mode-map (kbd "/") #'isearch-forward)
(define-key elogt-mode-map (kbd "o") #'elogt-open-entry)
(define-key elogt-mode-map (kbd "d") #'elogt-delete-entry)
(define-key elogt-mode-map (kbd "g") #'elogt-refresh-table)
elogt-mode-map)
"Key map for `elogt' major mode.")
(define-derived-mode elogt-mode tabulated-list-mode
"ElogT"
"Major mode for browsing Portage log files."
(setq tabulated-list-format
[("Priority" 10 t)
("Category" 20 t)
("Package" 30 t)
("Time" 20 t)])
(setq tabulated-list-sort-key (cons "Time" t))
(run-hooks 'elogt-mode-hook)
(use-local-map elogt-mode-map))
;; Main provided features
;;;###autoload
(defun elogt ()
"Browse Portage log files."
(interactive)
(let ((buffer (get-buffer-create "*ElogT*")))
(with-current-buffer buffer
(elogt-mode)
(elogt-refresh-table))
(display-buffer buffer)))
(provide 'elogt)
;;; elogt.el ends here