Personal Dictionary
Table of Contents
Introduction
Personal Dictionary
Background
I maintain a personal dictionary within my Emacs configuration. I initially started it for my fantasy writing project, because I wanted an easy way to quickly define and reference an in-universe thing, such as a town or item. I set it up without knowing an Elisp, but now that I know just a little Elisp, I'd like to re-examine my approach.
Here's an excerpt from my Emacs configuration, showing how the dictionary currently
(setq org-export-global-macros '(("ref" . "{{{define($1)}}} {{{source($1)}}}") ("define" . "{{{$1-definition}}}") ("source" . "{{{$1-source}}}") ("source-link" . "See [[$1][$1]]") ("domain-name-definition" . "A string (group of letters) defining the name of an area of the Internet.") ("domain-name-source" . "{{{source-link(https://en.wikipedia.org/wiki/Domain_name)}}}.")))
This lets me write {{{ref(domain-name)}}}
and get "A string (group of letters) defining the name of an area of the Internet. See https://en.wikipedia.org/wiki/Domain_name."
Here's a breakdown of how that works:
- Text inside
{{{ }}}
is interpetted by Org-mode as a macro, and replaced when the document is exported. (I think macros are evaluated first in the export process, but I don't know how exporting works yet.) So,{{{ref}}}
calls theref
macro defined above, and expanded into{{{define()}}} {{{src()}}}
$1
is replaced by the first argument given to the macro.$2
would be replaced by the second. So{{{ref(domain-name)}}}
expands to{{{define(domain-name)}}} {{{source(domain-name)}}}
.
- Any macros referenced in that macro are evaluated, and so on. So,
{{{define(domain-name)}}} {{{source(domain-name)}}}
would expand to{{{domain-name-definition}}} {{{domain-name-source}}}
. - Then that would become
A string (group of letters) defining the name of an area of the Internet. See {{{source-link(domain-name)}}}.
- That would, finally, become
A string (group of letters) defining the name of an area of the Internet. See [[https://en.wikipedia.org/wiki/Domain_name][https://en.wikipedia.org/wiki/Domain_name]].
There are quite a few handicaps in this approach though. The biggest and most immediate is that if I don't have both macros set per term, I can't use the ref
macro - it fails because it tries to call, eventually, fake-term-source
, which doesn't exist.
It also pretty well forces down a structure of how referecences must be constructed, and some other issues I'm having a tough time vocalizing here.
Getting Started
I'm going to start by instead, writing a little draft of what I'd like a personal dictionary entry to look like, as written as an Elisp declaration.
Define ems-dict
variable
(setq ems-dict '(("banana" ("definition" . "an elongated berry, technically") ("sources" ("Wikipedia" . "https://en.wikipedia.org/wiki/Banana") ("Wiktionary" . "https://en.wiktionary.org/wiki/banana")))))
Define ems-dict-get-def
function1
And here's a small function to pull out the definition:
(defun ems-dict-get-def (term) "Return the definition of TERM from ems-dict." (cdr (assoc "definition" (cdr (assoc term ems-dict)))))
So if I run that function, I should get the definition of the word banana.
(ems-dict-get-def "banana")
Define ems-cap-first-char
function
Appears to be working; but let's make a little function for capitalizing the first line of a string.
(defun ems-cap-first-char (&optional string) "Return STRING with the first character capitalized." (when (and string (> (length string) 0)) (let ((first-char (substring string nil 1)) (rest-str (substring string 1))) (concat (capitalize first-char) rest-str))))
Here's that in use.
(ems-cap-first-char (ems-dict-get-def "banana"))
This doesn't really help me in using it as a tool for referencing these terms in my writing though. For that, I'll need to set up some macros.
Define def
macro
(add-to-list 'org-export-global-macros '("def" . "(eval (ems-get-def $1))"))
So then if I write {{{def(banana)}}}
in a sentence, I should be able to get the definition. To test, below I'll write A banana is {{{def(banana)}}}.
A banana is a banana.
(I've exported this source file to HTML and it says "A banana is an eloganted berry, technically." Exactly what it should say.)
Define Def
macro
An easy extension of this is a similar macro:
(add-to-list 'org-export-global-macros '("Def" . "(eval (ems-cap-first-char (ems-get-def $1)))"))
This makes it easy to say the definition of a thing as a whole sentence. What's a banana? banana
In hindsight I could have writtten ems-def
and ems-Def
functions first and had it evaluate those, but now I need to take a break.
Define ems-dict-def
function
Define ems-dict-Def
function
Define ems-dict-add-term
function
- Should replace extant term.
Supplements
List of Figures
List of Listings
- Listing 1: My old dictionary-management macros.
- Listing 2: A declaration setting the
ems-dict
variable to a list of terms, the first and only being "banana". - Listing 3: A declaration of the
ems-dict-get-def
function, which returns the provided term's definition from theems-dict
list. - Listing 4: A test of the
ems-dict-get-def
function. - Listing 5: A function which takes a string and returns it with the first letter capitalized.
- Listing 6: A test of the
ems-cap-first-char
function. - Listing 7: A declaration to add
def
as an Org-mode macro, set to pass any arguments to theems-dict-get-def
function and evaluate. - Listing 8: A declaration to add
Def
as an Org-mode macro, set to pass any arguments to theems-get-def
function and evaluates, then runs it throughems-get-first-char
.
Dictionary
Coming soon? This document is about drafting how to assemble this sort of thing, after all. In the meantime, here's a list of the precise terms used in this text.
- personal dictionary entry
- Org-mode macro
- Org-mode
- personal dictionary
- Emacs configuration
- Emacs
- Emacs Lisp
Document Source
This is this document's source as it was written by me.
#+TITLE: Personal Dictionary #+HTML_HEAD: <link rel="stylesheet" type="text/css" href="./brutstrap.css"> #+EXPORT_FILE_NAME: ./personal-dictionary #+TOC: headlines 3 * Introduction :PROPERTIES: :CUSTOM_ID: introduction :END: * Personal Dictionary :PROPERTIES: :header-args: :noweb yes :exports both :results value silent :CUSTOM_ID: personal-dictionary :END: :PREREQUISITES: # Without these macros: #+MACRO: def $1 #+MACRO: Def $1 # the document fails to export, because they probably don't exist yet. # If there's missing text after exporting this the first time, # re-export now that the first export forced the evaluation of the # contained scripts. :END: ** Background :PROPERTIES: :CUSTOM_ID: background :END: I maintain a /personal dictionary/ within my /Emacs/ configuration. I initially started it for my fantasy writing project, because I wanted an easy way to quickly define and reference an in-universe thing, such as a town or item. I set it up without knowing an /Elisp/, but now that I know just a little /Elisp/, I'd like to re-examine my approach. Here's an excerpt from my /Emacs configuration/, showing how the dictionary currently #+name: old-dict-macros #+caption: My old dictionary-management macros. #+begin_src elisp :eval no :exports code (setq org-export-global-macros '(("ref" . "{{{define($1)}}} {{{source($1)}}}") ("define" . "{{{$1-definition}}}") ("source" . "{{{$1-source}}}") ("source-link" . "See [[$1][$1]]") ("domain-name-definition" . "A string (group of letters) defining the name of an area of the Internet.") ("domain-name-source" . "{{{source-link(https://en.wikipedia.org/wiki/Domain_name)}}}."))) #+end_src This lets me write ={{{ref(domain-name)}}}= and get "A string (group of letters) defining the name of an area of the Internet. See [[https://en.wikipedia.org/wiki/Domain_name][https://en.wikipedia.org/wiki/Domain_name]]." Here's a breakdown of how that works: 1) Text inside ={{{ }}}= is interpetted by /Org-mode/ as a /macro/, and replaced when the document is exported. (I think /macros/ are evaluated first in the export process, but I don't know how exporting works yet.) So, ={{{ref}}}= calls the =ref= macro defined above, and expanded into ={{{define()}}} {{{src()}}}= - =$1= is replaced by the first argument given to the macro. =$2= would be replaced by the second. So ={{{ref(domain-name)}}}= expands to ={{{define(domain-name)}}} {{{source(domain-name)}}}=. 2) Any /macros/ referenced in that /macro/ are evaluated, and so on. So, ={{{define(domain-name)}}} {{{source(domain-name)}}}= would expand to ={{{domain-name-definition}}} {{{domain-name-source}}}=. 3) Then that would become =A string (group of letters) defining the name of an area of the Internet. See {{{source-link(domain-name)}}}.= 4) That would, finally, become =A string (group of letters) defining the name of an area of the Internet. See [[https://en.wikipedia.org/wiki/Domain_name][https://en.wikipedia.org/wiki/Domain_name]].= There are quite a few handicaps in this approach though. The biggest and most immediate is that if I don't have both macros set per term, I can't use the =ref= macro - it fails because it tries to call, eventually, =fake-term-source=, which doesn't exist. It also pretty well forces down a structure of how referecences must be constructed, and some other issues I'm having a tough time vocalizing here. ** Getting Started :PROPERTIES: :CUSTOM_ID: getting-started :END: I'm going to start by instead, writing a little draft of what I'd like a /personal dictionary entry/ to look like, as written as an /Elisp/ declaration. *** Define =ems-dict= variable #+name: ems-dict #+caption: A declaration setting the =ems-dict= variable to a list of terms, the first and only being "banana". #+begin_src elisp (setq ems-dict '(("banana" ("definition" . "an elongated berry, technically") ("sources" ("Wikipedia" . "https://en.wikipedia.org/wiki/Banana") ("Wiktionary" . "https://en.wiktionary.org/wiki/banana"))))) #+end_src *** Define =ems-dict-get-def= function[fn:1] And here's a small function to pull out the definition: #+name: ems-dict-get-def #+caption: A declaration of the =ems-dict-get-def= function, which returns the provided term's definition from the =ems-dict= list. #+begin_src elisp (defun ems-dict-get-def (term) "Return the definition of TERM from ems-dict." (cdr (assoc "definition" (cdr (assoc term ems-dict))))) #+end_src So if I run that function, I should get the definition of the word banana. #+name: lookup-banana-test #+caption: A test of the =ems-dict-get-def= function. #+begin_src elisp :results value pp (ems-dict-get-def "banana") #+end_src *** Define =ems-cap-first-char= function Appears to be working; but let's make a little function for capitalizing the first line of a string. #+name: ems-cap-first-char #+caption: A function which takes a string and returns it with the first letter capitalized. #+begin_src elisp (defun ems-cap-first-char (&optional string) "Return STRING with the first character capitalized." (when (and string (> (length string) 0)) (let ((first-char (substring string nil 1)) (rest-str (substring string 1))) (concat (capitalize first-char) rest-str)))) #+end_src Here's that in use. #+name: cap-first-char-test #+caption: A test of the =ems-cap-first-char= function. #+begin_src elisp :results value pp (ems-cap-first-char (ems-dict-get-def "banana")) #+end_src This doesn't really help me in using it as a tool for referencing these terms in my writing though. For that, I'll need to set up some macros. *** Define =def= macro #+name: add-def-to-global-macros #+caption: A declaration to add =def= as an /Org-mode macro/, set to pass any arguments to the =ems-dict-get-def= function and evaluate. #+begin_src elisp (add-to-list 'org-export-global-macros '("def" . "(eval (ems-get-def $1))")) #+end_src So then if I write ={{{def(banana)}}}= in a sentence, I should be able to get the definition. To test, below I'll write =A banana is {{{def(banana)}}}.= A banana is a {{{def(banana)}}}. (I've exported this source file to HTML and it says "A banana is an eloganted berry, technically." Exactly what it should say.) *** Define =Def= macro An easy extension of this is a similar macro: #+name: add-Def-to-global-macros #+caption: A declaration to add =Def= as an /Org-mode macro/, set to pass any arguments to the =ems-get-def= function and evaluates, then runs it through =ems-get-first-char=. #+begin_src elisp (add-to-list 'org-export-global-macros '("Def" . "(eval (ems-cap-first-char (ems-get-def $1)))")) #+end_src This makes it easy to say the definition of a thing as a whole sentence. What's a banana? {{{Def(banana)}}} In hindsight I could have writtten =ems-def= and =ems-Def= functions first and had it evaluate those, but now I need to take a break. *** DRAFT Define =ems-dict-def= function *** DRAFT Define =ems-dict-Def= function *** DRAFT Define =ems-dict-add-term= function - Should replace extant term. * Supplements ** List of Figures #+TOC: listings ** Dictionary */Coming soon?/* This document is about drafting how to assemble this sort of thing, after all. In the meantime, here's a list of the precise terms used in this text. - personal dictionary entry :: - Org-mode macro :: - Org-mode :: - personal dictionary :: - Emacs configuration :: - Emacs :: - Emacs Lisp :: ** Document Source This is this document's source as it was written by me. #+INCLUDE: "./source.org" src ** Elisp Source This is a selection of the function and variable definitions from this file. #+begin_src elisp :noweb yes :tangle ./personal-dictionary.el <<ems-dict>> <<ems-get-def>> <<ems-cap-first-char>> #+end_src * Footnotes [fn:1] In an earlier draft this was called =ems-get-def= but I realized I didn't want to muddy my own namespace like that.
Elisp Source
This is a selection of the function and variable definitions from this file.
(setq ems-dict '(("banana" ("definition" . "an elongated berry, technically") ("sources" ("Wikipedia" . "https://en.wikipedia.org/wiki/Banana") ("Wiktionary" . "https://en.wiktionary.org/wiki/banana"))))) (defun ems-cap-first-char (&optional string) "Return STRING with the first character capitalized." (when (and string (> (length string) 0)) (let ((first-char (substring string nil 1)) (rest-str (substring string 1))) (concat (capitalize first-char) rest-str))))
Footnotes:
In an earlier draft this was called ems-get-def
but I realized I didn't want to muddy my own namespace like that.