by Adam Brett

Sculpin & CommonMark

This article was published on Tuesday, April 07, 2015 which was more than 18 months ago , this means the content may be out of date or no longer relevant. You should verify that the technical information in this article is still current before relying upon it for your own purposes.

It's no secret that I'm a huge proponent of static sites and static site generators, especially the PHP static site generator Sculpin1. Recently, Beau Simensen (the person behind sculpin) released his roadmap for Sculpin in 20152, and the future really does look bright for the project, if you haven't checked it out, you really should.

One thing I'd really like to see is CommonMark3 support. Sculpin currently uses php-markdown4, which is a great little library, but the industry is moving more towards the CommonMark standard and I'd like to follow suit.

If you're not familiar with CommonMark, it's an attempt at creating a single Markdown spec that rationalises all of the differences with the various markdown implementations in use around The Internet today (e.g. Github Flavoured Markdown vs Markdown.pl vs Markdown Extra). It's got some heavy hitting backers and is gaining some serious traction, if you use markdown for anything, you really need to check it out.

Fortunately for us, The League of Extraordinary Packages has been working on a pretty awesome CommonMark package5, so using CommonMark in PHP is super easy. All you need to do is require the composer package and you're good to go.

When it comes to Sculpin though, the markdown functionality is built-in, so we're going to need to find a way to replace it with the package from The League. Luckily, Sculpin has a really simple way for us to do this, and that's what we're going to learn today.

We'll start by writing a custom parser that uses the ParserInterface provided by Sculpin, then using an undocumented config setting in sculpin_kernel.yml to replace the name of the class in Sculpin's Dependency Injection Container.

We'll start by using the sculpin-blog-skeleton as a base for our project, so move into your projects directory and clone a copy of the repo.

cd ~/Projects
git clone [email protected]:sculpin/sculpin-blog-skeleton.git sculpin-commonmark

Next, we need to remove the old git details and start new, so we have a fresh project:

cd sculpin-commonmark
rm -rf .git
git init
git commit -am 'Import sculpin blog skeleton'

Now, we have a fresh project we can get started making out changes.

The first thing to remember is that sculpin contains an embedded version of composer, and that it's controlled by sculpin.json instead of composer.json. We can use the embedded composer to manage our dependencies without having to use composer directly. This is the recommended way of managing dependencies with Sculpin.

We need to add league/commonmark to our requirements, so open up sculpin.json in your favourite editor and update the require section to match the below:

"require": {
    "league/commonmark": "~0.7",
    "components/bootstrap": "~2.3.1",
    "components/jquery": "~1.9.1",
    "components/highlightjs": "~7.3.0"
},

This isn't the best way to add a composer requirement, we should use composer require league/commonmark from the terminal, which will sort the version requirement for you, but sculpin doesn't expose the require method from composer, so we need to manually edit the file.

The next thing we need to do is tell Sculpin where to find our custom classes. Again, this is exactly the same as doing it for composer. Sculpin already has two directories, one for config (app/config), and one for the website source (source). We should keep our code in app, as it's not currently being used for anything, and we don't want our PHP code to be part of the generated source, so add the following to sculpin.json, the same as you would for composer:

"autoload": {
    "psr-4": {
        "App\\": "app"
    }
}

We can now run sculpin update, and Sculpin will download all of the requirements and also update the autoloader to look for the App namespace in the ./app directory. To double check, you can take a look at the autoloader in .sculpin/composer/autoload_psr4.php.

Now we have the basic requirements setup, we need to create an Adapter for the League CommonMark package to make it work with the Sculpin ParserInterface.

This is far easier than it sounds. Create a new file in app/CommonMark.php and add the following:

<?php

namespace App;

use Sculpin\Core\Converter\ParserInterface;
use League\CommonMark\CommonMarkConverter;

class CommonMark extends CommonMarkConverter implements ParserInterface
{
    public function transform($text)
    {
        return $this->convertToHtml($text);
    }
}

All we're doing here is creating a new class that extends the League\CommonMarkConverter class AND implements the Sculpin\ParserIterface. It's perfectly acceptable to mix different classes and interfaces from different packages like this.

The ParserInterface class only has one required method, transform, which takes a $text parameter which is the raw markdown, and should return the formatted HTML. This happens to be the same method signature as the CommonMark convertToHtml method, so we can just pass off the heavy lifting straight to that method, without having to do anything else.

Finally, we need to tell Sculpin to use our new Parser class. Update app/config/sculpin_kernel.yml and add the following:

sculpin_markdown:
    parser_class: App\CommonMark

This is a config key used by Sculpin's Dependency Injector, and it tells Sculpin what class to load to do the conversion, it is just the fully-qualified (namespace + classname) classname . As long as it implements the ParserInterface, we could actually put any class name in here and it should work.

Now drop back to the terminal and run sculpin generate --watch --server and then visit http://localhost:8000. You should see the default sculpin-blog-skeleton page loaded, and the markdown will have been generated with CommonMark!

For exclusive content, including screen-casts, videos, and early beta access to my projects, subscribe to my email list below.


I love discussion, but not blog comments. If you want to comment on what's written above, head over to twitter.