On Process: Generating Documentation with Markdown, PHP, and HTMLdoc

Working on a fantastic project lately; a PHP platform developed with Smarty template engine that will power three (and more) websites. Each website has a shared library used to bootstrap the app and the site-specific logic is contained in the extensible smarty templates.

Each site has a large body of "documentation" files. The client's previous situation was such that each person responsible for the documentation was using their own (and thus, different) solution. So I recommended settling on a standardized documentation format.

I thought Markdown was a great fit and found a fantastic PHP port of John Gruber's tool. This particular version—Markdown Extra—extends the Markdown port to footnotes, tables, and other nice things. I created a very simple smarty plugin (the initial version below) for documentation writers to be able to load in a markdown file into a Smarty Template:

// smarty/plugins/block.markdown.php

function smarty_block_markdown($params, $content, &$template, &$repeat)
{
    if (!$repeat)
    {
        if (isset($params['source']))
        {
            $source = $params['source'];
            $contents = file_get_contents($source);
            $rendered = Markdown($contents);
        }
        else if (!empty($content))
        {
            $rendered = Markdown($content);
        }
        else
        {
            return "";
        }

        return $rendered;
    }
}

This is quite a simplified version of the final plugin. The final includes support for replaceable tokens among other things, but basically, it's used like so:

{* render the contents of a file called markdown/file/name.md as HTML *}
{markdown source="markdown/file/name"}{/markdown}

{* render the markdown source in the block directly *}
{markdown}
# header {#header-id}

This is some markdown.
{/markdown}

So why separate the markdown instead of just injecting it directly into the smarty views or just writing it in HTML? Well, part of the requirements of the project was that any documentation written for the website should be able to be easily converted to a PDF. So how do we do that? After some research on what tools would be easy for others to use as well, it ended up being a simple process; in short:

  1. Load markdown files, parse with Markdown Extra, converting to HTML.

  2. Take the concatenated HTML and pipe the output into HTMLDOC.

HTMLDOC is an open-source command-line tool that converts HTML documents into PDFs. I decided to use this tool to automate the build of the PDF documentation. So for web viewing, Markdown is converted to HTML and cached on subsequent requests as HTML via Smarty, and for download, the PDF docs are created using the same Markdown; a nice separation of concerns, I thought.

Here's a section of the build-docs PHP script that is run during the ANT build for each site. Prior to this section, the file looks in the documentation and loads specified markdown files and parses them with Markdown extra and loads the concatenated results into a temporary HTML file. This file is then piped into HTMLDOC:

$command = "htmldoc ";
$command .= "--book "; // generate in book format with TOC
$command .= "--links "; // link up hyperlinks
$command .= "--title "; // include a title page
$command .= "--toctitle " . escapeshellarg($toc_title) . " "; // TOC title
$command .= "--linkstyle "underline" "; // what to do with hyperlinks
if ($title_image)
{
    $command .= "--titleimage " . escapeshellarg($title_image) . " ";
}
$command .= "--footer h./ ";
$command .= "--header .t. ";
//$command .= "--bodyfont helvetica ";
$command .= "-t pdf14 ";
$command .= "-f {$output_file} {$temp_file}";

exec($command, $result, $return);

We execute the htmldoc command via PHP and the documentation PDF is generated. The variables allow us to create a bootstrap file for each site's documentation to configure the output a little bit.

The final requirement for the documentation was the addition of tables of contents to any markdown file and documentation. The documentation writers wanted to be able to have a list of the <h1-6> tags linked up to the corresponding section on the page. I accomplished this by adding another parsing block to Markdown Extra. In short, this block uses Markdown Extra's existing list of parsed headers and wraps them in a <ul> list with anchor tag links. The regular expression for matching the "toc" is as follows:

"/{toc(?:|?([1-6])(?::([1-6]))?)?}/

Ain't regex lovely? This matches some of the following, replacing it with the rendered unordered list where it appears in the markdown document.

  • {toc} renders the entire table of contents.
  • {toc|3} would display <h1> through <h3> tags in the table of contents.
  • {toc|2:5} would display <h2> through <h5> tags in the table of contents.

Anyway, that's some of the process stuff I've been plugging away on lately. I've sort of become obsessed with writing everything in Markdown now. If I need to send to another developer or client, I'll do something like the following:

alias markdown='/path/to/Markdown_1.0.1/Markdown.pl'
markdown my-doc-file.md | htmldoc --format pdf14 - > my-doc-file.pdf

(Obviously there'll be a bunch of params for htmldoc as seen in the PHP example above). Quite simply converting Markdown and piping to htmldoc. Of course, I don't get to use my fancy extended Markdown with tables of contents, but for day to day writing, it's ideal. Bam, lovely looking PDF.

Depending on the completion of these projects, I may release the updated Markdown Extra with Table of Contents and the related Markdown Smarty plugins. Check back later with me.

July 1st, 2011 | Permalink

Simple Subversion Merges

Subversion merging can be a complex task, one that's decidedly terrifying at times. I'm typically always worried when I perform an svn merge, and so I always consult the red bean manual when I'm about to do one. In the last few weeks I've written a handful of emails explaining how to do it quickly and painlessly, so I thought it beneficial to detail how I do basic merging as a reminder to myself and hopefully as a helpful tutorial for someone looking to make it as painless as possible via a command line interface.

Let's begin with a scenario; one that should be quite familiar (This may be a "no shit" scenario for you, but I want to give us some context.): you're going to publish your website for the first time. You want to publish the site live but your client wants you to develop a new feature. You want to be able to update the production build with bug fixes while you work on the new feature. So, you create a branch in your subversion repository:

svn cp trunk branches/release-1.0_launch

The release-1.0_launch branch will be uploaded to your host (however you might be doing that; FTP, svn check-out, rsync, et al) and remain live while you develop your new features in trunk. That's all find and dandy, but say you fix a number of bugs or quickly have to add a feature to the release-1.0_launch branch. Instead of "hackily" making the same bug fixes in trunk and having to copy and paste the new features, you'll do a merge from the branch to the trunk.

The merging process is basically two steps:

  1. Make sure everything is up to date locally (svn update the repo).

  2. Use svn merge to merge the branch into your trunk.

Where it gets somewhat complex is you need to let subversion know how much you want to merge, i.e. you can merge a number of commits from your branch to your trunk. The syntax is straightforward:

svn merge -r{FROM_REVISION}:{TO_REVISION} branches/release-1.0_launch trunk

Let's say you want to merge all the commits to the branch into the trunk. You can use the --stop-on-copy flag to figure out the revision number was when you copied from trunk:

svn log branches/release-1.0_launch --stop-on-copy

The earliest log number (FROM_REVISION) is where you want to copy from through the most recent commit number (TO_REVISION), which you can use svn update to get. Before you even run merge, definitely make sure you do a svn merge with the --dry-run flag first to make sure nothing alarming will result.

Taking the revision number from the --stop-on-copy and the current HEAD revision from svn update, you'll merge from branch to trunk. For example assuming our FROM_REVISION was "123" and our TO_REVISION was "126"

svn merge -r123:126 --dry-run branches/release-1.0_launch trunk

This merges revisions 123 through the most recent (HEAD) from the branch to the trunk. The --dry-run flag will tell you what should happen. You'll need to remove that to actually perform a merge.

Once you think the files changed in the potential merge look good, remove the --dry-run flag and run it. Running svn status will tell you what's different in the trunk after the merge. You may have to resolve some conflicts with the files in your trunk. One really useful task I use all the time is to get a diff of the changes and pipe it to TextMate:

svn diff | mate

This will open the diff file with TextMate (assuming you've set up the mate command from Textmate > Help > Terminal Usage) with some lovely syntax highlighting; typically makes it much easier to take in the changes with color coding!

Once you've merged and resolved your conflicts, obviously you'll want to run any unit tests and check the project to make sure it's working as expected. After that, commit the trunk with a thoughtful message like "Merged -r123:126 from release-1.0_launch to trunk" so you can look back and see what revisions were specifically merged.

June 30th, 2011 | Permalink

Work at Typeoneerror

Typeoneerror Studios featuring Mochi

I'm looking for another talented front-end developer for a 1–2 month contract position at the recently opened Typeoneerror Studios. The right candidate will work with me (and the T1E office Shiba Mochi!) on a number of different projects out of the studio in Ballard in Seattle. I'm looking for a commitment of 20–40 hours a week. I'd also be open to the right person working remotely.

This work will primarily involve developing HTML templates, CSS and Javascript, and also some PHP work. We're also doing Objective-C, Rails, some Django, Design, Wireframing, Flash, and numerous other fun design and development tasks, so if you have experience with any of these, you might help with that as well.

I'm hoping to find a great contractor that will not only learn from this experience and be paid well, but also I want to learn from you. I'm a very positive, sponge-like developer; I love learning new technologies and absorbing knowledge from others. So let's learn together.

Please email me at hello@typeoneerror.com if you are interested or know of someone who might be looking.

April 14th, 2011 | Permalink

Hacynth in Dub Vol 4

It has been a long time since I've done a dubstep mix so here's a particularly wobbly one for ya.

  1. Darkness - Excision, Subvert
  2. Alpha Centauri - Noisia
  3. Bad Tingz - We Bang, Getter
  4. Kick the Bass - Urban Assault
  5. South Paw [Revamped] - HD4000
  6. Waterdrops - Requake
  7. The End - Hyper
  8. The Blank [16bit Remix] - Skism
  9. Shawty Violating (Wup That Hoe) - Yo Gotti, La Chat
  10. Fools - Eptic
  11. Wheres Yo Head At [HULK Remix] - Bassment Jaxx
  12. Gizmo - Datsik
  13. This City - Kito, Reija Lee
  14. Crunked Up - Benga
  15. Enforcers - 2562


As always, enjoy the ride and make sure to follow us on Twitter if you want more music.

→ Download Hacynth in Dub Vol 4 from Dropbox

April 12th, 2011 | Permalink

Liquid Hacynth Vol 25

Loved that Ross D did a remix of the Bed Intruder song so I had to include it on this latest mix. A lot of good (and feelgood!) tunes on this mix including the awesome team-ups of Robert Owens & Brookes Brothers and 4hero's fantastic collaboration with Nu:Tone and Natalie Williams (tune of the year so far for me).

  1. Beautiful – Robert Owens, Brookes Brothers 
  2. Bed Intruder [Remix] – Ross D 
  3. Chess Funk – Lynx 
  4. Veteran – Syncopix 
  5. Invisible – Nu:Tone, 4hero, Natalie Williams 
  6. Come Show Me – Intelligent Manners 
  7. World Keeps Turning – Qumulus 
  8. Fessing Up About Nothing – Random Movement 
  9. Stuck – Lenzman 
  10. ASC – Reform 
  11. Bionics [Electrosoul System Remix] – Future Engineers 
  12. Afraid of Jazz – Madmen & Poets 
  13. Brady Bunch – Linky 
  14. Shanghai Skyline – Logistics
  15. Komatic – Untold Stories 
  16. Is Anybody Out There – Bcee, S.P.Y.


As always, enjoy the ride and make sure to follow us on Twitter if you want more music.

→ Download Liquid Hacynth Vol 25 from Dropbox

April 9th, 2011 | Permalink
prev 1 2 3 4 5 6 7 8 9 10 next