cltpt
note that this website was generated using
the goal of
to make this perist you may clone the repo into
then you may use the commandline like:
convert a bunch of files from org to html:
notice that you can stack multiple instances of the
this would generate two files,
convert a single file from org to latex (write it to
convert a bunch of files from org to latex:
the following command queries a directory of org-mode files and prints the title, id and filepath for each one in the specified format (
to print an agenda tree for a different range of timestamps, we use:
in my mind the syntax i wanted had to be customizable, extensible, and similar to lisp code so that it is easy to parse. even better, i wanted the syntax to just be lisp code embedded within arbitrary text, so that lisp code is a second-class citizen, unlike in lisp code files.
after a bit of thinking i put something together that i think makes some sense. this section will highlight some example usage of this markup "language".
with the lisp-based markup cltpt provides, the alternative would simply be:
both the opening and the closing tags are lisp expressions (which we call "text macros") that are evaluated to construct instances of
if a text macro isnt closed by another, it is taken to be its own text object without a "contained" region. for example, in the following text the macro is simply replaced with the evaluation result during conversion:
although this wouldnt work as expected because by default, the contents of the macro are escaped during conversion, so the read contents will not be interpreted as raw contents but as text to escape (for example
parsing a file and using its tree can be done like:
iterating through files and their
a few methods are defined for the
the basis of both
matches are used to construct text-objects, a match may or may not end up being a text-object depending on whether its
some related functions are:
cltpt from org-mode files. so this page/webapp can be a testament to whether org->html conversion works properly (broken text elements is an indicator of parser/conversion failure).
introduction
https://github.com/mahmoodsh36/cltpt is both a tool and a library that is meant to serve as a base for the upcoming organ-mode package for thelem text editor. it is very much a WIP, it is unstable and breaking changes may be introduced without warnings.
the goal of
cltpt is to maintain an editor-independent codebase and toolset (the existence of countless parsers for org-mode should be enough a motive for this). the core will be written in common lisp but will be independent of lem's libraries, but ofcourse this is supposed to (mainly) be a package for lem so some parts are bound to be lem-dependent. although lem itself can also be used as a library in common lisp, i think that this is still a good idea.
installation
nixos
if you are on nixos you may use the provided flake. running the commandline tool is as simple as something like:nix run github:mahmoodsh36/cltpt#cltpt -- -hquicklisp
since the dependencies are all listed incltpt.asd, cloning the code and then running (ql:quickload :cltpt) in the code directory should be enough to load the library.
to make this perist you may clone the repo into
~/.quicklisp/local-projects/. then you may overwrite main.lisp (which uses asdf directly without quicklisp) to make it work with quicklisp:
(ql:quickload :cltpt)
(cltpt/zoo:init)
(cltpt/commandline:commandline-main (uiop:command-line-arguments))run.sh -h. (in the following sections you may replace cltpt with run.sh and the commands will work.)
example commandline usage
this section will highlight what i think would be the most common usage for the commandline tool.converting from org-mode to html
convert a single file from org to html (the% part is for "formatting" with lisp code):
# this command writes the file to /tmp/test.html and the static files it links to to ~/tmp/~ aswell.
cltpt convert\
-f 'test.org'\
-d html\
-o '/tmp/%(getf *file-info* :filename-no-ext).html'\
-c '/tmp/%(getf *file-info* :filename)cltpt convert\
-r '(:path "/home/<user>/dir1/" :glob "*.org" :format "org-mode")'\
-r '(:path "/home/<user>/dir2/" :glob "*.org" :format "org-mode")'\
-r '(:path "/home/<user>/dir3/file.org" :glob "*.org" :format "org-mode")'\
-d html\
-o '/tmp/%(getf *file-info* :filename-no-ext).html'-r (or --rule) argument. this is also possible with the -f argument (which is used for single files):
cltpt convert\
-f ~/notes/file1.org\
-f ~/notes/file2.org\
-d html\
-o '/tmp/%(getf *file-info* :filename-no-ext).html'/tmp/file1.html and /tmp/file2.html.
converting from org-mode to latex
notice that this is very similar to the previous commands, with slight differences in the arguments passed to the tool, such as the name of the destination format (the format we want to convert our files to).convert a single file from org to latex (write it to
/tmp/test.tex):
cltpt convert\
-f 'test.org'\
-d latex\
-o '/tmp/%(getf *file-info* :filename-no-ext).tex'cltpt convert\
-r '(:path "/home/<user>/notes/" :glob "*.org" :format "org-mode")'\
-d latex\
-o '/tmp/%(getf *file-info* :filename-no-ext).tex'"roam" queries
the library provides an "org-roam-like" interface that works with different methods of identifying different types of objects in your text files. by default it recognizesorg-id "identifiers" and links (the ones org-roam use for headers/files). denote-like identifiers and (more importantly) blk-like identifiers which includes the previous ones.
the following command queries a directory of org-mode files and prints the title, id and filepath for each one in the specified format (
-o argument).
cltpt roam\
-r "(:path \"$ORG_DIR\" :glob \"*.org\" :format \"org-mode\")"\
-o 'title: %title, id: %id, file: %file'
title: my doc, id: NIL, file: /home/mahmooz/work/cltpt/tests/test.org title: header my secondary header, id: NIL, file: /home/mahmooz/work/cltpt/tests/test.org title: send the professor a mail, id: NIL, file: /home/mahmooz/work/cltpt/tests/test.org title: NIL, id: def-vector, file: /home/mahmooz/work/cltpt/tests/test.org title: do something else, id: NIL, file: /home/mahmooz/work/cltpt/tests/test.org title: header do something, id: NIL, file: /home/mahmooz/work/cltpt/tests/test.org title: another due task, id: NIL, file: /home/mahmooz/work/cltpt/tests/test.org title: due task, id: NIL, file: /home/mahmooz/work/cltpt/tests/test.org title: NIL, id: def-ac-standard, file: /home/mahmooz/work/cltpt/tests/test.org title: NIL, id: test-name, file: /home/mahmooz/work/cltpt/tests/test.org title: NIL, id: NIL, file: /home/mahmooz/work/cltpt/tests/test2.org title: some task, id: NIL, file: /home/mahmooz/work/cltpt/tests/test2.org title: part 1, univariate linear regression, id: NIL, file: /home/mahmooz/work/cltpt/tests/test2.org
"agenda" queries
the following command prints an agenda "tree" after retrieving all TODOs from the org files found in the specified directory. the default range displayed is 7 days from now.cltpt agenda\
-r "(:path \"$ORG_DIR\" :glob \"*.org\" :format \"org-mode\")"
├─ Tuesday 21 October │ ├─ 00:00 │ ├─ 02:00 │ ├─ 04:00 │ ├─ 06:00 │ ├─ 08:00 │ ├─ 10:00 │ ├─ 12:00 │ ├─ 14:00 │ ├─ 16:00 │ ├─ 18:00 │ ├─ 20:00 │ └─ 22:00 ├─ Wednesday 22 October ├─ Thursday 23 October ├─ Friday 24 October ├─ Saturday 25 October ├─ Sunday 26 October └─ Monday 27 October
cltpt agenda\
-r "(:path \"$ORG_DIR\" :glob \"*.org\" :format \"org-mode\")"\
-f '2025-10-13'\
-t '2025-10-18'
├─ Monday 13 October │ ├─ 00:00 │ ├─ 02:00 │ ├─ 04:00 │ ├─ 06:00 │ ├─ 08:00 │ ├─ 10:00 │ ├─ 12:00 │ ├─ 14:00 │ ├─ 16:00 │ ├─ 18:00 │ ├─ 20:00 │ └─ 22:00 ├─ Tuesday 14 October │ ├─ START: 00:00--00:00 part 1, univariate linear regression │ └─ START: 10:00--12:00 part 1, univariate linear regression ├─ Wednesday 15 October │ └─ START: 10:00--12:00 part 1, univariate linear regression ├─ Thursday 16 October │ └─ START: 10:00--12:00 part 1, univariate linear regression └─ Friday 17 October └─ START: 10:00--12:00 part 1, univariate linear regression
roadmap
notice that this roadmap is different from the roadmap fororgan-mode.
todos
an X may not mean that the feature is completely implemented but that it is functional for the most part.- [-] org-header
- [ ] priorities
- [X] todo state
- [X] tags
- [X] properties
- [X] timestamps, scheduling and deadlines
- [ ] state history
- [ ] completion status (e.g. completion percentage of children with tasks etc)
- [ ] org-list
- [ ] checkboxes
- [-] agenda
- [X] repeated tasks
- [ ] tags
- [ ] custom views
- [ ] task hierarchy in agenda tree
- [ ] state history tracking
- [ ] markdown
- [ ] support agenda for markdown
- [ ] support roam for markdown
- [X] inline lisp execution
- [-] commandline
- [X] conversion
- [X] roam
- [X] agenda
- [ ] advanced conversion with prewritten webapp templates
- [-] conversion (ideas from org-export)
- [X] org to html
- [X] org to latex
- [ ] org to markdown
- [ ] markdown to org
- [ ] latex
- [ ] recognize latex links (~\ref~)
- [ ] recognize latex labels (~\label~)
- [ ] babel
- [ ] code tangling
- [ ] code detangling
- [ ] sessions
- [ ] data pipelines
- [ ] library of babel
- [ ] noweb
- [-] roam (idea from org-roam)
- [-] node links
- [X] links to files
- [ ] links to headers
- [ ] links to blocks
- [-] node links
- [ ] org-clock
- [X] latex previews for html conversion
- [ ] org-attach
- [X] transclusions
org-element support
its not calledorg-element but a text-object in the source code.
| org-element | parsing | highlighting | conversion to html | conversion to latex |
| list | t | t | t | |
| table | t | t | t | |
| header | t | t | t | |
| link | t | t | t | |
| timestamp | t | t | ||
| src-block | t | t | t | |
| export-block | t | t | t | |
| block | t | t | t | t |
| prop-drawer | t | |||
| drawer | t | t | t | |
| latex-env | t | t | t | |
| keyword | t | |||
| display-math | t | t | t | |
| inline-math | t | t | t | |
| italic | t | t | t | |
| emph | t | t | t | |
| inline-code | t | t | t | |
| comment | t | t | t | |
| comment-block | ||||
| web-link | t | t | ||
| org-cite | ||||
| underline | ||||
| subscript | ||||
| superscript | ||||
| strike-through | ||||
| footnote | ||||
| dynamic-block | ||||
| inline-task | ||||
| paragraph |
babel functionality (code execution)
this section is yet to be written, currently only support for running python code is implemented.how markup is handled
yet to be written. this section will include:- what it means for some code to be format-agnostic.
- how escape sequences are handled.
a lisp-based markup
after having used org-mode for years, i was fed up with its syntax. many people tend to say that org has a saner markup syntax than markdown, but i personally never saw the value in the syntax itself but in the rest of the features that org-mode provides.in my mind the syntax i wanted had to be customizable, extensible, and similar to lisp code so that it is easy to parse. even better, i wanted the syntax to just be lisp code embedded within arbitrary text, so that lisp code is a second-class citizen, unlike in lisp code files.
after a bit of thinking i put something together that i think makes some sense. this section will highlight some example usage of this markup "language".
text macros and blocks
in my mind, a "block" of text is a portion of text that is specified by a opening and closing "tags". for example, the opening tag for a block in org-mode is#+begin_<type>. with this in mind, we consider the following org-mode text block:
,#+begin_definition
this is my definition block
,#+end_definition#(cltpt/base::make-block :type 'definition)
this my definition block
#(cltpt/base::block-end)text-object. block-end is a function that returns a value that the parser recognizes as the signal to close the region that was opened by make-block.
if a text macro isnt closed by another, it is taken to be its own text object without a "contained" region. for example, in the following text the macro is simply replaced with the evaluation result during conversion:
1-1+1 equals #(+ (- 1 1) 1).html templates
the lisp markup can serve as a templating engine for html files. we can simply write macros that will return read a file and return it as a text-object that simply stores the text that it read from the file, then during conversion, that text will be substituted inplace of the original macro. for example, we can use the following html file which reads another html file:<div class="header">
#(uiop:read-file-string "header.html")
</div>< gets replaced with < during html conversion). this is easy to overcome by writing a function that returns a text-object that overrides the conversion functionality to prevent escaping from happening, and using the function for the template:
<div class="header">
#(read-template-file-into-text-obj "header.html")
</div>lexer text macros and post-lexer text macros
to be written. good to atleast note for now that% is used for post-lexer macros while # is used for lexer macros.
org files to webapp
this functionality is not yet available but i will write functionality for converting a bunch of org files into a website. (its already written for my website but i need to integrate it into this codebase.)using the api
iterating through files
the repo https://github.com/mahmoodsh36/template could serve as a good example for how to work with files as it uses the api to generate this webapp.parsing a file and using its tree can be done like:
(let* ((tree (cltpt/base:parse-file cltpt/org-mode:*org-mode* "my/file.org")))
(cltpt/tree:tree-show tree)
(cltpt/base:map-text-object
tree
(lambda (obj)
(format t
"type is ~A, contents are ~A, position in original string is ~A~%"
(cltpt/base:text-object-begin-in-root obj)
(cltpt/base:text-object-text obj)
(type-of obj))))
tree)text-object trees can be done like:
(defun my-show-nodes (rmr)
(let* ((file-rules '((:path ("/home/mahmooz/brain/notes/")
:glob "*.org"
:format "org-mode")))
(rmr (cltpt/roam:from-files rmr-files)))
(loop for node in (cltpt/roam:roamer-nodes rmr)
for this-tree = (cltpt/roam:node-text-obj node)
do (cltpt/base:map-text-object
this-tree
(lambda (obj)
(format t
"title is ~A, id is ~A, type is ~A~%"
(cltpt/base:text-object-property obj :title)
(cltpt/base:text-object-property obj :id)
(type-of obj)))))))working with the code
this will highlight the main concepts and interfaces in the codebase. note that much of the code will likely change in the (near) future, but the main interfaces discussed here will probably remain the same.the core interface
the core interfacecltpt/base contains a few main CLOS interfaces:
-
text-object -
text-format
~text-object~ interface
each text object has a "rule" that is passed to the parser. rules are simply trees composed of combinator functions. any class that inherits fromtext-object needs to have such a rule slot.
a few methods are defined for the
text-object interface:
-
text-object-init: this is called immediately after parsing and recognizing the text object. -
text-object-finalize: this is called once the final text-object tree has been constructed, each object in the tree is finalized starting from the root (an instance ofdocument). -
text-object-convert: this is called during conversion for each object in the tree, starting from the root. the method returns a plist that determines the behavior of the conversion, this plist is handled by the functionconvert-tree.
~text-format~ interface
the parser
the parser is based on a "parser combinator" that tries to simplify and abstract away the work of parsing different types of text objects. before creating instances oftext-object's the results of parsing are instances of 'match', which is a tree-based data structure (just like text-object), except that its simpler and holds less properties and functionality.
the basis of both
text-object and match is a buffer. a buffer is a tree-based structure (buffers nested within buffers) in which each node essentially pinpoints a region of text. the buffer data structure knows how to adapt to incremental/regional changes in the text and is used extensively in the conversion pipeline.
matches are used to construct text-objects, a match may or may not end up being a text-object depending on whether its
id property corresponds to a text-object class.
some related functions are:
-
handle-matchis a function that takes a match tree and turns it into a text-object tree. -
cltpt/base:parsecallscltpt/combinator:parse, then on each match returned by the combinator it callshandle-match. then it proceeds to finalize the text-object tree.