feat(emacs-pkgs/notable): Begin implementation by adding note-taking
I'm writing a note taking system because I don't like any of the existing ones. This adds a note-taking function which prompts the user for note content and persists it in a user-configurable directory. Next steps are roughly: * listing notes * editing notes * archiving notes At which point I would consider this functional. The idea is to keep adding incremental improvements that fit my workflows after that, and if it ends up being useful for anyone else - great. Change-Id: I7ae0eb4015511ebdbaeff92d8d32502726eb57c4 Reviewed-on: https://cl.tvl.fyi/c/depot/+/1981 Tested-by: BuildkiteCI Reviewed-by: tazjin <mail@tazj.in>
This commit is contained in:
parent
96dbf4d686
commit
6b16e5c1ef
3 changed files with 124 additions and 0 deletions
2
tools/emacs-pkgs/notable/OWNERS
Normal file
2
tools/emacs-pkgs/notable/OWNERS
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
owners:
|
||||||
|
- tazjin
|
10
tools/emacs-pkgs/notable/default.nix
Normal file
10
tools/emacs-pkgs/notable/default.nix
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{ pkgs, ... }:
|
||||||
|
|
||||||
|
pkgs.emacsPackages.trivialBuild rec {
|
||||||
|
pname = "notable";
|
||||||
|
version = "1.0";
|
||||||
|
src = ./notable.el;
|
||||||
|
packageRequires = with pkgs.emacsPackages; [
|
||||||
|
dash f ht s
|
||||||
|
];
|
||||||
|
}
|
112
tools/emacs-pkgs/notable/notable.el
Normal file
112
tools/emacs-pkgs/notable/notable.el
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
;;; notable.el --- a simple note-taking app -*- lexical-binding: t; -*-
|
||||||
|
;;
|
||||||
|
;; Copyright (C) 2020 The TVL Contributors
|
||||||
|
;;
|
||||||
|
;; Author: Vincent Ambo <mail@tazj.in>
|
||||||
|
;; Version: 1.0
|
||||||
|
;; Package-Requires: (cl-lib dash f rx s subr-x)
|
||||||
|
;;
|
||||||
|
;;; Commentary:
|
||||||
|
;;
|
||||||
|
;; This package provides a simple note-taking application which can be
|
||||||
|
;; invoked from anywhere in Emacs, with several interactive
|
||||||
|
;; note-taking functions included.
|
||||||
|
;;
|
||||||
|
;; As is tradition for my software, the idea here is to reduce
|
||||||
|
;; friction which I see even with tools like `org-capture', because
|
||||||
|
;; `org-mode' does a ton of things I don't care about.
|
||||||
|
;;
|
||||||
|
;; Notable stores its notes in simple JSON files in the folder
|
||||||
|
;; specified by `notable-note-dir'.
|
||||||
|
|
||||||
|
(require 'cl-lib)
|
||||||
|
(require 'dash)
|
||||||
|
(require 'f)
|
||||||
|
(require 'ht)
|
||||||
|
(require 'rx)
|
||||||
|
(require 's)
|
||||||
|
(require 'subr-x)
|
||||||
|
|
||||||
|
;; User-facing customisation options
|
||||||
|
|
||||||
|
(defgroup notable nil
|
||||||
|
"Simple note-taking application."
|
||||||
|
:group 'applications)
|
||||||
|
|
||||||
|
;; TODO(tazjin): Use whatever the XDG state dir thing is for these by
|
||||||
|
;; default.
|
||||||
|
(defcustom notable-note-dir (expand-file-name "~/.notable/")
|
||||||
|
"File path to the directory containing notable's notes."
|
||||||
|
:type 'string
|
||||||
|
:group 'notable)
|
||||||
|
|
||||||
|
;; Package internal definitions
|
||||||
|
|
||||||
|
(cl-defstruct (notable--note (:constructor notable--make-note))
|
||||||
|
"Structure containing the fields of a single notable note."
|
||||||
|
time ;; UNIX timestamp at which the note was taken
|
||||||
|
content ;; Textual content of the note
|
||||||
|
)
|
||||||
|
|
||||||
|
(defvar notable--note-lock (make-mutex "notable-notes")
|
||||||
|
"Exclusive lock for note operations with shared state.")
|
||||||
|
|
||||||
|
(defvar notable--next-note
|
||||||
|
(let ((next 0)
|
||||||
|
(note-regexp (rx "note-"
|
||||||
|
(group (one-or-more (any num)))
|
||||||
|
".json")))
|
||||||
|
(-each (f-entries notable-note-dir)
|
||||||
|
(lambda (file)
|
||||||
|
(when-let* ((match (string-match note-regexp file))
|
||||||
|
(id (string-to-number
|
||||||
|
(match-string 1 file)))
|
||||||
|
(larger (> id next)))
|
||||||
|
(setq next id))))
|
||||||
|
(+ 1 next))
|
||||||
|
"Next ID to use for notes. Initial value is determined based on
|
||||||
|
the existing notes files.")
|
||||||
|
|
||||||
|
(defun notable--serialize-note (note)
|
||||||
|
"Serialise NOTE into JSON format."
|
||||||
|
(check-type note notable--note)
|
||||||
|
(json-serialize (ht ("time" (notable--note-time note))
|
||||||
|
("content" (notable--note-content note)))))
|
||||||
|
|
||||||
|
(defun notable--deserialize-note (json)
|
||||||
|
"Deserialise JSON into a notable note."
|
||||||
|
(check-type json string)
|
||||||
|
(let ((parsed (json-parse-string json)))
|
||||||
|
(unless (and (ht-contains? parsed "time")
|
||||||
|
(ht-contains-p parsed "content"))
|
||||||
|
(error "Missing required keys in note structure!"))
|
||||||
|
(notable--make-note :time (ht-get parsed "time")
|
||||||
|
:content (ht-get parsed "content"))))
|
||||||
|
|
||||||
|
(defun notable--next-id ()
|
||||||
|
"Return the next note ID and increment the counter."
|
||||||
|
(with-mutex notable--note-lock
|
||||||
|
(let ((id notable--next-note))
|
||||||
|
(setq notable--next-note (+ 1 id))
|
||||||
|
id)))
|
||||||
|
|
||||||
|
(defun notable--add-note (content)
|
||||||
|
"Add a note with CONTENT to the note store."
|
||||||
|
(let* ((id (notable--next-id))
|
||||||
|
(note (notable--make-note :time (time-convert nil 'integer)
|
||||||
|
:content content))
|
||||||
|
(path (f-join notable-note-dir (format "note-%d.json" id))))
|
||||||
|
(when (f-exists? path) (error "Note file '%s' already exists!" path))
|
||||||
|
(f-write-text (notable--serialize-note note) 'utf-8 path)
|
||||||
|
(message "Saved note %d" id)))
|
||||||
|
|
||||||
|
;; User-facing functions
|
||||||
|
|
||||||
|
(defun notable-take-note (content)
|
||||||
|
"Interactively prompt the user for a note that should be stored
|
||||||
|
in Notable."
|
||||||
|
(interactive "sEnter note: ")
|
||||||
|
(check-type content string)
|
||||||
|
(notable--add-note content))
|
||||||
|
|
||||||
|
(provide 'notable)
|
Loading…
Reference in a new issue