Watch for changes in Sculpin Source post

Posted on 2018-03-25 by jwage


I recently rebuilt jwage.com using Sculpin and GitHub Pages. It was previously hosted on Tumblr and over time I grew frustrated with the lack of control. I wanted a mobile friendly design and I wasn't looking forward to working with HTML & CSS in the Tumblr browser user interface so I decided to give Sculpin a try. I was immediately happy with the choice because it is built using familiar technologies like Symfony and Twig.

Here are a few handy utilities I hacked together to help with my development.

build.sh

For easily building the site in the way that GitHub pages expects:

./build.sh dev

Or to build it for prod:

./build.sh prod

Here is the source:

#!/bin/bash

vendor/bin/sculpin generate --env=$1
if [ $? -ne 0 ]; then echo "Could not generate the site"; exit 1; fi

mv docs/CNAME output_$1/CNAME
rm -rf docs
mv output_$1 docs

publish.sh

To make it easy to publish and deploy a new version of the site:

#!/bin/bash

./build.sh prod

git add --all .
git commit -m "New version of jwage.com"
git push origin master

# Put the dev version back after deploying prod
./build.sh dev

watch.php

For watching changes to your source and automatically triggering build.sh:

<?php

$buildScriptPath = __DIR__.'/build.sh dev';

$startPaths = [
    __DIR__.'/app/config/*',
    __DIR__.'/source/*',
];

$lastTime = time();

while (true) {
    $files = recursiveGlob($startPaths);

    foreach ($files as $file) {
        $time = filemtime($file);

        if ($time > $lastTime) {
            $lastTime = time();

            echo sprintf("%s was changed. Building...\n", $file);

            echo shell_exec($buildScriptPath)."\n";

            file_put_contents(__DIR__.'/docs/changed', time());
        }
    }
}

function recursiveGlob(array $paths)
{
    $files = [];

    foreach ($paths as $path) {
        $files = array_merge($files, glob($path));

        foreach ($files as $file) {
            if (is_dir($file)) {
                $dirPath = $file.'/*';

                $dirFiles = recursiveGlob([$dirPath]);

                $files = array_merge($files, $dirFiles);
            }
        }
    }

    return $files;
}

Run this script in the root of your project:

php watch.php

Then save a file in your project and it should rebuild automatically:

/data/jwage.com/source/_posts/2018-03-25-sculpin-watch-rebuild.md was changed. Building...
Detected new or updated files
Generating: 100% (249 sources / 0.01 seconds)
Converting: 100% (272 sources / 0.14 seconds)
Formatting: 100% (272 sources / 0.03 seconds)
Processing completed in 0.24 seconds

Browser Refresh

Everytime watch.php detects a change, it writes an updated timestamp to a file named changed with the current timestamp. Now you can poll this file for changes in your site to automatically refresh the browser. Put this in your layout.

{% if site.env == 'dev' %}
    <script type="text/javascript">
        var changedUrl = '{{ site.url }}/changed';
    </script>
    <script src="{{ site.url }}/js/watch.js"></script>
{% endif %}

Then in source/js/watch.js place the following code:

function setCookie(cname, cvalue, exdays) {
    var d = new Date();
    d.setTime(d.getTime() + (exdays*24*60*60*1000));
    var expires = "expires="+ d.toUTCString();
    document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}

function getCookie(cname) {
    var name = cname + "=";
    var decodedCookie = decodeURIComponent(document.cookie);
    var ca = decodedCookie.split(';');
    for(var i = 0; i <ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') {
            c = c.substring(1);
        }
        if (c.indexOf(name) == 0) {
            return c.substring(name.length, c.length);
        }
    }
    return "";
}

var lastTimestamp = getCookie('lastTimestamp');

var watchCallback = function(timestamp) {
    if (lastTimestamp != timestamp) {
        lastTimestamp = timestamp;

        setCookie('lastTimestamp', lastTimestamp, 1);

        window.location.reload();
    }
};

setInterval(function() {
    $.get(changedUrl, watchCallback);
}, 500);

Maybe eventually I will put this together in to proper code and packaging so that other people can use it more easily. For now, feel free to copy and paste this in to your project.

Categories: articles

Tags: php, sculpin, symfony, twig