Просмотр исходного кода

[2.0] build system updates, esp for language file generation

Adam Shaw 12 лет назад
Родитель
Сommit
05ab0a7cf8
2 измененных файлов с 271 добавлено и 10 удалено
  1. 62 10
      Gruntfile.js
  2. 209 0
      tasks/generateLanguages.js

+ 62 - 10
Gruntfile.js

@@ -33,7 +33,10 @@ module.exports = function(grunt) {
 	grunt.registerTask('default', 'archive');
 
 	// Bare minimum for debugging
-	grunt.registerTask('dev', 'lumbar:build');
+	grunt.registerTask('dev', [
+		'lumbar:build',
+		'generateLanguages'
+	]);
 
 
 
@@ -41,6 +44,7 @@ module.exports = function(grunt) {
 	----------------------------------------------------------------------------------------------------*/
 
 	grunt.registerTask('modules', 'Build the FullCalendar modules', [
+		'clean:modules',
 		'lumbar:build',
 		'concat:moduleVariables',
 		'uglify:modules'
@@ -75,7 +79,37 @@ module.exports = function(grunt) {
 		ext: '.min.js'
 	}
 
-	config.clean.modules = 'build/out/*';
+	config.clean.modules = 'build/out/**';
+
+
+
+	/* Languages
+	----------------------------------------------------------------------------------------------------*/
+
+	grunt.registerTask('languages', [
+		'clean:languages',
+		'generateLanguages',
+		'uglify:languages'
+	]);
+
+	config.generateLanguages = {
+		moment: 'lib/moment/lang/',
+		datepicker: 'lib/jquery-ui/ui/i18n/',
+		fullCalendar: 'lang/',
+		dest: 'build/out/lang/'
+	};
+
+	config.uglify.languages = {
+		expand: true,
+		cwd: 'build/out/lang/',
+		src: '*.js',
+		dest: 'build/out/lang-min/'
+	};
+
+	config.clean.languages = [
+		'build/out/lang/*',
+		'build/out/lang-min/*'
+	];
 
 
 
@@ -83,10 +117,12 @@ module.exports = function(grunt) {
 	----------------------------------------------------------------------------------------------------*/
 
 	grunt.registerTask('archive', 'Create a distributable ZIP archive', [
-		'clean:modules',
 		'clean:archive',
 		'modules',
+		'languages',
 		'copy:archiveModules',
+		'copy:archiveLanguages',
+		'copy:archiveMoment',
 		'copy:archiveJQuery',
 		'concat:archiveJQueryUI',
 		'copy:archiveDemos',
@@ -103,6 +139,18 @@ module.exports = function(grunt) {
 		dest: 'build/archive/fullcalendar/'
 	};
 
+	config.copy.archiveLanguages = {
+		expand: true,
+		cwd: 'build/out/lang-min/',
+		src: '*.js',
+		dest: 'build/archive/fullcalendar/lang/'
+	};
+
+	config.copy.archiveMoment = {
+		src: 'lib/moment/min/moment.min.js',
+		dest: 'build/archive/lib/moment.min.js'
+	};
+
 	config.copy.archiveJQuery = {
 		src: 'lib/jquery/jquery.min.js',
 		dest: 'build/archive/lib/jquery.min.js'
@@ -129,7 +177,7 @@ module.exports = function(grunt) {
 				return content;
 			}
 		},
-		src: 'demos/*',
+		src: 'demos/**',
 		dest: 'build/archive/'
 	};
 
@@ -138,14 +186,15 @@ module.exports = function(grunt) {
 		expand: true,
 		cwd: 'lib/jquery-ui/themes/cupertino/',
 		src: [ 'jquery-ui.min.css', 'images/*' ],
-		dest: 'build/archive/demos/cupertino/'
+		dest: 'build/archive/lib/cupertino/'
 	};
 
 	// in demo HTML, rewrites paths to work in the archive
 	function transformDemoPath(path) {
+		path = path.replace('../lib/moment/moment.js', '../lib/moment.min.js');
 		path = path.replace('../lib/jquery/jquery.js', '../lib/jquery.min.js');
 		path = path.replace('../lib/jquery-ui/ui/jquery-ui.js', '../lib/jquery-ui.custom.min.js');
-		path = path.replace('../lib/jquery-ui/themes/', '');
+		path = path.replace('../lib/jquery-ui/themes/cupertino/', '../lib/cupertino/');
 		path = path.replace('../build/out/', '../fullcalendar/');
 		path = path.replace('/fullcalendar.js', '/fullcalendar.min.js');
 		return path;
@@ -153,8 +202,10 @@ module.exports = function(grunt) {
 
 	// copy license and changelog
 	config.copy.archiveMisc = {
-		src: [ 'license.txt', 'changelog.txt' ],
-		dest: 'build/archive/'
+		files: {
+			'build/archive/license.txt': 'license.txt',
+			'build/archive/changelog.txt': 'changelog.md'
+		}
 	};
 
 	// create the ZIP
@@ -177,7 +228,6 @@ module.exports = function(grunt) {
 	----------------------------------------------------------------------------------------------------*/
 
 	grunt.registerTask('bower', 'Build the FullCalendar Bower component', [
-		'clean:modules',
 		'clean:bower',
 		'modules',
 		'copy:bowerModules',
@@ -220,7 +270,6 @@ module.exports = function(grunt) {
 	----------------------------------------------------------------------------------------------------*/
 
 	grunt.registerTask('cdnjs', 'Build files for CDNJS\'s hosted version of FullCalendar', [
-		'clean:modules',
 		'clean:cdnjs',
 		'modules',
 		'copy:cdnjsModules',
@@ -253,4 +302,7 @@ module.exports = function(grunt) {
 
 	// finally, give grunt the config object...
 	grunt.initConfig(config);
+
+
+	grunt.loadTasks('tasks');
 };

+ 209 - 0
tasks/generateLanguages.js

@@ -0,0 +1,209 @@
+
+module.exports = function(grunt) {
+
+	var config = grunt.config('generateLanguages');
+
+
+	grunt.registerTask('generateLanguages', function() {
+
+		var combinedJS = '';
+		var languageCnt = 0;
+		var skippedLangCodes = [];
+
+		grunt.file.mkdir(config.dest, 0755);
+
+		grunt.file.expand(config.moment + '/*.js').forEach(function(momentPath) {
+
+			var langCode = momentPath.match(/([^\/]*)\.js$/)[1];
+			var js = getLangJS(langCode, momentPath);
+
+			if (js) {
+
+				grunt.file.write(
+					config.dest + '/' + langCode + '.js',
+					wrapWithUMD(js)
+				);
+
+				combinedJS += wrapWithClosure(js) + '\n';
+
+				languageCnt++;
+			}
+			else {
+				skippedLangCodes.push(langCode);
+			}
+
+		});
+
+		// code for resetting the language back to English
+		combinedJS += '\nmoment.lang("en");';
+		combinedJS += '\n$.fullCalendar.lang("en");';
+		combinedJS += '\nif ($.datepicker) $.datepicker.setDefaults($.datepicker.regional[""]);';
+
+		grunt.file.write(
+			config.dest + '/all.js',
+			wrapWithUMD(combinedJS)
+		);
+
+		grunt.log.writeln(skippedLangCodes.length + ' skipped languages: ' + skippedLangCodes.join(', '));
+		grunt.log.writeln(languageCnt + ' generated languages.');
+
+	});
+
+
+	function getLangJS(langCode, momentPath) {
+		
+		var shortLangCode;
+		var momentLangJS;
+		var datepickerLangJS;
+		var fullCalendarLangJS;
+
+		// given "fr-ca", get just "fr"
+		if (langCode.indexOf('-') != -1) {
+			shortLangCode = langCode.replace(/-.*/, '');
+		}
+
+		momentLangJS = getMomentLangJS(momentPath);
+
+		datepickerLangJS = getDatepickerLangJS(langCode);
+		if (!datepickerLangJS && shortLangCode) {
+			datepickerLangJS = getDatepickerLangJS(shortLangCode, langCode);
+		}
+
+		fullCalendarLangJS = getFullCalendarLangJS(langCode);
+		if (!fullCalendarLangJS && shortLangCode) {
+			fullCalendarLangJS = getFullCalendarLangJS(shortLangCode, langCode);
+		}
+
+		// If this is an "en" language, only the Moment config is needed.
+		// For all other languages, all 3 configs are needed.
+		if (momentLangJS && (shortLangCode == 'en' || (datepickerLangJS && fullCalendarLangJS))) {
+
+			// if there is no definition, we still need to tell FC to set the default
+			if (!fullCalendarLangJS) {
+				fullCalendarLangJS = '$.fullCalendar.lang("' + langCode + '");';
+			}
+
+			datepickerLangJS = datepickerLangJS || '';
+
+			return momentLangJS + '\n' +
+				datepickerLangJS + '\n' +
+				fullCalendarLangJS;
+		}
+	}
+
+
+	function wrapWithUMD(body) {
+		return [
+			'(function(factory) {',
+			'    if (typeof define === "function" && define.amd) {',
+			'        define([ "jquery", "moment" ], factory);',
+			'    }',
+			'    else {',
+			'        factory(jQuery, moment);',
+			'    }',
+			'})(function($, moment) {',
+			'',
+			body,
+			'',
+			'});'
+		].join('\n');
+	}
+
+
+	function wrapWithClosure(body) {
+		return [
+			'(function() {',
+			'',
+			body,
+			'',
+			'})();'
+		].join('\n');
+	}
+
+
+	function getMomentLangJS(path) { // file assumed to exist
+
+		var js = grunt.file.read(path);
+
+		js = js.replace( // remove the UMD wrap
+			/\(\s*function[\S\s]*?function\s*\(\s*moment\s*\)\s*\{([\S\s]*)\}\)\);?/,
+			function(m0, body) {
+				body = body.replace(/^    /mg, ''); // remove 1 level of indentation
+				return body;
+			}
+		);
+
+		js = js.replace( // replace the `return` statement so execution continues
+			/^(\s*)return moment\.lang\(/m,
+			'$1moment.lang('
+		);
+
+		return js;
+	}
+
+
+	function getDatepickerLangJS(langCode, targetLangCode) {
+
+		// convert "en-ca" to "en-CA"
+		var datepickerLangCode = langCode.replace(/\-(\w+)/, function(m0, m1) {
+			return '-' + m1.toUpperCase();
+		});
+
+		var path = config.datepicker + '/jquery.ui.datepicker-' + datepickerLangCode + '.js';
+		var js;
+
+		try {
+			js = grunt.file.read(path);
+		}
+		catch (ex) {
+			return false;
+		}
+
+		js = js.replace(
+			/^jQuery\([\S\s]*?\{([\S\s]*)\}\);?/m, // inside the jQuery(function) wrap,
+			function(m0, body) {                   // use only the function body, modified.
+
+				var match = body.match(/\$\.datepicker\.regional[\S\s]*?(\{[\S\s]*?\});?/);
+				var props = match[1];
+
+				// remove 1 level of tab indentation
+				props = props.replace(/^\t/mg, '');
+
+				return "$.fullCalendar.datepickerLang(" +
+					"'" + (targetLangCode || langCode) + "', " + // for FullCalendar
+					"'" + datepickerLangCode + "', " + // for datepicker
+					props +
+					");";
+			}
+		);
+
+		return js;
+	}
+
+
+	function getFullCalendarLangJS(langCode, targetLangCode) {
+
+		var path = config.fullCalendar + '/' + langCode + '.js';
+		var js;
+
+		try {
+			js = grunt.file.read(path);
+		}
+		catch (ex) {
+			return false;
+		}
+
+		// if we originally wanted "ar-ma", but only "ar" is available, we have to adjust
+		// the declaration
+		if (targetLangCode && targetLangCode != langCode) {
+			js = js.replace(
+				/\$\.fullCalendar\.lang\(['"]([^'"]*)['"]/,
+				'$.fullCalendar.lang("' + targetLangCode + '"'
+			);
+		}
+
+		return js;
+	}
+
+
+};