diff options
| author | 0xhenrique <[email protected]> | 2025-05-30 18:37:11 +0100 |
|---|---|---|
| committer | 0xhenrique <[email protected]> | 2025-05-30 18:37:11 +0100 |
| commit | aa94e94347e99441beaf78adf2feda627fd8f7e7 (patch) | |
| tree | 260b37bcbe68112d48e95b4bdfbe86f0939409ab /esb.el | |
add files
Diffstat (limited to 'esb.el')
| -rw-r--r-- | esb.el | 188 |
1 files changed, 188 insertions, 0 deletions
@@ -0,0 +1,188 @@ +;;; esb.el --- Emacs Simple Bookmark -*- lexical-binding: t; -*- + +;;; Commentary: +;; This is a simple encrypted bookmark manager for Emacs that +;; stores bookmarks in an encrypted file suitable for syncing via Git. +;; It uses GPG encryption to keep your bookmarks secure while allowing +;; you to store them in public repositories. + +;;; Code: + +(require 'epa-file) +(require 'json) + +(defgroup esb nil + "Encrypted bookmark manager." + :group 'tools) + +(defcustom esb-bookmarks-file "~/.bookmarks.gpg" + "Path to the encrypted bookmarks file." + :type 'string + :group 'esb) + +(defvar esb-bookmarks-cache nil + "In-memory cache of decrypted bookmarks.") + +(defvar esb-cache-dirty nil + "Flag indicating if cache needs to be saved.") + +;;; Core functions + +(defun esb--ensure-epa-setup () + "Ensure EPA file encryption is properly configured." + (unless (member epa-file-handler file-name-handler-alist) + (epa-file-enable))) + +(defun esb--read-bookmarks () + "Read and decrypt bookmarks from file." + (esb--ensure-epa-setup) + (if (file-exists-p esb-bookmarks-file) + (with-temp-buffer + (insert-file-contents esb-bookmarks-file) + (condition-case err + (json-parse-string (buffer-string) :array-type 'list :object-type 'alist) + (json-error + (message "Error parsing bookmarks file: %s" err) + nil))) + nil)) + +(defun esb--write-bookmarks (bookmarks) + "Encrypt and write BOOKMARKS to file." + (esb--ensure-epa-setup) + (with-temp-buffer + (insert (json-encode bookmarks)) + (write-file esb-bookmarks-file)) + (setq esb-cache-dirty nil)) + +(defun esb--get-bookmarks () + "Get bookmarks from cache or file." + (unless esb-bookmarks-cache + (setq esb-bookmarks-cache (or (esb--read-bookmarks) '()))) + esb-bookmarks-cache) + +(defun esb--save-if-dirty () + "Save bookmarks to file if cache is dirty." + (when esb-cache-dirty + (esb--write-bookmarks esb-bookmarks-cache))) + +(defun esb--bookmark-urls () + "Get list of bookmark URLs." + (mapcar (lambda (bookmark) (alist-get 'url bookmark)) (esb--get-bookmarks))) + +(defun esb--find-bookmark-by-url (url) + "Find bookmark by URL." + (seq-find (lambda (bookmark) (string= (alist-get 'url bookmark) url)) + (esb--get-bookmarks))) + +;;; Interactive functions + +(defun esb-add-bookmark (url &optional description) + "Add a new bookmark with URL and optional DESCRIPTION." + (interactive "sBookmark URL: \nsDescription (optional): ") + (let* ((bookmarks (esb--get-bookmarks)) + (existing (esb--find-bookmark-by-url url))) + (if existing + (message "Bookmark already exists: %s" url) + (let ((new-bookmark `((url . ,url) + (description . ,(if (string-empty-p description) nil description))))) + (setq esb-bookmarks-cache (append bookmarks (list new-bookmark))) + (setq esb-cache-dirty t) + (esb--save-if-dirty) + (message "Added bookmark: %s" url))))) + +(defun esb-delete-bookmark () + "Delete a bookmark by selecting from list." + (interactive) + (let* ((bookmarks (esb--get-bookmarks)) + (urls (esb--bookmark-urls))) + (if (null urls) + (message "No bookmarks found") + (let* ((selected-url (completing-read "Delete bookmark: " urls nil t)) + (updated-bookmarks (seq-remove (lambda (bookmark) + (string= (alist-get 'url bookmark) selected-url)) + bookmarks))) + (setq esb-bookmarks-cache updated-bookmarks) + (setq esb-cache-dirty t) + (esb--save-if-dirty) + (message "Deleted bookmark: %s" selected-url))))) + +(defun esb-list-bookmarks () + "Display all bookmarks in a buffer." + (interactive) + (let ((bookmarks (esb--get-bookmarks))) + (if (null bookmarks) + (message "No bookmarks found") + (with-output-to-temp-buffer "*Esb Bookmarks*" + (princ "Bookmarks:\n\n") + (dolist (bookmark bookmarks) + (let ((url (alist-get 'url bookmark)) + (desc (alist-get 'description bookmark))) + (princ (format "• %s\n" url)) + (when desc + (princ (format " %s\n" desc))) + (princ "\n"))))))) + +(defun esb-select-bookmark () + "Select a bookmark and copy URL to clipboard." + (interactive) + (let ((urls (esb--bookmark-urls))) + (if (null urls) + (message "No bookmarks found") + (let ((selected-url (completing-read "Select bookmark: " urls nil t))) + (kill-new selected-url) + (message "Copied to clipboard: %s" selected-url))))) + +(defun esb-edit-bookmark () + "Edit description of an existing bookmark." + (interactive) + (let* ((bookmarks (esb--get-bookmarks)) + (urls (esb--bookmark-urls))) + (if (null urls) + (message "No bookmarks found") + (let* ((selected-url (completing-read "Edit bookmark: " urls nil t)) + (bookmark (esb--find-bookmark-by-url selected-url)) + (current-desc (or (alist-get 'description bookmark) "")) + (new-desc (read-string "Description: " current-desc))) + (setf (alist-get 'description bookmark) (if (string-empty-p new-desc) nil new-desc)) + (setq esb-cache-dirty t) + (esb--save-if-dirty) + (message "Updated bookmark: %s" selected-url))))) + +(defun esb-reload-bookmarks () + "Reload bookmarks from file (useful after git pull)." + (interactive) + (setq esb-bookmarks-cache nil) + (setq esb-cache-dirty nil) + (esb--get-bookmarks) + (message "Bookmarks reloaded from %s" esb-bookmarks-file)) + +(defun esb-initialize () + "Initialize bookmark file if it doesn't exist." + (interactive) + (if (file-exists-p esb-bookmarks-file) + (message "Bookmark file already exists at: %s" esb-bookmarks-file) + (esb--write-bookmarks '()) + (message "Initialized empty bookmark file at: %s" esb-bookmarks-file))) + +;;; Key bindings + +(defvar esb-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "C-c b s") 'esb-select-bookmark) + (define-key map (kbd "C-c b a") 'esb-add-bookmark) + (define-key map (kbd "C-c b d") 'esb-delete-bookmark) + (define-key map (kbd "C-c b l") 'esb-list-bookmarks) + (define-key map (kbd "C-c b e") 'esb-edit-bookmark) + (define-key map (kbd "C-c b r") 'esb-reload-bookmarks) + (define-key map (kbd "C-c b i") 'esb-initialize) + map) + "Keymap for esb bookmark commands.") + +(define-minor-mode esb-mode + "Minor mode for encrypted bookmark management." + :global t + :keymap esb-mode-map) + +(provide 'esb) + +;;; esb.el ends here |
