From 60e9a4e5c8b57e684a0fc373458aefe710032bef Mon Sep 17 00:00:00 2001 From: Tingan Ho Date: Tue, 5 Feb 2013 14:30:28 +0800 Subject: [PATCH] Added grunt-cli to CI --- .travis.yml | 2 + Gruntfile.js | 63 ++++++++++++ tasks/helpers/config.js | 147 +++++++++++++++++++++++++++ tasks/helpers/engine.js | 202 +++++++++++++++++++++++++++++++++++++ tasks/helpers/operators.js | 1 + 5 files changed, 415 insertions(+) create mode 100755 Gruntfile.js create mode 100644 tasks/helpers/config.js create mode 100644 tasks/helpers/engine.js create mode 100644 tasks/helpers/operators.js diff --git a/.travis.yml b/.travis.yml index e969ae2c..ade1fba9 100755 --- a/.travis.yml +++ b/.travis.yml @@ -3,3 +3,5 @@ node_js: - 0.8 before_install: - npm install + - npm install grunt-cli -g + diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100755 index 00000000..f759aca1 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,63 @@ +module.exports = function(grunt) { + + 'use strict'; + + // Project configuration. + grunt.initConfig({ + nodeunit: { + files: ['test/**/*.js'] + }, + watch: { + files: '', + tasks: 'default' + }, + jshint: { + options: { + curly: true, + eqeqeq: true, + loopfunc: true, + forin: false, + immed: true, + latedef: true, + newcap: true, + noarg: true, + sub: true, + undef: true, + boss: true, + eqnull: true, + node: true, + es5: true, + supernew: true + }, + globals: { + gt: true + }, + files: ['grunt.js', 'tasks/**/*.js'] + }, + + translate: { + options: { + configDir: './test/translations', + requireJS: true, + defaultLanguage: 'en' // grunt-translate use it to update translation. + }, + compile: { + output: './test/translations/output' + }, + update: { + src: ['./test/example/**/*.js'] + } + } + }); + // Load local tasks. + grunt.loadTasks('tasks'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-nodeunit'); + + + // Default task. + grunt.registerTask('default', 'lint translate:update translate:compile test'); + + + +}; diff --git a/tasks/helpers/config.js b/tasks/helpers/config.js new file mode 100644 index 00000000..a7234eeb --- /dev/null +++ b/tasks/helpers/config.js @@ -0,0 +1,147 @@ +var grunt = require('grunt'), + engine = require('./engine'), + path = require('path'), + OPERATORS = require('./operators'); + +var config = {}; + +/** + Get all locales + return Array of all locales + */ +config.getAllLocales = function(options){ + return Object.keys(grunt.file.readJSON(options.configDir + '/locales.json')); +}; + +/** + Check for error duplicate + @param Object res result that has already the key + @param String key + @param Array vars Translation vars + @throws Duplicate Translation Keys Error + */ +config.hasErrorDuplicate = function(res, key, vars){ + + // Check for error duplicate + var errorDuplicate = false; + if(vars.length !== res[key].vars.length) { + errorDuplicate = true; + } else { + for(var i in vars) { + if(res[key].vars[i] !== vars[i]) { + errorDuplicate = true; + break; + } + } + } + if(errorDuplicate) { + throw { + name: 'Duplicate Translation Keys', + message: 'You have used gt(\'' + key + '\') and input two different vars: \n' + 'Variables: ' + res[key].vars.join(',') + '\n' + 'Is not equal: ' + vars.join(',') + }; + } +}; + +/** + Get all locales + @return Object of all locales + */ +config.getAllTranslations = function(options){ + var files = grunt.file.expand({filter: 'isFile'}, options.configDir + '/locales/*.json'); + var locales = {}; + files.forEach(function(locale){ + locales[path.basename(locale, '.json')] = grunt.file.readJSON(locale); + }); + return locales; +}; + +/** + Returns translation key from a translation function string + @param String fn string of the function + @return String Translation key + */ +config.getTranslationKey = function(fn){ + return (fn.match(/['|"][\w|\-|\s|\&|<|>|\/]+['|"]/))[0].replace(/'/g, ''); +}; + +/** + Returns all the vars in a translation function + @param String fn Function string + @return Array of all vars + */ +config.getVars = function(fn){ + var json = fn.match(/\{[\w|\s|:|"|'|\-|\{|\}|\,|\/]*\}/); + if(json === null) { + return []; + } + json = json[0].replace(/\s*\w+\s*\:/g, function(m){ + var key = m.match(/\w+/); + return '"' + key + '":'; + }).replace(/'/g, function(){ + return '"'; + }); + var vars = JSON.parse(json); + return Object.keys(vars); +}; + +/** + Checks if an operand has the right syntax + @param String text + @return Boolean + */ +config.isTranslationText = function(text, key) { + if(typeof text === 'undefined') { + throw { + name: 'Undefined translation', + message: 'You have an undefined translation in:\n' + key + }; + } else if(text.substr(0,1) !== '"' || text.substr(-1) !== '"') { + throw { + name: 'Translation Text Wrong Syntax', + message: 'You have missed quotation in:\n' + text + }; + } +}; + +/** + Checks if operands and operators has the correct syntax + @param String operand1 + @param String operator + @param String operand2 + @return Boolean true + @throw Syntax Wrong in Translation JSON + */ +config.isConditions = function(operand1, operator, operand2) { + + // Check operands + if(!this.isOperand(operand1) || !this.isOperand(operand2)) { + throw { + name: 'Syntax Wrong in Translation JSON', + message: 'One of the operands have wrong syntax: ' + operand1 + ' or ' + operand2 + }; + } + + // Validate operator + if(OPERATORS.indexOf(operator) === -1) { + throw { + name: 'Syntax Wrong in Translation JSON', + message: '"' + operator + '" should be one of ' + OPERATORS.join(',') + }; + } + return true; +}; + +/** + Checks if an operand has the right syntax + @param String operand + @return Boolean + */ +config.isOperand = function(operand) { + if(/^"?\$?\w+"?$/.test(operand)) { + return true; + } + return false; +}; + +module.exports = config; + diff --git a/tasks/helpers/engine.js b/tasks/helpers/engine.js new file mode 100644 index 00000000..9c737cc2 --- /dev/null +++ b/tasks/helpers/engine.js @@ -0,0 +1,202 @@ +var grunt = require('grunt'), + config = require('./config') + + +var engine = {}; + +/** + Helper for appending js module content + @return String RequireJS content + */ +engine.appendModuleContent = function(options) { + var js = 'var ' + options.translationFunctionName + ' = (function() {' + grunt.util.linefeed; + return js; +}; + +/** + Reformats a translation JSON variable to javascript string + @param String operand + @param Array vars Vars is an array of defined variables + @return String + */ +engine.reformatOperandIfVariable = function(operand, vars) { + + if(/^[a-zA-Z]\w+$/.test(operand)) { + // Re-formats all params/vars + operand = operand.replace('$', ''); + if(vars.indexOf(operand) === -1) { + throw { + name: 'Use of Undefined Variable', + message: 'You have defined a variable in the translated text that is not used: \n' + operand.replace('$', '') + ' should instead be one of ' + vars.join(', ') + }; + } + operand = 'it.' + operand; + } + return operand; +}; + +/** + Helper for appending translation content + @param Array files + @return String Translation content + */ +engine.appendTranslationContent = function(file) { + + // Store every function in a hash + var t = ' var t = {' + grunt.util.linefeed; + + // Append translation content + var translations = grunt.file.readJSON(file); + var n = 0; + + for( var key in translations ) { + if(translations.hasOwnProperty(key)) { + + // Define function body + var fb = ''; + + // Append a comma for previous hashes + if(n !== 0) { + t += ',' + grunt.util.linefeed; + } + if(translations[key].translations.length === 0){ + + fb += ' return "HASH_NOT_TRANSLATED: ' + key + '";'; + + } else if(translations[key].translations[0][0] === 'if') { + + var trans = translations[key].translations; + trans.forEach(function(condition){ + + var conditionAdditionIndex = 4; + + // Check if conditions are right will throw an error if not + if(condition[0] !== 'else') { + config.isConditions(condition[1], condition[2], condition[3]); + + // Reformat variables + condition[1] = engine.reformatOperandIfVariable(condition[1], translations[key].vars); + condition[3] = engine.reformatOperandIfVariable(condition[3], translations[key].vars); + + fb += ' ' + condition[0] + '( ' + condition[1] + ' ' + condition[2] + ' ' + condition[3] + ' '; + + while(condition[conditionAdditionIndex] === '&&' || + condition[conditionAdditionIndex] === '||') { + + // Give some space + fb += condition[conditionAdditionIndex] + ' '; + + // Check if conditions are right will throw an error if not + config.isConditions(condition[conditionAdditionIndex + 1], condition[conditionAdditionIndex + 2], condition[conditionAdditionIndex + 3]); + + // Reformat variables + condition[conditionAdditionIndex + 1] = engine.reformatOperandIfVariable(condition[conditionAdditionIndex + 1], translations[key].vars); + condition[conditionAdditionIndex + 3] = engine.reformatOperandIfVariable(condition[conditionAdditionIndex + 3], translations[key].vars); + + // Add conditions + for(var i = 1; i <= 3; i++) { + fb += condition[conditionAdditionIndex + i] + ' '; + } + conditionAdditionIndex += 4; + } + + // Check translated text + config.isTranslationText( condition[conditionAdditionIndex], key ); + + // Add + fb += ') {' + grunt.util.linefeed; + fb += ' return ' + engine.reformatTranslatedText(condition[conditionAdditionIndex], translations[key].vars) + ';' + grunt.util.linefeed; // Return the translated text + fb += ' }' + grunt.util.linefeed; + + } else {// If an else statement + + // Check translated text + config.isTranslationText(condition[1], key); + + fb += ' else {'+ grunt.util.linefeed; + fb += ' return ' + engine.reformatTranslatedText(condition[1], translations[key].vars) + ';' + grunt.util.linefeed; + fb += ' }'; + + } + + }); + + } else { + + // Check translated text + config.isTranslationText(translations[key].translations, key); + + fb += ' return ' + engine.reformatTranslatedText(translations[key].translations, translations[key].vars) + ';'; + } + + /*jshint evil:true */ + t += ' "' + key + '": ' + (new Function(['it'], fb)).toString(); + t = t.slice(0, -1) + ' }'; + /*jshint evil:false */ + + // Update loop + n++; + + + } + } + + t += grunt.util.linefeed + ' };' + grunt.util.linefeed; + + // Return the t function + t += ' return function(translationKey) {' + grunt.util.linefeed; + t += ' if(!(translationKey in t)) {' + grunt.util.linefeed; + t += ' console.log("You have used an undefined translation key:" + translationKey);' + grunt.util.linefeed; + t += ' return false;' + grunt.util.linefeed; + t += ' }' + grunt.util.linefeed; + t += ' delete arguments[0];' + grunt.util.linefeed; + t += ' if("1" in arguments) {' + grunt.util.linefeed; + t += ' arguments[0] = arguments[1];' + grunt.util.linefeed; + t += ' }' + grunt.util.linefeed; + t += ' delete arguments[1];' + grunt.util.linefeed; + t += ' return t[translationKey].apply(undefined, arguments);' + grunt.util.linefeed; + t += ' };' + grunt.util.linefeed; + + // Return string + return t; + +}; + +/** + Reformat translated text + @param String text + @return String translated text + */ +engine.reformatTranslatedText = function(text, vars){ + + return text.replace(/\$\{\w+\}/g, function( m ){ + + m = m.substring(2, m.length - 1); + if(vars.indexOf(m) === -1) { + throw { + name: 'Use of undefined variable', + message: '"' + m + '" is never defined' + }; + } + return '\" + it.' + m + ' + \"'; + }); + +}; + +/** + Helper for appending requirejs file content + @return String RequireJS content + */ +engine.appendRequireJSContent = function() { + var js = ''; + js += 'if( typeof define !== "function" ) {' + grunt.util.linefeed; + js += ' var define = require( "amdefine" )( module );' + grunt.util.linefeed; + js += '}' + grunt.util.linefeed; + js += 'define(function() {' + grunt.util.linefeed; + return js; +}; + +module.exports = engine; + + + diff --git a/tasks/helpers/operators.js b/tasks/helpers/operators.js new file mode 100644 index 00000000..5033102d --- /dev/null +++ b/tasks/helpers/operators.js @@ -0,0 +1 @@ +module.exports = ['<', '>', '===', '>==', '<==', '==', '>=', '<='];