Last active
July 7, 2020 14:41
-
-
Save andymantell/8b1bb037341f38990a4d019894abd83e to your computer and use it in GitHub Desktop.
Gulp task to convert govuk-frontend Nunjucks macros to Jinja2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* NOTE: This script is deprecated and I no longer recommend it's use. | |
* Within HM Land Registry it is supereceded by https://github.com/LandRegistry/govuk-frontend-jinja | |
* This repository was born from the outputs of this conversion script, but has had numerous fixes since | |
* so if you are looking for a stable and accurate jinja implementation of govuk-frontend I would look there first. | |
*/ | |
const path = require('path') | |
const fs = require('fs') | |
const es = require('event-stream') | |
const filter = require('gulp-filter') | |
module.exports = (gulp, config) => { | |
const govukTemplatePath = path.dirname(require.resolve('govuk-frontend/README.md')) | |
const templateFilter = filter([ | |
'**', | |
'!**/components/**/template.njk' // We want everything *except* the templates (i.e. just the macros) | |
]) | |
gulp.task('copyGovTemplates', () => { | |
return gulp | |
.src(path.join(govukTemplatePath, '**/*.njk')) | |
.pipe(templateFilter) | |
.pipe(es.map(function(file, cb) { | |
var contents = file.contents.toString() | |
// "Unwrap" the include directly inside the macro since it causes problems in Jinja, | |
// and the only reason it's done this way is for the govuk design system's demo code | |
matches = contents.match(/(?:{%-? include [\'\"](.*)[\"\'] -?%})/) | |
if (matches) { | |
includePath = path.join(path.dirname(file.path), matches[1]) | |
templateContents = fs.readFileSync(includePath) | |
contents = contents.replace(matches[0], templateContents) | |
} | |
// Rename file | |
file.path = file.path.replace('.njk', '.html') | |
// Simple conversions from nunjucks/js to jinja/python | |
contents = contents.replace(/true/g, 'True') | |
contents = contents.replace(/false/g, 'False') | |
contents = contents.replace(/\.njk/g, '.html') | |
// Quoting dict keys, because nunjucks doesn't require them but jinja does | |
contents = contents.replace(/^([ ]*)([^ '"#\r\n:]+?)\s*:/gm, "$1'$2':") | |
// Gov template uses .items, which is a reserved word in python | |
contents = contents.replace(/\.items/g, "['items']") | |
// No such thing as elseif - it's elif in Python | |
contents = contents.replace(/elseif/g, "elif") | |
// Python doesn't like inline ifs that don't have elses | |
// See phase banner for example | |
contents = contents.replace(/\b(.+) if \1(?! else)/g, "$1 if $1 else ''") | |
// Concatenating loop index onto strings doesn't work implicitly in Python | |
// Jinja has a specific operator for doing this | |
contents = contents.replace(/\+ loop.index/g, '~ loop.index') | |
// Remove all indentation and trimming which causes problems in jinja whereby the | |
// indent filter fails to mark stuff as safe, and instead escapes the html special characters | |
// In addition, this kind of formatting is not necessary in production code and is an unnecessary overhead | |
contents = contents.replace(/\s?\|\s?trim/g, '', contents) | |
contents = contents.replace(/\s?\|\s?indent(?:[(\d)]*)/g, '', contents) | |
// Additional code needed for looping over dicts in python | |
// Adds in the .items() suffix, but also guards against iterating over empty dicts which is something | |
// that you can "get away with" in JS but not Python | |
contents = contents.replace(/in (.*).attributes %}/g, 'in ($1.attributes.items() if $1.attributes else {}.items()) %}', contents) | |
// Resolve paths to the templates as jinja does not support relative paths as nunjucks does | |
contents = contents.replace(/\.\.\//g, 'app/vendor/.govuk-frontend/' + path.relative(govukTemplatePath, path.dirname(path.resolve(file.path, '..'))) + '/') | |
contents = contents.replace(/\.\//g, 'app/vendor/.govuk-frontend/' + path.relative(govukTemplatePath, path.dirname(file.path)) + '/') | |
// Hardcoded replacements for deeply nested dict access, which requires | |
// more protection in Python than it does in JS | |
// Not perfect to have to keep this hardcoded here, but at the time of writing, there's only a handful of examples | |
replacements = { | |
'params.hint.classes': '(params.hint.classes if params.hint and params.hint.classes)', | |
'item.hint.text': '(item.hint.text if item.hint and item.hint.text)', | |
'item.hint.html': '(item.hint.html if item.hint and item.hint.html)', | |
'item.label.attributes': '(item.label.attributes if item.label and item.label.attributes)', | |
"(' ' + item.label.classes if item.label.classes else '')": "(' ' + item.label.classes if item.classes and item.label.classes else '')" | |
} | |
for (key in replacements) { | |
contents = contents.replace(key, replacements[key]) | |
} | |
file.contents = Buffer.from(contents, 'utf8') | |
cb(null, file) | |
})) | |
.pipe(gulp.dest(path.join(config.applicationPath, 'templates/vendor/.govuk-frontend'))) | |
} | |
) | |
gulp.task('copyGovAssets', () => | |
gulp | |
.src(path.join(govukTemplatePath, 'assets/**/*.*')) | |
.pipe(gulp.dest(path.join(config.destinationPath, '.govuk-frontend'))) | |
) | |
gulp.task( | |
'copyGov', | |
gulp.parallel([ | |
'copyGovTemplates', | |
'copyGovAssets' | |
]) | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment