diff --git a/Gruntfile.coffee b/Gruntfile.coffee index 3b0d7294c..53e5ff766 100644 --- a/Gruntfile.coffee +++ b/Gruntfile.coffee @@ -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', [ diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index f33ed6b92..e501fe881 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -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", diff --git a/package.json b/package.json index 33621d7bf..910f44f26 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/tools/templates.coffee b/tools/templates.coffee new file mode 100644 index 000000000..03f218d10 --- /dev/null +++ b/tools/templates.coffee @@ -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