aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.org321
1 files changed, 317 insertions, 4 deletions
diff --git a/README.org b/README.org
index 659fc89..34ee6fa 100644
--- a/README.org
+++ b/README.org
@@ -1,19 +1,332 @@
#+TITLE: gloss — Glossary Lookup with Online-Sourced Selection
#+OPTIONS: toc:nil
+[[#features][Features]] | [[#installation][Installation]] | [[#quick-start][Quick Start]] | [[#keybindings][Keybindings]] | [[#configuration][Configuration]] | [[#extending-sources][Extending Sources]] | [[#org-drill][org-drill]] | [[#troubleshooting][Troubleshooting]] | [[#development][Development]]
+
A personal Emacs glossary. =C-h g= looks up terms in a single git-tracked org file. On a local miss, =gloss= fetches candidate definitions from Wiktionary and prompts you to pick which one to save — with provenance recorded. The same org file feeds =org-drill= for spaced-repetition study.
* Status
-In active development. v1 not yet released. See [[file:docs/design/gloss.org][docs/design/gloss.org]] for the full design.
+In active development. v1 not yet released. Core features land; first-week shakedown pending. See [[file:docs/design/gloss.org][docs/design/gloss.org]] for the full design and [[file:docs/decisions/][docs/decisions/]] for the recorded ADRs.
-* Why not just =quick-sdcv= or =dictionary=?
+* Why not =quick-sdcv= or =M-x dictionary=?
+:PROPERTIES:
+:CUSTOM_ID: why
+:END:
Domain jargon — government acronyms, technical terms, philosophy vocabulary, project-specific names — doesn't live in any general dictionary, so =quick-sdcv= and =M-x dictionary= can't help. =gloss= grows by use: encounter a term, save it once, and it's permanently looked-up-able and study-card-able.
-* Quick start
+* Features
+:PROPERTIES:
+:CUSTOM_ID: features
+:END:
+
+- *Single-file storage.* One git-tracked org file, one =* term= heading per entry, alphabetical order maintained on insert. Diff-clean, hand-editable.
+- *Online fallback on cache miss.* Looks up missing terms in Wiktionary, presents candidate definitions in a side-buffer picker, saves the one you chose with provenance.
+- *Auto-save when there's only one definition.* No picker shown; the single definition lands in the glossary and the entry is displayed.
+- *Manual add* via a side-window editor with a read-only header (term + underline) and an editable body region. =C-c C-c= saves; =C-c C-k= cancels.
+- *Edit-in-place.* =C-h g e= jumps to the source org file at the entry's heading. A buffer-local =after-save-hook= refreshes the cache when you save.
+- *org-drill export.* Tag every entry as =:drill:= with =:DRILL_CARD_TYPE: twosided= via =C-h g D=. =M-x org-drill= runs the session unmodified.
+- *Layered architecture.* =gloss-core= (data) + =gloss-fetch= (network) + =gloss-display= (UI) + =gloss-drill= (drill export) + =gloss= (orchestration). Each layer mocks at its own boundary.
+- *Pluggable sources for v2+.* The =gloss-fetch--sources= alist registry walks fetchers in =gloss-fetch-sources= order; v1 ships only Wiktionary. Adding DictionaryAPI.dev or Wordnik later is one alist entry plus one fetcher function.
+- *Diagnostic =*gloss-debug*=* opt-in log buffer for layer-prefixed event tracing without polluting =*Messages*=.
+
+* Installation
+:PROPERTIES:
+:CUSTOM_ID: installation
+:END:
+
+Not on MELPA. =gloss= is on GitHub at [[https://github.com/cjennings/gloss]] and on cjennings.net at the bare repo behind the GitHub mirror.
+
+*Requirements:* Emacs 27.1+, org-mode 9.3+. Online fetching also requires an Emacs built with libxml2 (most distribution builds ship it).
+
+** package-vc-install (Emacs 29+)
+
+#+begin_src emacs-lisp
+(unless (package-installed-p 'gloss)
+ (package-vc-install "https://github.com/cjennings/gloss"))
+(require 'gloss)
+(gloss-install-prefix) ; binds the C-h g sub-map
+#+end_src
+
+** use-package with =:vc= (Emacs 29+)
+
+#+begin_src emacs-lisp
+(use-package gloss
+ :vc (:url "https://github.com/cjennings/gloss" :rev :newest)
+ :commands (gloss-lookup gloss-add gloss-edit
+ gloss-fetch-online gloss-list-terms gloss-stats
+ gloss-reload gloss-drill-export gloss-toggle-debug)
+ :init
+ (gloss-install-prefix))
+#+end_src
+
+** straight.el
+
+#+begin_src emacs-lisp
+(straight-use-package
+ '(gloss :type git :host github :repo "cjennings/gloss"))
+(require 'gloss)
+(gloss-install-prefix)
+#+end_src
+
+** Manual installation
+
+#+begin_src bash
+git clone https://github.com/cjennings/gloss.git ~/path/to/gloss
+#+end_src
+
+Then in your init:
+
+#+begin_src emacs-lisp
+(add-to-list 'load-path "~/path/to/gloss")
+(require 'gloss)
+(gloss-install-prefix)
+#+end_src
+
+* Quick Start
+:PROPERTIES:
+:CUSTOM_ID: quick-start
+:END:
+
+After installing and running =(gloss-install-prefix)=, =C-h g= becomes the gloss prefix. The single most common command is =C-h g g= (lookup):
+
+1. *Type the term* you want to look up. =gloss-lookup= reads from the minibuffer with =word-at-point= as the default, so just =RET= grabs the word under point.
+2. *Cache hit:* a side window opens on the right showing the term and its body.
+3. *Cache miss with one definition online:* the package fetches Wiktionary, saves the definition silently with =:source: wiktionary=, and shows it.
+4. *Cache miss with multiple definitions:* a picker appears in the minibuffer with each option formatted as ~[wiktionary] ...~. Pick one — that's what gets saved.
+5. *Cache miss with no definitions:* the echo area shows ~gloss: no definition found for X~. No save.
+6. *Network failure:* the echo area shows ~gloss: couldn't reach any source for X~. No save. Try again later.
+
+To add a term manually (no online fetch), =C-h g a=:
+
+1. Minibuffer prompts =Add term:=. Type the term, =RET=.
+2. A side-window buffer opens with the term and a =====~ underline as a read-only header. Point lands in the editable body region underneath.
+3. Type the definition.
+4. =C-c C-c= saves the entry with =:source: manual= and closes the side window.
+5. =C-c C-k= cancels — no save, side window closes.
+
+* Keybindings
+:PROPERTIES:
+:CUSTOM_ID: keybindings
+:END:
+
+After =(gloss-install-prefix)=, all commands live under =C-h g=:
+
+| Key | Command | What it does |
+|-----------+----------------------+-----------------------------------------------------------|
+| =C-h g g= | =gloss-lookup= | Look up a term; fetch online on miss. |
+| =C-h g a= | =gloss-add= | Add a term manually (read-only header + editable body). |
+| =C-h g e= | =gloss-edit= | Jump to the source org file at the term's heading. |
+| =C-h g o= | =gloss-fetch-online= | Force online fetch, bypassing cache. |
+| =C-h g D= | =gloss-drill-export= | Tag every entry for =org-drill=. |
+| =C-h g l= | =gloss-list-terms= | Browse glossary terms via =completing-read=. |
+| =C-h g s= | =gloss-stats= | Show total / by-source / drill-tagged / size / mtime. |
+| =C-h g r= | =gloss-reload= | Force reload of the cache from disk. |
+| =C-h g d= | =gloss-toggle-debug= | Toggle the =*gloss-debug*= log buffer. |
+
+You can change the prefix by passing an argument to =gloss-install-prefix=:
+
+#+begin_src emacs-lisp
+(gloss-install-prefix (kbd "C-c g"))
+#+end_src
+
+Or skip the helper entirely and bind =gloss-prefix-map= where you want.
+
+In =gloss-add-mode= (the =*gloss-add: TERM*= buffer):
+
+| Key | Command | What it does |
+|-----------+--------------------+---------------------------------------|
+| =C-c C-c= | =gloss-add-finish= | Save the body and close the buffer. |
+| =C-c C-k= | =gloss-add-abort= | Cancel; close without saving. |
+
+In =gloss-mode= (the =*gloss: TERM*= side buffer): inherits =special-mode=, so =q= dismisses the window.
+
+* Configuration
+:PROPERTIES:
+:CUSTOM_ID: configuration
+:END:
+
+Four defcustoms. All of them have sensible defaults; configure only if you want different behaviour.
+
+** =gloss-file=
+
+The org file that holds the glossary. Default:
+
+#+begin_src emacs-lisp
+(expand-file-name "gloss.org"
+ (or org-directory user-emacs-directory))
+#+end_src
+
+If your =org-directory= is set, the glossary lives next to your other org files. Otherwise it lands under =user-emacs-directory=. To override:
+
+#+begin_src emacs-lisp
+(setq gloss-file "~/notes/glossary.org")
+#+end_src
+
+The file is created on first save with a =#+TITLE: Glossary= header. Parent directory is created if missing. See [[file:docs/decisions/0001-storage-path-default.org][ADR-1]] for the rationale.
+
+** =gloss-fetch-sources=
+
+The list of online sources to try, in order. Default:
+
+#+begin_src emacs-lisp
+(setq gloss-fetch-sources '(wiktionary))
+#+end_src
+
+v1 ships with only =wiktionary=. Set to nil to disable all online fetching:
+
+#+begin_src emacs-lisp
+(setq gloss-fetch-sources nil)
+#+end_src
+
+When more sources land in v2+, you'll be able to reorder them or pick a subset. See [[#extending-sources][Extending Sources]] below.
+
+** =gloss-fetch-timeout=
+
+Maximum time in seconds to wait for any single source to respond. Default 5. Bump higher on slow connections, lower if you'd rather fail fast.
+
+#+begin_src emacs-lisp
+(setq gloss-fetch-timeout 10)
+#+end_src
+
+** =gloss-debug=
+
+When non-nil, =gloss= writes layer-prefixed diagnostic events to a =*gloss-debug*= buffer. Off by default. Toggle interactively with =C-h g d= or set:
+
+#+begin_src emacs-lisp
+(setq gloss-debug t)
+#+end_src
+
+The debug buffer is opt-in for everything beyond user-facing events; =*Messages*= still shows the things you actually did or asked for.
+
+* Extending Sources
+:PROPERTIES:
+:CUSTOM_ID: extending-sources
+:END:
+
+For v2+, register an additional fetcher in the source registry. The shape is an alist mapping a source symbol to a fetcher function. Each fetcher takes a =TERM= string and returns a per-source result plist:
+
+#+begin_src emacs-lisp
+;; Per-source result shape:
+;; (:source SYM :status STATUS [:defs (DEF ...)] [:reason STRING])
+;;
+;; STATUS values:
+;; :ok :defs (def1 def2 ...) — success, defs is a non-empty list
+;; :no-defs — server reached, term not there
+;; :unreachable — DNS, refused, timeout
+;; :server-error — HTTP 5xx, malformed JSON, schema mismatch
+;; :rate-limited — HTTP 429
+;;
+;; A definition shape:
+;; (:source SYM :text "definition text...")
+#+end_src
+
+Register your fetcher:
+
+#+begin_src emacs-lisp
+(defun my-dictionaryapi-fetcher (term)
+ "Fetch TERM from DictionaryAPI.dev. Return per-source result plist."
+ ;; ... call the API, build the plist ...
+ )
+
+(add-to-list 'gloss-fetch--sources
+ '(dictionary-api . my-dictionaryapi-fetcher))
+(add-to-list 'gloss-fetch-sources 'dictionary-api t)
+#+end_src
+
+The orchestrator walks =gloss-fetch-sources= in order and aggregates each source's result into the user-facing rollup. See [[file:gloss-fetch.el][gloss-fetch.el]] for the Wiktionary fetcher as a worked example.
+
+* org-drill Integration
+:PROPERTIES:
+:CUSTOM_ID: org-drill
+:END:
+
+Once you have a few entries saved, =C-h g D= tags every top-level heading with =:drill:= and adds =:DRILL_CARD_TYPE: twosided= as a property. =M-x org-drill= then runs the spaced-repetition session against =gloss-file= unmodified.
+
+The export is idempotent — running it twice in a row touches nothing on the second pass. To remove the tags and properties, =M-x gloss-drill-untag-all= reverses every entry.
+
+=gloss-drill-export= signals a =user-error= if =org-drill= isn't installed. Install it with:
+
+#+begin_src elisp
+M-x package-install RET org-drill RET
+#+end_src
+
+Or via =:vc= for the maintained fork:
+
+#+begin_src emacs-lisp
+(use-package org-drill
+ :vc (:url "https://github.com/cjennings/org-drill" :rev :newest))
+#+end_src
+
+See [[file:docs/decisions/0003-drill-direction.org][ADR-3]] for why every export uses =twosided=.
+
+* Troubleshooting
+:PROPERTIES:
+:CUSTOM_ID: troubleshooting
+:END:
+
+** =Online fetch requires Emacs built with libxml2=
+
+The Wiktionary fetcher uses =libxml-parse-html-region= to strip HTML from the raw API response. If your Emacs build doesn't include libxml2, online fetching is disabled package-wide for the session.
+
+Manual =gloss-add= still works without libxml. To get online fetching, install an Emacs build with libxml support — most distribution packages include it.
+
+** =gloss: couldn't reach any source for TERM=
+
+Network failure. Either the host is unreachable (DNS, no connection, firewall), the request timed out (=gloss-fetch-timeout=), or the server returned an error (5xx, rate limited).
+
+For technical detail, enable =gloss-debug= and re-run the lookup. The =*gloss-debug*= buffer records the per-source =:reason= string (=timeout (5s)=, =HTTP 503=, etc.). =*Messages*= keeps the user-facing rollup only.
+
+** =gloss: glossary file appears corrupt=
+
+The org parser failed on =gloss-file=. The cache is preserved (so existing lookups still work), but recent changes haven't loaded. Open =gloss-file=, fix the syntax error, then =M-x gloss-reload=.
+
+The most common cause is a hand edit that broke an entry's =:PROPERTIES:= drawer. Find the offending heading, fix the drawer, save.
+
+** =Term not in glossary= for a term you saved earlier
+
+Cache and disk are out of sync. Try =M-x gloss-reload= first — that re-reads from disk. If it still misses, the term may have been hand-edited under a different heading; check =gloss-file= directly with =M-x gloss-edit=.
+
+The cache auto-refreshes on every lookup if =gloss-file='s mtime advances, so out-of-band edits via =git pull= or another Emacs session are picked up automatically. =gloss-reload= is the manual escape hatch.
+
+** Side window won't dismiss
+
+In a =*gloss: TERM*= buffer, =q= calls =quit-window= (inherited from =special-mode=). If your config rebinds =q=, the window won't dismiss the same way. Use =C-x 1= as a fallback, or rebind =q= back in =gloss-mode-map=.
+
+* Development
+:PROPERTIES:
+:CUSTOM_ID: development
+:END:
+
+** Test infrastructure
+
+=gloss= uses =Cask= for dependency management and =ert-runner= for tests. Install once:
+
+#+begin_src bash
+cask install
+#+end_src
+
+Common targets:
+
+#+begin_src bash
+make help # List all targets
+make test # Run the full suite
+make test-file FILE=tests/test-foo.el # One file
+make test-name TEST=pattern # By test name pattern
+make validate-parens # check-parens on every .el
+make compile # Byte-compile package files
+make lint # elisp-lint pass
+make clean # Remove .elc
+#+end_src
+
+The current suite is 129 tests across the four layers and the orchestration layer. The pure helpers (=gloss--orchestrate-fetch-result=, =gloss-display--format-candidate=, =gloss-display--render-entry=, =gloss--add-finish-internal=, =gloss--stats-text=) get full Normal/Boundary/Error coverage. Mode-glue gets smoke tests only — Emacs already tests its own prompts and major-mode mechanics.
+
+** Contributing
+
+Pull requests welcome. Match the existing test style: per-function file, three category coverage, real production code via =require= (never inlined). Mock at boundaries (=url-retrieve-synchronously=, =completing-read=, =display-buffer=); never mock internal helpers.
-(This section will fill out as v1 lands. For now, see [[file:docs/design/gloss.org][docs/design/gloss.org]].)
+For non-trivial changes, open an issue first to discuss the design.
* License