Daniel Keast

Makefile for Markdown

programming

I have a little git repository for keeping recipes. They’re all Markdown files, and I use pandoc to convert them into pdfs. I’ve created a Makefile for doing the conversion:

mds=$(wildcard *.md)
pdfs=$(mds:%.md=%.pdf)

.PHONY: all watch clean

all: $(pdfs)

%.pdf: %.md
        pandoc -o $@ < $<

watch:
        while inotifywait -e MOVE_SELF $${FILE}.md; do\
            make $${FILE}.pdf && xdg-open $${FILE}.pdf;\
        done

clean:
        rm -f *.pdf

The first line creates a variable named mds that contains a space separated list of all the Markdown files in the directory. This means that it doesn’t support filenames with spaces, but then, I’d prefer all the files to not have spaces anyway:

mds=$(wildcard *.md)

The second line performs string replacement on the mds variable, creating a list of pdf filenames for all of the Markdown files that currently exist:

pdfs=$(mds:%.md=%.pdf)

After that I define some phony tagets. Usually make targets are files that you want created, and make checks the file modification times to see if they need recreating. Phony targets are ones that don’t actually create any files. This means that they’ll run every time you request them, regardless of whether a file exists with the same name as the target.

.PHONY: all watch clean

Next is the first real target, which means that it is the one that runs if you just type ‘make’. It is one of the phony targets I defined above, and it is simply defined as a list of prerequisites, being the list of pdfs I created. Currently Make doesn’t know how to actually make any pdfs though.

all: $(pdfs)

I define how to create pdfs next, using wildcards for both the target and prerequisites. The ‘$@’ variable resolves to the name of the currently building target (@ looks like a literal target), and ‘$<’ resolves to the name of first prerequisite (it’s pointing left).

Recipes must be indented with tabs, not spaces.

%.pdf: %.md
        pandoc -o $@ < $<

Then I have a watch target. The recipe for this one is a bash while loop running the inotifywait command. It waits for the ‘MOVE_SELF’ event, which is what vim fires when you write to a file (vim actually saves to a temporary file, and then renames it to the file being edited to protect against corruption during saving).

This recipe runs over multiple lines, and escapes the newlines with backslashes. If you don’t do this then each line of the recipe runs inside of a separate shell, which won’t work in this case.

The xdg-open command interrogates my desktop environment to check which command is set as the default for the file type and then executes it. This triggers my pdf reader to grab focus and show me the updated pdf file, and the while loop to trigger the inotifywait command again, waiting for more changes.

The $${FILE} is a variable that is provided through the command line. So to run the watch command you type something like ‘make watch FILE=markdown-file’.

watch:
        while inotifywait -e MOVE_SELF $${FILE}.md; do\
            make $${FILE}.pdf && xdg-open $${FILE}.pdf;\
        done

Last is a simple clean target that deletes all the pdf files. The -f flag is there to stop rm complaining if there were no pdfs to delete.

clean:
        rm -f *.pdf