Skip to content

Kungsgeten/lozenge.el

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 

Repository files navigation

Lozenge

Introduction

Pollen is a publishing system that helps authors make functional and beautiful digital books. When you write documents in Pollen you usually write regular text (like in org-mode or Markdown), but you can also use a special type of syntax to call functions and run code within the document. This package makes that style of syntax available for Emacs. Pollen uses a lozenge to mark the start of a command. A lozenge looks like this: ◊. That’s why this package is named lozenge.

The primary target for lozenge was to use it in org-mode when exporting to HTML. However lozenge can be used in any major-mode.

Setup

Get lozenge into your Emacs and require it. You also need dash and xmlgen. To make lozenge work on org-mode export, use M-x lozenge-org-export-enable (or call that function in your init-file). There’s a similar command available for markdown-mode which you can activate with M-x lozenge-markdown-export-enable. If you’re not using org-mode org markdown-mode but still want to use lozenge you can name your file in a special way. Let’s say that you want a file named my-file.tex and that you want to use the lozenge functionality in it. Simply rename the file to my-file.loz.tex. Running M-x lozenge-export will evaluate all lozenges and output the result to my-file.tex.

The ◊-character can be a bit cumbersome to insert, since it isn’t bound to any key on the keyboard. lozenge provides a command named lozenge-insert-lozenge, which you can run with M-x. Now I suggest that you bind this command to a key. I myself use key-chord and bind the command to ,,. There’s also a command to insert the ⟠-character, named lozenge-insert-half-lozenge.

Syntax

At its most basic, you could write something like this ◊(upcase "hello"). The ◊-sexp will be evaluated by Emacs, and the result will be inserted in the document when you export. If the result is a string or number, it will be inserted as is. If the result is a list it will get special treatment: in org-mode (with the 'html backend) and =markdown-mode it is exported to HTML. More about this later.

If you want to insert the value of a variable, you could do that as well: ◊user-full-name. You could even define your own variables in your document and use them later: ◊(defconst loz/my-var "This is my variable"). Notice that I used the prefix loz/ before my-var. The reason for this is to avoid name conflicts with other parts of Emacs, but actually the loz/ prefix gets special treatment by lozenge. I can later type ◊my-var and when exporting lozenge will check if there’s a symbol named loz/my-var and use that (if there isn’t, it will use the plain my-var). This works with functions too. You can also use other prefixes to futher avoid clashes. If you’re in a project named foo you can use a prefix named foo/loz/. Another alternative is to make it specific to the current major-mode, for instance loz/org-mode/. See the documentation on the function lozenge-real-sexp for more information.

An alternate syntax to call a function is like this: ◊upcase{uppercased text}. This takes the text between the braces and sends it (as a string) to the first parameter of the function. Its equivalent to writing ◊(upcase "uppercased text"). You can also mix, so you could do ◊(upcase){uppercased text} if you like. A nice thing with this mixed variant is that it allows for additional arguments to the function. So you could do something like ◊(string-trim "-" "."){-Yeah.}, which would be the same as writing ◊(string-trim "-Yeah." "-" "."). This is especially useful if you use/write functions with optional arguments.

Another syntax feature has to do with format strings. In Emacs there’s a function named format which replaces parts of a string with arguments: (format "Hello %s!" user-full-name). In lozenge you can use format brackets to send text as arguments to the format function. If the result of the ◊-sexp is a string, that string will be sent to format along with the arguments you write. A very simple example would be ◊"Hello %s, nice %s."[handsome lad][hat]. In reality you’d probably have a function which generates the format string though, and you supply the arguments.

If the result of the ◊-sexp is a list, it gets special treatment depending on the current major-mode. In org-mode it also depends on the export backend. Check the variable ==lozenge-list-processing-functions= for more info. By default org-mode with 'html backend, and markdown-mode, converts the list to HTML. lozenge uses the xmlgen package to do this. You could for instance write ◊'(h2 :class "subtitle" "My subtitle"). You could also write a function which returns a list, and that list will be converted into HTML. In org-mode inline HTML is written like this: @@html:<h2 class="subtitle">My subtitle</h2>@@. All other org-mode syntax (like *bold* or /italics/) are ignored here. To remedy this lozenge uses the format brackets a bit different when the result of the ◊-sexp is a list. You could write ◊'(h2 :class "subtitle" "%s")[My *bold* subtitle] which in org-mode syntax would look like @@html:<h2 class="subtitle">@@My *bold* subtitle@@html:</h2>@@. For details about how this is implemented, see the function lozenge-list-to-org-html.

When you use a quoted list as the ◊-sexp, the text braces behaves a bit differently. The text will be appended as the last argument of the list, as a format string. In the previous example we wrote ◊'(h2 :class "subtitle" "%s")[My *bold* subtitle]. We could instead have written ◊'(h2 :class "subtitle"){My *bold* subtitle.}.

Another common use-case is to just add a div/span tag with a class to a set of text. lozenge has special syntax for this. If the car of your ◊-sexp is a keyword (like :blue) then that keyword will be used as a class. If you want a span tag you write like this ◊(:blue This is blue text in a span) and if you want a div tag you use the text braces like this ◊(:blue){This is a blue div}. You can add more than one class this way by separating the classes with dots: ◊(:blue.big This text is blue and big).

Syntax summary

  1. ◊sexp{text arg}[format arg 1][format arg 2][...] (the text args and format args are optional).
    • If there’s a symbol named loz/sexp it will be used instead of sexp.
    • If sexp is a function call (or a symbol), the text arg is used as the first argument when calling the function.
    • When sexp evaluates to a string, the following code will be run (apply 'format sexp-result format-args).
    • When sexp evaluates to a list, it will be treated according to lozenge-list-processing-functions. Any %s in the list will be replaced by (in order) the format-args.
  2. ◊'(list){text arg}[format arg 1][format arg 2][...] (the text args and format args are optional).
    • When the ◊-sexp is written as a quoted list, the text arg is appended to the list. It also converts it into a format arg (the last one, if you have others).
  3. ◊(:class1.class2.class3 Some text)
    • Some text will be wrapped in a span with the classes.
  4. ◊(:class1.class2.class3){Some text}
    • Some text will be wrapped in a div with the classes.

The half-lozenge

For the most part you’ll use the ◊-char to write lozenge expressions. However there’s also a half-lozenge ⟠-char available. When exporting the half-lozenges is evaluated before the regular lozenges.

In org-mode there are two hooks named org-export-before-parsing-hook and org-export-before-processing-hook. Processing is done before parsing (see the documentation on these variables for more information). The code which replaces lozenge ◊ is run in org-export-before-parsing-hook, which is usually what you want. However if you want to do replacements in org-export-before-processing-hook you can do so by using the half-lozenge ⟠, otherwise it has the same functionality as the normal lozenge.

I don’t like lozenges…

You can change the chars used by lozenge by modifying lozenge-before-parsing-char (◊ by default) and lozenge-before-processing-char (⟠ by default).

Org-mode examples

If you want to define functions which should only be used in your document, you could put a source block near the top of your file with emacs lisp :exports results :results none as the header args.

Example 1: Font Awesome

Font Awesome is popular when it comes to using icons on the web. In HTML it usually looks like this <i class="far fa-coffee"></i>

(defun loz/fa (icon &optional style &rest classes)
  `(i :class ,(string-join
               `(,(concat "fa" (or style "r"))
                 ,(concat "fa-" icon)
                 ,@classes)
               " ")
      ;; Empty string to get a </i> instead of self closing
      ""))

Now you could use ◊fa{coffee} to get the icon. If you want a solid icon, you could write ◊(fa "s"){coffee} instead. If you wanted to apply other classes, that would be ◊(fa "s" "fa-xs" "fa-rotate-180"){coffee}.

We could do something similar without lozenge in org-mode by using inline source blocks. However the syntax would be a bit more cluttered (and in my opinion a bit harder to remember): src_emacs-lisp[:results html]{(xmlgen (loz/fa "coffee"))}. Ofcourse the xmlgen part isn’t needed if we put it directly into the loz/fa function though. Another way of doing it would be to add a replacement macro, but those doesn’t accept optional arguments (though you could define three separate macros to get the functionality of loz/fa).

Example 2: Bridge hands

I like to play contract bridge (a card game) and often I want to notate a hand of cards. This is usually done by writing suit symbols followed by the cards, similar to this: ♠KJ82 ♥AK3 ♦J8532 ♣Q. Let’s say I want a function for that.

(defun loz/hand ()
  '(span :class "hand"
         (span :class "suit" "" "%s")
         (span :class "suit" "" "%s")
         (span :class "suit" "" "%s")
         (span :class "suit" "" "%s"))

Now I could write ◊hand[KJ82][AK3][J8532][Q]. The reason for using format-args (instead of arguments directly to the function) is if I want to put extra org-mode syntax into the cards. Like ◊hand[AKxxx][\mdash][KQJT2][Jxx]. However we could make the function a bit easier to use by using a single string argument, and the function itself splits it into format-args:

(defun loz/hand2 (hand-text)
  (lozenge-list-to-org-html
   '(span :class "hand"
          (span :class "suit" "" "%s")
          (span :class "suit" "" "%s")
          (span :class "suit" "" "%s")
          (span :class "suit" "" "%s"))
   (split-string hand-text " ")))

lozenge-list-to-org-html takes the list as the first argument, and the format-args as the second argument. Using this we could write ◊hand2{KJ82 AK3 J8532 Q}.

If you were to do this in org-mode without lozenge then you’d probably rewrite the function into something like this:

(defun loz/hand3 (hand-text)
  (apply 'format
         (xmlgen '(span :class "hand"
                        (span :class "suit" "" "%s")
                        (span :class "suit" "" "%s")
                        (span :class "suit" "" "%s")
                        (span :class "suit" "" "%s")))
         (split-string hand-text " ")))

Now we could use src_emacs-lisp[:results html]{(loz/hand3 "KJ82 AK3 J8532 Q")} to insert the hand. However now it isn’t possible to use org-mode markup inside of the hand string. I tried various ways of getting that to work, but couldn’t figure it out. If anyone has a solution, please leave a pull request.

Example 3: Bridge deal diagram

There are four players in bridge, so if you want to notate all four hands a diagram is often used. Let’s say we want to put all four hands in an HTML table. We already have our loz/hand2 from the previous example, so we could do like this:

◊(:deal){
|                          | ◊hand2{QJ 94 Q87543 853} |                          |
| ◊hand2{T8652 32 KJT9 A7} |                          | ◊hand2{9 KQJT86 A6 KQJ9} |
|                          | ◊hand2{AK743 A75 2 T642} |                          |
}

We take a normal org-table and wrap it in a div with the class deal and put the hands in the table. Normally this isn’t the way you notate bridge deals though, so it may be a bit hard to read. Here’s another function, using the very handy org-table-to-lisp.

(defun loz/deal (deal)
  "DEAL must be a 3x12 org-table."
  (let* ((lisp-table (org-table-to-lisp deal))
         (north (mapcar #'cadr (-take 4 lisp-table)))
         (west  (mapcar #'car   (-slice lisp-table 4 8)))
         (east  (mapcar #'caddr (-slice lisp-table 4 8)))
         (south (mapcar #'cadr  (-slice lisp-table 8 12))))
    (org-lozenge-list-to-org-html
     '(table :class deal
             (tr (td)
                 (td "%s")
                 (td))
             (tr (td "%s")
                 (td)
                 (td "%s"))
             (tr (td)
                 (td "%s")
                 (td)))
     (list (loz/hand2 (string-join north " "))
           (loz/hand2 (string-join west " "))
           (loz/hand2 (string-join east " "))
           (loz/hand2 (string-join south " "))))))

Now we could use the following to notate our deal:

◊deal{
|       | QJ     |        |
|       | 94     |        |
|       | Q87543 |        |
|       | 853    |        |
| T8652 |        | 9      |
| 32    |        | KQJT86 |
| KJT9  |        | A6     |
| A7    |        | KQJ9   |
|       | AK743  |        |
|       | A75    |        |
|       | 2      |        |
|       | T642   |        |
}

Doing this in org-mode without lozenge we could use source-blocks with var header-arguments. We could then call the source-block using the #+call syntax. However we’d have to hide our input table somehow, because otherwise the table would show on export . Here’s one solution (if anyone has a better one, please send a pull request), it uses loz/hand3 from example 2 above:

* Data                             :noexport:

  #+name: loz/deal2
  #+header: :var table=0 :results html :exports none
  #+begin_src emacs-lisp
    (let* ((north (mapcar #'cadr (-take 4 table)))
           (west  (mapcar #'car   (-slice table 4 8)))
           (east  (mapcar #'caddr (-slice table 4 8)))
           (south (mapcar #'cadr  (-slice table 8 12))))
      (format
       (xmlgen
        `(table :class "deal"
                (tr (td)
                    (td "%s")
                    (td))
                (tr (td "%s")
                    (td)
                    (td "%s"))
                (tr (td)
                    (td "%s")
                    (td))))
       (loz/hand3 (string-join north " "))
       (loz/hand3 (string-join west " "))
       (loz/hand3 (string-join east " "))
       (loz/hand3 (string-join south " "))))
  #+end_src

  #+tblname: deal1
  |       | QJ     |        |
  |       | "94"   |        |
  |       | Q87543 |        |
  |       | "853"  |        |
  | T8652 |        | "9"    |
  | "32"  |        | KQJT86 |
  | KJT9  |        | A6     |
  | A7    |        | KQJ9   |
  |       | AK743  |        |
  |       | A75    |        |
  |       | "2"    |        |
  |       | T642   |        |

* Real document

  #+call: loz/deal2(table=deal1)

Notice that we have to add quotes around the numbers in the table, otherwise org-mode won’t treat them as strings.

Wishlist

  • Some sort of syntax highlighting for the ◊-sexps would be nice.
  • Instead of (or in addition to) the (:class) syntax it would be nice if we just could write hiccup code. However I know of no Emacs package which converts hiccup to HTML.

About

Pollen inspired lozenge syntax for Emacs

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published