Leanpub has a robust web interface, but it seemed appropriate to create "Writing PHP with Emacs" using Emacs itself. This is how I do it, from managing ideas to publishing the content itself.
Managing ideas
Before I started writing the book I had a single text file that I collected ideas in. This worked okay in the beginning, but once things started moving along it became unwieldy to manage.
I switched to keeping my notes in zetteldeft which made life much easier. There
is a single note titled "Writing PHP with Emacs" which I use as an entry
point. All related notes are linked to this one, and I also tag them with
#php-with-emacs
so I can quickly search for them.
Not all of these notes are directly related to the book, but they might contain information that I want to include in the future.
Planning
The entire book plan is stored in a single TODO.org
file in the project
directory. I use it to keep a list of the overall contents, as well as
planning out individual chapters.
The plan looks a little like this (with content removed for brevity):
#+TITLE: PHP with Emacs - TODO List #+SEQ_TODO: TODO STARTED TO-EDIT | COMPLETE POSTPONED CANCELLED * TODO Milestones [4/5] ** COMPLETE Version 0.2 CLOSED: [2020-06-09 Tue 19:55] DEADLINE: <2020-05-31 Sun> *** COMPLETE php-mode: Customization - Using TAGS files [3/3] CLOSED: [2020-05-30 Sat 10:26] SCHEDULED: <2020-05-28 Thu> :PROPERTIES: :EFFORT: 1:00 :END: Prefer to use php-lsp, but this is an option too. - [X] What TAGS files can be used for - [X] Generating - [X] Navigating using a TAGS file Shortcuts and everything go here. * TODO Backlog [1/22] ** TODO Configuring a project for WordPress ** TODO Configuring a project for Drupal
I use org-columns
for quickly viewing the list of milestones. From this view I
can set estimates and view time worked on each milestone and sub-task.

The Backlog
heading keeps a list of all future content that I want to work
on. When it's time to move an item to the plan, I'll refile it to a milestone
using a custom function called pn/org-refile-in-file
:
(defvar pn/org-last-refile-marker nil "Marker for last refile") (defun pn/org-refile-in-file (&optional prefix) "Refile to a target within the current file." (interactive) (let ((helm-org-headings-actions '(("Refile to this heading" . helm-org--refile-heading-to)))) (save-excursion (helm-org-in-buffer-headings) (org-end-of-subtree t) (setq pn/org-last-refile-marker (point-marker)))))
I use this instead of org-refile
as I have a lot of org files that I don't
want to be used as targets. Once a headline has been moved to a milestone I'll
add a time estimate and schedule it. I use org-projectile so any scheduled tasks
in the book TODO show up in my agenda.
Organizing files
The project directory contains the manuscript
directory, which is used by
Leanpub to generate the book. It also contains a lightweight Emacs user
directory which I use when generating screenshots.
I use binder to keep a list of manuscript files. binder
also supports adding
file-specific notes, although I prefer to keep things in my TODO
file.

For most navigation I'll use projectile
, but binder
is helpful when trying
to get an overall look at the book or when I'm jumping between chapters.
Writing
Leanpub has its own offshoot of Markdown called Markua
, so everything is
written and edited using markdown-mode+
. I would have preferred to write in
org-mode
- and there is an ox-leanpub
exporter - but I didn't want to add an
extra step to the write -> publish process. Maybe next time.
One of the things that caught me out at the start was line breaks. Markdown will
normally ignore a single line break, so hard-wrapped lines will still be treated
as a single line. Markua
treats them as hard line breaks, so it altered the
look of the book and made pages too narrow. I switched to visual-line-mode
which wraps lines visually but doesn't alter the text.
I use a .dir-locals.el
file to automatically enable specific modes and to
increase the font size.
((markdown-mode . ((eval . (progn (turn-off-auto-fill) (text-scale-set 1) (turn-on-olivetti-mode))) (fill-column . 80) (visual-fill-column-width . 80) (mode . flyspell) (mode . binder) (mode . whitespace-cleanup) (mode . visual-line) (mode . visual-fill-column))))
The modes I use are:
flyspell
– Enables real time spell-checking in the buffer.binder
– Enables binder navigation for moving forwards and backwards between chapters.whitespace-cleanup
– Strips trailing whitespace when saving a buffer.visual-line
– Enables soft word-wrapping.visual-fill-column
– Adds word-wrapping at a specific column, instead of the window's edge.
The code in the eval
block turns off hard-wrapping, increases the font size,
and enables olivetti-mode
. olivetti centers the text in the current window
which I prefer when writing words.
For taking screenshots, I have a separate Emacs configuration that contains just PHP and web-specific packages. It loads only the packages I want, and sizes the window/fonts to a width that fits on the page. Startup looks like this:
emacs --no-init-file --load "emacs-env/init.el"
The --no-init-file=
option tells Emacs to ignore my usual init.el
file, and
then I manually load the screenshot initializer.
Publishing changes
The "standard" Leanpub plan allows the use of git for managing book files. I use
git every day, so the workflow is something I'm very accustomed to. The master
branch contains content that will be published, and WIP content is stored in a
named branch (usually something like add-XYZ-chapter
).
Once content is ready I'll push it to the remote repository and then manually publish it from within Leanpub. There is an API, but that's reserved for the "PRO" plan.
I have two Beeminder goals set up for the book:
- write-php-book – This goal is updated any time I make a commit to the book repository. This makes sure I'm writing often enough to get things done.
- publish-php-book – Leanpub can notify a webhook when a new version is published, so I use this to add a data point to Beeminder. This makes sure I'm publishing changes I write instead of waiting until things are "perfect".
I originally started with a word count goal. This worked well when I was trying to write all of the initial content, but once I started editing it made more sense to change to a goal that didn't focus on raw words.
2 Comments
(disclaimer: I'm the author and maintainer of the
ox-leanpub
package)Nice post! I really like the way you do the idea management and planning - this is something that I have not invested a lot of time in, so I may borrow some of your techniques :)
I would say the extra step of using
ox-leanpub
to export the book is trivial (it can even be automated with an after-save hook), and then you can avoid having to write Markua directly and having to worry about the line breaks and other aspects of it. I have a book (published on Leanpub) describing my workflow: https://leanpub.com/emacs-org-leanpub, and the book source itself is available as an example at https://github.com/zzamboni/emacs-org-leanpub.If you do give it a try, I would love to get your feedback!
Thank you for the recommendations! I'm reading through your book and I really like how it looks and how it's laid out. If I do another book I'll definitely go down the
ox-leanpub
route.