Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/Kungsgeten/lozenge.el
Pollen inspired lozenge syntax for Emacs
https://github.com/Kungsgeten/lozenge.el
Last synced: about 2 months ago
JSON representation
Pollen inspired lozenge syntax for Emacs
- Host: GitHub
- URL: https://github.com/Kungsgeten/lozenge.el
- Owner: Kungsgeten
- License: mit
- Created: 2019-09-07T23:09:00.000Z (about 5 years ago)
- Default Branch: master
- Last Pushed: 2019-09-10T09:29:26.000Z (about 5 years ago)
- Last Synced: 2024-04-14T02:59:37.103Z (5 months ago)
- Language: Emacs Lisp
- Homepage:
- Size: 13.7 KB
- Stars: 29
- Watchers: 5
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.org
- License: LICENSE.org
Awesome Lists containing this project
README
#+TITLE:Lozenge
* Introduction
[[https://pollenpub.com][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 [[https://docs.racket-lang.org/pollen/pollen-command-syntax.html][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 [[https://github.com/magnars/dash.el][dash]] and [[https://github.com/philjackson/xmlgen][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 [[https://www.emacswiki.org/emacs/KeyChord][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 [[https://www.emacswiki.org/emacs/XmlGen][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:
My subtitle
@@~. 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:@@My *bold* subtitle@@html:
@@~. 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
[[https://fontawesome.com/][Font Awesome]] is popular when it comes to using icons on the web. In HTML it usually looks like this ~~
#+BEGIN_SRC emacs-lisp
(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 instead of self closing
""))
#+END_SRCNow 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 [[https://orgmode.org/manual/Structure-of-code-blocks.html][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 [[https://orgmode.org/manual/Macro-replacement.html][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.
#+BEGIN_SRC emacs-lisp
(defun loz/hand ()
'(span :class "hand"
(span :class "suit" "♠" "%s")
(span :class "suit" "♥" "%s")
(span :class "suit" "♦" "%s")
(span :class "suit" "♣" "%s"))
#+END_SRCNow 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:
#+BEGIN_SRC emacs-lisp
(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 " ")))
#+END_SRC=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:
#+BEGIN_SRC emacs-lisp
(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 " ")))
#+END_SRCNow 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:
#+BEGIN_SRC org
◊(:deal){
| | ◊hand2{QJ 94 Q87543 853} | |
| ◊hand2{T8652 32 KJT9 A7} | | ◊hand2{9 KQJT86 A6 KQJ9} |
| | ◊hand2{AK743 A75 2 T642} | |
}
#+END_SRCWe 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=.
#+BEGIN_SRC emacs-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 " "))))))
#+END_SRCNow we could use the following to notate our deal:
#+BEGIN_SRC org
◊deal{
| | QJ | |
| | 94 | |
| | Q87543 | |
| | 853 | |
| T8652 | | 9 |
| 32 | | KQJT86 |
| KJT9 | | A6 |
| A7 | | KQJ9 |
| | AK743 | |
| | A75 | |
| | 2 | |
| | T642 | |
}
#+END_SRCDoing 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:
#+BEGIN_SRC org
,* 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)
#+END_SRCNotice 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 [[https://github.com/weavejester/hiccup][hiccup]] code. However I know of no Emacs package which converts hiccup to HTML.