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:

  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

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

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:

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.

Author: emsenn

Created: 2019-06-16 Sun 20:55

Validate