UNPKG

globalize-compiler

Version:

Globalize.js runtime compiler for your formatters and parsers

259 lines (225 loc) 7.45 kB
var COMPILED_ORDER, DEPENDENCIES, DEPENDENCIES_VARS, dataCache, fnPlaceholder, fnPlaceholderRe, regexpPlaceholder, regexpPlaceholderRe, template, undefinedPlaceholder, undefinedPlaceholderRe, extend = require( "util" )._extend, fs = require( "fs" ); COMPILED_ORDER = [ // No dependencies. "numberToPartsFormatter", "numberFormatter", "numberParser", "pluralGenerator", // Depends on plural. "messageFormatter", // Depends on number and/or plural. "currencyToPartsFormatter", "currencyFormatter", "dateToPartsFormatter", "dateFormatter", "dateParser", "relativeTimeFormatter", "unitFormatter" ]; DEPENDENCIES = { currencyFormatter: { currency: true, number: true }, dateFormatter: { date: true }, dateToPartsFormatter: { date: true }, dateParser: { date: true }, messageFormatter: { message: true }, numberFormatter: { number: true }, numberParser: { number: true }, pluralGenerator: { plural: true }, relativeTimeFormatter: { number: true, plural: true, "relative-time": true }, unitFormatter: { number: true, plural: true, unit: true } }; DEPENDENCIES_VARS = { currencyFormatter: { currencyFormatterFn: true }, currencyToPartsFormatter: { currencyToPartsFormatterFn: true }, dateFormatter: { dateFormatterFn: true }, dateToPartsFormatter: { dateToPartsFormatterFn: true }, dateParser: { dateParserFn: true }, messageFormatter: { messageFormatterFn: true }, numberFormatter: { numberFormatterFn: true, numberRound: true }, numberToPartsFormatter: { numberToPartsFormatterFn: true, numberRound: true }, numberParser: { numberParserFn: true }, pluralGenerator: { pluralGeneratorFn: true }, relativeTimeFormatter: { relativeTimeFormatterFn: true }, unitFormatter: { unitFormatterFn: true } }; dataCache = {}; template = fs.readFileSync( __dirname + "/compile.template" ).toString( "utf-8" ); function functionName( fn ) { return /^function\s+([\w\$]+)\s*\(/.exec( fn.toString() )[ 1 ]; } fnPlaceholder = "fnPlaceholderBRVOhnVwmkNxKbCxydG9dZLhwf4puXOzkscBSgwk", fnPlaceholderRe = new RegExp( "\"" + fnPlaceholder + "\"", "g" ), regexpPlaceholder = "regexpPlaceholderBRVOhnVwmkNxKbCxydG9dZLhwf4puXOzkscBSgwk", regexpPlaceholderRe = new RegExp( "\"" + regexpPlaceholder + "\"", "g" ), undefinedPlaceholder = "undefinedPlaceholderBRVOhnVwmkNxKbCxydG9dZLhwf4puXOzkscBSgwk", undefinedPlaceholderRe = new RegExp( "\"" + undefinedPlaceholder + "\"", "g" ); function stringifyIncludingFunctionsAndRegExpsAndUndefined( object ) { var json, fns = [], regexps = []; json = JSON.stringify( object, function( key, value ) { if ( typeof value === "function" ) { fns.push( value ); return fnPlaceholder; } else if ( value instanceof RegExp ) { regexps.push( value ); return regexpPlaceholder; } else if ( value === undefined ) { return undefinedPlaceholder; } return value; }); return json.replace( fnPlaceholderRe, function() { var fn = fns.shift(); if ( "generatorString" in fn ) { return fn.generatorString(); } else if ( "dataCacheKey" in fn ) { dataCache[ fn.dataCacheKey ] = fn(); return "getCache(\"" + fn.dataCacheKey + "\")"; } else { return fn.toString(); } }).replace( regexpPlaceholderRe, function() { return regexps.shift().toString(); }).replace( undefinedPlaceholderRe, "" ); } function compile( formatterOrParser ) { var fnName = /^function\s+([\w\$]+)\s*\(/.exec( formatterOrParser.toString() )[ 1 ], runtimeKey = formatterOrParser.runtimeKey, runtimeArgs = formatterOrParser.runtimeArgs; runtimeArgs = runtimeArgs.map( stringifyIncludingFunctionsAndRegExpsAndUndefined ).join( ", " ); return "Globalize." + runtimeKey + " = " + fnName + "Fn(" + runtimeArgs + ");"; } function deduceDependenciesVars( formatterOrParser ) { return DEPENDENCIES_VARS[ functionName( formatterOrParser ) ]; } /** * Default template function * properties: * - compiled * - dependencies */ function defaultTemplate( properties ) { var params = {}; params.compiled = properties.code; params.dependenciesAmd = JSON.stringify( properties.dependencies ); params.dependenciesCjs = properties.dependencies.map(function( dependency ) { return "require(\"globalize/dist/" + dependency + "\")"; }).join( ", " ); return template.replace( /{{[a-zA-Z]+}}/g, function( name ) { name = name.slice( 2, -2 ); return params[ name ]; }); } /** * Returns a string with the compiled formatters and parsers. * * @param {formattersAndParsers} object or array * @param {options} object */ function compiler( formattersAndParsers, options ) { var code, dependencies, dependenciesVars, templateFn, dataCacheCode = "", extractedFormattersAndParsers = [], formattersAndParsersKeys = []; options = options || {}; templateFn = options.template || defaultTemplate; // Extract Formatters and Parsers from arguments (and its nested formatters and parsers). function extractFormattersAndParsers( object ) { JSON.stringify( object, function( key, value ) { // If a node is a formatter or a parser function, push it to our Array. if ( typeof value === "function" && "runtimeArgs" in value ) { extractedFormattersAndParsers.push( value ); // ... and do the same for its runtimeArgs (extract nested formatters or parsers). extractFormattersAndParsers( value.runtimeArgs ); } return value; }); } function uniqueFormattersAndParsers( formatterOrParser ) { var filter = formattersAndParsersKeys.indexOf( formatterOrParser.runtimeKey ) === -1; formattersAndParsersKeys.push( formatterOrParser.runtimeKey ); return filter; } extractFormattersAndParsers( formattersAndParsers ); extractedFormattersAndParsers = extractedFormattersAndParsers.filter( uniqueFormattersAndParsers ); if ( !extractedFormattersAndParsers.length ) { throw new Error( "No formatters or parsers has been provided" ); } // Generate the compiled functions. code = extractedFormattersAndParsers.sort(function( a, b ) { a = functionName( a ); b = functionName( b ); return COMPILED_ORDER.indexOf( a ) - COMPILED_ORDER.indexOf( b ); }).map( compile ).join( "\n" ); // Generate the optional dataCache. if ( Object.keys( dataCache ).length ) { dataCacheCode = "var dataCache = " + JSON.stringify( dataCache ) + ";\n" + "function getCache(key) {\n" + " return function() {\n" + " return dataCache[key];\n" + " };\n" + "}\n"; } // Generate dependency assignments and requirements. dependencies = Object.keys( extractedFormattersAndParsers.map( functionName ).reduce(function( sum, i ) { return extend( sum, DEPENDENCIES[ i ] ); }, {})); dependencies = dependencies.map(function( dependency ) { return "globalize-runtime/" + dependency; }); dependenciesVars = extractedFormattersAndParsers .map( deduceDependenciesVars ) .reduce(function( sum, i ) { return extend( sum, i ); }, {}); dependenciesVars = Object.keys( dependenciesVars ) .map(function( dependency ) { return "var " + dependency + " = Globalize._" + dependency + ";"; }).join( "\n" ); code = dependenciesVars + "\n\n" + dataCacheCode + code; /* // Generate exports. if ( args.length === 1 ) { properties.exports = args[ 0 ]; } else { properties.exports = args; } properties.exports = "return " + stringifyIncludingFunctionsAndUndefined( properties.exports ); */ return templateFn({ code: code, dependencies: dependencies }); } module.exports = compiler;