ox-leanpub icon indicating copy to clipboard operation
ox-leanpub copied to clipboard

Org-mode exporter for Leanpub books - mirrored from GitLab

#+title: ox-leanpub: Leanpub book exporter for Org mode #+author: Diego Zamboni #+email: [email protected]

[[https://melpa.org/#/ox-leanpub][file:https://melpa.org/packages/ox-leanpub-badge.svg]]

=Ox-leanpub= includes [[https://orgmode.org/][Org Mode]] export backends to publish books and courses with [[https://leanpub.com/][Leanpub]]. =ox-leanpub= allows you to write your material entirely in Org mode, and manages the production of the files and directories needed for Leanpub to render your book. I use this package to publish [[https://leanpub.com/u/zzamboni][my books]].

This package contains three libraries:

  • =ox-leanpub-markua.el= exports Org files in Leanpub's [[https://leanpub.com/markua/read][Markua]] format, the default and recommended format for Leanpub books and courses
  • =ox-leanpub-markdown.el= exports Org files in [[https://leanpub.com/lfm/read][Leanpub Flavored Markdown]] (LFM), the original markup format for Leanpub books.
  • =ox-leanpub-book.el= exports an Org file in multiple files and directories in the structure [[https://leanpub.com/manual/read?#writing-your-book-in-github-mode][required by Leanpub]], including the necessary =manuscript/= directory and the =Book.txt=, =Sample.txt= and =Subset.txt= files. It can use either Markua or LFM as the export backend.

Note: you should use the Markua exporter, as it's more mature, complete and actively developed by me. Some Org constructs might not be exported correctly to Markdown.

If you have any feedback or bug reports, please open an issue at https://gitlab.com/zzamboni/ox-leanpub/-/issues.

If you want to see a real-world example of how to use it, you can find at https://github.com/zzamboni/emacs-org-leanpub the source of my book [[https://leanpub.com/emacs-org-leanpub][Publishing with Emacs, Org mode and Leanpub]].

  • Table of Contents :toc:noexport:
  • [[#installation][Installation]]
    • [[#from-melpa][From MELPA]]
    • [[#in-doom-emacs][In Doom Emacs]]
    • [[#from-source][From source]]
  • [[#usage][Usage]]
    • [[#special-heading-tags][Special heading tags]]
    • [[#attributes][Attributes]]
    • [[#block-elements][Block elements]]
    • [[#exporting-books-and-courses][Exporting books and courses]]
    • [[#code-block-captions][Code block captions]]
    • [[#index-entries][Index entries]]
    • [[#calling-from-emacs-lisp][Calling from Emacs LISP]]
    • [[#configuration][Configuration]]
  • [[#faq][FAQ]]
    • [[#headlines-below-a-certain-level-are-not-exported-correctly][Headlines below a certain level are not exported correctly]]
  • [[#credits][Credits]]
  • [[#check-out-my-books][Check out my books!]]
  • [[#disclaimer][Disclaimer]]
  • Installation

** From MELPA

You can install the package directly from MELPA. For example, using =use-package=:

#+begin_src emacs-lisp (use-package ox-leanpub :after org) #+end_src

Note: installing the =ox-leanpub= package will also install =ox-gfm=, which is used for exporting tables in Markua format.

By default, the =ox-leanpub= module sets things up for exporting books in Markua format. If you want to export your books in LFM format, you need to additionally load the =ox-leanpub-markdown= exporter and tell =ox-leanpub-book= to set up the corresponding menu entries, as follows:

#+begin_src emacs-lisp (use-package ox-leanpub :after org :config (require 'ox-leanpub-markdown) (org-leanpub-book-setup-menu-markdown)) #+end_src

** In Doom Emacs

If you use [[https://github.com/hlissner/doom-emacs/][Doom Emacs]], add the following line to your =packages.el= file:

#+begin_src emacs-lisp (package! ox-leanpub) #+end_src

And the following to your =config.el= file:

#+begin_src emacs-lisp (use-package! ox-leanpub :after org) #+end_src

If you need to export to Markdown, add the =:config= section exactly as shown before.

** From source

If you want to install from the source in GitLab, you can clone this repository using =git=. For example:

#+begin_src shell cd ~/.emacs.d/lisp git clone https://gitlab.com/zzamboni/ox-leanpub.git #+end_src

=ox-leanpub-markua= depends on [[https://github.com/larstvei/ox-gfm][ox-gfm]] for exporting tables, so you need to install it as well. Easiest is to install it from MELPA using =M-x package-install=, or =use-package=:

#+begin_src emacs-lisp (use-package ox-gfm :after org) #+end_src

Finally, you need to tell Emacs to load the module from the path where you installed it. For example:

#+begin_src emacs-lisp (use-package ox-leanpub :path "~/.emacs.d/lisp/ox-leanpub" :after org :config (require 'ox-leanpub-markdown) (org-leanpub-book-setup-menu-markdown)) #+end_src

  • Usage

Depending on whether you load the Markua or Markdown exporter, you will see the corresponding new sections in Org's export menu (~C-c C-e~), called "Export to Leanpub Markua" and "Export to Leanpub Markdown":

#+begin_example [M] Export to Leanpub Markua [M] To temporary buffer [m] To file [o] To file and open [b] Book: Whole book [s] Book: Subset

[L] Export to Leanpub Markdown [L] To temporary buffer [l] To file [o] To file and open [b] Book: Whole book [s] Book: Subset #+end_example

The "buffer" and "file" options export the whole file to the corresponding format, but without any further structuring. You can use these if you want to convert a whole book for using with Leanpub's in-browser editor, for example.

The "Book" options do whole-book export in the structure required by Leanpub:

  • "Book: Whole book" exports the whole book as one-file-per-chapter;
  • "Book: Subset" exports only the chapters that should be included in =Subset.txt= (if any), according to the rules listed below, to be able to quickly preview them using [[http://help.leanpub.com/en/articles/3025574-i-only-want-to-do-preview-of-a-specific-part-of-my-book-how-do-i-so-a-subset-preview][LeanPub's subset-preview feature]];
    • The subset export can be temporarily restricted to the current chapter (regardless of the =#+LEANPUB_BOOK_WRITE_SUBSET= setting, see below) by pressing =C-s= in the Org Mode Export screen to set "Export scope" to "Subtree".

The first time you do a Book export, the following directory and symlink structure will be created:

#+begin_example . ├── images -> manuscript/resources/images └── manuscript ├── images -> resources/images └── resources └── images #+end_example

In short, this is what the Book export operation does:

  • Creates a =manuscript= folder if needed, under which all other files are stored.
  • A =resources/images= directory is created inside =manuscript=, as required by the Leanpub Markua processor (this is not required by the LFM processor, but the same structure is used).
  • Symlinks to the =images= directory are created both from the top-level directory, and from the =manuscript= directory, to allow referencing the same image files both from the Org file and from the exported Markua files.
  • Exports one =.markua= or =.md= file for each top-level header (chapter) in your book.
  • Creates the =Book.txt= file with the filenames corresponding to the chapters of your book.
    • Depending on the exporter settings (see below), the =Subset.txt= and =Sample.txt= files may also be created.

The book files are created inside =manuscript= and populated as follows:

  • =Book.txt= with all chapters, except those tagged with =noexport=.
  • =Sample.txt= with all chapters tagged with =sample=. Note: this file is only created when exporting LFM. In Markua output, all headings tagged with =sample= are given the =sample: true= attribute as [[https://leanpub.com/markua/read#conditional-inclusion][documented in the Markua manual]].
  • =Subset.txt= with chapters depending on the value of the =#+LEANPUB_WRITE_SUBSET= file property (see [[#configuration][Configuration]] below):
    • Default or =none=: not created.
    • =tagged=: use all chapters tagged =subset=.
    • =all=: use the same chapters as =Book.txt=.
    • =sample=: use same chapters as =Sample.txt=.
    • =current=: export the current chapter (where the cursor is at the moment of the export) as the contents of =Subset.txt=. This can be set temporarily (for a single export) by pressing =C-s= in the Export screen to set "Export scope" to "Subtree".

The exported chapter files are named as follows:

  1. If the heading has an =EXPORT_FILE_NAME= property, it is used, unless the =#+LEANPUB_BOOK_RECOMPUTE_FILENAMES= file property is set.
  • Note: this filename should already specify the output directory and extension, e.g. =manuscript/chapter.markua=
  1. If the =#+LEANPUB_BOOK_ID_AS_FILENAME= is set and the heading has a =NAME=, =CUSTOM_ID= or =ID= property, it is used as the base filename, and used to construct the filename inside =manuscript=. The resulting final filename is stored in the =EXPORT_FILE_NAME= property.
  2. Otherwise, the filename is generated based on the heading title by lowercasing it and replacing all non-alphanumeric characters with hyphens. The resulting final filename is likewise stored in =EXPORT_FILE_NAME=.

The last-used filename is stored in the =EXPORT_FILE_NAME= property of the corresponding heading. By default, once this property is set it is not modified on future exports. If you set the =#+LEANPUB_BOOK_RECOMPUTE_FILENAMES= attribute in your file, the =EXPORT_FILE_NAME= property will be updated every time the book is exported. This can be useful to keep the filenames in sync when you change the heading titles in your document, but be aware that the file exported with the old name will not be removed automatically.

** Special heading tags

If a heading has the =frontmatter=, =mainmatter= or =backmatter= tags, the [[https://leanpub.com/markua/read#directives][corresponding directive]] (they work in both Markdown and Markup modes) is inserted in the output, before the headline. This way, you only need to tag the first chapter of the front, main, and backmatter, respectively.

If a level-1 heading has the =part= tag, it is exported as a part heading ("# Title #" in [[https://leanpub.com/markua/read#headings][Markua]], "-# Title" in [[https://leanpub.com/lfm/read#leanpub-auto-parts][LFM]]).

If a heading has the =sample= tag in a Markua export, the [[https://leanpub.com/markua/read#conditional-inclusion][conditional attribute]] ={sample: true}= is inserted before the heading in the output, to indicate that the section should be included in the book sample generated by Leanpub. If a heading has the =sample= tag in a Markdown export, the corresponding chapter is added to the =Sample.txt= file.

If a heading has the =nobook= tag, the [[https://leanpub.com/markua/read#conditional-inclusion][conditional attribute]] ={book: false}= is inserted before the heading in the output, to indicate that the section should not be included in the book. You can specify both the =nobook= and =sample= tags to flag a section which should only be included in the sample. The =nobook= tag has no effect in Markdown exports.

Note: =noexport= and =nobook= are similar but have different semantics. =noexport= is interpreted by Org when exporting your file, and it completely omits the corresponding headings from the output, whereas =nobook= includes the text, but flags it accordingly for Leanpub to ignore it when rendering the final book.

** Attributes

Both LFM and Leanpub support specifying attributes for different elements using /attribute lines/. Both =ox-leanpub-markua= and =ox-leanpub-markdown= support specifying attributes as follows:

  • An element's =#+NAME=, =ID= or =CUSTOM_ID=, if specified, are used for the =id= attribute.
  • An element's =#+CAPTION=, if specified, is used for the =caption= attribute in Markua and the =title= attribute in LFM (see [[#block-captions][Block Captions]] for details of how captions are produced in block elements).
  • Other attributes can be specified in an =#+ATTR_LEANPUB= line before the corresponding element. The syntax is the same as for Org header arguments. These are merged with the previous one if specified. Attributes specified in =#+ATTR_LEANPUB= override those specified through other mechanisms.

Example: #+begin_src org ,#+name: system-diagram ,#+caption: Architecture diagram ,#+attr_leanpub: :width 30% [[file:images/diagram.png]] #+end_src

Gets exported in Markua as: #+begin_src text {width: "30%", id: "system-diagram", caption: "Architecture diagram"} Architecture diagram #+end_src

And in LFM as: #+begin_src text {width="30%", id="system-diagram", title="Architecture diagram"} Architecture diagram #+end_src

** Block elements :PROPERTIES: :CUSTOM_ID: block-elements :END:

=ox-leanpub= supports all Leanpub [[https://leanpub.com/markua/read#leanpub-auto-block-elements][block elements]] in Markua export:

| Block type | Gets exported as | |-------------------------+-------------------------------| | =#+begin/end_aside= | ={aside}= | | =#+begin/end_blockquote= | ={blockquote}= | | =#+begin/end_blurb= | ={blurb}= | | =#+begin/end_center= | ={blurb, class: "center"}= | | =#+begin/end_discussion= | ={blurb, class: "discussion"}= | | =#+begin/end_error= | ={blurb, class: "error"}= | | =#+begin/end_exercise= | ={blurb, class: "exercise"}= | | =#+begin/end_information= | ={blurb, class: "information"}= | | =#+begin/end_note= | ={blurb, class: "information"}= | | =#+begin/end_question= | ={blurb, class: "question"}= | | =#+begin/end_quote= | ={blockquote}= | | =#+begin/end_tip= | ={blurb, class: "tip"}= | | =#+begin/end_warning= | ={blurb, class: "warning"}= |

You can specify a [[https://leanpub.com/markua/read#leanpub-auto-using-extension-attributes-on-blurbs-to-add-icon-support][custom icon]] for a block using the =:icon= attribute in an =#+ATTR_LEANPUB= line. For example:

#+begin_src org ,#+ATTR_LEANPUB: :icon github ,#+begin_tip Tip with a GitHub icon instead of the default. ,#+end_tip #+end_src

You can change the default icon for a block for the whole document, or you can even define your own block types, by using =#+MARKUA_BLOCK= lines. The syntax is:

#+begin_src org ,#+MARKUA_BLOCK: blockname [:class classname] [:icon iconname] #+end_src

Where =blockname= and at least one of =:class= or =:icon= needs to be specified:

  • =blockname= is the name of the block to define. Can be one of the existing block names (to redefine it) or a new one.
  • =classname= (optional) is the name of an existing supported Markua block class (as listed in the table above). It can be omitted to use a plain ={blurb}= block.
  • =iconname= (optional) is a [[https://leanpub.com/markua/read#leanpub-auto-using-extension-attributes-on-blurbs-to-add-icon-support][valid icon name]] to use for the block.

You can define multiple block types, each on their own =#+MARKUA_BLOCK= line. For example, you can change the default icon of =tip= blocks to be a lightbulb instead of the default key icon:

#+begin_src org ,#+MARKUA_BLOCK: tip :class tip :icon lightbulb

,#+begin_tip Tip with a lightbulb! ,#+end_tip #+end_src

You can also define completely new block types:

#+begin_src org ,#+MARKUA_BLOCK: leanpub :icon leanpub

,#+begin_leanpub Leanpub block! ,#+end_leanpub #+end_src

*** Block captions :PROPERTIES: :CUSTOM_ID: block-captions :END:

If a =#+CAPTION= is specified for a block, it is exported as a headline at the top of the block. By default, the level of the headline is one below the current level (e.g. if the block is under a level-2 headline, its caption will be produced as a level-3 headline). You can configure this for the whole document by setting the =#+MARKUA_BLOCK_CAPTION_LEVEL= option, or for individual blocks by specifying the =:caption-level= option in the =#+ATTR_LEANPUB= line. Valid values for this option are:

  • =same=: the caption will be produced as a same-level headline;
  • A number 1-9: the caption will be produced as a headline of the specified level;
  • =below= (or anything else): default behavior, caption will be produced at one level below the current one.

** Exporting books and courses

Leanpub Markua supports exporting both books and courses. The results are largely the same, currently with one exception:

  • Org blocks of type =exercise= (=#+begin_exercise= / =#+end_exercise=) are exported as [[https://leanpub.com/markua/read#leanpub-auto-syntactic-sugar-for-specific-blurb-classes-d-e-i-q-t-w-x]["X>" blurbs]] in books, and as [[https://leanpub.com/markua/read#leanpub-auto-quizzes-and-exercises][{exercise} blocks]] in courses.

You can tell =ox-leanpub-markua= how your buffer should be exported by setting the =#+MARKUA_EXPORT_TYPE= option. Its default value is ="book"=. If you are exporting a course, set it as follows:

#+begin_src org ,#+MARKUA_EXPORT_TYPE: course #+end_src

You can also set this parameter for an individual block by specifying the =:export-type= argument in =#+ATTR_LEANPUB=, as follows:

#+begin_src org ,#+ATTR_LEANPUB: :export-type course ,#+begin_exercise ... ,#+end_exercise #+end_src

** Code block captions

Normally, a caption for a code block is specified using the standard =#+CAPTION= attribute, like this:

#+begin_src org ,#+caption: My code block ,#+begin_src bash echo "Hi" ,#+end_src #+end_src

You can configure =ox-leanpub-markua= to automatically generate the caption using the =:tangle= or =:noweb-ref= attributes, if present, using the =#+MARKUA_TANGLE_CAPTION= and =#+MARKUA_NOWEB_REF_CAPTION= options. Either or both of them can be specified. The format of the generated captions can be configured, see [[#configuration][Configuration]] below for the details. Note: generating captions based on =:tangle= or =:noweb-ref= only works if the =org-export-use-babel= variable is set to =nil=. This is due to a limitation in =org-export= (the code block headers are not visible to the exporter if this variable is =t=, since they are processed before).

Even when these options are enabled, a manually specified =#+CAPTION= will always take precedence.

** Index entries

Leanpub supports producing indices [[https://community.leanpub.com/t/first-version-of-index-entry-support-is-live-only-for-markua-0-30-books/3859][for books using Markua 0.30]], so =ox-leanpub-markua= exports [[https://orgmode.org/manual/Generating-an-index.html][Org-mode index entries]] using the ={i:...}= syntax used by Markua. The value given to =#+INDEX= will be passed as-is into the ={i:...}= attributes (including any formatting markup, which needs to be provided in Markua format), with one exception: for =see= and =seealso= entries, the format should be =see=otherentry= or =seealso=otherentry=, and it will be converted to the correct syntax on export. The value given to =#+INDEX= must not be enclosed in quotes, but the value passed to =see= or =seealso= may be enclosed in quotes. Separators such as ~!~ and ~|~ must be escaped with a backslash.

| Org-mode source | Exported Markua | | =#+INDEX: "hello"= | error | | =#+INDEX: hello= | ={i: "hello"}= | | =#+INDEX: Zamboni, Diego= | ={i: "Zamboni, Diego"}= | | =#+INDEX: Yahoo!= | ={i: "Yahoo!"}= | | =#+INDEX: hello= | ={i: "hello"}= | | =#+INDEX: hello= | ={i: "hello"}= | | =#+INDEX: hello!Diego= | ={i: "hello!Diego"}= | | =#+INDEX: hello!Diego= | ={i: "hello!Diego"}= | | =#+INDEX: hello!Diego= | ={i: "hello!Diego"}= | | =#+INDEX: Diego¦see=hello= | ={i: "Diego¦see{i:'hello'}"}= |

** Calling from Emacs LISP

There are multiple endpoints which can be useful when calling from Emacs LISP, for example from hooks to automatically export the book under certain conditions. Some of the most useful are:

  • =org-leanpub-book-export-markdown= and =org-leanpub-book-export-markua=: both can be called without arguments, and export the whole book in the corresponding format.

** Configuration :PROPERTIES: :CUSTOM_ID: configuration :END:

The modules provide reasonable defaults, but you can configure some parameters by specifying keywords at the top of your Org file. The following are recognized:

| Keyword | Default value | Description | | =#+LEANPUB_BOOK_ID_AS_FILENAME= | =nil= | If set (regardless of its value), use a heading's =NAME=, =CUSTOM_ID= or =ID= properties (if it has them) to construct the output filename, instead of always using the title. | | =#+LEANPUB_BOOK_OUTPUT_DIR= | "manuscript" | Subdirectory where the exported files will be created. | | =#+LEANPUB_BOOK_RECOMPUTE_FILENAMES= | =nil= | If set (regardless of its value), update =EXPORT_FILE_NAME= for all headings on each export, based on the title. Note that if a chapter title has changed since the last export, it will be exported to a new filename, but the old file will not be deleted, you need to do this manually. | | =#+LEANPUB_BOOK_WRITE_SUBSET= | "none" | What to write to the =Subset.txt= file. Possible values: =none=, =tagged=, =all=, =sample=, =current=. | | =#+MARKUA_BLOCK= | =nil= | Redefine or define a new block type. See [[#block-elements][Block Elements]] for the syntax details. | | =#+MARKUA_EXPORT_TYPE= | "book" | (only for Markua export) Determines the type of export being done. Valid values are "book" and "course". | | =#+MARKUA_NOWEB_REF_CAPTION= | =nil= | (only for Markua export) If set (regardless of its value), use the value of the =:noweb-ref= header argument for the caption of source code blocks. For this to work, the =org-export-use-babel= variable must be set to =nil=. | | =#+MARKUA_NOWEB_REF_CAPTION_FMT= | "«%s»≡" | Format to use for captions generated from the =:noweb-ref= attribute. The string =%s= is replaced by the =:noweb-ref= value. The default value can be used (depending on the formatting of your book) to emulate the default output format produced by [[https://en.wikipedia.org/wiki/Noweb][noweb]]. | | =#+MARKUA_TANGLE_CAPTION= | nil | (only for Markua export) If set (regardless of its value), use the value of the =:tangle= header argument for the caption of source code blocks. For this to work, the =org-export-use-babel= variable must be set to =nil=. | | =#+MARKUA_TANGLE_CAPTION_FMT= | "[%s]" | Format to use for captions generated from the =:tangle= attribute. The string =%s= is replaced by the =:tangle= value. | | =#+MARKUA_TANGLE_NOWEB_CAPTION_FMT= | "[%1$s] «%2$s»≡" | Format to use when both =:noweb-ref= and =:tangle= are used to generate the caption. The string =%1$s= is replaced by the value of =:tangle=, and =%2$s= by the value of =:noweb-ref=. |

  • FAQ

** Headlines below a certain level are not exported correctly

This is controlled by the Org-mode [[https://orgmode.org/manual/Export-Settings.html#index-org_002dexport_002dheadline_002dlevels]["H" export option]]. Its default value is 3, which causes all lower-level headlines to be exported as lists instead. To fix this, you have to increase the value of this option.

This can be done in each file with a line like this: #+begin_src org ,#+options: h:9 #+end_src You can also change its default by setting the =org-export-headline-levels= variable.

  • Credits
  • The original version of =ox-leanpub-markdown.el= was written by [[http://juanreyero.com/open/ox-leanpub/index.html][Juan Reyero]] as =ox-leanpub.el= and is still available at https://github.com/juanre/ox-leanpub. I made many changes to fix some bugs and process additional markup elements, and =ox-leanpub-markua.el= is also derived from it. This repository started as a fork of the original, but given the amount of changes I have recreated it as a standalone repo, to avoid confusion.
  • =ox-leanpub-book.el= was based originally on [[https://medium.com/@lakshminp/publishing-a-book-using-org-mode-9e817a56d144][code by Lakshmi Narasimhan]], but also heavily modified.
  • =ox-leanpub-markua.el= delegates the work of exporting tables to [[https://github.com/larstvei/ox-gfm][ox-gfm]].
  • Check out my books!

If you find this package useful, consider supporting me by purchasing my book [[https://leanpub.com/emacs-org-leanpub][Publishing with Emacs, Org Mode and Leanpub]], or any of my other [[https://leanpub.com/u/zzamboni][books at Leanpub]]!

  • Disclaimer

I am not associated with Leanpub other than being a happy author. Leanpub is not responsible for this code.