var deepmerge = require('deepmerge'); module.exports = function(grunt) { require('time-grunt')(grunt); require('jit-grunt')(grunt); grunt.util.linefeed = '\n'; function removeJshint(src) { return src .replace(/\/\*jshint [a-z:]+ \*\/\r?\n\r?\n?/g, '') .replace(/\/\*jshint -[EWI]{1}[0-9]{3} \*\/\r?\n\r?\n?/g, ''); } function process_lang(file, src, wrapper) { var lang = file.split(/[\/\.]/)[2]; var content = JSON.parse(src); wrapper = wrapper || ['','']; grunt.config.set('lang_locale', content.__locale || lang); grunt.config.set('lang_author', content.__author); var header = grunt.template.process('<%= langBanner %>'); loaded_plugins.forEach(function(p) { var plugin_file = 'src/plugins/'+ p +'/i18n/'+ lang +'.json'; if (grunt.file.exists(plugin_file)) { content = deepmerge(content, grunt.file.readJSON(plugin_file)); } }); return header + '\n\n' + wrapper[0] + 'QueryBuilder.regional[\'' + lang + '\'] = ' + JSON.stringify(content, null, 2) + ';\n\n' + 'QueryBuilder.defaults({ lang_code: \'' + lang + '\' });' + wrapper[1]; } var all_plugins = {}, all_langs = {}, loaded_plugins = [], loaded_langs = [], js_core_files = [ 'src/main.js', 'src/defaults.js', 'src/core.js', 'src/public.js', 'src/data.js', 'src/template.js', 'src/model.js', 'src/utils.js', 'src/jquery.js' ], js_files_to_load = js_core_files.slice(), js_files_for_standalone = [ 'bower_components/jquery-extendext/jQuery.extendext.js', 'bower_components/doT/doT.js', 'dist/js/query-builder.js' ]; (function(){ // list available plugins and languages grunt.file.expand('src/plugins/**/plugin.js') .forEach(function(f) { var n = f.split('/')[2]; all_plugins[n] = f; }); grunt.file.expand('src/i18n/*.json') .forEach(function(f) { var n = f.split(/[\/\.]/)[2]; all_langs[n] = f; }); // parse 'plugins' parameter var arg_plugins = grunt.option('plugins'); if (typeof arg_plugins === 'string') { arg_plugins.replace(/ /g, '').split(',').forEach(function(p) { if (all_plugins[p]) { js_files_to_load.push(all_plugins[p]); loaded_plugins.push(p); } else { grunt.fail.warn('Plugin '+ p +' unknown'); } }); } else if (arg_plugins === undefined) { for (var p in all_plugins) { js_files_to_load.push(all_plugins[p]); loaded_plugins.push(p); } } // default language js_files_to_load.push('.temp/i18n/en.js'); loaded_langs.push('en'); // parse 'lang' parameter var arg_langs = grunt.option('languages'); if (typeof arg_langs === 'string') { arg_langs.replace(/ /g, '').split(',').forEach(function(l) { if (all_langs[l]) { if (l !== 'en') { js_files_to_load.push(all_langs[l].replace(/^src/, '.temp').replace(/json$/, 'js')); loaded_langs.push(l); } } else { grunt.fail.warn('Language '+ l +' unknown'); } }); } }()); grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), banner: '/*!\n'+ ' * jQuery QueryBuilder <%= pkg.version %>\n'+ ' * Copyright 2014-<%= grunt.template.today("yyyy") %> Damien "Mistic" Sorel (http://www.strangeplanet.fr)\n'+ ' * Licensed under MIT (http://opensource.org/licenses/MIT)\n'+ ' */', langBanner: '/*!\n'+ ' * jQuery QueryBuilder <%= pkg.version %>\n'+ ' * Locale: <%= lang_locale %>\n'+ '<% if (lang_author) { %> * Author: <%= lang_author %>\n<% } %>'+ ' * Licensed under MIT (http://opensource.org/licenses/MIT)\n'+ ' */', // bump version bump: { options: { files: ['package.json', 'bower.json', 'composer.json'], createTag: false, commit: false, push: false } }, // watchers watch: { js: { files: ['src/*.js', 'src/plugins/**/plugin.js'], tasks: ['build_js'] }, css: { files: ['src/scss/*.scss', 'src/plugins/**/plugin.scss'], tasks: ['build_css'] }, lang: { files: ['src/i18n/*.json', 'src/plugins/**/i18n/*.json'], tasks: ['build_lang'] } }, // copy SASS files copy: { sass_core: { files: [{ expand: true, flatten: true, src: ['src/scss/*.scss'], dest: 'dist/scss' }] }, sass_plugins: { files: loaded_plugins.map(function(name) { return { src: 'src/plugins/'+ name +'/plugin.scss', dest: 'dist/scss/plugins/' + name + '.scss' }; }) } }, concat: { // concat all JS js: { src: js_files_to_load, dest: 'dist/js/query-builder.js', options: { stripBanners: false, separator: '\n\n', process: function(src) { return removeJshint(src).replace(/\r\n/g, '\n'); } } }, // create standalone version js_standalone: { src: js_files_for_standalone, dest: 'dist/js/query-builder.standalone.js', options: { stripBanners: false, separator: '\n\n', process: function(src, file) { var name = file.match(/([^\/]+?).js$/)[1]; return removeJshint(src) .replace(/\r\n/g, '\n') .replace(/define\((.*?)\);/, 'define(\'' + name + '\', $1);'); } } }, // compile language files with AMD wrapper lang: { files: Object.keys(all_langs).map(function(name) { return { src: 'src/i18n/'+ name +'.json', dest: 'dist/i18n/query-builder.' + name + '.js' }; }), options: { process: function(src, file) { var wrapper = grunt.file.read('src/i18n/.wrapper.js').replace(/\r\n/g, '\n').split(/@@js\n/); return process_lang(file, src, wrapper); } } }, // comp�le language files without wrapper lang_temp: { files: Object.keys(all_langs).map(function(name) { return { src: 'src/i18n/'+ name +'.json', dest: '.temp/i18n/' + name + '.js' }; }), options: { process: function(src, file) { return process_lang(file, src); } } }, // add banner to CSS files css: { options: { banner: '<%= banner %>\n\n', }, files: [{ expand: true, src: ['dist/css/*.css', 'dist/scss/*.scss'], dest: '' }] } }, wrap: { // add AMD wrapper and banner js: { src: ['dist/js/query-builder.js'], dest: '', options: { separator: '', wrapper: function() { var wrapper = grunt.file.read('src/.wrapper.js').replace(/\r\n/g, '\n').split(/@@js\n/); if (loaded_plugins.length) { wrapper[0] = '// Plugins: ' + loaded_plugins.join(', ') + '\n' + wrapper[0]; } if (loaded_langs.length) { wrapper[0] = '// Languages: ' + loaded_langs.join(', ') + '\n' + wrapper[0]; } wrapper[0] = grunt.template.process('<%= banner %>\n\n') + wrapper[0]; return wrapper; } } }, // add plugins SASS imports sass: { src: ['dist/scss/default.scss'], dest: '', options: { separator: '', wrapper: function() { return ['', loaded_plugins.reduce(function(wrapper, name) { if (grunt.file.exists('dist/scss/plugins/' + name + '.scss')) { wrapper+= '\n@import \'plugins/' + name + '\';'; } return wrapper; }, '\n')]; } } } }, // parse scss sass: { options: { sourcemap: 'none', style: 'expanded' }, dist: { files: [{ expand: true, flatten: true, src: ['dist/scss/*.scss'], dest: 'dist/css', ext: '.css', rename: function(dest, src) { return dest + '/query-builder.' + src; } }] } }, // compress js uglify: { options: { banner: '<%= banner %>\n\n', mangle: { except: ['$'] } }, dist: { files: [{ expand: true, flatten: true, src: ['dist/js/*.js', '!dist/js/*.min.js'], dest: 'dist/js', ext: '.min.js', extDot: 'last' }] } }, // compress css cssmin: { dist: { files: [{ expand: true, flatten: true, src: ['dist/css/*.css', '!dist/css/*.min.css'], dest: 'dist/css', ext: '.min.css', extDot: 'last' }] } }, // clean build dir clean: { temp: ['.temp'] }, // jshint tests jshint: { lib: { options: { '-W069': true // accesses to "regional" in language files }, src: js_files_to_load } }, // inject all source files and test modules in the test file 'string-replace': { test: { src: 'tests/index.html', dest: 'tests/index.html', options: { replacements: [{ pattern: /(<!-- qunit:imports -->)(?:[\s\S]*)(<!-- \/qunit:imports -->)/m, replacement: function(match, m1, m2) { var scripts = '\n'; js_core_files.forEach(function(file) { scripts+= '<script src="../' + file + '" data-cover></script>\n'; }); scripts+= '\n'; for (var p in all_plugins) { scripts+= '<script src="../' + all_plugins[p] + '" data-cover></script>\n'; } return m1 + scripts + m2; } }, { pattern: /(<!-- qunit:modules -->)(?:[\s\S]*)(<!-- \/qunit:modules -->)/m, replacement: function(match, m1, m2) { var scripts = '\n'; grunt.file.expand('tests/*.module.js').forEach(function(file) { scripts+= '<script src="../' + file + '"></script>\n'; }); return m1 + scripts + m2; } }] } } }, // qunit test suite qunit: { all: { options: { urls: ['tests/index.html?coverage=true'], noGlobals: true } } }, // save LCOV files qunit_blanket_lcov: { all: { files: [{ expand: true, src: ['src/*.js', 'src/plugins/**/plugin.js'] }], options: { dest: '.coverage-results/all.lcov' } } }, // coveralls data coveralls: { options: { force: true }, all: { src: '.coverage-results/all.lcov', } } }); // list the triggers and changes in core code grunt.registerTask('describe_triggers', 'List QueryBuilder triggers.', function() { var triggers = {}; for (var f in js_core_files) { grunt.file.read(js_core_files[f]).split(/\r?\n/).forEach(function(line, i) { var matches = /(e = )?(?:this|that)\.(trigger|change)\('(\w+)'([^)]*)\);/.exec(line); if (matches !== null) { triggers[matches[3]] = { name: matches[3], type: matches[2], file: js_core_files[f], line: i, args: matches[4].slice(2), prevent: !!matches[1] }; } }); } grunt.log.writeln('\nTriggers in QueryBuilder:\n'); for (var t in triggers) { grunt.log.write((triggers[t].name)['cyan'] + ' ' + triggers[t].type); if (triggers[t].prevent) grunt.log.write(' (*)'['yellow']); grunt.log.write('\n'); grunt.log.write(' ' + (triggers[t].file +':'+ triggers[t].line)['red'] + ' ' + triggers[t].args); grunt.log.write('\n\n'); } }); // display available modules grunt.registerTask('list_modules', 'List QueryBuilder plugins and languages.', function() { grunt.log.writeln('\nAvailable QueryBuilder plugins:\n'); for (var p in all_plugins) { grunt.log.write(p['cyan']); if (grunt.file.exists(all_plugins[p].replace(/js$/, 'scss'))) { grunt.log.write(' + CSS'); } grunt.log.write('\n'); } grunt.log.writeln('\nAvailable QueryBuilder languages:\n'); for (var l in all_langs) { if (l !== 'en') { grunt.log.writeln(l['cyan']); } } }); grunt.registerTask('build_js', [ 'concat:lang_temp', 'concat:js', 'wrap:js', 'concat:js_standalone', 'uglify', 'clean:temp' ]); grunt.registerTask('build_css', [ 'copy:sass_core', 'copy:sass_plugins', 'wrap:sass', 'sass', 'cssmin', 'concat:css' ]); grunt.registerTask('build_lang', [ 'concat:lang' ]); grunt.registerTask('default', [ 'build_lang', 'build_js', 'build_css' ]); grunt.registerTask('test', [ 'default', 'jshint', 'string-replace:test', 'qunit_blanket_lcov', 'qunit' ]); };