Move template interpolation out of grunt into standalone script.

This commit is contained in:
ccd0 2016-04-09 04:55:29 -07:00
parent 11ab484acf
commit c296b5b6cf
4 changed files with 191 additions and 85 deletions

View File

@ -5,68 +5,6 @@ JSZip = require 'jszip'
module.exports = (grunt) ->
grunt.util.linefeed = '\n'
json = (data) ->
"`#{JSON.stringify(data).replace(/`/g, '\\`')}`"
importCSS = (filenames...) ->
grunt.template.process(
filenames.map((name) -> grunt.file.read "src/css/#{name}.css").join(''),
{data: grunt.config 'pkg'}
).trim().replace(/\n+/g, '\n').split(/^/m).map(JSON.stringify).join(' +\n').replace(/`/g, '\\`')
importHTML = (filename) ->
html grunt.template.process(grunt.file.read("src/#{filename}.html").replace(/^ +/gm, '').replace(/\r?\n/g, ''), data: grunt.config('pkg'))
parseTemplate = (template, context='') ->
context0 = context
parts = []
text = template
while text
if part = text.match /^(?:[^{}\\]|\\.)+(?!{)/
text = text[part[0].length..]
unescaped = part[0].replace /\\(.)/g, '$1'
context = (context + unescaped)
.replace(/(=['"])[^'"<>]*/g, '$1')
.replace(/(<\w+)( [\w-]+((?=[ >])|=''|=""))*/g, '$1')
.replace(/^([^'"<>]+|<\/?\w+>)*/, '')
parts.push json unescaped
else if part = text.match /^([^}]){([^}`]*)}/
text = text[part[0].length..]
unless context is '' or (part[1] is '$' and /\=['"]$/.test context) or part[1] is '?'
throw new Error "Illegal insertion into HTML template (at #{context}): #{template}"
parts.push switch part[1]
when '$' then "E(`#{part[2]}`)"
when '&' then "`#{part[2]}`.innerHTML"
when '@' then "E.cat(`#{part[2]}`)"
when '?'
args = ['""', '""']
for i in [0...2]
break if text[0] isnt '{'
text = text[1..]
[args[i], text] = parseTemplate text, context
if text[0] isnt '}'
throw new Error "Unexpected characters in subtemplate (#{text}): #{template}"
text = text[1..]
"(if `#{part[2]}` then #{args[0]} else #{args[1]})"
else
throw new Error "Unrecognized substitution operator (#{part[1]}): #{template}"
else
break
if context isnt context0
throw new Error "HTML template is ill-formed (at #{context}): #{template}"
output = if parts.length is 0 then '""' else parts.join ' + '
[output, text]
html = (template) ->
[output, remaining] = parseTemplate template
if remaining
throw new Error "Unexpected characters in template (#{remaining}): #{template}"
"(innerHTML: #{output})"
assert = (statement, objs...) ->
return '' unless grunt.config('pkg').tests_enabled
"throw new Error 'Assertion failed: ' + #{json statement} unless #{statement}"
loadPkg = ->
pkg = grunt.file.readJSON 'package.json'
version = grunt.file.readJSON 'version.json'
@ -78,17 +16,6 @@ module.exports = (grunt) ->
pkg: loadPkg()
concat:
options: process: Object.create(null, data:
get: ->
pkg = grunt.config 'pkg'
pkg.importCSS = importCSS
pkg.importHTML = importHTML
pkg.html = html
pkg.assert = assert
pkg.tests_enabled or= false
pkg
enumerable: true
)
coffee:
src: [
'src/General/Config.coffee'
@ -130,11 +57,9 @@ module.exports = (grunt) ->
'src/General/Settings.coffee'
'src/General/Main.coffee'
]
dest: 'tmp/script-<%= pkg.type %>.coffee'
dest: 'tmp/script.coffee'
crx:
files:
'testbuilds/updates<%= pkg.meta.suffix[pkg.channel] %>.xml': 'src/meta/updates.xml'
'testbuilds/crx<%= pkg.meta.suffix[pkg.channel] %>/manifest.json': 'src/meta/manifest.json'
'testbuilds/crx<%= pkg.meta.suffix[pkg.channel] %>/script.js': [
'src/meta/botproc.js'
'LICENSE'
@ -144,10 +69,9 @@ module.exports = (grunt) ->
'testbuilds/crx<%= pkg.meta.suffix[pkg.channel] %>/eventPage.js': 'tmp/eventPage-crx.js'
userscript:
files:
'testbuilds/<%= pkg.name %><%= pkg.meta.suffix[pkg.channel] %>.meta.js': 'src/meta/metadata.js'
'testbuilds/<%= pkg.name %><%= pkg.meta.suffix[pkg.channel] %>.user.js': [
'src/meta/botproc.js'
'src/meta/metadata.js'
'testbuilds/<%= pkg.name %><%= pkg.meta.suffix[pkg.channel] %>.meta.js'
'LICENSE'
'src/meta/usestrict.js'
'tmp/script-userscript.js'
@ -195,6 +119,17 @@ module.exports = (grunt) ->
stdout: true
stderr: true
failOnError: true
'templates-crx':
command: 'node_modules/.bin/coffee tools/templates.coffee tmp/script.coffee tmp/script-crx.coffee crx - <%= pkg.tests_enabled || "" %>'
'templates-crx-meta':
command: """
node_modules/.bin/coffee tools/templates.coffee src/meta/updates.xml testbuilds/updates<%= pkg.meta.suffix[pkg.channel] %>.xml crx <%= pkg.channel %>
node_modules/.bin/coffee tools/templates.coffee src/meta/manifest.json testbuilds/crx<%= pkg.meta.suffix[pkg.channel] %>/manifest.json crx <%= pkg.channel %>
""".split('\n').join('&&')
'templates-userscript':
command: 'node_modules/.bin/coffee tools/templates.coffee tmp/script.coffee tmp/script-userscript.coffee userscript - <%= pkg.tests_enabled || "" %>'
'templates-userscript-meta':
command: 'node_modules/.bin/coffee tools/templates.coffee src/meta/metadata.js testbuilds/<%= pkg.name %><%= pkg.meta.suffix[pkg.channel] %>.meta.js userscript <%= pkg.channel %>'
commit:
command: """
git commit -am "Release <%= pkg.meta.name %> v<%= pkg.meta.version %>."
@ -374,10 +309,12 @@ module.exports = (grunt) ->
grunt.registerTask 'build', [
'shell:npm'
'concat:coffee'
'concurrent:build'
]
grunt.registerTask 'build-crx-channel', [
'shell:templates-crx-meta'
'concat:crx'
'copy:crx'
'zip-crx'
@ -385,7 +322,7 @@ module.exports = (grunt) ->
grunt.registerTask 'build-crx', [
'set-build:crx'
'concat:coffee'
'shell:templates-crx'
'coffee:script'
'coffee:eventPage'
'jshint:script'
@ -427,27 +364,34 @@ module.exports = (grunt) ->
'sign-channel:noupdate'
]
grunt.registerTask 'build-userscript-channel', [
'shell:templates-userscript-meta'
'concat:userscript'
]
grunt.registerTask 'build-userscript', [
'set-build:userscript'
'concat:coffee'
'shell:templates-userscript'
'coffee:script'
'jshint:script'
'set-channel:stable'
'concat:userscript'
'build-userscript-channel'
'set-channel:beta'
'concat:userscript'
'build-userscript-channel'
'set-channel:noupdate'
'concat:userscript'
'build-userscript-channel'
'set-channel:dev'
'concat:userscript'
'build-userscript-channel'
'clean:tmpuserscript'
'copy:install'
]
grunt.registerTask 'build-tests', [
'shell:npm'
'enable-tests'
'build-userscript'
'concat:coffee'
'build-crx'
'build-userscript'
]
grunt.registerTask 'full', [

62
npm-shrinkwrap.json generated
View File

@ -3,6 +3,10 @@
"npm-shrinkwrap-version": "200.5.1",
"node-version": "v4.3.1",
"dependencies": {
"coffee-script": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.10.0.tgz"
},
"crx": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/crx/-/crx-3.0.3.tgz",
@ -193,6 +197,60 @@
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.5.0.tgz"
},
"glob": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz",
"dependencies": {
"inflight": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz",
"dependencies": {
"wrappy": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
}
}
},
"inherits": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
},
"minimatch": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz",
"dependencies": {
"brace-expansion": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz",
"dependencies": {
"balanced-match": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz"
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
}
}
}
}
},
"once": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
"dependencies": {
"wrappy": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
}
}
},
"path-is-absolute": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz"
}
}
},
"grunt": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.1.tgz",
@ -1911,6 +1969,10 @@
}
}
},
"lodash": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.9.0.tgz"
},
"npm-shrinkwrap": {
"version": "200.5.1",
"resolved": "https://registry.npmjs.org/npm-shrinkwrap/-/npm-shrinkwrap-200.5.1.tgz",

View File

@ -59,8 +59,10 @@
}
},
"devDependencies": {
"coffee-script": "^1.10.0",
"crx": "^3.0.3",
"font-awesome": "4.5.0",
"glob": "^7.0.3",
"grunt": "^1.0.1",
"grunt-concurrent": "^2.3.0",
"grunt-contrib-clean": "^1.0.0",
@ -74,6 +76,7 @@
"grunt-webstore-upload": "^0.8.10",
"jszip": "^2.6.0",
"load-grunt-tasks": "^3.5.0",
"lodash": "^4.9.0",
"npm-shrinkwrap": "^200.5.1"
},
"repository": {

97
tools/templates.coffee Normal file
View File

@ -0,0 +1,97 @@
fs = require 'fs'
path = require 'path'
_ = require 'lodash'
glob = require 'glob'
# disable ES6 delimiters
_.templateSettings.interpolate = /<%=([\s\S]+?)%>/g
read = (filename) -> fs.readFileSync filename, 'utf8'
pkg = JSON.parse(read 'package.json')
_.assign pkg.meta, JSON.parse(read 'version.json')
json = (data) ->
"`#{JSON.stringify(data).replace(/`/g, '\\`')}`"
importCSS = (filenames...) ->
text = filenames.map((name) -> read "src/css/#{name}.css").join('')
text = _.template(text)(pkg)
text.trim().replace(/\n+/g, '\n').split(/^/m).map(JSON.stringify).join(' +\n').replace(/`/g, '\\`')
importHTML = (filename) ->
text = read("src/#{filename}.html").replace(/^ +/gm, '').replace(/\r?\n/g, '')
text = _.template(text)(pkg)
html text
parseTemplate = (template, context='') ->
context0 = context
parts = []
text = template
while text
if part = text.match /^(?:[^{}\\]|\\.)+(?!{)/
text = text[part[0].length..]
unescaped = part[0].replace /\\(.)/g, '$1'
context = (context + unescaped)
.replace(/(=['"])[^'"<>]*/g, '$1')
.replace(/(<\w+)( [\w-]+((?=[ >])|=''|=""))*/g, '$1')
.replace(/^([^'"<>]+|<\/?\w+>)*/, '')
parts.push json unescaped
else if part = text.match /^([^}]){([^}`]*)}/
text = text[part[0].length..]
unless context is '' or (part[1] is '$' and /\=['"]$/.test context) or part[1] is '?'
throw new Error "Illegal insertion into HTML template (at #{context}): #{template}"
parts.push switch part[1]
when '$' then "E(`#{part[2]}`)"
when '&' then "`#{part[2]}`.innerHTML"
when '@' then "E.cat(`#{part[2]}`)"
when '?'
args = ['""', '""']
for i in [0...2]
break if text[0] isnt '{'
text = text[1..]
[args[i], text] = parseTemplate text, context
if text[0] isnt '}'
throw new Error "Unexpected characters in subtemplate (#{text}): #{template}"
text = text[1..]
"(if `#{part[2]}` then #{args[0]} else #{args[1]})"
else
throw new Error "Unrecognized substitution operator (#{part[1]}): #{template}"
else
break
if context isnt context0
throw new Error "HTML template is ill-formed (at #{context}): #{template}"
output = if parts.length is 0 then '""' else parts.join ' + '
[output, text]
html = (template) ->
[output, remaining] = parseTemplate template
if remaining
throw new Error "Unexpected characters in template (#{remaining}): #{template}"
"(innerHTML: #{output})"
assert = (statement, objs...) ->
return '' unless pkg.tests_enabled
"throw new Error 'Assertion failed: ' + #{json statement} unless #{statement}"
_.assign pkg, {importCSS, importHTML, html, assert}
pkg.grunt = file:
read: (filename, options) ->
if options?.encoding is 'base64'
fs.readFileSync(filename).toString('base64')
else
read filename
readJSON: (filename) -> JSON.parse read filename
expand: glob.sync
pkg.type = process.argv[4]
pkg.channel = process.argv[5]
pkg.tests_enabled = !!process.argv[6]
dir = path.dirname process.argv[3]
fs.mkdirSync dir unless fs.existsSync dir
text = read process.argv[2]
text = _.template(text)(pkg)
fs.writeFileSync process.argv[3], text