Experiments with Publishing My Markdown Notes

Kyle Bowman

Summary

Find | Pandoc

Single doc:

pandoc --from=markdown+wikilinks_title_before_pipe --to=html5 -o build/note2 src/note2.md

Note: Pandoc complains if the build directory doesn’t already exist.

I think this should work. It grabs all the files in the src directory. For each file it finds, run pandoc ... and output to build/note1 or build/note2.

find . -path './src/*' -exec pandoc --from=markdown+wikilinks_title_before_pipe --to=html5 -o build/$(basename -s .md {}) {} \

Output

pandoc: build/./src/note2.md: withFile: does not exist (No such file or directory)
pandoc: build/./src/note1.md: withFile: does not exist (No such file or directory)

To confirm that the basename shenanigans is doing what I want:

find . -path './src/*' -exec basename -s .md {} \;
# Output: note2\nnote1

Furthermore, without the output specified, it concatenates the input files and writes to stdout:

find . -path './src/*' -exec pandoc --from=markdown+wikilinks_title_before_pipe --to=html5 {} \;

This seems to identify the culprit! Something about passing the found files into a subshell.

find . -path './src/*' -exec echo build/$(basename -s .md {}) \;
# Output build/./src/note2.md...

This does it!

find . -path './src/*' -exec sh -c 'pandoc --from=markdown+wikilinks_title_before_pipe --to=html5 -o build/$(basename -s .md $1) $1' _ {} \;

This is a pattern that I didn’t expect for shells, but it’s used elsewhere. It’s like defining a lambda function and applying it in one step. Only we are using a subshell to define the lambda function. Here’s the source from Stack Overflow.

  1. In a subshell, define a function that you want to run.
  2. When you close the subshell, the rest of the exec function applies the function.

There is no passing the {} into the subshell. The {} exists in the original -exec shell. Instead, you’re doing some kooky function definition.

Note: We are using $1 to align with the convention that $0 is the name and $1 is the first argument.

From the man page of bash (emphasis mine):

-c If the -c option is present, then commands are read from the first non-option argument command_string. If there are arguments after the command_string, the first argument is assigned to $0 and any remaining arguments are assigned to the positional parameters. The assignment to $0 sets the name of the shell, which is used in warning and error messages.

Regarding Map in Bash

Map would be nice because then you don’t have to loop inside definitions But looping doesn’t play nice with spaces. I was pleasantly suprised it worked. Maybe there’s a way around the space issue, but today isn’t the day for that.

map() {
    for item in $2; do
        # shellcheck disable=SC2005
        echo "$("$1" "$item")"
    done
}

BUG: Map breaks on spaces

Context map "make_li" "$1".

When $1 contains a space, it breaks there. e.g. when it contains <a href=..., the make_li function creates <li><a</li>\n<li>href=...</li> and so on. To be clear, this is not the fault of make_li. This is due to $1 begin a space-delimited list and map breaking on spaces. Also, to be fair, that’s just the way shells work by default. There’s probably a clever way to manipulate lists as strings, but I don’t know what the idiomatic shell way of doing it is.

Cut Fiasco

cut ... $1 reads the file itself. echo $1 | cut ... reads the string in $1.

Conclusion

After tidying it all up, this command builds my entire stack of HTML and the links work!

find ~/notes/rocketbowman/tiddlers/ -path '*.md' -exec sh -c 'pandoc --from=markdown+wikilinks_title_before_pipe --to=html5 -o ~/tmp/build/$(basename -s .md $1) $1' _ {} \;

A better conclusion

Turns out that a Makefile is the way to go. Create a rule for a 1-1 conversion. Then add a make all command.