<commit_before>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

/**
 * Execute a cordova command.  It is up to the native side whether this action
 * is synchronous or asynchronous.  The native side can return:
 *      Synchronous: PluginResult object as a JSON string
 *      Asynchronous: Empty string ""
 * If async, the native side will cordova.callbackSuccess or cordova.callbackError,
 * depending upon the result of the action.
 *
 * @param {Function} success    The success callback
 * @param {Function} fail       The fail callback
 * @param {String} service      The name of the service to use
 * @param {String} action       Action to be run in cordova
 * @param {String[]} [args]     Zero or more arguments to pass to the method
 */
var cordova = require('cordova'),
    nativeApiProvider = require('cordova/plugin/android/nativeapiprovider'),
    utils = require('cordova/utils'),
    base64 = require('cordova/base64'),
    jsToNativeModes = {
        PROMPT: 0,
        JS_OBJECT: 1,
        // This mode is currently for benchmarking purposes only. It must be enabled
        // on the native side through the ENABLE_LOCATION_CHANGE_EXEC_MODE
        // constant within CordovaWebViewClient.java before it will work.
        LOCATION_CHANGE: 2
    },
    nativeToJsModes = {
        // Polls for messages using the JS->Native bridge.
        POLLING: 0,
        // For LOAD_URL to be viable, it would need to have a work-around for
        // the bug where the soft-keyboard gets dismissed when a message is sent.
        LOAD_URL: 1,
        // For the ONLINE_EVENT to be viable, it would need to intercept all event
        // listeners (both through addEventListener and window.ononline) as well
        // as set the navigator property itself.
        ONLINE_EVENT: 2,
        // Uses reflection to access private APIs of the WebView that can send JS
        // to be executed.
        // Requires Android 3.2.4 or above.
        PRIVATE_API: 3
    },
    jsToNativeBridgeMode,  // Set lazily.
    nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,
    pollEnabled = false,
    messagesFromNative = [];

function androidExec(success, fail, service, action, args) {
    // Set default bridge modes if they have not already been set.
    // By default, we use the failsafe, since addJavascriptInterface breaks too often
    if (jsToNativeBridgeMode === undefined) {
        androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
    }

    // Process any ArrayBuffers in the args into a string.
    for (var i = 0; i < args.length; i++) {
        if (utils.typeName(args[i]) == 'ArrayBuffer') {
            args[i] = utils.encodeBase64(args[i]);
        }
    }

    var callbackId = service + cordova.callbackId++,
        argsJson = JSON.stringify(args);

    if (success || fail) {
        cordova.callbacks[callbackId] = {success:success, fail:fail};
    }

    if (jsToNativeBridgeMode == jsToNativeModes.LOCATION_CHANGE) {
        window.location = 'http://cdv_exec/' + service + '#' + action + '#' + callbackId + '#' + argsJson;
    } else {
        var messages = nativeApiProvider.get().exec(service, action, callbackId, argsJson);
        // If argsJson was received by Java as null, try again with the PROMPT bridge mode.
        // This happens in rare circumstances, such as when certain Unicode characters are passed over the bridge on a Galaxy S2.  See CB-2666.
        if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && messages === "@Null arguments.") {
            androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
            androidExec(success, fail, service, action, args);
            androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
            return;
        } else {
            androidExec.processMessages(messages);
        }
    }
}

function pollOnce() {
    var msg = nativeApiProvider.get().retrieveJsMessages();
    androidExec.processMessages(msg);
}

function pollingTimerFunc() {
    if (pollEnabled) {
        pollOnce();
        setTimeout(pollingTimerFunc, 50);
    }
}

function hookOnlineApis() {
    function proxyEvent(e) {
        cordova.fireWindowEvent(e.type);
    }
    // The network module takes care of firing online and offline events.
    // It currently fires them only on document though, so we bridge them
    // to window here (while first listening for exec()-releated online/offline
    // events).
    window.addEventListener('online', pollOnce, false);
    window.addEventListener('offline', pollOnce, false);
    cordova.addWindowEventHandler('online');
    cordova.addWindowEventHandler('offline');
    document.addEventListener('online', proxyEvent, false);
    document.addEventListener('offline', proxyEvent, false);
}

hookOnlineApis();

androidExec.jsToNativeModes = jsToNativeModes;
androidExec.nativeToJsModes = nativeToJsModes;

androidExec.setJsToNativeBridgeMode = function(mode) {
    if (mode == jsToNativeModes.JS_OBJECT && !window._cordovaNative) {
        console.log('Falling back on PROMPT mode since _cordovaNative is missing. Expected for Android 3.2 and lower only.');
        mode = jsToNativeModes.PROMPT;
    }
    nativeApiProvider.setPreferPrompt(mode == jsToNativeModes.PROMPT);
    jsToNativeBridgeMode = mode;
};

androidExec.setNativeToJsBridgeMode = function(mode) {
    if (mode == nativeToJsBridgeMode) {
        return;
    }
    if (nativeToJsBridgeMode == nativeToJsModes.POLLING) {
        pollEnabled = false;
    }

    nativeToJsBridgeMode = mode;
    // Tell the native side to switch modes.
    nativeApiProvider.get().setNativeToJsBridgeMode(mode);

    if (mode == nativeToJsModes.POLLING) {
        pollEnabled = true;
        setTimeout(pollingTimerFunc, 1);
    }
};

// Processes a single message, as encoded by NativeToJsMessageQueue.java.
function processMessage(message) {
    try {
        var firstChar = message.charAt(0);
        if (firstChar == 'J') {
            eval(message.slice(1));
        } else if (firstChar == 'S' || firstChar == 'F') {
            var success = firstChar == 'S';
            var keepCallback = message.charAt(1) == '1';
            var spaceIdx = message.indexOf(' ', 2);
            var status = +message.slice(2, spaceIdx);
            var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1);
            var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx);
            var payloadKind = message.charAt(nextSpaceIdx + 1);
            var payload;
            if (payloadKind == 's') {
                payload = message.slice(nextSpaceIdx + 2);
            } else if (payloadKind == 't') {
                payload = true;
            } else if (payloadKind == 'f') {
                payload = false;
            } else if (payloadKind == 'N') {
                payload = null;
            } else if (payloadKind == 'n') {
                payload = +message.slice(nextSpaceIdx + 2);
            } else if (payloadKind == 'A') {
                var data = message.slice(nextSpaceIdx + 2);
                var bytes = window.atob(data);
                var arraybuffer = new Uint8Array(bytes.length);
                for (var i = 0; i < bytes.length; i++) {
                    arraybuffer[i] = bytes.charCodeAt(i);
                }
                payload = arraybuffer.buffer;
            } else if (payloadKind == 'S') {
                payload = window.atob(message.slice(nextSpaceIdx + 2));
            } else {
                payload = JSON.parse(message.slice(nextSpaceIdx + 1));
            }
            cordova.callbackFromNative(callbackId, success, status, [payload], keepCallback);
        } else {
            console.log("processMessage failed: invalid message:" + message);
        }
    } catch (e) {
        console.log("processMessage failed: Message: " + message);
        console.log("processMessage failed: Error: " + e);
        console.log("processMessage failed: Stack: " + e.stack);
    }
}

// This is called from the NativeToJsMessageQueue.java.
androidExec.processMessages = function(messages) {
    if (messages) {
        messagesFromNative.push(messages);
        // Check for the reentrant case, and enqueue the message if that's the case.
        if (messagesFromNative.length > 1) {
            return;
        }
        while (messagesFromNative.length) {
            // Don't unshift until the end so that reentrancy can be detected.
            messages = messagesFromNative[0];
            // The Java side can send a * message to indicate that it
            // still has messages waiting to be retrieved.
            if (messages == '*') {
                messagesFromNative.shift();
                window.setTimeout(pollOnce, 0);
                return;
            }

            var spaceIdx = messages.indexOf(' ');
            var msgLen = +messages.slice(0, spaceIdx);
            var message = messages.substr(spaceIdx + 1, msgLen);
            messages = messages.slice(spaceIdx + msgLen + 1);
            processMessage(message);
            if (messages) {
                messagesFromNative[0] = messages;
            } else {
                messagesFromNative.shift();
            }
        }
    }
};

module.exports = androidExec;
<commit_msg>[CB-4004] Fix Android JS bridge break caused by bad rebase before 5ba835c<commit_after>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

/**
 * Execute a cordova command.  It is up to the native side whether this action
 * is synchronous or asynchronous.  The native side can return:
 *      Synchronous: PluginResult object as a JSON string
 *      Asynchronous: Empty string ""
 * If async, the native side will cordova.callbackSuccess or cordova.callbackError,
 * depending upon the result of the action.
 *
 * @param {Function} success    The success callback
 * @param {Function} fail       The fail callback
 * @param {String} service      The name of the service to use
 * @param {String} action       Action to be run in cordova
 * @param {String[]} [args]     Zero or more arguments to pass to the method
 */
var cordova = require('cordova'),
    nativeApiProvider = require('cordova/plugin/android/nativeapiprovider'),
    utils = require('cordova/utils'),
    base64 = require('cordova/base64'),
    jsToNativeModes = {
        PROMPT: 0,
        JS_OBJECT: 1,
        // This mode is currently for benchmarking purposes only. It must be enabled
        // on the native side through the ENABLE_LOCATION_CHANGE_EXEC_MODE
        // constant within CordovaWebViewClient.java before it will work.
        LOCATION_CHANGE: 2
    },
    nativeToJsModes = {
        // Polls for messages using the JS->Native bridge.
        POLLING: 0,
        // For LOAD_URL to be viable, it would need to have a work-around for
        // the bug where the soft-keyboard gets dismissed when a message is sent.
        LOAD_URL: 1,
        // For the ONLINE_EVENT to be viable, it would need to intercept all event
        // listeners (both through addEventListener and window.ononline) as well
        // as set the navigator property itself.
        ONLINE_EVENT: 2,
        // Uses reflection to access private APIs of the WebView that can send JS
        // to be executed.
        // Requires Android 3.2.4 or above.
        PRIVATE_API: 3
    },
    jsToNativeBridgeMode,  // Set lazily.
    nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,
    pollEnabled = false,
    messagesFromNative = [];

function androidExec(success, fail, service, action, args) {
    // Set default bridge modes if they have not already been set.
    // By default, we use the failsafe, since addJavascriptInterface breaks too often
    if (jsToNativeBridgeMode === undefined) {
        androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
    }

    // Process any ArrayBuffers in the args into a string.
    for (var i = 0; i < args.length; i++) {
        if (utils.typeName(args[i]) == 'ArrayBuffer') {
            args[i] = base64.fromArrayBuffer(args[i]);
        }
    }

    var callbackId = service + cordova.callbackId++,
        argsJson = JSON.stringify(args);

    if (success || fail) {
        cordova.callbacks[callbackId] = {success:success, fail:fail};
    }

    if (jsToNativeBridgeMode == jsToNativeModes.LOCATION_CHANGE) {
        window.location = 'http://cdv_exec/' + service + '#' + action + '#' + callbackId + '#' + argsJson;
    } else {
        var messages = nativeApiProvider.get().exec(service, action, callbackId, argsJson);
        // If argsJson was received by Java as null, try again with the PROMPT bridge mode.
        // This happens in rare circumstances, such as when certain Unicode characters are passed over the bridge on a Galaxy S2.  See CB-2666.
        if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT && messages === "@Null arguments.") {
            androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT);
            androidExec(success, fail, service, action, args);
            androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT);
            return;
        } else {
            androidExec.processMessages(messages);
        }
    }
}

function pollOnce() {
    var msg = nativeApiProvider.get().retrieveJsMessages();
    androidExec.processMessages(msg);
}

function pollingTimerFunc() {
    if (pollEnabled) {
        pollOnce();
        setTimeout(pollingTimerFunc, 50);
    }
}

function hookOnlineApis() {
    function proxyEvent(e) {
        cordova.fireWindowEvent(e.type);
    }
    // The network module takes care of firing online and offline events.
    // It currently fires them only on document though, so we bridge them
    // to window here (while first listening for exec()-releated online/offline
    // events).
    window.addEventListener('online', pollOnce, false);
    window.addEventListener('offline', pollOnce, false);
    cordova.addWindowEventHandler('online');
    cordova.addWindowEventHandler('offline');
    document.addEventListener('online', proxyEvent, false);
    document.addEventListener('offline', proxyEvent, false);
}

hookOnlineApis();

androidExec.jsToNativeModes = jsToNativeModes;
androidExec.nativeToJsModes = nativeToJsModes;

androidExec.setJsToNativeBridgeMode = function(mode) {
    if (mode == jsToNativeModes.JS_OBJECT && !window._cordovaNative) {
        console.log('Falling back on PROMPT mode since _cordovaNative is missing. Expected for Android 3.2 and lower only.');
        mode = jsToNativeModes.PROMPT;
    }
    nativeApiProvider.setPreferPrompt(mode == jsToNativeModes.PROMPT);
    jsToNativeBridgeMode = mode;
};

androidExec.setNativeToJsBridgeMode = function(mode) {
    if (mode == nativeToJsBridgeMode) {
        return;
    }
    if (nativeToJsBridgeMode == nativeToJsModes.POLLING) {
        pollEnabled = false;
    }

    nativeToJsBridgeMode = mode;
    // Tell the native side to switch modes.
    nativeApiProvider.get().setNativeToJsBridgeMode(mode);

    if (mode == nativeToJsModes.POLLING) {
        pollEnabled = true;
        setTimeout(pollingTimerFunc, 1);
    }
};

// Processes a single message, as encoded by NativeToJsMessageQueue.java.
function processMessage(message) {
    try {
        var firstChar = message.charAt(0);
        if (firstChar == 'J') {
            eval(message.slice(1));
        } else if (firstChar == 'S' || firstChar == 'F') {
            var success = firstChar == 'S';
            var keepCallback = message.charAt(1) == '1';
            var spaceIdx = message.indexOf(' ', 2);
            var status = +message.slice(2, spaceIdx);
            var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1);
            var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx);
            var payloadKind = message.charAt(nextSpaceIdx + 1);
            var payload;
            if (payloadKind == 's') {
                payload = message.slice(nextSpaceIdx + 2);
            } else if (payloadKind == 't') {
                payload = true;
            } else if (payloadKind == 'f') {
                payload = false;
            } else if (payloadKind == 'N') {
                payload = null;
            } else if (payloadKind == 'n') {
                payload = +message.slice(nextSpaceIdx + 2);
            } else if (payloadKind == 'A') {
                var data = message.slice(nextSpaceIdx + 2);
                var bytes = window.atob(data);
                var arraybuffer = new Uint8Array(bytes.length);
                for (var i = 0; i < bytes.length; i++) {
                    arraybuffer[i] = bytes.charCodeAt(i);
                }
                payload = arraybuffer.buffer;
            } else if (payloadKind == 'S') {
                payload = window.atob(message.slice(nextSpaceIdx + 2));
            } else {
                payload = JSON.parse(message.slice(nextSpaceIdx + 1));
            }
            cordova.callbackFromNative(callbackId, success, status, [payload], keepCallback);
        } else {
            console.log("processMessage failed: invalid message:" + message);
        }
    } catch (e) {
        console.log("processMessage failed: Message: " + message);
        console.log("processMessage failed: Error: " + e);
        console.log("processMessage failed: Stack: " + e.stack);
    }
}

// This is called from the NativeToJsMessageQueue.java.
androidExec.processMessages = function(messages) {
    if (messages) {
        messagesFromNative.push(messages);
        // Check for the reentrant case, and enqueue the message if that's the case.
        if (messagesFromNative.length > 1) {
            return;
        }
        while (messagesFromNative.length) {
            // Don't unshift until the end so that reentrancy can be detected.
            messages = messagesFromNative[0];
            // The Java side can send a * message to indicate that it
            // still has messages waiting to be retrieved.
            if (messages == '*') {
                messagesFromNative.shift();
                window.setTimeout(pollOnce, 0);
                return;
            }

            var spaceIdx = messages.indexOf(' ');
            var msgLen = +messages.slice(0, spaceIdx);
            var message = messages.substr(spaceIdx + 1, msgLen);
            messages = messages.slice(spaceIdx + msgLen + 1);
            processMessage(message);
            if (messages) {
                messagesFromNative[0] = messages;
            } else {
                messagesFromNative.shift();
            }
        }
    }
};

module.exports = androidExec;
<|endoftext|>


<commit_before>
module.exports = appendpoints;
appendpoints.endpoints = appendpoints;

appendpoints.usage = "fhc appendpoints <app-id> [dev|live]";

var log = require("./utils/log");
var fhc = require("./fhc");
var fhreq = require("./utils/request");
var common = require("./common");
var util = require('util');
var async = require('async');
var path = require('path');
var fs = require('fs');
var url = require('url');
var https = require('https');
var exec = require("./utils/exec.js");
var millicore = require("./utils/millicore.js");
var ini = require('./utils/ini');
var Table = require('cli-table');

// Main ping entry point
function appendpoints (args, cb) {
  if (args.length === 0){
    return cb(appendpoints.usage);
  }

  var appId = fhc.appId(args[0]);
  var deployTarget = args[1];
  return getAppEndPoints(appId, deployTarget, cb);
};

function getAppEndPoints(appId, target, cb){
  target = target || "dev";
  common.doApiCall(fhreq.getFeedHenryUrl(), "box/srv/1.1/ide/" + fhc.target + "/app/endpoints", {guid: appId, deploytarget: target}, "Error getting app endpoints: ", function(err, data) {
    if (err) return cb(err);
    log.info("Endpoints: " + data);
    log.silly(data, "app endpoints");
    return cb(err, data);
  });
}

<commit_msg>Tweak to log.info<commit_after>
module.exports = appendpoints;
appendpoints.endpoints = appendpoints;

appendpoints.usage = "fhc appendpoints <app-id> [dev|live]";

var log = require("./utils/log");
var fhc = require("./fhc");
var fhreq = require("./utils/request");
var common = require("./common");
var util = require('util');
var async = require('async');
var path = require('path');
var fs = require('fs');
var url = require('url');
var https = require('https');
var exec = require("./utils/exec.js");
var millicore = require("./utils/millicore.js");
var ini = require('./utils/ini');
var Table = require('cli-table');

// Main ping entry point
function appendpoints (args, cb) {
  if (args.length === 0){
    return cb(appendpoints.usage);
  }

  var appId = fhc.appId(args[0]);
  var deployTarget = args[1];
  return getAppEndPoints(appId, deployTarget, cb);
};

function getAppEndPoints(appId, target, cb){
  target = target || "dev";
  common.doApiCall(fhreq.getFeedHenryUrl(), "box/srv/1.1/ide/" + fhc.target + "/app/endpoints", {guid: appId, deploytarget: target}, "Error getting app endpoints: ", function(err, data) {
    if (err) return cb(err);
    log.info("Endpoints: " + util.inspect(data));
    return cb(err, data);
  });
}

<|endoftext|>


<commit_before>/**
 * Created by pgotthardt on 07/12/15.
 */

var Promise = require('bluebird');

/**
 * Choose the most correct version of webpack, prefer locally installed version,
 * fallback to the own dependency if there's none.
 * @returns {*}
 */
function getWebpack() {
    try {
        return require(process.cwd() + '/node_modules/webpack');
    } catch(e) {
        return require('webpack');
    }
}

function getAppName(webpackConfig) {
    return webpackConfig.name || webpackConfig.output.filename;
}

function getOutputOptions(webpackConfig, options) {
    var outputOptions = Object.create(webpackConfig.stats || {});
    if(typeof options.modulesSort !== 'undefined') {
        outputOptions.modulesSort = options.modulesSort;
    }
    if(typeof options.chunksSort !== 'undefined') {
        outputOptions.chunksSort = options.chunksSort;
    }
    if(typeof options.assetsSort !== 'undefined') {
        outputOptions.assetsSort = options.assetsSort;
    }
    if(typeof options.exclude !== 'undefined') {
        outputOptions.exclude = options.exclude;
    }
    if(typeof options.colors !== 'undefined') {
        outputOptions.colors = options.colors;
    }
    return outputOptions;
}

/**
 * Create a single webpack build using the specified configuration.
 * Calls the done callback once it has finished its work.
 *
 * @param {string} configuratorFileName The app configuration filename
 * @param {boolean} watch If `true`, then the webpack watcher is being run; if `false`, runs only ones
 * @param {Number} index The configuration index
 * @param {Function} done The callback that should be invoked once this worker has finished the build.
 */
module.exports = function(configuratorFileName, options, index, done) {
    var config = require(configuratorFileName),
        watch = !!options.watch,
        silent = !!options.json;
    if(Array.isArray(config)) {
        config = config[index];
    }
    Promise.resolve(config).then(function(webpackConfig) {
        var webpack = getWebpack(),
            outputOptions = getOutputOptions(webpackConfig, options),
            finishedCallback = function(err, stats) {
                if(err) {
                    if(!silent) {
                        console.error('[WEBPACK] Error building %s', getAppName(webpackConfig));
                        console.log(err);
                    }
                    done(err);
                    return;
                }
                if(!silent) {
                    console.log(stats.toString(outputOptions));
                    console.log('[WEBPACK] Finished building %s', getAppName(webpackConfig));
                }
                if(!watch) {
                    done(null, JSON.stringify(stats.toJson(outputOptions), null, 2));
                }
            };
        if(!silent) {
            console.log('[WEBPACK] Started %s %s', watch ? 'watching' : 'building', getAppName(webpackConfig));
        }
        var compiler = webpack(webpackConfig),
            watcher;
        compiler.plugin('done', function(stats) {
            if (stats.compilation.errors && stats.compilation.errors.length) {
                if(!silent) {
                    console.log(stats.compilation.errors);
                }
                done(JSON.stringify(stats.toJson(outputOptions), null, 2));
                process.exit(1);
            }
        });
        if(watch || webpack.watch) {
            watcher = compiler.watch({}, finishedCallback);
        } else {
            compiler.run(finishedCallback);
        }

        process.on('SIGINT', function() {
            if(!silent) {
                console.log('[WEBPACK] Forcefully shutting down %s', getAppName(webpackConfig));
            }
            if(watcher) watcher.close(done);
        });
    });
};
<commit_msg>DOCUMENTATION: Update the webpackWorker signature<commit_after>/**
 * Created by pgotthardt on 07/12/15.
 */

var Promise = require('bluebird');

/**
 * Choose the most correct version of webpack, prefer locally installed version,
 * fallback to the own dependency if there's none.
 * @returns {*}
 */
function getWebpack() {
    try {
        return require(process.cwd() + '/node_modules/webpack');
    } catch(e) {
        return require('webpack');
    }
}

function getAppName(webpackConfig) {
    return webpackConfig.name || webpackConfig.output.filename;
}

function getOutputOptions(webpackConfig, options) {
    var outputOptions = Object.create(webpackConfig.stats || {});
    if(typeof options.modulesSort !== 'undefined') {
        outputOptions.modulesSort = options.modulesSort;
    }
    if(typeof options.chunksSort !== 'undefined') {
        outputOptions.chunksSort = options.chunksSort;
    }
    if(typeof options.assetsSort !== 'undefined') {
        outputOptions.assetsSort = options.assetsSort;
    }
    if(typeof options.exclude !== 'undefined') {
        outputOptions.exclude = options.exclude;
    }
    if(typeof options.colors !== 'undefined') {
        outputOptions.colors = options.colors;
    }
    return outputOptions;
}

/**
 * Create a single webpack build using the specified configuration.
 * Calls the done callback once it has finished its work.
 *
 * @param {string} configuratorFileName The app configuration filename
 * @param {Object} options The build options
 * @param {boolean} options.watch If `true`, then the webpack watcher is being run; if `false`, runs only ones
 * @param {boolean} options.json If `true`, then the webpack watcher will only report the result as JSON but not produce any other output
 * @param {Number} index The configuration index
 * @param {Function} done The callback that should be invoked once this worker has finished the build.
 */
module.exports = function(configuratorFileName, options, index, done) {
    var config = require(configuratorFileName),
        watch = !!options.watch,
        silent = !!options.json;
    if(Array.isArray(config)) {
        config = config[index];
    }
    Promise.resolve(config).then(function(webpackConfig) {
        var webpack = getWebpack(),
            outputOptions = getOutputOptions(webpackConfig, options),
            finishedCallback = function(err, stats) {
                if(err) {
                    if(!silent) {
                        console.error('[WEBPACK] Error building %s', getAppName(webpackConfig));
                        console.log(err);
                    }
                    done(err);
                    return;
                }
                if(!silent) {
                    console.log(stats.toString(outputOptions));
                    console.log('[WEBPACK] Finished building %s', getAppName(webpackConfig));
                }
                if(!watch) {
                    done(null, JSON.stringify(stats.toJson(outputOptions), null, 2));
                }
            };
        if(!silent) {
            console.log('[WEBPACK] Started %s %s', watch ? 'watching' : 'building', getAppName(webpackConfig));
        }
        var compiler = webpack(webpackConfig),
            watcher;
        compiler.plugin('done', function(stats) {
            if (stats.compilation.errors && stats.compilation.errors.length) {
                if(!silent) {
                    console.log(stats.compilation.errors);
                }
                done(JSON.stringify(stats.toJson(outputOptions), null, 2));
                process.exit(1);
            }
        });
        if(watch || webpack.watch) {
            watcher = compiler.watch({}, finishedCallback);
        } else {
            compiler.run(finishedCallback);
        }

        process.on('SIGINT', function() {
            if(!silent) {
                console.log('[WEBPACK] Forcefully shutting down %s', getAppName(webpackConfig));
            }
            if(watcher) watcher.close(done);
        });
    });
};
<|endoftext|>


<commit_before>'use strict'

var Path = require('path')
var spawn = require('child_process').spawn
var version = require('version')

var Log = require(Path.join(__base, 'src/worker/log.js'))
var LogWorker = new Log({
  module: 'Update'
})

var pkg = require(Path.join(__base, 'package.json'))

function Update (cb) {
  version.fetch('lunik-torrent', function (error, version) {
    if (error) {
      LogWorker.error(error)
      process.exit(1)
    } else {
      if (version === pkg.version) {
        LogWorker.info('You have the last version: ' + version)
        cb()
      } else {
        LogWorker.warning("You don't have the last version: " + version)
        if (__config.autoUpdate) {
          LogWorker.info('Auto updating the app.')
          var pull = spawn('git', ['pull'])
          pull.stdout.on('data', (data) => {
            LogWorker.info(data)
          })
          pull.stderr.on('data', (data) => {
            LogWorker.error(data)
          })
          pull.on('exit', (code) => {
            if (code) {
              LogWorker.error('Git pull fail with code: ' + code)
              process.exit(1)
            }
            var checkout = spawn('git', ['checkout', 'tags/' + version])
            checkout.stdout.on('data', (data) => {
              LogWorker.info(data)
            })
            checkout.stderr.on('data', (data) => {
              LogWorker.error(data)
            })
            checkout.on('exit', (code) => {
              if (code) {
                LogWorker.error('Git pull fail with code: ' + code)
                process.exit(1)
              }
              LogWorker.info('Update to ' + version + 'succeed.')
              cb()
            })
          })
        } else {
          LogWorker.warning('Auto update is disabled.')
          cb()
        }
      }
    }
  })
}
module.exports = Update
<commit_msg>remove checkout<commit_after>'use strict'

var Path = require('path')
var spawn = require('child_process').spawn
var version = require('version')

var Log = require(Path.join(__base, 'src/worker/log.js'))
var LogWorker = new Log({
  module: 'Update'
})

var pkg = require(Path.join(__base, 'package.json'))

function Update (cb) {
  version.fetch('lunik-torrent', function (error, version) {
    if (error) {
      LogWorker.error(error)
      process.exit(1)
    } else {
      if (version === pkg.version) {
        LogWorker.info('You have the last version: ' + version)
        cb()
      } else {
        LogWorker.warning("You don't have the last version: " + version)
        if (__config.autoUpdate) {
          LogWorker.info('Auto updating the app.')
          var pull = spawn('git', ['pull'])
          pull.stdout.on('data', (data) => {
            LogWorker.info(data)
          })
          pull.stderr.on('data', (data) => {
            LogWorker.error(data)
          })
          pull.on('exit', (code) => {
            if (code) {
              LogWorker.error('Git pull fail with code: ' + code)
              process.exit(1)
            }
            LogWorker.info('Update to ' + version + 'succeed.')
            cb()
          })
        } else {
          LogWorker.warning('Auto update is disabled.')
          cb()
        }
      }
    }
  })
}
module.exports = Update
<|endoftext|>


<commit_before>var stylize = require('../vows/console').stylize;
var inspect = require('../vows/console').inspect;

require('assert').AssertionError.prototype.toString = function () {
    var that = this,
        source;

    if (this.stack) {
        source = this.stack.match(/([a-zA-Z0-9._-]+\.js)(:\d+):\d+/);
    }

    function parse(str) {
        return str.replace(/{actual}/g,   inspect(that.actual)).
                   replace(/{operator}/g, stylize(that.operator, 'bold')).
                   replace(/{expected}/g, (that.expected instanceof Function)
                                          ? that.expected.name
                                          : inspect(that.expected));
    }

    if (this.message) {
        return stylize(parse(this.message), 'yellow') +
               (source) ? stylize(' // ' + source[1] + source[2], 'grey') : '';
    } else {
        return stylize([
            this.expected,
            this.operator,
            this.actual
        ].join(' '), 'yellow');
    }
};

<commit_msg>[fix] Fix failed assertions output<commit_after>var stylize = require('../vows/console').stylize;
var inspect = require('../vows/console').inspect;

require('assert').AssertionError.prototype.toString = function () {
    var that = this,
        source;

    if (this.stack) {
        source = this.stack.match(/([a-zA-Z0-9._-]+\.js)(:\d+):\d+/);
    }

    function parse(str) {
        return str.replace(/{actual}/g,   inspect(that.actual)).
                   replace(/{operator}/g, stylize(that.operator, 'bold')).
                   replace(/{expected}/g, (that.expected instanceof Function)
                                          ? that.expected.name
                                          : inspect(that.expected));
    }

    if (this.message) {
        return stylize(parse(this.message), 'yellow') +
               ((source) ? stylize(' // ' + source[1] + source[2], 'grey') : '');
    } else {
        return stylize([
            this.expected,
            this.operator,
            this.actual
        ].join(' '), 'yellow');
    }
};

<|endoftext|>


<commit_before>var fs = require('fs')
	, Buffer = require('buffer').Buffer
	, request = require('request')
	, Step = require('step')
	, jsmin = require('./../deps/jsmin').minify
	, htmlmin = require('./../deps/htmlmin').minify
	, cssmin = require('./../deps/cssmin').minify
	, crypto = require('crypto');

var zlib;
try {
	zlib = require('zlib');
} catch(e) {}

var cache = {}
	, settings = {}
	, cacheHashes = {}
	, cacheTimestamps = {};

module.exports = function assetManager (assets) {
	var self = this;

	settings = assets || settings;
	if (!settings) {
		throw new Exception('No asset groups found');
	}

	if (!settings.forEach) {
		settings.forEach = function(callback) {
			Object.keys(this).forEach(function(key) {
				if (key !== 'forEach') {
					callback(settings[key], key);
				}
			});
		};
	}

	Step(function() {
		var grouping = this.group();
		settings.forEach(function(group, groupName) {
			var patterns = []
			, insertions = []
			, matchInsertionCount = {};

			group.files.forEach(function(fileName, index) {
				var pattern = null;
				if (fileName.exec) { // Got a RegEx
					pattern = fileName;
				} else if (fileName.trim() === '*') {
					pattern = /\.[a-z]+$/i; // Anything with a extension
				}

				if (pattern) {
					patterns.push({
						pattern: pattern,
						index: index
					});
					matchInsertionCount['insert-'+index] = 0;
				}
			});

			var fileFetchCallback = grouping();
			fs.readdir(group.path, function(err, files) {
				if (err) {
					throw err;
				}
				files.forEach(function(fileName, index) {
					var alreadyIncluded = false,
						matchedPattern = false;

					group.files.forEach(function(includedFile) {
						if (alreadyIncluded || includedFile.trim && (includedFile.trim() === fileName.trim())) {
							alreadyIncluded = true;
						}
					});

					if (!alreadyIncluded) {
						patterns.forEach(function(pattern) {
							if (!matchedPattern && pattern.pattern.exec(fileName)) {
								matchedPattern = pattern;
							}
						});
					}
					if (matchedPattern) {
						insertions.push({
							file: fileName,
							index: matchedPattern.index
						});
					}
				});
				insertions.forEach(function(insertion, index) {
					if (!matchInsertionCount['insert-'+insertion.index]) {
						group.files.splice(insertion.index, 1, insertion.file);
					} else {
						group.files.splice(insertion.index+matchInsertionCount['insert-'+insertion.index], 0, insertion.file);
					}
					matchInsertionCount['insert-'+insertion.index] += 1;
				});
				fileFetchCallback(null, true);
			});
		});
	}, function(err, contents) {
		settings.forEach(function (group, groupName) {
			if (!group.stale) {
				group.files.forEach(function (file, index) {
					if (!file.match) {
						console.log('No match for: '+file);
						group.files.splice(index, 1);
						return;
					}
					if (file.match(/^https?:\/\//)) {
						return;
					}
					fs.watchFile(group.path + file, function (old, newFile) {
						if (old.mtime.toString() != newFile.mtime.toString()) {
							self.generateCache(groupName);
						}
					});
				});
			}
		});
		self.generateCache();
	});

	this.generateCache = function (generateGroup) {
		var self = this;
		settings.forEach(function (group, groupName) {
			var userAgentMatches = {};
			if (group.preManipulate) {
				Object.keys(group.preManipulate).forEach(function(key) {
					userAgentMatches[key] = true;
				});
			}
			if (group.postManipulate) {
				Object.keys(group.postManipulate).forEach(function(key) {
					userAgentMatches[key] = true;
				});
			}
			if (!Object.keys(userAgentMatches).length) {
				userAgentMatches = ['^'];
			} else {
				userAgentMatches = Object.keys(userAgentMatches);
			}

			userAgentMatches.forEach(function(match) {
				var path = group.path;
				Step(function () {
					var grouping = this.group();
					group.files.forEach(function (file) {
						if (!generateGroup || generateGroup && groupName === generateGroup) {
							self.getFile(file, path, groupName, grouping());
						}
					});
				}, function (err, contents) {
					if (err) {
						throw err;
					}
					var grouping = this.group();
					var lastModified = null;

					for (var i = 0, l = contents.length; i < l; i++) {
						var file = contents[i];
						if (typeof file == "string"){
							continue;
						}
						if (typeof file.modified != "undefined"){
							file.modified = new Date();
						}
						if (Object.prototype.toString.call(file.modified) === "[object Date]" && !isNaN(file.modified)){

						} else {
							file.modified = new Date();
						}
						if (!lastModified || lastModified.getTime() < file.modified.getTime()) {
							lastModified = file.modified;
						}
						if (!group.preManipulate) {
							group.preManipulate = {};
						}

						self.manipulate(group.preManipulate[match], file.content, file.filePath, i, i === l - 1, grouping());
					};
					if (!lastModified && !contents.length) {
						grouping();
						return;
					}
					cacheTimestamps[groupName] = lastModified.getTime();
					if (!cache[groupName]) {
						cache[groupName] = {};
					}
					cache[groupName][match] = {
						'modified': lastModified.toUTCString()
					};
				}, function (err, contents) {
					if (err) {
						throw err;
					}
					var grouping = this.group();

					var content = '';
					for (var i=0; i < contents.length; i++) {
						content += contents[i] + "\n";
					};
					var dataTypeLowerCase = group.dataType.toLowerCase();
					if (!group.debug) {
						if (dataTypeLowerCase === 'javascript' || dataTypeLowerCase === 'js') {
							(function (callback){callback(null, jsmin(content));})(grouping());
						} else if (dataTypeLowerCase === 'html') {
							(function (callback){callback(null, htmlmin(content));})(grouping());
						} else if (dataTypeLowerCase === 'css') {
							(function (callback){callback(null, cssmin(content));})(grouping());
						}
					} else {
						grouping()(null, content);
					}
				}, function (err, contents) {
					if (err) {
						throw err;
					}

					var grouping = this.group();

					var content = '';
					for (var i=0; i < contents.length; i++) {
						content += contents[i];
					};

					if (!group.postManipulate) {
						group.postManipulate = {};
					}
					self.manipulate(group.postManipulate[match], content, null, 0, true, grouping());

				}, function (err, contents) {
					if (err) {
						throw err;
					}

					var content = '';
					for (var i=0; i < contents.length; i++) {
						content += contents[i];
					};

					cacheHashes[groupName] = crypto.createHash('md5').update(content).digest('hex');

					cache[groupName][match].encodings = {};
					var encodings = cache[groupName][match].encodings;

					var utf8Buffer = new Buffer(content, 'utf8');
					encodings.utf8 = {
						'buffer': utf8Buffer,
						'length': utf8Buffer.length,
						'encoding': 'utf8'
					};

					if(zlib) {
						var gzipBuffer = zlib.gzip(utf8Buffer, function(error, result) {
							encodings.gzip = {
								'buffer': result,
								'length': result.length,
								'encoding': 'gzip'
					    		};
						});
					}
				});
			});
		});
	};

	this.manipulate = function (manipulateInstructions, fileContent, path, index, last, callback) {
		if (manipulateInstructions && Array.isArray(manipulateInstructions)) {
				var callIndex = 0;
				(function modify(content, path, index, last) {
					if (callIndex < manipulateInstructions.length) {
						callIndex++;
						manipulateInstructions[callIndex-1](content, path, index, last, function (content) {
							modify(content, path, index, last);
						});
					} else {
						callback(null, content);
					}
				})(fileContent, path, index, last);
		} else if (manipulateInstructions && typeof manipulateInstructions === 'function') {
			manipulateInstructions(fileContent, path, index, last, callback);
		} else {
			callback(null, fileContent);
		}
	};

	this.getFile = function (file, path, groupName, callback) {
		var isExternal = false;
		if (file && file.match(/^https?:\/\//)) {
			isExternal = true;
		}

		var fileInfo = {
			'filePath': isExternal ? file: path+file
		};

		if (isExternal) {
			request({uri: file}, function(err, res, body) {
				fileInfo.content = body;
				fileInfo.external = true;
				if (typeof res != "undefined" && res != null){
					fileInfo.modified = new Date(res.headers['last-modified']);
				}
				callback(null, fileInfo);
			});
		} else {
			setTimeout(function() {
				fs.readFile(path+file, function (err, data) {
					if (err) {
						console.log('Could not find: '+file);
						callback(null, '');
						return;
					}
					fileInfo.content = data.toString();

					fs.stat(path+file, function (err, stat) {
						fileInfo.modified = stat.mtime;
						callback(null, fileInfo);
					});
				});
			}, 100);
		}
	};

	this.acceptsGzip = function(req) {
	    var accept = req.headers["accept-encoding"];
	    return accept && accept.toLowerCase().indexOf('gzip') !== -1;
	}

	function assetManager (req, res, next) {
		var self = this;
		var found = false;
		var response = {};
		var mimeType = 'text/plain';
		var groupServed;
		settings.forEach(function (group, groupName) {
			if (group.route.test(req.url)) {
				var userAgent = req.headers['user-agent'] || '';
				groupServed = group;
				if (group.dataType === 'javascript') {
					mimeType = 'application/javascript';
				}
				else if (group.dataType === 'html') {
					mimeType = 'text/html';
				}
				else if (group.dataType === 'css') {
					mimeType = 'text/css';
				}
				if (cache[groupName]) {
					Object.keys(cache[groupName]).forEach(function(match) {
						if (!found && userAgent.match(new RegExp(match, 'i'))) {
							found = true;
							var item = cache[groupName][match];

							var content = item.encodings.utf8;
							if(zlib && item.encodings.gzip && this.acceptsGzip(req)) {
								content = item.encodings.gzip;
							}

							response = {
								contentLength: content.length
								, modified: item.modified
								, contentBuffer: content.buffer
								, encoding: content.encoding
							};
						}
					});
				}
			}
		});

		if (!found) {
			next();
		} else {
			if (groupServed.serveModify) {
				groupServed.serveModify(req, res, response, function(response) {
					serveContent(response);
				});
			} else {
				serveContent(response);
			}
			function serveContent(response) {
				res.writeHead(200, {
					'Content-Type': mimeType,
					'Content-Length': response.contentLength,
					'Last-Modified': response.modified,
					'Date': (new Date).toUTCString(),
					'Cache-Control': 'public max-age=' + 31536000,
					'Content-Encoding': response.encoding,
					'Expires': response.expires || (new Date(new Date().getTime()+63113852000)).toUTCString()
				});
				res.end(response.contentBuffer);
			}
			return;
		}
	};

	assetManager.cacheTimestamps = cacheTimestamps;
	assetManager.cacheHashes = cacheHashes;

	return assetManager;
};
<commit_msg>No longer sending Content-Encoding on non-gzip. Identation fixes<commit_after>var fs = require('fs')
	, Buffer = require('buffer').Buffer
	, request = require('request')
	, Step = require('step')
	, jsmin = require('./../deps/jsmin').minify
	, htmlmin = require('./../deps/htmlmin').minify
	, cssmin = require('./../deps/cssmin').minify
	, crypto = require('crypto');

var zlib;
try {
	zlib = require('zlib');
} catch(e) {}

var cache = {}
	, settings = {}
	, cacheHashes = {}
	, cacheTimestamps = {};

module.exports = function assetManager (assets) {
	var self = this;

	settings = assets || settings;
	if (!settings) {
		throw new Exception('No asset groups found');
	}

	if (!settings.forEach) {
		settings.forEach = function(callback) {
			Object.keys(this).forEach(function(key) {
				if (key !== 'forEach') {
					callback(settings[key], key);
				}
			});
		};
	}

	Step(function() {
		var grouping = this.group();
		settings.forEach(function(group, groupName) {
			var patterns = []
			, insertions = []
			, matchInsertionCount = {};

			group.files.forEach(function(fileName, index) {
				var pattern = null;
				if (fileName.exec) { // Got a RegEx
					pattern = fileName;
				} else if (fileName.trim() === '*') {
					pattern = /\.[a-z]+$/i; // Anything with a extension
				}

				if (pattern) {
					patterns.push({
						pattern: pattern,
						index: index
					});
					matchInsertionCount['insert-'+index] = 0;
				}
			});

			var fileFetchCallback = grouping();
			fs.readdir(group.path, function(err, files) {
				if (err) {
					throw err;
				}
				files.forEach(function(fileName, index) {
					var alreadyIncluded = false,
						matchedPattern = false;

					group.files.forEach(function(includedFile) {
						if (alreadyIncluded || includedFile.trim && (includedFile.trim() === fileName.trim())) {
							alreadyIncluded = true;
						}
					});

					if (!alreadyIncluded) {
						patterns.forEach(function(pattern) {
							if (!matchedPattern && pattern.pattern.exec(fileName)) {
								matchedPattern = pattern;
							}
						});
					}
					if (matchedPattern) {
						insertions.push({
							file: fileName,
							index: matchedPattern.index
						});
					}
				});
				insertions.forEach(function(insertion, index) {
					if (!matchInsertionCount['insert-'+insertion.index]) {
						group.files.splice(insertion.index, 1, insertion.file);
					} else {
						group.files.splice(insertion.index+matchInsertionCount['insert-'+insertion.index], 0, insertion.file);
					}
					matchInsertionCount['insert-'+insertion.index] += 1;
				});
				fileFetchCallback(null, true);
			});
		});
	}, function(err, contents) {
		settings.forEach(function (group, groupName) {
			if (!group.stale) {
				group.files.forEach(function (file, index) {
					if (!file.match) {
						console.log('No match for: '+file);
						group.files.splice(index, 1);
						return;
					}
					if (file.match(/^https?:\/\//)) {
						return;
					}
					fs.watchFile(group.path + file, function (old, newFile) {
						if (old.mtime.toString() != newFile.mtime.toString()) {
							self.generateCache(groupName);
						}
					});
				});
			}
		});
		self.generateCache();
	});

	this.generateCache = function (generateGroup) {
		var self = this;
		settings.forEach(function (group, groupName) {
			var userAgentMatches = {};
			if (group.preManipulate) {
				Object.keys(group.preManipulate).forEach(function(key) {
					userAgentMatches[key] = true;
				});
			}
			if (group.postManipulate) {
				Object.keys(group.postManipulate).forEach(function(key) {
					userAgentMatches[key] = true;
				});
			}
			if (!Object.keys(userAgentMatches).length) {
				userAgentMatches = ['^'];
			} else {
				userAgentMatches = Object.keys(userAgentMatches);
			}

			userAgentMatches.forEach(function(match) {
				var path = group.path;
				Step(function () {
					var grouping = this.group();
					group.files.forEach(function (file) {
						if (!generateGroup || generateGroup && groupName === generateGroup) {
							self.getFile(file, path, groupName, grouping());
						}
					});
				}, function (err, contents) {
					if (err) {
						throw err;
					}
					var grouping = this.group();
					var lastModified = null;

					for (var i = 0, l = contents.length; i < l; i++) {
						var file = contents[i];
						if (typeof file == "string"){
							continue;
						}
						if (typeof file.modified != "undefined"){
							file.modified = new Date();
						}
						if (Object.prototype.toString.call(file.modified) === "[object Date]" && !isNaN(file.modified)){

						} else {
							file.modified = new Date();
						}
						if (!lastModified || lastModified.getTime() < file.modified.getTime()) {
							lastModified = file.modified;
						}
						if (!group.preManipulate) {
							group.preManipulate = {};
						}

						self.manipulate(group.preManipulate[match], file.content, file.filePath, i, i === l - 1, grouping());
					};
					if (!lastModified && !contents.length) {
						grouping();
						return;
					}
					cacheTimestamps[groupName] = lastModified.getTime();
					if (!cache[groupName]) {
						cache[groupName] = {};
					}
					cache[groupName][match] = {
						'modified': lastModified.toUTCString()
					};
				}, function (err, contents) {
					if (err) {
						throw err;
					}
					var grouping = this.group();

					var content = '';
					for (var i=0; i < contents.length; i++) {
						content += contents[i] + "\n";
					};
					var dataTypeLowerCase = group.dataType.toLowerCase();
					if (!group.debug) {
						if (dataTypeLowerCase === 'javascript' || dataTypeLowerCase === 'js') {
							(function (callback){callback(null, jsmin(content));})(grouping());
						} else if (dataTypeLowerCase === 'html') {
							(function (callback){callback(null, htmlmin(content));})(grouping());
						} else if (dataTypeLowerCase === 'css') {
							(function (callback){callback(null, cssmin(content));})(grouping());
						}
					} else {
						grouping()(null, content);
					}
				}, function (err, contents) {
					if (err) {
						throw err;
					}

					var grouping = this.group();

					var content = '';
					for (var i=0; i < contents.length; i++) {
						content += contents[i];
					};

					if (!group.postManipulate) {
						group.postManipulate = {};
					}
					self.manipulate(group.postManipulate[match], content, null, 0, true, grouping());

				}, function (err, contents) {
					if (err) {
						throw err;
					}

					var content = '';
					for (var i=0; i < contents.length; i++) {
						content += contents[i];
					};

					cacheHashes[groupName] = crypto.createHash('md5').update(content).digest('hex');

					cache[groupName][match].encodings = {};
					var encodings = cache[groupName][match].encodings;

					var utf8Buffer = new Buffer(content, 'utf8');
					encodings.utf8 = {
						'buffer': utf8Buffer,
						'length': utf8Buffer.length,
						'encoding': false
					};

					if(zlib) {
						var gzipBuffer = zlib.gzip(utf8Buffer, function(error, result) {
							encodings.gzip = {
								'buffer': result,
								'length': result.length,
								'encoding': 'gzip'
							};
						});
					}
				});
			});
		});
	};

	this.manipulate = function (manipulateInstructions, fileContent, path, index, last, callback) {
		if (manipulateInstructions && Array.isArray(manipulateInstructions)) {
				var callIndex = 0;
				(function modify(content, path, index, last) {
					if (callIndex < manipulateInstructions.length) {
						callIndex++;
						manipulateInstructions[callIndex-1](content, path, index, last, function (content) {
							modify(content, path, index, last);
						});
					} else {
						callback(null, content);
					}
				})(fileContent, path, index, last);
		} else if (manipulateInstructions && typeof manipulateInstructions === 'function') {
			manipulateInstructions(fileContent, path, index, last, callback);
		} else {
			callback(null, fileContent);
		}
	};

	this.getFile = function (file, path, groupName, callback) {
		var isExternal = false;
		if (file && file.match(/^https?:\/\//)) {
			isExternal = true;
		}

		var fileInfo = {
			'filePath': isExternal ? file: path+file
		};

		if (isExternal) {
			request({uri: file}, function(err, res, body) {
				fileInfo.content = body;
				fileInfo.external = true;
				if (typeof res != "undefined" && res != null){
					fileInfo.modified = new Date(res.headers['last-modified']);
				}
				callback(null, fileInfo);
			});
		} else {
			setTimeout(function() {
				fs.readFile(path+file, function (err, data) {
					if (err) {
						console.log('Could not find: '+file);
						callback(null, '');
						return;
					}
					fileInfo.content = data.toString();

					fs.stat(path+file, function (err, stat) {
						fileInfo.modified = stat.mtime;
						callback(null, fileInfo);
					});
				});
			}, 100);
		}
	};

	this.acceptsGzip = function(req) {
		var accept = req.headers["accept-encoding"];
		return accept && accept.toLowerCase().indexOf('gzip') !== -1;
	}

	function assetManager (req, res, next) {
		var self = this;
		var found = false;
		var response = {};
		var mimeType = 'text/plain';
		var groupServed;
		settings.forEach(function (group, groupName) {
			if (group.route.test(req.url)) {
				var userAgent = req.headers['user-agent'] || '';
				groupServed = group;
				if (group.dataType === 'javascript') {
					mimeType = 'application/javascript';
				}
				else if (group.dataType === 'html') {
					mimeType = 'text/html';
				}
				else if (group.dataType === 'css') {
					mimeType = 'text/css';
				}
				if (cache[groupName]) {
					Object.keys(cache[groupName]).forEach(function(match) {
						if (!found && userAgent.match(new RegExp(match, 'i'))) {
							found = true;
							var item = cache[groupName][match];

							var content = item.encodings.utf8;
							if(zlib && item.encodings.gzip && this.acceptsGzip(req)) {
								content = item.encodings.gzip;
							}

							response = {
								contentLength: content.length
								, modified: item.modified
								, contentBuffer: content.buffer
								, encoding: content.encoding
							};
						}
					});
				}
			}
		});

		if (!found) {
			next();
		} else {
			if (groupServed.serveModify) {
				groupServed.serveModify(req, res, response, function(response) {
					serveContent(response);
				});
			} else {
				serveContent(response);
			}
			function serveContent(response) {
				var headers = {
					'Content-Type': mimeType,
					'Content-Length': response.contentLength,
					'Last-Modified': response.modified,
					'Date': (new Date).toUTCString(),
					'Cache-Control': 'public max-age=' + 31536000,
					'Expires': response.expires || (new Date(new Date().getTime()+63113852000)).toUTCString()
				};

				if(response.encoding) {
					headers['Content-Encoding'] = response.encoding
				}

				res.writeHead(200, headers);
				res.end(response.contentBuffer);
			}
			return;
		}
	};

	assetManager.cacheTimestamps = cacheTimestamps;
	assetManager.cacheHashes = cacheHashes;

	return assetManager;
};
<|endoftext|>


<commit_before>require('express-hijackresponse');
require('bufferjs');

module.exports = function (options) {
    if (typeof options.options === undefined) {
        // Default options for autoprefixer. See https://github.com/ai/autoprefixer#browsers
        options.options = [
            '> 1%',
            'last 2 versions',
            'Firefox ESR',
            'Opera 12.1'
        ];
    }
    if (typeof options.options === 'string') {
        options.options = options.options.split(',');
    }
    if (!Array.isArray(options.options)) {
        throw new Error('express-autoprefixer: options must be provided as string or list');
    }

    var autoprefixer = options.autoprefixer || require('autoprefixer');

    return function (req, res, next) {
        // Prevent If-None-Match revalidation with the downstream middleware with ETags that aren't suffixed with "-autoprefixer":
        var ifNoneMatch = req.headers['if-none-match'];
        if (ifNoneMatch) {
            var validIfNoneMatchTokens = ifNoneMatch.split(" ").filter(function (etag) {
                return /-autoprefixer\"$/.test(etag);
            });
            if (validIfNoneMatchTokens.length > 0) {
                req.headers['if-none-match'] = validIfNoneMatchTokens.join(" ");
            } else {
                delete req.headers['if-none-match'];
            }
        }
        delete req.headers['if-modified-since']; // Prevent false positive conditional GETs after enabling autoprefixer
        res.hijack(function (err, res) {
            var contentType = res.getHeader('Content-Type'),
            matchContentType = contentType && contentType.match(/^text\/css(?:;\s*charset=([a-z0-9\-]+))?$/i);
            // The mime module doesn't support less yet, so we fall back:
            if (matchContentType) {
                var chunks = [];
                res.on('error', function () {
                    res.unhijack();
                    next();
                }).on('data', function (chunk) {
                    chunks.push(chunk);
                }).on('end', function () {
                    if (!chunks.length) {
                        return res.send(res.statusCode);
                    }
                    var cssText = Buffer.concat(chunks).toString('utf-8'); // No other charsets are really relevant, right?

                    cssText = autoprefixer.apply(null, options.options).process(cssText).css;

                    res.setHeader('Content-Type', 'text/css');
                    res.setHeader('Content-Length', Buffer.byteLength(cssText));
                    res.end(cssText);
                });
            } else {
                res.unhijack(true);
            }
        });
        next();
    };
};
<commit_msg>split strings on comma followed by optional space<commit_after>require('express-hijackresponse');
require('bufferjs');

module.exports = function (options) {
    if (typeof options.options === undefined) {
        // Default options for autoprefixer. See https://github.com/ai/autoprefixer#browsers
        options.options = [
            '> 1%',
            'last 2 versions',
            'Firefox ESR',
            'Opera 12.1'
        ];
    }
    if (typeof options.options === 'string') {
        options.options = options.options.split(/,\s*/);
    }
    if (!Array.isArray(options.options)) {
        throw new Error('express-autoprefixer: options must be provided as string or list');
    }

    var autoprefixer = options.autoprefixer || require('autoprefixer');

    return function (req, res, next) {
        // Prevent If-None-Match revalidation with the downstream middleware with ETags that aren't suffixed with "-autoprefixer":
        var ifNoneMatch = req.headers['if-none-match'];
        if (ifNoneMatch) {
            var validIfNoneMatchTokens = ifNoneMatch.split(" ").filter(function (etag) {
                return /-autoprefixer\"$/.test(etag);
            });
            if (validIfNoneMatchTokens.length > 0) {
                req.headers['if-none-match'] = validIfNoneMatchTokens.join(" ");
            } else {
                delete req.headers['if-none-match'];
            }
        }
        delete req.headers['if-modified-since']; // Prevent false positive conditional GETs after enabling autoprefixer
        res.hijack(function (err, res) {
            var contentType = res.getHeader('Content-Type'),
            matchContentType = contentType && contentType.match(/^text\/css(?:;\s*charset=([a-z0-9\-]+))?$/i);
            // The mime module doesn't support less yet, so we fall back:
            if (matchContentType) {
                var chunks = [];
                res.on('error', function () {
                    res.unhijack();
                    next();
                }).on('data', function (chunk) {
                    chunks.push(chunk);
                }).on('end', function () {
                    if (!chunks.length) {
                        return res.send(res.statusCode);
                    }
                    var cssText = Buffer.concat(chunks).toString('utf-8'); // No other charsets are really relevant, right?

                    cssText = autoprefixer.apply(null, options.options).process(cssText).css;

                    res.setHeader('Content-Type', 'text/css');
                    res.setHeader('Content-Length', Buffer.byteLength(cssText));
                    res.end(cssText);
                });
            } else {
                res.unhijack(true);
            }
        });
        next();
    };
};
<|endoftext|>


<commit_before>/*
 * Copyright 2013 Andrey Sitnik <andrey@sitnik.ru>,
 * sponsored by Evil Martians.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

var rework = require('rework');

// Return array, that doesn’t contain duplicates.
var uniq = function (array) {
    var filtered = [];
    array.forEach(function (i) {
        if ( filtered.indexOf(i) === -1 ) {
            filtered.push(i);
        }
    });
    return filtered;
};

// Split prefix and property name.
var splitPrefix = function (prop) {
    if ( prop[0] === '-' ) {
        var sep = prop.indexOf('-', 1) + 1;
        return { prefix: prop.slice(0, sep), name: prop.slice(sep) };
    } else {
        return { prefix: null, name: prop };
    }
};

// Generate RegExp to test, does CSS value contain some `word`.
var containRegexp = function (word) {
    return new RegExp('(^|\\s|,|\\()' +
               word.replace(/([.?*+\^\$\[\]\\(){}|\-])/g, "\\$1") +
               '($|\\s|\\()', 'ig');
};

// Throw error with `messages with mark, that is from Autoprefixer.
var error = function (message) {
    var err = new Error(message);
    err.autoprefixer = true;
    throw err;
};

// Parse CSS and add prefixed properties and values by Can I Use database
// for actual browsers.
//
//   var prefixed = autoprefixer.compile(css);
//
// By default, it add prefixes for last 2 releases of each browsers.
// You can use global statistics to select browsers:
//
//   autoprefixer.compile(css, '> 1%');
//
// versions fo each browsers:
//
//   autoprefixer.compile(css, 'last 1 version');
//
// or set them manually:
//
//   autoprefixer.compile(css, ['chrome 26', 'ff 20', 'ie 10']);
//
// If you want to combine Autoprefixer with another Rework filters,
// you can use it as separated filter:
//
//   rework(css).
//     use(autoprefixer.rework(last 1 version')).
//     toString();
var autoprefixer = {
    // Load data
    data: {
        browsers: require('../data/browsers'),
        values:   require('../data/values'),
        props:    require('../data/props')
    },

    // Parse `css` by Rework and add prefixed properties for browsers
    // in `requirements`.
    compile: function (css, requirements) {
        return rework(css).use(autoprefixer.rework(requirements)).toString();
    },

    // Return Rework filter, which will add necessary prefixes for browsers
    // in `requirements`.
    rework: function (requirements) {
        if ( !requirements ) {
            requirements = [];
        } else if ( !Array.isArray(requirements) ) {
            requirements = [requirements];
        }

        var browsers   = this.parse(requirements);
        var values     = this.filter(this.data.values, browsers);
        var props      = this.filter(this.data.props,  browsers);
        var prefixes   = this.prefixes(props, values);
        var unprefixes = this.unprefixes(props, values);

        return function (style, css) {
            autoprefixer.unprefixer(unprefixes, style, css);
            autoprefixer.prefixer(prefixes, style, css);
        };
    },

    // Change `style` declarations in parsed `css`, to add prefixes for `props`.
    prefixer: function (props, style, css) {
        if ( props['@keyframes'] ) {
            // Keyframes
            style.rules.forEach(function(rule) {
                if ( !rule.keyframes ) {
                    return;
                }

                props['@keyframes'].prefixes.forEach(function (prefix) {
                    var contain = style.rules.some(function (other) {
                        return other.keyframes && rule.name === other.name &&
                               other.vendor === prefix;
                    });
                    if ( contain ) {
                        return;
                    }

                    var clone = { name: rule.name };
                    clone.vendor = rule.vendor;
                    clone.keyframes = [];
                    rule.keyframes.forEach(function (keyframe) {
                        var keyframeClone          = { };
                        keyframeClone.values       = keyframe.values.slice();
                        keyframeClone.declarations = keyframe.declarations.map(
                            function (i) {
                                return { property: i.property, value: i.value };
                            });

                        clone.keyframes.push(keyframeClone);
                    });

                    clone.vendor = prefix;
                    style.rules.push(clone);
                });
             });
        }

        var all = props['*'];
        var transitions = { };
        for ( var i in props ) {
            if ( props[i].transition && props[i].prefixes ) {
                transitions[i] = props[i];
            }
        }
        var isTransition = /(-(webkit|moz|ms|o)-)?transition(-property)?/;

        rework.visit.declarations(style, function (rules, node) {
            var vendor   = node.vendor;
            var prefixes = ['-webkit-', '-moz-', '-ms-', '-o-'];

            var contain = function (rules, prop, value) {
                return rules.some(function (rule) {
                    return rule.property === prop && rule.value === value;
                });
            };
            var add = function (rules, num, prop, value) {
                if ( contain(rules, prop, value) ) {
                    return num;
                }

                rules.splice(num, 0, { property: prop, value: value });
                return num + 1;
            };

            // Properties
            for ( var num = 0; num < rules.length; num += 1 ) {
                var rule = rules[num];
                var prop = props[rule.property];

                if ( !prop || !prop.prefixes ) {
                    continue;
                }
                if ( prop.check && !prop.check.call(rule.value, rule) ) {
                    continue;
                }

                prop.prefixes.forEach(function (prefix) {
                    if ( vendor && vendor !== prefix ) {
                        return;
                    }
                    var wrong = prefixes.some(function (other) {
                        if ( other === prefix ) {
                            return false;
                        }
                        return rule.value.indexOf(other) !== -1;
                    });
                    if ( wrong ) {
                        return;
                    }

                    num = add(rules, num, prefix + rule.property, rule.value);
                });
            };

            // Values
            for ( var num = 0; num < rules.length; num += 1 ) {
                var rule = rules[num];

                var split = splitPrefix(rule.property);
                var prop  = props[split.name];
                var propVendor = split.prefix || vendor;

                var valuePrefixer = function (values) {
                    var prefixed = { };

                    for ( var name in values ) {
                        var value = values[name];
                        if ( !rule.value.match(value.regexp) ) {
                            continue;
                        }

                        value.prefixes.forEach(function (prefix) {
                            if ( propVendor && propVendor !== prefix ) {
                                return;
                            }
                            if ( !prefixed[prefix]) {
                                prefixed[prefix] = rule.value;
                            }
                            if ( value.replace ) {
                                if ( prefixed[prefix].match(value.regexp) ) {
                                    var replaced = value.replace(
                                        prefixed[prefix], prefix);
                                    if ( replaced ) {
                                        prefixed[prefix] = replaced;
                                        return;
                                    }
                                }
                            }
                            prefixed[prefix] = prefixed[prefix].replace(
                                value.regexp, '$1' + prefix + name + '$2');
                        });
                    }

                    for ( var prefix in prefixed ) {
                        if ( prefixed[prefix] !== rule.value ) {
                            if ( propVendor ) {
                                var exists = contain(rules, rule.property,
                                                     prefixed[prefix]);
                                if ( exists ) {
                                    rules.splice(num, 1);
                                } else {
                                    rule.value = prefixed[prefix];
                                }
                            } else {
                                num = add(rules, num, rule.property,
                                          prefixed[prefix]);
                            }
                        }
                    }
                };

                if ( all ) {
                    valuePrefixer(all.values);
                }
                if ( prop ) {
                    valuePrefixer(prop.values);
                }
                if ( rule.property.match(isTransition) ) {
                    valuePrefixer(transitions);
                }
            }
        });
    },

    // Change `style` declarations in parsed `css`, to remove `remove`.
    unprefixer: function (remove, style, css) {
        var all = remove.values['*'];

        // Keyframes
        style.rules = style.rules.filter(function (rule) {
            return !(rule.keyframes && remove.keyframes[rule.vendor]);
        });

        rework.visit.declarations(style, function (rules, node) {
            for ( var num = 0; num < rules.length; num += 1 ) {
                var rule = rules[num];

                // Properties
                if ( remove.props[rule.property] ) {
                    rules.splice(num, 1);
                    continue;
                }

                // Values
                var prop   = splitPrefix(rule.property).name;
                var values = all;
                if ( remove.values[prop] ) {
                    values = values.concat(remove.values[prop]);
                }
                if ( prop === 'transition' || prop === 'transition-property' ) {
                    values = values.concat(remove.transition);
                }
                values.forEach(function (value) {
                    if ( rule.value.match(value) ) {
                        rules.splice(num, 1);
                        return false;
                    }
                });
            }
        });
    },

    // Return array of browsers for requirements in free form.
    parse: function (requirements) {
        if ( requirements.length === 0 ) {
            requirements = ['last 2 versions'];
        }

        var match;
        var browsers = [];
        requirements.map(function (req) {

          if ( match = req.match(/^last (\d+) versions?$/i) ) {
              return autoprefixer.browsers(function(browser) {
                  return browser.versions.slice(0, match[1]);
              });

          } else if ( match = req.match(/^> (\d+(\.\d+)?)%$/i) ) {
              return autoprefixer.browsers(function(browser) {
                  return browser.versions.filter(function (version, i) {
                      return browser.popularity[i] > match[1];
                  });
              });

          } else {
              return [autoprefixer.check(req)];
          }

        }).forEach(function (reqBrowsers) {
            browsers = browsers.concat(reqBrowsers);
        });
        return uniq(browsers);
    },

    // Select browsers by some `criteria`.
    browsers: function (criteria) {
        var selected = [];
        for ( var name in this.data.browsers ) {
            var browser  = this.data.browsers[name];
            var versions = criteria(browser).map(function (version) {
                return name + ' ' + version;
            });
            selected = selected.concat(versions);
        }
        return selected;
    },

    // Check browser name and reduce version if them from future.
    check: function (req) {
        req = req.split(/\s+/);
        if ( req.length > 2 ) {
            error('Unknown browsers requirement `' + req.join(' ')  + '`');
        }

        var name    = req[0];
        var version = parseFloat(req[1]);

        var data = this.data.browsers[name];
        if ( !data ) {
            error('Unknown browser `' + name + '`');
        }
        if ( !version ) {
            error("Can't recognize version in `" + req + '`');
        }

        var last = data.versions[0];
        if ( data.future && data.future[0] ) {
            last = data.future[0];
        }

        if ( version > last ) {
            version = last;
        }
        if ( version < data.versions[data.versions.length - 1] ) {
            version = data.versions[data.versions.length - 1];
        }

        return name + ' ' + version;
    },

    // Return new `data` only with items, which need prefixes
    // for selected `browsers`.
    filter: function (data, browsers, exclude) {
        var selected = { };
        for ( var name in data ) {
            var need     = data[name].browsers;
            var prefixes = browsers.filter(function (browser) {
                return need.indexOf(browser) !== -1;
            }).map(function (browser) {
                var key = browser.split(' ')[0];
                return autoprefixer.data.browsers[key].prefix;
            }).sort(function (a, b) { return b.length - a.length });

            if ( prefixes.length ) {
                prefixes = uniq(prefixes);

                var obj = { prefixes: prefixes }
                for ( var key in data[name] ) {
                    if ( key === 'browsers' ) {
                        continue;
                    }
                    obj[key] = data[name][key];
                }

                if ( obj.props || obj.transition ) {
                    obj.regexp = containRegexp(name);
                }

                selected[name] = obj;
            }
        }
        return selected;
    },

    // Return properties, which them prefixed values inside.
    prefixes: function (props, values) {
        for ( var name in values ) {
            values[name].props.forEach(function (prop) {
                if ( !props[prop] ) {
                    props[prop] = { values: { } };
                } else if ( !props[prop].values ) {
                    props[prop].values = { };
                }

                props[prop].values[name] = values[name];
            });
        }

        return props;
    },

    // Return old properties and values to remove.
    unprefixes: function (props, values) {
        var remove = { props: { }, values: { }, transition: [], keyframes: { } }

        for ( var name in this.data.props ) {
            var prop     = this.data.props[name];
            var prefixes = prop.browsers.map(function (b) {
                var key = b.split(' ')[0];
                return autoprefixer.data.browsers[key].prefix;
            });
            uniq(prefixes).filter(function (prefix) {
                if ( !props[name] ) {
                    return true;
                }
                return props[name].prefixes.indexOf(prefix) === -1
            }).forEach(function (prefix) {
                if ( prop.transition ) {
                    remove.transition.push(containRegexp(prefix + name));
                }
                if ( name == '@keyframes' ) {
                    remove.keyframes[prefix] = true;
                } else {
                    if ( prop.prefixed && prop.prefixed[prefix] ) {
                        remove.props[prop.prefixed[prefix]] = true;
                    } else {
                        remove.props[prefix + name] = true
                    }
                }
            })
        }

        for ( var name in this.data.values ) {
            var value = this.data.values[name];
            var prefixes = value.browsers.map(function (b) {
                var key = b.split(' ')[0];
                return autoprefixer.data.browsers[key].prefix;
            });
            var names = uniq(prefixes).filter(function (prefix) {
                if ( !values[name] ) {
                    return true;
                }
                return values[name].prefixes.indexOf(prefix) === -1
            }).map(function (prefix) {
                return containRegexp(prefix + name);
            });

            value.props.forEach(function (prop) {
                if ( !remove.values[prop] ) {
                    remove.values[prop] = [];
                }
                remove.values[prop] = remove.values[prop].concat(names);
            });
        }

        return remove;
    }
};

module.exports = autoprefixer;
<commit_msg>Clean code by JSHint<commit_after>/*
 * Copyright 2013 Andrey Sitnik <andrey@sitnik.ru>,
 * sponsored by Evil Martians.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function () {
'use strict';

var rework = require('rework');

// Return array, that doesn’t contain duplicates.
var uniq = function (array) {
    var filtered = [];
    array.forEach(function (i) {
        if ( filtered.indexOf(i) === -1 ) {
            filtered.push(i);
        }
    });
    return filtered;
};

// Split prefix and property name.
var splitPrefix = function (prop) {
    if ( prop[0] === '-' ) {
        var sep = prop.indexOf('-', 1) + 1;
        return { prefix: prop.slice(0, sep), name: prop.slice(sep) };
    } else {
        return { prefix: null, name: prop };
    }
};

// Generate RegExp to test, does CSS value contain some `word`.
var containRegexp = function (word) {
    return new RegExp('(^|\\s|,|\\()' +
               word.replace(/([.?*+\^\$\[\]\\(){}|\-])/g, "\\$1") +
               '($|\\s|\\()', 'ig');
};

// Throw error with `messages with mark, that is from Autoprefixer.
var error = function (message) {
    var err = new Error(message);
    err.autoprefixer = true;
    throw err;
};

// Parse CSS and add prefixed properties and values by Can I Use database
// for actual browsers.
//
//   var prefixed = autoprefixer.compile(css);
//
// By default, it add prefixes for last 2 releases of each browsers.
// You can use global statistics to select browsers:
//
//   autoprefixer.compile(css, '> 1%');
//
// versions fo each browsers:
//
//   autoprefixer.compile(css, 'last 1 version');
//
// or set them manually:
//
//   autoprefixer.compile(css, ['chrome 26', 'ff 20', 'ie 10']);
//
// If you want to combine Autoprefixer with another Rework filters,
// you can use it as separated filter:
//
//   rework(css).
//     use(autoprefixer.rework(last 1 version')).
//     toString();
var autoprefixer = {
    // Load data
    data: {
        browsers: require('../data/browsers'),
        values:   require('../data/values'),
        props:    require('../data/props')
    },

    // Parse `css` by Rework and add prefixed properties for browsers
    // in `requirements`.
    compile: function (css, requirements) {
        return rework(css).use(autoprefixer.rework(requirements)).toString();
    },

    // Return Rework filter, which will add necessary prefixes for browsers
    // in `requirements`.
    rework: function (requirements) {
        if ( !requirements ) {
            requirements = [];
        } else if ( !Array.isArray(requirements) ) {
            requirements = [requirements];
        }

        var browsers   = this.parse(requirements);
        var values     = this.filter(this.data.values, browsers);
        var props      = this.filter(this.data.props,  browsers);
        var prefixes   = this.prefixes(props, values);
        var unprefixes = this.unprefixes(props, values);

        return function (style) {
            autoprefixer.unprefixer(unprefixes, style);
            autoprefixer.prefixer(prefixes, style);
        };
    },

    // Change `style` declarations in parsed CSS, to add prefixes for `props`.
    prefixer: function (props, style) {
        if ( props['@keyframes'] ) {
            // Keyframes
            style.rules.forEach(function(rule) {
                if ( !rule.keyframes ) {
                    return;
                }

                props['@keyframes'].prefixes.forEach(function (prefix) {
                    var contain = style.rules.some(function (other) {
                        return other.keyframes && rule.name === other.name &&
                               other.vendor === prefix;
                    });
                    if ( contain ) {
                        return;
                    }

                    var clone = { name: rule.name };
                    clone.vendor = rule.vendor;
                    clone.keyframes = [];
                    rule.keyframes.forEach(function (keyframe) {
                        var keyframeClone          = { };
                        keyframeClone.values       = keyframe.values.slice();
                        keyframeClone.declarations = keyframe.declarations.map(
                            function (i) {
                                return { property: i.property, value: i.value };
                            });

                        clone.keyframes.push(keyframeClone);
                    });

                    clone.vendor = prefix;
                    style.rules.push(clone);
                });
             });
        }

        var all = props['*'];
        var transitions = { };
        for ( var i in props ) {
            if ( props[i].transition && props[i].prefixes ) {
                transitions[i] = props[i];
            }
        }
        var isTransition = /(-(webkit|moz|ms|o)-)?transition(-property)?/;

        rework.visit.declarations(style, function (rules, node) {
            var vendor   = node.vendor;
            var prefixes = ['-webkit-', '-moz-', '-ms-', '-o-'];

            var contain = function (rules, prop, value) {
                return rules.some(function (rule) {
                    return rule.property === prop && rule.value === value;
                });
            };
            var add = function (rules, num, prop, value) {
                if ( contain(rules, prop, value) ) {
                    return num;
                }

                rules.splice(num, 0, { property: prop, value: value });
                return num + 1;
            };
            var num, rule, prop;

            // Properties
            for ( num = 0; num < rules.length; num += 1 ) {
                rule = rules[num];
                prop = props[rule.property];

                if ( !prop || !prop.prefixes ) {
                    continue;
                }
                if ( prop.check && !prop.check.call(rule.value, rule) ) {
                    continue;
                }

                prop.prefixes.forEach(function (prefix) {
                    if ( vendor && vendor !== prefix ) {
                        return;
                    }
                    var wrong = prefixes.some(function (other) {
                        if ( other === prefix ) {
                            return false;
                        }
                        return rule.value.indexOf(other) !== -1;
                    });
                    if ( wrong ) {
                        return;
                    }

                    num = add(rules, num, prefix + rule.property, rule.value);
                });
            }

            // Values
            for ( num = 0; num < rules.length; num += 1 ) {
                rule = rules[num];

                var split      = splitPrefix(rule.property);
                var propVendor = split.prefix || vendor;
                prop = props[split.name];

                var valuePrefixer = function (values) {
                    var prefixed = { };

                    for ( var name in values ) {
                        var value = values[name];
                        if ( !rule.value.match(value.regexp) ) {
                            continue;
                        }

                        value.prefixes.forEach(function (prefix) {
                            if ( propVendor && propVendor !== prefix ) {
                                return;
                            }
                            if ( !prefixed[prefix]) {
                                prefixed[prefix] = rule.value;
                            }
                            if ( value.replace ) {
                                if ( prefixed[prefix].match(value.regexp) ) {
                                    var replaced = value.replace(
                                        prefixed[prefix], prefix);
                                    if ( replaced ) {
                                        prefixed[prefix] = replaced;
                                        return;
                                    }
                                }
                            }
                            prefixed[prefix] = prefixed[prefix].replace(
                                value.regexp, '$1' + prefix + name + '$2');
                        });
                    }

                    for ( var prefix in prefixed ) {
                        if ( prefixed[prefix] !== rule.value ) {
                            if ( propVendor ) {
                                var exists = contain(rules, rule.property,
                                                     prefixed[prefix]);
                                if ( exists ) {
                                    rules.splice(num, 1);
                                } else {
                                    rule.value = prefixed[prefix];
                                }
                            } else {
                                num = add(rules, num, rule.property,
                                          prefixed[prefix]);
                            }
                        }
                    }
                };

                if ( all ) {
                    valuePrefixer(all.values);
                }
                if ( prop ) {
                    valuePrefixer(prop.values);
                }
                if ( rule.property.match(isTransition) ) {
                    valuePrefixer(transitions);
                }
            }
        });
    },

    // Change `style` declarations in parsed CSS, to remove `remove`.
    unprefixer: function (remove, style) {
        var all = remove.values['*'];

        // Keyframes
        style.rules = style.rules.filter(function (rule) {
            return !(rule.keyframes && remove.keyframes[rule.vendor]);
        });

        rework.visit.declarations(style, function (rules) {
            for ( var num = 0; num < rules.length; num += 1 ) {
                var rule = rules[num];

                // Properties
                if ( remove.props[rule.property] ) {
                    rules.splice(num, 1);
                    continue;
                }

                // Values
                var prop   = splitPrefix(rule.property).name;
                var values = all;
                if ( remove.values[prop] ) {
                    values = values.concat(remove.values[prop]);
                }
                if ( prop === 'transition' || prop === 'transition-property' ) {
                    values = values.concat(remove.transition);
                }
                values.forEach(function (value) {
                    if ( rule.value.match(value) ) {
                        rules.splice(num, 1);
                        return false;
                    }
                });
            }
        });
    },

    // Return array of browsers for requirements in free form.
    parse: function (requirements) {
        if ( requirements.length === 0 ) {
            requirements = ['last 2 versions'];
        }

        var match;
        var browsers = [];
        requirements.map(function (req) {

          if ( match = req.match(/^last (\d+) versions?$/i) ) {
              return autoprefixer.browsers(function(browser) {
                  return browser.versions.slice(0, match[1]);
              });

          } else if ( match = req.match(/^> (\d+(\.\d+)?)%$/i) ) {
              return autoprefixer.browsers(function(browser) {
                  return browser.versions.filter(function (version, i) {
                      return browser.popularity[i] > match[1];
                  });
              });

          } else {
              return [autoprefixer.check(req)];
          }

        }).forEach(function (reqBrowsers) {
            browsers = browsers.concat(reqBrowsers);
        });
        return uniq(browsers);
    },

    // Select browsers by some `criteria`.
    browsers: function (criteria) {
        var selected = [];
        for ( var name in this.data.browsers ) {
            var browser  = this.data.browsers[name];
            var versions = criteria(browser).map(function (version) {
                return name + ' ' + version;
            });
            selected = selected.concat(versions);
        }
        return selected;
    },

    // Check browser name and reduce version if them from future.
    check: function (req) {
        req = req.split(/\s+/);
        if ( req.length > 2 ) {
            error('Unknown browsers requirement `' + req.join(' ')  + '`');
        }

        var name    = req[0];
        var version = parseFloat(req[1]);

        var data = this.data.browsers[name];
        if ( !data ) {
            error('Unknown browser `' + name + '`');
        }
        if ( !version ) {
            error("Can't recognize version in `" + req + '`');
        }

        var last = data.versions[0];
        if ( data.future && data.future[0] ) {
            last = data.future[0];
        }

        if ( version > last ) {
            version = last;
        }
        if ( version < data.versions[data.versions.length - 1] ) {
            version = data.versions[data.versions.length - 1];
        }

        return name + ' ' + version;
    },

    // Return new `data` only with items, which need prefixes
    // for selected `browsers`.
    filter: function (data, browsers) {
        var selected = { };
        for ( var name in data ) {
            var need     = data[name].browsers;
            var prefixes = browsers.filter(function (browser) {
                return need.indexOf(browser) !== -1;
            }).map(function (browser) {
                var key = browser.split(' ')[0];
                return autoprefixer.data.browsers[key].prefix;
            }).sort(function (a, b) { return b.length - a.length; });

            if ( prefixes.length ) {
                prefixes = uniq(prefixes);

                var obj = { prefixes: prefixes };
                for ( var key in data[name] ) {
                    if ( key === 'browsers' ) {
                        continue;
                    }
                    obj[key] = data[name][key];
                }

                if ( obj.props || obj.transition ) {
                    obj.regexp = containRegexp(name);
                }

                selected[name] = obj;
            }
        }
        return selected;
    },

    // Return properties, which them prefixed values inside.
    prefixes: function (props, values) {
        for ( var name in values ) {
            values[name].props.forEach(function (prop) {
                if ( !props[prop] ) {
                    props[prop] = { values: { } };
                } else if ( !props[prop].values ) {
                    props[prop].values = { };
                }

                props[prop].values[name] = values[name];
            });
        }

        return props;
    },

    // Return old properties and values to remove.
    unprefixes: function (props, values) {
        var remove = { props: {}, values: {}, transition: [], keyframes: {} };
        var name, prefixes, prop, value, names;

        for ( name in this.data.props ) {
            prop     = this.data.props[name];
            prefixes = prop.browsers.map(function (b) {
                var key = b.split(' ')[0];
                return autoprefixer.data.browsers[key].prefix;
            });
            uniq(prefixes).filter(function (prefix) {
                if ( !props[name] ) {
                    return true;
                }
                return props[name].prefixes.indexOf(prefix) === -1;
            }).forEach(function (prefix) {
                if ( prop.transition ) {
                    remove.transition.push(containRegexp(prefix + name));
                }
                if ( name === '@keyframes' ) {
                    remove.keyframes[prefix] = true;
                } else {
                    if ( prop.prefixed && prop.prefixed[prefix] ) {
                        remove.props[prop.prefixed[prefix]] = true;
                    } else {
                        remove.props[prefix + name] = true;
                    }
                }
            });
        }

        for ( name in this.data.values ) {
            value = this.data.values[name];
            prefixes = value.browsers.map(function (b) {
                var key = b.split(' ')[0];
                return autoprefixer.data.browsers[key].prefix;
            });
            names = uniq(prefixes).filter(function (prefix) {
                if ( !values[name] ) {
                    return true;
                }
                return values[name].prefixes.indexOf(prefix) === -1;
            }).map(function (prefix) {
                return containRegexp(prefix + name);
            });

            value.props.forEach(function (prop) {
                if ( !remove.values[prop] ) {
                    remove.values[prop] = [];
                }
                remove.values[prop] = remove.values[prop].concat(names);
            });
        }

        return remove;
    }
};

module.exports = autoprefixer;
})();
<|endoftext|>