<commit_before>var _ = require('underscore'),
    async = require('async'),
    build = require('./build'),
    combine = require('./jsCombine'),
    configLoader = require('./config'),
    Context = require('./context'),
    EventEmitter = require('events').EventEmitter,
    fs = require('fs'),
    fu = require('./fileUtil'),
    Mixin = require('./mixins'),
    plugin = require('./plugin'),
    watch = require('./watcher.js');

exports.build = build;
exports.fileUtil = fu;
exports.plugin = plugin.plugin;
exports.combine = combine.combine;
exports.config = configLoader;

/**
 *
 * @name init
 * @function This function initializes a Lumbar instance
 * @param {string} lumbarFile The lumbarFile is the main
 *  file. Its responsible to define all the platforms,
 *  packages, modules, and templates for Lumbar to use.
 * @param {Object} options supports the following options:
 *   packageConfigFile (string): name of the package config file.
 *   outdir (string): path to directory of where to output the files.
 *   minimize (boolean): Should we minimize the files?
 * @return {Object.<Function>}
 */
exports.init = function(lumbarFile, options) {
    var outdir = options.outdir,
        plugins = plugin.create(options),
        mixins = new Mixin(options),
        config,
        configCache,
        context;

    function logError(err) {
      if (err) {
        event.emit('error', err);
      }
    }

    function loadConfig(path, callback) {
      try {
        fu.resetCache();

        configCache = {};
        config = configLoader.load(path);
        plugins.initialize(config);

        config.outdir = options.outdir = outdir || config.attributes.output;

        context = new Context(options, config, plugins, mixins, event);
        context.options = options;
        context.configCache = configCache;

        mixins.initialize(context, function(err) {
          if (err) {
            callback(err);
          }

          plugins.loadConfig(context, function(err) {
            if (err) {
              callback(err);
            }

            // Ensure that we have the proper build output
            if (!config.outdir) {
              return callback(new Error('Output must be defined on the command line or config file.'));
            }
            context.outdir = config.outdir;

            fu.ensureDirs(config.outdir + '/.', function(err) {
              var stat = fs.statSync(config.outdir);
              if (!stat.isDirectory()) {
                callback(new Error("Output must be a directory"));
              } else {
                callback();
              }
            });
          });
        });
      } catch (err) {
        callback(err);
      }
    }

    function buildPlatform(context, callback) {
      var modes = context.mode ? [context.mode] : plugins.modes();

      async.forEach(modes, function(mode, callback) {
          buildMode(mode, context, callback);
        },
        callback);
    }
    function buildMode(mode, context, callback) {
      var modules = context.module ? [context.module] : config.moduleList(context.package);

      context = context.clone();
      context.mode = mode;

      if (context.fileConfig) {
        processConfig(context.fileConfig, callback);
      } else {
        plugins.outputConfigs(context, function(err, configs) {
          if (err) {
            return callback(err);
          }
          async.forEach(configs, processConfig, callback);
        });
      }

      function processConfig(fileConfig, callback) {
        var fileContext = context.clone(true);
        fileContext.fileConfig = fileConfig;
        fileContext.resources = [];
        fileContext.combineResources = {};
        fileContext.fileCache = fileContext.combined ? {} : undefined;

        async.forEach(modules, function(module, callback) {
          var moduleContext = fileContext.clone();
          moduleContext.module = module;

          buildModule(moduleContext, callback);
        },
        function(err) {
          if (err) {
            return callback(err);
          }

          plugins.modeComplete(fileContext, callback);
        });
      }
    }
    function buildModule(context, callback) {
      context.module = config.module(context.module);
      context.fileCache = context.combined ? context.fileCache : {};
      context.moduleCache = {};

      var resource = context.resource;
      if (resource) {
        resource = resource.originalResource || resource;
        processResources([resource]);
      } else {
        // Load all resources associated with this module
        build.loadResources(context, function(err, resources) {
          if (err) {
            return callback(err);
          }
          processResources(resources);
        });
      }

      function processResources(resources) {
        build.processResources(resources, context, function(err, resources) {
          if (err) {
            return callback(err);
          }

          context.moduleResources = resources;
          plugins.module(context, callback);
        });
      }
    }

    var event = new EventEmitter(),
        watching = {};

    function watchOutputHandler(status) {
      // If we are already watching a file don't do it again. Note that this prevents inputs
      // from changing without the config also changing.
      if (watching[status.fileName]) {
        return;
      }

      var originalConfig = config;
      var rebuild = _.debounce(function() {
        if (config !== originalConfig) {
          // Ignore builds that may have occured at the same time as a config file change (i.e. a branch switch)
          return;
        }

        buildPlatform(context.clone(status), logError);
      }, 500);

      var input = status.inputs.map(function(input) { return fu.resolvePath(input.dir || input); });
      watch.watchFile({ virtual: status.fileName }, input, function(type, filename, sourceChange) {
          event.emit('watch-change', {fileName: sourceChange, output: status.fileName});
          fu.resetCache(sourceChange);
          rebuild();
        });
      watching[status.fileName] = true;
    }

    return _.extend(event, {
      config: function() {
        return config;
      },

      use: function(plugin) {
        plugins.use(plugin);
      },
      /**
       *
       * @name build
       * @function This function builds out the package(s).
       * @param {string} packageName the name of the package listed under
       *  'packages' from the lumbarFile passed in during the call to init().
       * @param {Function} callback the node process Function
       */
      build: function(packageName, moduleName, callback) {
        if (!callback) {
          callback = moduleName;
          moduleName = undefined;
        }

        loadConfig(lumbarFile, function(err) {
          if (err) {
            return callback(err);
          }

          var options = {
            'module': moduleName
          };
          if (typeof packageName === "object") {
            options = packageName;
            packageName = options.package;
          }

          var packageNames = packageName ? [packageName] : config.packageList(),
              contexts = [];

          packageNames.forEach(function(pkg) {
            options.package = pkg;

            var platforms = config.platformList(pkg);
            platforms.forEach(function(platform) {
              options.platform = platform;

              var newContext = context.clone(options);
              contexts.push(newContext);
            });
          });

          async.forEach(contexts, buildPlatform, callback);
        });
      },
      watch: function(packageName, moduleName, callback) {
        if (!fs.watch) {
          throw new Error('Watch requires fs.watch, introduced in Node v0.6.0');
        }

        var self = this;
        loadConfig(lumbarFile, function(err) {
          if (err) {
            logError(err);
          }

          if (!callback) {
            callback = moduleName;
            moduleName = undefined;
          }

          var packages = packageName ? [packageName] : config.packageList();

          // Watch for changes in the config file
          // WARN : This isn't really safe for multiple executions, this is a concious design decision
          watch.watchFile(lumbarFile, [], function() {
            event.emit('watch-change', {fileName: lumbarFile, config: true});
            config = undefined;

            // Cleanup what we can, breaking things along the way
            watch.unwatchAll();
            watching = {};

            // Give it a chance to cleanup before start recreating. Attempting to avoid the too many files error.
            setTimeout(function() {
              self.watch(packageName, callback);
            }, 1000);
          });

          // Watch the individual components
          event.removeListener('output', watchOutputHandler);
          event.on('output', watchOutputHandler);

          // Actual build everything
          packages.forEach(function(name) {
            self.build(name, moduleName, logError);
          });
        });
      },
      unwatch: function() {
        watch.unwatchAll();
      }
    });
};
<commit_msg>split the optional module name argument<commit_after>var _ = require('underscore'),
    async = require('async'),
    build = require('./build'),
    combine = require('./jsCombine'),
    configLoader = require('./config'),
    Context = require('./context'),
    EventEmitter = require('events').EventEmitter,
    fs = require('fs'),
    fu = require('./fileUtil'),
    Mixin = require('./mixins'),
    plugin = require('./plugin'),
    watch = require('./watcher.js');

exports.build = build;
exports.fileUtil = fu;
exports.plugin = plugin.plugin;
exports.combine = combine.combine;
exports.config = configLoader;

/**
 *
 * @name init
 * @function This function initializes a Lumbar instance
 * @param {string} lumbarFile The lumbarFile is the main
 *  file. Its responsible to define all the platforms,
 *  packages, modules, and templates for Lumbar to use.
 * @param {Object} options supports the following options:
 *   packageConfigFile (string): name of the package config file.
 *   outdir (string): path to directory of where to output the files.
 *   minimize (boolean): Should we minimize the files?
 * @return {Object.<Function>}
 */
exports.init = function(lumbarFile, options) {
    var outdir = options.outdir,
        plugins = plugin.create(options),
        mixins = new Mixin(options),
        config,
        configCache,
        context;

    function logError(err) {
      if (err) {
        event.emit('error', err);
      }
    }

    function loadConfig(path, callback) {
      try {
        fu.resetCache();

        configCache = {};
        config = configLoader.load(path);
        plugins.initialize(config);

        config.outdir = options.outdir = outdir || config.attributes.output;

        context = new Context(options, config, plugins, mixins, event);
        context.options = options;
        context.configCache = configCache;

        mixins.initialize(context, function(err) {
          if (err) {
            callback(err);
          }

          plugins.loadConfig(context, function(err) {
            if (err) {
              callback(err);
            }

            // Ensure that we have the proper build output
            if (!config.outdir) {
              return callback(new Error('Output must be defined on the command line or config file.'));
            }
            context.outdir = config.outdir;

            fu.ensureDirs(config.outdir + '/.', function(err) {
              var stat = fs.statSync(config.outdir);
              if (!stat.isDirectory()) {
                callback(new Error("Output must be a directory"));
              } else {
                callback();
              }
            });
          });
        });
      } catch (err) {
        callback(err);
      }
    }

    function buildPlatform(context, callback) {
      var modes = context.mode ? [context.mode] : plugins.modes();

      async.forEach(modes, function(mode, callback) {
          buildMode(mode, context, callback);
        },
        callback);
    }
    function buildMode(mode, context, callback) {
      var modules = context.module ?
          context.module.split(',') : config.moduleList(context.package);

      context = context.clone();
      context.mode = mode;

      if (context.fileConfig) {
        processConfig(context.fileConfig, callback);
      } else {
        plugins.outputConfigs(context, function(err, configs) {
          if (err) {
            return callback(err);
          }
          async.forEach(configs, processConfig, callback);
        });
      }

      function processConfig(fileConfig, callback) {
        var fileContext = context.clone(true);
        fileContext.fileConfig = fileConfig;
        fileContext.resources = [];
        fileContext.combineResources = {};
        fileContext.fileCache = fileContext.combined ? {} : undefined;

        async.forEach(modules, function(module, callback) {
          var moduleContext = fileContext.clone();
          moduleContext.module = module;

          buildModule(moduleContext, callback);
        },
        function(err) {
          if (err) {
            return callback(err);
          }

          plugins.modeComplete(fileContext, callback);
        });
      }
    }
    function buildModule(context, callback) {
      context.module = config.module(context.module);
      context.fileCache = context.combined ? context.fileCache : {};
      context.moduleCache = {};

      var resource = context.resource;
      if (resource) {
        resource = resource.originalResource || resource;
        processResources([resource]);
      } else {
        // Load all resources associated with this module
        build.loadResources(context, function(err, resources) {
          if (err) {
            return callback(err);
          }
          processResources(resources);
        });
      }

      function processResources(resources) {
        build.processResources(resources, context, function(err, resources) {
          if (err) {
            return callback(err);
          }

          context.moduleResources = resources;
          plugins.module(context, callback);
        });
      }
    }

    var event = new EventEmitter(),
        watching = {};

    function watchOutputHandler(status) {
      // If we are already watching a file don't do it again. Note that this prevents inputs
      // from changing without the config also changing.
      if (watching[status.fileName]) {
        return;
      }

      var originalConfig = config;
      var rebuild = _.debounce(function() {
        if (config !== originalConfig) {
          // Ignore builds that may have occured at the same time as a config file change (i.e. a branch switch)
          return;
        }

        buildPlatform(context.clone(status), logError);
      }, 500);

      var input = status.inputs.map(function(input) { return fu.resolvePath(input.dir || input); });
      watch.watchFile({ virtual: status.fileName }, input, function(type, filename, sourceChange) {
          event.emit('watch-change', {fileName: sourceChange, output: status.fileName});
          fu.resetCache(sourceChange);
          rebuild();
        });
      watching[status.fileName] = true;
    }

    return _.extend(event, {
      config: function() {
        return config;
      },

      use: function(plugin) {
        plugins.use(plugin);
      },
      /**
       *
       * @name build
       * @function This function builds out the package(s).
       * @param {string} packageName the name of the package listed under
       *  'packages' from the lumbarFile passed in during the call to init().
       * @param {Function} callback the node process Function
       */
      build: function(packageName, moduleName, callback) {
        if (!callback) {
          callback = moduleName;
          moduleName = undefined;
        }

        loadConfig(lumbarFile, function(err) {
          if (err) {
            return callback(err);
          }

          var options = {
            'module': moduleName
          };
          if (typeof packageName === "object") {
            options = packageName;
            packageName = options.package;
          }

          var packageNames = packageName ? [packageName] : config.packageList(),
              contexts = [];

          packageNames.forEach(function(pkg) {
            options.package = pkg;

            var platforms = config.platformList(pkg);
            platforms.forEach(function(platform) {
              options.platform = platform;

              var newContext = context.clone(options);
              contexts.push(newContext);
            });
          });

          async.forEach(contexts, buildPlatform, callback);
        });
      },
      watch: function(packageName, moduleName, callback) {
        if (!fs.watch) {
          throw new Error('Watch requires fs.watch, introduced in Node v0.6.0');
        }

        var self = this;
        loadConfig(lumbarFile, function(err) {
          if (err) {
            logError(err);
          }

          if (!callback) {
            callback = moduleName;
            moduleName = undefined;
          }

          var packages = packageName ? [packageName] : config.packageList();

          // Watch for changes in the config file
          // WARN : This isn't really safe for multiple executions, this is a concious design decision
          watch.watchFile(lumbarFile, [], function() {
            event.emit('watch-change', {fileName: lumbarFile, config: true});
            config = undefined;

            // Cleanup what we can, breaking things along the way
            watch.unwatchAll();
            watching = {};

            // Give it a chance to cleanup before start recreating. Attempting to avoid the too many files error.
            setTimeout(function() {
              self.watch(packageName, callback);
            }, 1000);
          });

          // Watch the individual components
          event.removeListener('output', watchOutputHandler);
          event.on('output', watchOutputHandler);

          // Actual build everything
          packages.forEach(function(name) {
            self.build(name, moduleName, logError);
          });
        });
      },
      unwatch: function() {
        watch.unwatchAll();
      }
    });
};
<|endoftext|>


<commit_before>import querystring from 'qs';

import { API_PROXY_PLUGIN_NAME } from '../';
import { coerceBool, toCamelCase } from './stringUtils';

// match escpaed unicode characters that are treated as newline literals in JS
const ESCAPED_UNICODE_NEWLINES = /\\u2028|\\u2029/g;

/**
 * Convert the X-Meetup-Variants response header into a state-ready object
 *
 * @see {@link https://meetup.atlassian.net/wiki/display/MUP/X-Meetup-Variants}
 * @returns {Object} {
 *   [experiment]: {
 *     [context (member/chapter id)]: variantName
 *   }
 * }
 */
export const parseVariantsHeader = variantsHeader =>
	variantsHeader.split(' ').reduce((variants, keyval) => {
		const [experiment, val] = keyval.split('=');
		variants[experiment] = variants[experiment] || {};
		const [context, variant] = val.split('|');
		variants[experiment][context] = variant || null;
		return variants;
	}, {});

const X_HEADERS = ['x-total-count'];

export const parseMetaHeaders = headers => {
	const meetupHeaders = Object.keys(headers)
		.filter(h => h.startsWith('x-meetup-'))
		.reduce((meta, h) => {
			const key = toCamelCase(h.replace('x-meetup-', ''));
			meta[key] = headers[h];
			return meta;
		}, {});

	// special case handling for flags
	if (meetupHeaders.flags) {
		meetupHeaders.flags = querystring.parse(meetupHeaders.flags, {
			delimiter: ', ',
			decoder: coerceBool,
		});
	}

	// special case handling for variants
	if (meetupHeaders.variants) {
		meetupHeaders.variants = parseVariantsHeader(meetupHeaders.variants);
	}

	const xHeaders = X_HEADERS.reduce((meta, h) => {
		const key = toCamelCase(h.replace('x-', ''));
		if (h in headers) {
			meta[key] = headers[h];
		}
		return meta;
	}, {});

	const linkHeader =
		headers.link &&
		headers.link.split(',').reduce((links, link) => {
			const [urlString, relString] = link.split(';');
			const url = urlString.replace(/<|>/g, '').trim();
			var rel = relString.replace(/rel="([^"]+)"/, '$1').trim();
			links[rel] = url;
			return links;
		}, {});
	const meta = {
		...meetupHeaders,
		...xHeaders,
	};

	if (linkHeader) {
		meta.link = linkHeader;
	}
	return meta;
};

/**
 * Accept an Error and return an object that will be used in place of the
 * expected API return value
 * @param {Error} err the error to populate
 * @return {Object} { value, error? }
 */
function formatApiError(err, value = null) {
	return {
		value,
		error: err.message,
	};
}

const parseBody = body => {
	if (!body) {
		return null;
	}
	// Some newline literals will not work in JS string literals - they must be
	// converted to an escaped newline character that will work end to end ('\n')
	// treat non-success HTTP code as an error
	const safeBody = body.replace(ESCAPED_UNICODE_NEWLINES, '\\n');
	return JSON.parse(safeBody);
};

/**
 * The externalRequest callback provides a `response` object and a `body` string
 * that need to be parsed in order to determine the appropriate 'value'.
 *
 * This function determines the { value, error } based on the status code and
 * body of the response - it will always set an 'error' value when a non-2xx
 * response is received, but it may provide additional error details that are
 * included in the 'body' of the response - body error details will usually
 * be JSON parsed into `value.errors`, but that is determined by the JSON
 * returned by the REST API.
 *
 * @return {Object} { value, error? }
 */
export const parseApiValue = ([response, body]) => {
	if (response.statusCode === 204) {
		// NoContent response type
		return { value: null };
	}

	try {
		const value = parseBody(body);
		if (response.statusCode < 200 || response.statusCode > 399) {
			return formatApiError(new Error(response.statusMessage), value);
		}
		if (value && value.problem) {
			return formatApiError(
				new Error(`API problem: ${value.problem}: ${value.details}`),
				value
			);
		}
		return { value };
	} catch (err) {
		return formatApiError(err);
	}
};

/**
 *
 * mostly error handling - any case where the API does not satisfy the
 * "api response" formatting requirement: plain object containing the requested
 * values
 *
 * This utility is specific to the response format of the API being consumed
 * @param {Array} the callback args for npm request - [response, body], where
 * `response` is an `Http.IncomingMessage` and `body` is the body text of the
 * response.
 * @return responseObj the JSON-parsed text, possibly with error info
 */
export const makeParseApiResponse = query => ([response, body]) => {
	const { value, error } = parseApiValue([response, body]);
	const meta = {
		...parseMetaHeaders(response.headers),
		endpoint: query.endpoint,
		statusCode: response.statusCode,
	};

	return {
		value,
		error,
		meta,
	};
};
/**
 * Format apiResponse to match expected state structure
 *
 * @param {Object} apiResponse JSON-parsed api response data
 */
export const makeApiResponseToQueryResponse = query => ({
	value,
	error,
	meta,
}) => ({
	type: query.type,
	ref: query.ref,
	value,
	error,
	meta,
});

export const makeLogResponse = request => ([response, body]) => {
	const {
		elapsedTime,
		request: { id, uri: { query, pathname, href }, method },
		statusCode,
	} = response;

	const logger = request.server.app.logger;
	// production logs will automatically be JSON-parsed in Stackdriver
	const log = ((statusCode >= 400 && logger.error) ||
		(statusCode >= 300 && logger.warn) ||
		logger.info)
		.bind(logger);

	log(
		{
			type: 'response',
			direction: 'in',
			info: {
				url: href,
				query: (query || '').split('&').reduce((acc, keyval) => {
					const [key, val] = keyval.split('=');
					acc[key] = val;
					return acc;
				}, {}),
				method,
				id,
				originRequestId: request.id,
				statusCode: statusCode,
				time: elapsedTime,
				body: body.length > 256 ? `${body.substr(0, 256)}...` : body,
			},
		},
		`Incoming response ${method.toUpperCase()} ${pathname} ${response.statusCode}`
	);
};

/**
 * When a tough-cookie cookie jar is provided, forward the cookies along with
 * the overall /api response back to the client
 */
export const makeInjectResponseCookies = request => ([response, _, jar]) => {
	if (!jar) {
		return;
	}
	const requestUrl = response.toJSON().request.uri.href;
	jar.getCookies(requestUrl).forEach(cookie => {
		const cookieOptions = {
			domain: cookie.domain,
			path: cookie.path,
			isHttpOnly: cookie.httpOnly,
			isSameSite: false,
			isSecure: request.server.settings.app.isProd,
			strictHeader: false, // Can't enforce RFC 6265 cookie validation on external services
		};

		request.plugins[API_PROXY_PLUGIN_NAME].setState(
			cookie.key,
			cookie.value,
			cookieOptions
		);
	});
};

export const makeReceive = request => {
	const logResponse = makeLogResponse(request);
	const injectResponseCookies = makeInjectResponseCookies(request);
	return query => {
		const parseApiResponse = makeParseApiResponse(query);
		const apiResponseToQueryResponse = makeApiResponseToQueryResponse(query);
		return response => {
			logResponse(response); // this will leak private API response data into production logs
			injectResponseCookies(response);
			try {
				return apiResponseToQueryResponse(parseApiResponse(response));
			} catch (err) {
				return {
					...formatApiError(err),
					meta: {
						endpoint: query.endpoint,
					},
				};
			}
		};
	};
};
<commit_msg>use console.error to make stderr log<commit_after>import querystring from 'qs';

import { API_PROXY_PLUGIN_NAME } from '../';
import { coerceBool, toCamelCase } from './stringUtils';

// match escpaed unicode characters that are treated as newline literals in JS
const ESCAPED_UNICODE_NEWLINES = /\\u2028|\\u2029/g;

/**
 * Convert the X-Meetup-Variants response header into a state-ready object
 *
 * @see {@link https://meetup.atlassian.net/wiki/display/MUP/X-Meetup-Variants}
 * @returns {Object} {
 *   [experiment]: {
 *     [context (member/chapter id)]: variantName
 *   }
 * }
 */
export const parseVariantsHeader = variantsHeader =>
	variantsHeader.split(' ').reduce((variants, keyval) => {
		const [experiment, val] = keyval.split('=');
		variants[experiment] = variants[experiment] || {};
		const [context, variant] = val.split('|');
		variants[experiment][context] = variant || null;
		return variants;
	}, {});

const X_HEADERS = ['x-total-count'];

export const parseMetaHeaders = headers => {
	const meetupHeaders = Object.keys(headers)
		.filter(h => h.startsWith('x-meetup-'))
		.reduce((meta, h) => {
			const key = toCamelCase(h.replace('x-meetup-', ''));
			meta[key] = headers[h];
			return meta;
		}, {});

	// special case handling for flags
	if (meetupHeaders.flags) {
		meetupHeaders.flags = querystring.parse(meetupHeaders.flags, {
			delimiter: ', ',
			decoder: coerceBool,
		});
	}

	// special case handling for variants
	if (meetupHeaders.variants) {
		meetupHeaders.variants = parseVariantsHeader(meetupHeaders.variants);
	}

	const xHeaders = X_HEADERS.reduce((meta, h) => {
		const key = toCamelCase(h.replace('x-', ''));
		if (h in headers) {
			meta[key] = headers[h];
		}
		return meta;
	}, {});

	const linkHeader =
		headers.link &&
		headers.link.split(',').reduce((links, link) => {
			const [urlString, relString] = link.split(';');
			const url = urlString.replace(/<|>/g, '').trim();
			var rel = relString.replace(/rel="([^"]+)"/, '$1').trim();
			links[rel] = url;
			return links;
		}, {});
	const meta = {
		...meetupHeaders,
		...xHeaders,
	};

	if (linkHeader) {
		meta.link = linkHeader;
	}
	return meta;
};

/**
 * Accept an Error and return an object that will be used in place of the
 * expected API return value
 * @param {Error} err the error to populate
 * @return {Object} { value, error? }
 */
function formatApiError(err, value = null) {
	return {
		value,
		error: err.message,
	};
}

const parseBody = body => {
	if (!body) {
		return null;
	}
	// Some newline literals will not work in JS string literals - they must be
	// converted to an escaped newline character that will work end to end ('\n')
	// treat non-success HTTP code as an error
	const safeBody = body.replace(ESCAPED_UNICODE_NEWLINES, '\\n');
	return JSON.parse(safeBody);
};

/**
 * The externalRequest callback provides a `response` object and a `body` string
 * that need to be parsed in order to determine the appropriate 'value'.
 *
 * This function determines the { value, error } based on the status code and
 * body of the response - it will always set an 'error' value when a non-2xx
 * response is received, but it may provide additional error details that are
 * included in the 'body' of the response - body error details will usually
 * be JSON parsed into `value.errors`, but that is determined by the JSON
 * returned by the REST API.
 *
 * @return {Object} { value, error? }
 */
export const parseApiValue = ([response, body]) => {
	if (response.statusCode === 204) {
		// NoContent response type
		return { value: null };
	}

	try {
		const value = parseBody(body);
		if (response.statusCode < 200 || response.statusCode > 399) {
			return formatApiError(new Error(response.statusMessage), value);
		}
		if (value && value.problem) {
			return formatApiError(
				new Error(`API problem: ${value.problem}: ${value.details}`),
				value
			);
		}
		return { value };
	} catch (err) {
		return formatApiError(err);
	}
};

/**
 *
 * mostly error handling - any case where the API does not satisfy the
 * "api response" formatting requirement: plain object containing the requested
 * values
 *
 * This utility is specific to the response format of the API being consumed
 * @param {Array} the callback args for npm request - [response, body], where
 * `response` is an `Http.IncomingMessage` and `body` is the body text of the
 * response.
 * @return responseObj the JSON-parsed text, possibly with error info
 */
export const makeParseApiResponse = query => ([response, body]) => {
	const { value, error } = parseApiValue([response, body]);
	const meta = {
		...parseMetaHeaders(response.headers),
		endpoint: query.endpoint,
		statusCode: response.statusCode,
	};

	return {
		value,
		error,
		meta,
	};
};
/**
 * Format apiResponse to match expected state structure
 *
 * @param {Object} apiResponse JSON-parsed api response data
 */
export const makeApiResponseToQueryResponse = query => ({
	value,
	error,
	meta,
}) => ({
	type: query.type,
	ref: query.ref,
	value,
	error,
	meta,
});

export const makeLogResponse = request => ([response, body]) => {
	const {
		elapsedTime,
		request: { id, uri: { query, pathname, href }, method },
		statusCode,
	} = response;
	const parsedQuery = (query || '').split('&').reduce((acc, keyval) => {
		const [key, val] = keyval.split('=');
		acc[key] = val;
		return acc;
	}, {});

	if (statusCode >= 500) {
		// use console.error to highlight these cases in Stackdriver
		console.error(
			JSON.stringify({
				message: 'REST API error response',
				info: {
					url: href,
					query: parsedQuery,
					method,
					id,
					statusCode,
					time: elapsedTime,
					originRequestId: request.id,
					body,
				},
			})
		);
	}
	const logger = request.server.app.logger;
	// production logs will automatically be JSON-parsed in Stackdriver
	const log = ((statusCode >= 400 && logger.error) ||
		(statusCode >= 300 && logger.warn) ||
		logger.info)
		.bind(logger);

	log(
		{
			type: 'response',
			direction: 'in',
			info: {
				url: href,
				query: parsedQuery,
				method,
				id,
				originRequestId: request.id,
				statusCode,
				time: elapsedTime,
				body: body.length > 256 ? `${body.substr(0, 256)}...` : body,
			},
		},
		`Incoming response ${method.toUpperCase()} ${pathname} ${response.statusCode}`
	);
};

/**
 * When a tough-cookie cookie jar is provided, forward the cookies along with
 * the overall /api response back to the client
 */
export const makeInjectResponseCookies = request => ([response, _, jar]) => {
	if (!jar) {
		return;
	}
	const requestUrl = response.toJSON().request.uri.href;
	jar.getCookies(requestUrl).forEach(cookie => {
		const cookieOptions = {
			domain: cookie.domain,
			path: cookie.path,
			isHttpOnly: cookie.httpOnly,
			isSameSite: false,
			isSecure: request.server.settings.app.isProd,
			strictHeader: false, // Can't enforce RFC 6265 cookie validation on external services
		};

		request.plugins[API_PROXY_PLUGIN_NAME].setState(
			cookie.key,
			cookie.value,
			cookieOptions
		);
	});
};

export const makeReceive = request => {
	const logResponse = makeLogResponse(request);
	const injectResponseCookies = makeInjectResponseCookies(request);
	return query => {
		const parseApiResponse = makeParseApiResponse(query);
		const apiResponseToQueryResponse = makeApiResponseToQueryResponse(query);
		return response => {
			logResponse(response); // this will leak private API response data into production logs
			injectResponseCookies(response);
			try {
				return apiResponseToQueryResponse(parseApiResponse(response));
			} catch (err) {
				return {
					...formatApiError(err),
					meta: {
						endpoint: query.endpoint,
					},
				};
			}
		};
	};
};
<|endoftext|>


<commit_before>var Imagemin = require('imagemin');
var imageminJpegRecompress = require('imagemin-jpeg-recompress');
var imageminPngquant = require('imagemin-pngquant');
var rename = require('gulp-rename');
var imageminAdvpng = require('imagemin-advpng');
var imageminZopfli = require('imagemin-zopfli');
var imageminPngcrush = require('imagemin-pngcrush');
var imageminJpegRecompress = require('imagemin-jpeg-recompress');
var imageminMozjpeg = require('imagemin-mozjpeg');
var imageminGifsicle = require('imagemin-gifsicle');
var caltime = require("./caltime")
var aliutil = require("./aliutil")

var minify = module.exports = exports = {}

minify.png_minify = function(obj, callback) {
    //var input = ;
    var image = new Imagemin()
        .src(obj.data)
        .dest(obj.output_dic)
    //.use(imageminJpegRecompress({loops: 3,quality:'low',min:5,target:0.999,progressive:true}))
    .use(imageminPngquant({
        floyd: 0,
        nofs: false,
        quality: '50-80',
        speed: 5,
        posterize: 1
    }))
        .use(rename(obj.output_nm))
    //.use(imageminPngcrush({reduce: true}))
    // .use(imageminZopfli({'8bit':true}))
    // .use(imageminAdvpng({optimizationLevel: 4}))
    .run(function(err, files) {

        aliutil.putImage({
            bucket: 'hmm-images',
            file_nm: obj.output_nm,
            data: files[0].contents,
            mime_type: obj.mime_type
        }, function(data) {
            caltime({
                time: obj.time,
                in_len: obj.data.length,
                out_len: files[0].contents.length,
                oss_return:data
            }, function(compress){
                	callback(compress);
                });
        })
    });
}

minify.jpg_minify = function(obj, callback) {
    var image = new Imagemin()
        .src(obj.data)
        .dest(obj.output_dic)
        .use(imageminJpegRecompress({
            loops: 3,
            quality: 'low',
            min: 5,
            target: 0.999
        }))
        .use(rename(obj.output_nm))
        .run(function(err, files) {
            aliutil.putImage({
                bucket: 'hmm-images',
                file_nm: obj.output_nm,
                data: files[0].contents,
                mime_type: obj.mime_type
            }, function(data) {
                caltime({
                    time: obj.time,
                    in_len: obj.data.length,
                    out_len: files[0].contents.length,
                    oss_return:data
                }, function(compress){
                	callback(compress);
                });
            })
        });

}

minify.mozjpg_minify = function(obj, callback) {
    var image = new Imagemin()
        .src(obj.data)
        .dest(obj.output_dic)
        .use(imageminMozjpeg({
            quality: 75,
            fastcrush: true
        }))
        .use(rename(obj.output_nm))
        .run(function(err, files) {
            aliutil.putImage({
                bucket: 'hmm-images',
                file_nm: obj.output_nm,
                data: files[0].contents,
                mime_type: obj.mime_type
            }, function(data) {
                caltime({
                    time: obj.time,
                    in_len: obj.data.length,
                    out_len: files[0].contents.length,
                    oss_return:data
                }, function(compress){
                	callback(compress);
                });
            })
        });
}

minify.gif_minify = function(obj, callback) {

    var image = new Imagemin()
        .src(obj.data)
        .dest(obj.output_dic)
        .use(imageminGifsicle())
        .use(rename(obj.output_nm))
        .run(function(err, files) {
            aliutil.putImage({
                bucket: 'hmm-images',
                file_nm: obj.output_nm,
                data: files[0].contents,
                mime_type: obj.mime_type
            }, function(data) {
                caltime({
                    time: obj.time,
                    in_len: obj.data.length,
                    out_len: files[0].contents.length,
                    oss_return:data
                }, function(compress){
                	callback(compress);
                });
            })
        });
}
<commit_msg>asdfasdf<commit_after>var Imagemin = require('imagemin');
var imageminJpegRecompress = require('imagemin-jpeg-recompress');
var imageminPngquant = require('imagemin-pngquant');
var rename = require('gulp-rename');
var imageminAdvpng = require('imagemin-advpng');
var imageminZopfli = require('imagemin-zopfli');
var imageminPngcrush = require('imagemin-pngcrush');
var imageminJpegRecompress = require('imagemin-jpeg-recompress');
var imageminMozjpeg = require('imagemin-mozjpeg');
var imageminGifsicle = require('imagemin-gifsicle');
var caltime = require("./caltime")
var aliutil = require("./aliutil")

var minify = module.exports = exports = {}

minify.png_minify = function(obj, callback) {
    //var input = ;
    var image = new Imagemin()
        .src(obj.data)
        .dest(obj.output_dic)
    //.use(imageminJpegRecompress({loops: 3,quality:'low',min:5,target:0.999,progressive:true}))
    .use(imageminPngquant({
        floyd: 0,
        nofs: false,
        quality: '50-80',
        speed: 5,
        posterize: 1
    }))
        .use(rename(obj.output_nm))
    //.use(imageminPngcrush({reduce: true}))
    // .use(imageminZopfli({'8bit':true}))
    // .use(imageminAdvpng({optimizationLevel: 4}))
    .run(function(err, files) {

        aliutil.putImage({
            bucket: 'hmm-images',
            file_nm: obj.output_nm,
            data: files[0].contents,
            mime_type: obj.mime_type
        }, function(data) {
            caltime({
                time: obj.time,
                in_len: obj.data.length,
                out_len: files[0].contents.length,
                oss_return:data
            }, function(compress){
                	setTimeout(callback(compress),1000);
                });
        })
    });
}

minify.jpg_minify = function(obj, callback) {
    var image = new Imagemin()
        .src(obj.data)
        .dest(obj.output_dic)
        .use(imageminJpegRecompress({
            loops: 3,
            quality: 'low',
            min: 5,
            target: 0.999
        }))
        .use(rename(obj.output_nm))
        .run(function(err, files) {
            aliutil.putImage({
                bucket: 'hmm-images',
                file_nm: obj.output_nm,
                data: files[0].contents,
                mime_type: obj.mime_type
            }, function(data) {
                caltime({
                    time: obj.time,
                    in_len: obj.data.length,
                    out_len: files[0].contents.length,
                    oss_return:data
                }, function(compress){
                	callback(compress);
                });
            })
        });

}

minify.mozjpg_minify = function(obj, callback) {
    var image = new Imagemin()
        .src(obj.data)
        .dest(obj.output_dic)
        .use(imageminMozjpeg({
            quality: 75,
            fastcrush: true
        }))
        .use(rename(obj.output_nm))
        .run(function(err, files) {
            aliutil.putImage({
                bucket: 'hmm-images',
                file_nm: obj.output_nm,
                data: files[0].contents,
                mime_type: obj.mime_type
            }, function(data) {
				console.log(colors.red(JSON.strinify(data)));
                caltime({
                    time: obj.time,
                    in_len: obj.data.length,
                    out_len: files[0].contents.length,
                    oss_return:data
                }, function(compress){
                	setTimeout(callback(compress), 1000);
                });
            })
        });
}

minify.gif_minify = function(obj, callback) {

    var image = new Imagemin()
        .src(obj.data)
        .dest(obj.output_dic)
        .use(imageminGifsicle())
        .use(rename(obj.output_nm))
        .run(function(err, files) {
            aliutil.putImage({
                bucket: 'hmm-images',
                file_nm: obj.output_nm,
                data: files[0].contents,
                mime_type: obj.mime_type
            }, function(data) {
                caltime({
                    time: obj.time,
                    in_len: obj.data.length,
                    out_len: files[0].contents.length,
                    oss_return:data
                }, function(compress){
                	callback(compress);
                });
            })
        });
}
<|endoftext|>


<commit_before>import objectAssign from 'object-assign';

import copy from './utils/copy';
import {selectKey} from './$traverse';
import {pathKey} from './$nav';
import arrayify from './utils/arrayify';
import {$setKey} from './$set';
import {$defaultKey} from './$default';
import {$applyKey} from './$apply';
import {$lensKey} from './$lens';
import {$setContextKey} from './$setContext';
import $none, {$noneKey, undefinedIfNone, isNone} from './$none';
import {isReduced} from './utils/reduced';

// `traverseEach` is the heart of `qim`. It's a little ugly because it needs to
// be performant, and it's generic across select and update.
export const traverseEach = (
  navKey, // select or update?
  state, // state to carry around, so select can add new values
  resultFn, // like a reduce function, called for each selected value
  path, // the whole query
  object, // the current object that we're traversing
  pathIndex, // index into the path
  returnFn, // function to "return" to, if we've navigated into a sub-query
  context, // current context: modified with setContext and passed to transforms
  mutationMarker // performance signal that it's safe to mutate instead of clone
) => {

  if (pathIndex >= path.length) {
    // If we're in a sub-query (with $nav), we need to return.
    if (returnFn) {
      return returnFn(object, context);
    }
    // Default to identity if there's no result function.
    return resultFn ? resultFn(state, object) : object;
  }

  const nav = path[pathIndex];

  // null/undefined selects/updates nothing.
  if (nav == null) {
    return navKey === selectKey ? $none : object;
  }

  // Primitives are handled right here inside traverseEach to state efficient.
  if (typeof nav === 'string' || typeof nav === 'number' || typeof nav === 'boolean') {
    // select, which is pretty simple
    if (navKey === selectKey) {
      // If we have an object, just grab the nested object and keep digging.
      if (object && typeof object === 'object') {
        const subObject = object[nav];
        // Special case so `has` can differentiate between missing keys and keys
        // pointing to undefined values. Maybe a better way? Maybe just don't
        // worry about `has(['x'], {x: undefined}) === false`?
        if (typeof subObject === 'undefined' && !(nav in object) && pathIndex === path.length - 1) {
          return $none;
        }
        return traverseEach(navKey, state, resultFn, path, subObject, pathIndex + 1, returnFn, context);
      } else {
        return $none;
      }
    }
    // update, which is not as simple
    if (object && typeof object === 'object') {
      const value = object[nav];
      // Dig in for the new value.
      const newValue = traverseEach(navKey, state, resultFn, path, value, pathIndex + 1, returnFn, context);
      // A new value of $none means we've removed it.
      if (isNone(newValue)) {
        if (!(nav in object)) {
          return object;
        }
        if (Array.isArray(object)) {
          const newObject = object.slice(0);
          // Could have a silly edge here where nav was a property and not an
          // index, which means it disappears. If it's still here, that means
          // it's an index.
          if (nav in object) {
            newObject.splice(nav, 1);
          }
          return newObject;
        }
        const newObject = objectAssign({}, object);
        delete newObject[nav];
        return newObject;
      }
      // If we got back the same value, it's a no-op.
      if (value === newValue) {
        return object;
      }
      // Some dangerous mutation going on here! Be careful!
      if (mutationMarker) {
        // If we have a mutation marker, and we've already mutated, then we can
        // safely mutate and avoid the cost of re-cloning the object.
        if (mutationMarker.hasMutated) {
          object[nav] = newValue;
          return object;
        // Otherwise, we're going to mutate, so mark that we've mutated.
        } else {
          mutationMarker.hasMutated = true;
        }
      }
      // Clone our object and set the property to the new value.
      const newObject = copy(object);
      newObject[nav] = newValue;
      return newObject;
    // If we got back null/undefined, then intelligently create a new object.
    // It's debatable on whether or not this is a good idea. Integers used to
    // auto-create arrays, but that's probably wrong more than right. An
    // integer like 123456 is probably an id and not an array index, so it's
    // probably a mistake to create an array of that size. If an array is
    // actually wanted, `default([])` can be added. We could do nothing, which
    // would keep you from making typos, but then you have to add a lot of
    // `default({})`, and then you're right back to being able to make typos.
    // So defaulting to create an object seems like a sane balance.
    } else if (object == null) {
      const newValue = traverseEach(navKey, state, resultFn, path, undefined, pathIndex + 1, returnFn, context);
      // Trying to set a key to $none is a no-op.
      if (isNone(newValue)) {
        return object;
      }
      const newObject = {};
      newObject[nav] = newValue;
      return newObject;
    } else {
      throw new Error(`Cannot update property ${nav} (at path index ${pathIndex}) for non-object.`);
    }
  }

  // Predicate navigator, keep going if there's a match.
  if (typeof nav === 'function') {
    if (nav(object)) {
      return traverseEach(navKey, state, resultFn, path, object, pathIndex + 1, returnFn, context);
    } else {
      return navKey === selectKey ? $none : object;
    }
  }

  // Some built-in navigators for transform and context.
  switch (nav['@@qim/nav']) {
    // Transform the current value into another value.
    case $applyKey: {
      return traverseEach(navKey, state, resultFn, path, nav.fn(object, context), pathIndex + 1, returnFn, context);
    }
    // Transform the current value into another value.
    // For updates, apply another transform on the way back.
    case $lensKey: {
      const result = traverseEach(navKey, state, resultFn, path, nav.fn(object, context), pathIndex + 1, returnFn, context);
      if (navKey === selectKey || !nav.fromFn) {
        return result;
      }
      return nav.fromFn(result, object, context);
    }
    // Set the current value to a new constant value.
    case $setKey:
      return traverseEach(navKey, state, resultFn, path, nav.value, pathIndex + 1, returnFn, context);
    // Set the current value to a new constant value if the current value is
    // undefined.
    case $defaultKey: {
      if (typeof object === 'undefined') {
        return traverseEach(navKey, state, resultFn, path, nav.value, pathIndex + 1, returnFn, context);
      }
      return traverseEach(navKey, state, resultFn, path, object, pathIndex + 1, returnFn, context);
    }
    // Remove the current value.
    case $noneKey:
      return $none;
    // Set a context value, to later be used by $apply.
    case $setContextKey: {
      context = nav.setContext(nav, nav.fn(object, context || {}), context);
      return traverseEach(navKey, state, resultFn, path, object, pathIndex + 1, returnFn, context);
    }
  }

  let navPath = nav[pathKey];

  // TODO: Combine this with array navigator.
  // Path navigators.
  if (navPath) {
    // A path navigator can potentially go down multiple paths.
    const moreNavPaths = nav.moreNavPaths;
    let moreNavPathsIndex = 0;
    let navResult = object;
    do {
      // If it's a function, get our path dynamically.
      if (typeof navPath === 'function') {
        navPath = nav.hasArgs ? navPath(nav.args, object, context) : navPath(object, context);
        navPath = arrayify(navPath);
      }
      // TODO: Use mutationMarker.
      // An empty query just means continue.
      if (navPath.length === 0) {
        navResult = traverseEach(navKey, state, resultFn, path, object, pathIndex + 1, returnFn, context);
      // Otherwise, recurse in with our new query, but pass a `returnFn` so we can
      // continue where we left off.
      } else {
        navResult = traverseEach(navKey,
          state, resultFn, navPath, object, 0,
          (_object, _context) => traverseEach(navKey, state, resultFn, path, _object, pathIndex + 1, returnFn, _context),
          context
        );
      }
      if (moreNavPaths) {
        // Selects should be applied to the same object, since nothing has changed. If we've reduced, then we're done.
        if (navKey === selectKey) {
          if (isReduced(navResult)) {
            return navResult;
          }
        // Updates should be applied to the new object.
        } else {
          object = undefinedIfNone(navResult);
        }
        navPath = moreNavPaths[moreNavPathsIndex];
        moreNavPathsIndex++;
      } else {
        navPath = undefined;
      }
    } while (navPath);
    return navResult;
  }

  // Core select/update navigator.
  if (nav[navKey]) {
    if (nav.hasArgs) {
      return nav[navKey](
        nav.args, object,
        (subObject) => traverseEach(navKey, state, resultFn, path, subObject, pathIndex + 1, returnFn, context),
        path, pathIndex
      );
    }
    return nav[navKey](
      object,
      (subObject) => traverseEach(navKey, state, resultFn, path, subObject, pathIndex + 1, returnFn, context),
      path, pathIndex
    );
  }

  // Nested sub-query.
  if (Array.isArray(nav)) {
    // A nested select will branch off an add items to the current select state.
    // So it's semantically a bit different than a nested update sub-query.
    if (navKey === selectKey) {
      const subResult = traverseEach(navKey, state, resultFn, nav, object, 0, returnFn, context);
      if (pathIndex + 1 === path.length) {
        return subResult;
      }
      return traverseEach(navKey, state, resultFn, path, object, pathIndex + 1, returnFn, context);
    }

    // A nested update will branch off and mutate the object and then return to
    // the current object. It's kind of just a fancy $apply.
    // Create a mutation marker so we can efficiently apply many sub-queries to
    // the current object.
    mutationMarker = mutationMarker || {
      hasMutated: false
    };
    const nestedResult = undefinedIfNone(
      traverseEach(navKey, state, resultFn, nav, object, 0, undefined, context, mutationMarker)
    );
    return traverseEach(navKey, state, resultFn, path, nestedResult, pathIndex + 1, returnFn, context, mutationMarker);
  }

  throw new Error(`Invalid navigator ${nav} at path index ${pathIndex}.`);
};
<commit_msg>fix typo<commit_after>import objectAssign from 'object-assign';

import copy from './utils/copy';
import {selectKey} from './$traverse';
import {pathKey} from './$nav';
import arrayify from './utils/arrayify';
import {$setKey} from './$set';
import {$defaultKey} from './$default';
import {$applyKey} from './$apply';
import {$lensKey} from './$lens';
import {$setContextKey} from './$setContext';
import $none, {$noneKey, undefinedIfNone, isNone} from './$none';
import {isReduced} from './utils/reduced';

// `traverseEach` is the heart of `qim`. It's a little ugly because it needs to
// be performant, and it's generic across select and update.
export const traverseEach = (
  navKey, // select or update?
  state, // state to carry around, so select can add new values
  resultFn, // like a reduce function, called for each selected value
  path, // the whole query
  object, // the current object that we're traversing
  pathIndex, // index into the path
  returnFn, // function to "return" to, if we've navigated into a sub-query
  context, // current context: modified with setContext and passed to transforms
  mutationMarker // performance signal that it's safe to mutate instead of clone
) => {

  if (pathIndex >= path.length) {
    // If we're in a sub-query (with $nav), we need to return.
    if (returnFn) {
      return returnFn(object, context);
    }
    // Default to identity if there's no result function.
    return resultFn ? resultFn(state, object) : object;
  }

  const nav = path[pathIndex];

  // null/undefined selects/updates nothing.
  if (nav == null) {
    return navKey === selectKey ? $none : object;
  }

  // Primitives are handled right here inside traverseEach to stay efficient.
  if (typeof nav === 'string' || typeof nav === 'number' || typeof nav === 'boolean') {
    // select, which is pretty simple
    if (navKey === selectKey) {
      // If we have an object, just grab the nested object and keep digging.
      if (object && typeof object === 'object') {
        const subObject = object[nav];
        // Special case so `has` can differentiate between missing keys and keys
        // pointing to undefined values. Maybe a better way? Maybe just don't
        // worry about `has(['x'], {x: undefined}) === false`?
        if (typeof subObject === 'undefined' && !(nav in object) && pathIndex === path.length - 1) {
          return $none;
        }
        return traverseEach(navKey, state, resultFn, path, subObject, pathIndex + 1, returnFn, context);
      } else {
        return $none;
      }
    }
    // update, which is not as simple
    if (object && typeof object === 'object') {
      const value = object[nav];
      // Dig in for the new value.
      const newValue = traverseEach(navKey, state, resultFn, path, value, pathIndex + 1, returnFn, context);
      // A new value of $none means we've removed it.
      if (isNone(newValue)) {
        if (!(nav in object)) {
          return object;
        }
        if (Array.isArray(object)) {
          const newObject = object.slice(0);
          // Could have a silly edge here where nav was a property and not an
          // index, which means it disappears. If it's still here, that means
          // it's an index.
          if (nav in object) {
            newObject.splice(nav, 1);
          }
          return newObject;
        }
        const newObject = objectAssign({}, object);
        delete newObject[nav];
        return newObject;
      }
      // If we got back the same value, it's a no-op.
      if (value === newValue) {
        return object;
      }
      // Some dangerous mutation going on here! Be careful!
      if (mutationMarker) {
        // If we have a mutation marker, and we've already mutated, then we can
        // safely mutate and avoid the cost of re-cloning the object.
        if (mutationMarker.hasMutated) {
          object[nav] = newValue;
          return object;
        // Otherwise, we're going to mutate, so mark that we've mutated.
        } else {
          mutationMarker.hasMutated = true;
        }
      }
      // Clone our object and set the property to the new value.
      const newObject = copy(object);
      newObject[nav] = newValue;
      return newObject;
    // If we got back null/undefined, then intelligently create a new object.
    // It's debatable on whether or not this is a good idea. Integers used to
    // auto-create arrays, but that's probably wrong more than right. An
    // integer like 123456 is probably an id and not an array index, so it's
    // probably a mistake to create an array of that size. If an array is
    // actually wanted, `default([])` can be added. We could do nothing, which
    // would keep you from making typos, but then you have to add a lot of
    // `default({})`, and then you're right back to being able to make typos.
    // So defaulting to create an object seems like a sane balance.
    } else if (object == null) {
      const newValue = traverseEach(navKey, state, resultFn, path, undefined, pathIndex + 1, returnFn, context);
      // Trying to set a key to $none is a no-op.
      if (isNone(newValue)) {
        return object;
      }
      const newObject = {};
      newObject[nav] = newValue;
      return newObject;
    } else {
      throw new Error(`Cannot update property ${nav} (at path index ${pathIndex}) for non-object.`);
    }
  }

  // Predicate navigator, keep going if there's a match.
  if (typeof nav === 'function') {
    if (nav(object)) {
      return traverseEach(navKey, state, resultFn, path, object, pathIndex + 1, returnFn, context);
    } else {
      return navKey === selectKey ? $none : object;
    }
  }

  // Some built-in navigators for transform and context.
  switch (nav['@@qim/nav']) {
    // Transform the current value into another value.
    case $applyKey: {
      return traverseEach(navKey, state, resultFn, path, nav.fn(object, context), pathIndex + 1, returnFn, context);
    }
    // Transform the current value into another value.
    // For updates, apply another transform on the way back.
    case $lensKey: {
      const result = traverseEach(navKey, state, resultFn, path, nav.fn(object, context), pathIndex + 1, returnFn, context);
      if (navKey === selectKey || !nav.fromFn) {
        return result;
      }
      return nav.fromFn(result, object, context);
    }
    // Set the current value to a new constant value.
    case $setKey:
      return traverseEach(navKey, state, resultFn, path, nav.value, pathIndex + 1, returnFn, context);
    // Set the current value to a new constant value if the current value is
    // undefined.
    case $defaultKey: {
      if (typeof object === 'undefined') {
        return traverseEach(navKey, state, resultFn, path, nav.value, pathIndex + 1, returnFn, context);
      }
      return traverseEach(navKey, state, resultFn, path, object, pathIndex + 1, returnFn, context);
    }
    // Remove the current value.
    case $noneKey:
      return $none;
    // Set a context value, to later be used by $apply.
    case $setContextKey: {
      context = nav.setContext(nav, nav.fn(object, context || {}), context);
      return traverseEach(navKey, state, resultFn, path, object, pathIndex + 1, returnFn, context);
    }
  }

  let navPath = nav[pathKey];

  // TODO: Combine this with array navigator.
  // Path navigators.
  if (navPath) {
    // A path navigator can potentially go down multiple paths.
    const moreNavPaths = nav.moreNavPaths;
    let moreNavPathsIndex = 0;
    let navResult = object;
    do {
      // If it's a function, get our path dynamically.
      if (typeof navPath === 'function') {
        navPath = nav.hasArgs ? navPath(nav.args, object, context) : navPath(object, context);
        navPath = arrayify(navPath);
      }
      // TODO: Use mutationMarker.
      // An empty query just means continue.
      if (navPath.length === 0) {
        navResult = traverseEach(navKey, state, resultFn, path, object, pathIndex + 1, returnFn, context);
      // Otherwise, recurse in with our new query, but pass a `returnFn` so we can
      // continue where we left off.
      } else {
        navResult = traverseEach(navKey,
          state, resultFn, navPath, object, 0,
          (_object, _context) => traverseEach(navKey, state, resultFn, path, _object, pathIndex + 1, returnFn, _context),
          context
        );
      }
      if (moreNavPaths) {
        // Selects should be applied to the same object, since nothing has changed. If we've reduced, then we're done.
        if (navKey === selectKey) {
          if (isReduced(navResult)) {
            return navResult;
          }
        // Updates should be applied to the new object.
        } else {
          object = undefinedIfNone(navResult);
        }
        navPath = moreNavPaths[moreNavPathsIndex];
        moreNavPathsIndex++;
      } else {
        navPath = undefined;
      }
    } while (navPath);
    return navResult;
  }

  // Core select/update navigator.
  if (nav[navKey]) {
    if (nav.hasArgs) {
      return nav[navKey](
        nav.args, object,
        (subObject) => traverseEach(navKey, state, resultFn, path, subObject, pathIndex + 1, returnFn, context),
        path, pathIndex
      );
    }
    return nav[navKey](
      object,
      (subObject) => traverseEach(navKey, state, resultFn, path, subObject, pathIndex + 1, returnFn, context),
      path, pathIndex
    );
  }

  // Nested sub-query.
  if (Array.isArray(nav)) {
    // A nested select will branch off an add items to the current select state.
    // So it's semantically a bit different than a nested update sub-query.
    if (navKey === selectKey) {
      const subResult = traverseEach(navKey, state, resultFn, nav, object, 0, returnFn, context);
      if (pathIndex + 1 === path.length) {
        return subResult;
      }
      return traverseEach(navKey, state, resultFn, path, object, pathIndex + 1, returnFn, context);
    }

    // A nested update will branch off and mutate the object and then return to
    // the current object. It's kind of just a fancy $apply.
    // Create a mutation marker so we can efficiently apply many sub-queries to
    // the current object.
    mutationMarker = mutationMarker || {
      hasMutated: false
    };
    const nestedResult = undefinedIfNone(
      traverseEach(navKey, state, resultFn, nav, object, 0, undefined, context, mutationMarker)
    );
    return traverseEach(navKey, state, resultFn, path, nestedResult, pathIndex + 1, returnFn, context, mutationMarker);
  }

  throw new Error(`Invalid navigator ${nav} at path index ${pathIndex}.`);
};
<|endoftext|>


<commit_before>/*
 Copyright 2013 TestingBot

 Licensed 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.
 */

exports.Node = function(host, port, capabilities, json) {
    this.host = null;
    this.port = 5556;
    this.available = true;
    this.lastSeen = 0;
    this.capabilities = [];
    this.platform = '';

    this._construct = function(host, port, capabilities, json) {
        this.host = host;
        this.port = parseInt(port, 10);
        this.capabilities = capabilities;

        this.lastSeen = (new Date()).getTime();
        this.json = json;
        this.platform = this.capabilities[0].platform;
    };

    this._construct(host, port, capabilities, json);
};

exports.Capability = function(cap) {
    this.platform = null;
    this.seleniumprotocol = null;
    this.browsername = null;
    this.maxinstances = 1;
    this.version = null;

    this._construct = function(cap) {
        this.platform = cap.platform;
        this.seleniumprotocol = cap.seleniumProtocol;
        this.browsername = cap.browserName;
        this.maxinstances = cap.maxInstances;
        this.version = cap.version;
        for (var key in cap) {
            this[key] = cap[key];
        }
    };

    this._construct(cap);
};

exports.Response = function(statusCode, body) {
    this.statusCode = 200;
    this.headers = {'Content-Type': 'text/plain'};
    this.body = '';

    this._construct = function(statusCode, body) {
        this.statusCode = statusCode;
        this.body = body;
    };

    this._construct(statusCode, body);
};

exports.JsonResponse = function(statusCode, body) {
    this.statusCode = 200;
    this.headers = {'Content-Type': 'application/json'};
    this.body = '';

    this._construct = function(statusCode, body) {
        this.statusCode = statusCode;
        this.body = JSON.stringify(body);
    };

    this._construct(statusCode, body);
};

exports.Session = function(type, nodeHost, nodePort, sessionID) {
    this.nodeHost = null;
    this.nodePort = 0;
    this.type = null;
    this.sessionId = null;
    this.lastUsed = 0;
    this.startTime = 0;
    this.timeoutIntervalID = null;
    // TODO: remove? (not used)
    this.platform = "";
    this.capabilities = null;
    this.desiredCapabilities = null;
    this.response = null;

    // TODO: remove? (not used)
    // diagnostics
    this.lastResponseTime = 0;
    this.lastSentTime = 0;

    this.lastResponseBody = '';
    this.lastSentBody = '';

    this._construct = function(type, nodeHost, nodePort, sessionID) {
        this.type = type;
        this.nodeHost = nodeHost;
        this.nodePort = nodePort;
        this.sessionId = sessionID;
        this.lastUsed = (new Date()).getTime();
    };

    this._construct(type, nodeHost, nodePort, sessionID);
};
<commit_msg>Remove unused Response and JsonResponse models<commit_after>/*
 Copyright 2013 TestingBot

 Licensed 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.
 */

exports.Node = function(host, port, capabilities, json) {
    this.host = null;
    this.port = 5556;
    this.available = true;
    this.lastSeen = 0;
    this.capabilities = [];
    this.platform = '';

    this._construct = function(host, port, capabilities, json) {
        this.host = host;
        this.port = parseInt(port, 10);
        this.capabilities = capabilities;

        this.lastSeen = (new Date()).getTime();
        this.json = json;
        this.platform = this.capabilities[0].platform;
    };

    this._construct(host, port, capabilities, json);
};

exports.Capability = function(cap) {
    this.platform = null;
    this.seleniumprotocol = null;
    this.browsername = null;
    this.maxinstances = 1;
    this.version = null;

    this._construct = function(cap) {
        this.platform = cap.platform;
        this.seleniumprotocol = cap.seleniumProtocol;
        this.browsername = cap.browserName;
        this.maxinstances = cap.maxInstances;
        this.version = cap.version;
        for (var key in cap) {
            this[key] = cap[key];
        }
    };

    this._construct(cap);
};

exports.Session = function(type, nodeHost, nodePort, sessionID) {
    this.nodeHost = null;
    this.nodePort = 0;
    this.type = null;
    this.sessionId = null;
    this.lastUsed = 0;
    this.startTime = 0;
    this.timeoutIntervalID = null;
    // TODO: remove? (not used)
    this.platform = "";
    this.capabilities = null;
    this.desiredCapabilities = null;
    this.response = null;

    // TODO: remove? (not used)
    // diagnostics
    this.lastResponseTime = 0;
    this.lastSentTime = 0;

    this.lastResponseBody = '';
    this.lastSentBody = '';

    this._construct = function(type, nodeHost, nodePort, sessionID) {
        this.type = type;
        this.nodeHost = nodeHost;
        this.nodePort = nodePort;
        this.sessionId = sessionID;
        this.lastUsed = (new Date()).getTime();
    };

    this._construct(type, nodeHost, nodePort, sessionID);
};
<|endoftext|>


<commit_before>var async = require('async');
var _ = require('underscore');

var ModestCompiler = require('./ModestCompiler');

// Default options

compile.options = {
  previewScript : 'modest-preview.js'
};

// compile(
//  params: -- instance parameters (extending function options) {
//    dirs : [string[]] -- Array of directories to enter (relative to process cwd)
//    previewScript : [string] -- What to name the preview script placed in each directory
//    See ModestCompiler for more... (params are forwarded to ModestCompiler)
//  }
// )

function compile(params){
  params = _.extend(compile.options,params);
  var compiler = new ModestCompiler(params);

  var series = [];
  var origPath = process.cwd();

  // Compile each directory in series, so we can reuse the same compiler

  // TODO: add an option to compile in parallel, creating a new ModestCompiler for each dir

  _.each(params.dirs, function(path){
    series.push(function(callback){
      process.chdir(origPath);
      if(!params.quiet)
        console.log('entering ' + path);
      process.chdir(path);
      if(fs.existsSync(params.previewScript))
        fs.unlinkSync(params.previewScript);
      fs.linkSync(__dirname + '/modest-preview.js', params.previewScript);
      compiler.compileFiles(callback);
    });
  });

  async.series(series,function(err){
    if(err)
      throw(err);
    else
      process.chdir(origPath);
  });

}

module.exports = {
  compile : compile,
  compiler : ModestCompiler
}<commit_msg>fix comment<commit_after>var async = require('async');
var _ = require('underscore');

var ModestCompiler = require('./ModestCompiler');

// Default options

compile.options = {
  previewScript : 'modest-preview.js'
};

// compile(
//  params: -- instance parameters (extending function options) {
//    dirs : [string[]] -- Array of directories to enter (relative to process cwd)
//    quiet: [boolean] -- supress output
//    previewScript : [string] -- What to name the preview script placed in each directory
//    See ModestCompiler for more... (params are forwarded to ModestCompiler)
//  }
// )

function compile(params){
  params = _.extend(compile.options,params);
  var compiler = new ModestCompiler(params);

  var series = [];
  var origPath = process.cwd();

  // Compile each directory in series, so we can reuse the same compiler

  // TODO: add an option to compile in parallel, creating a new ModestCompiler for each dir

  _.each(params.dirs, function(path){
    series.push(function(callback){
      process.chdir(origPath);
      if(!params.quiet)
        console.log('entering ' + path);
      process.chdir(path);
      if(fs.existsSync(params.previewScript))
        fs.unlinkSync(params.previewScript);
      fs.linkSync(__dirname + '/modest-preview.js', params.previewScript);
      compiler.compileFiles(callback);
    });
  });

  async.series(series,function(err){
    if(err)
      throw(err);
    else
      process.chdir(origPath);
  });

}

module.exports = {
  compile : compile,
  compiler : ModestCompiler
}<|endoftext|>


<commit_before>'use strict'

import path from 'path'
import fs from 'fs'
import {uniq} from 'lodash'
import hash from 'hash-sum'
import {chainFn} from './utils'

class Module {
  constructor (nuxt) {
    this.nuxt = nuxt
    this.options = nuxt.options
  }

  addVendor (vendor) {
    if (!vendor) {
      return
    }
    this.options.build.vendor = uniq(this.options.build.vendor.concat(vendor))
  }

  addTemplate (template) {
    if (!template) {
      return
    }
    // Validate & parse source
    const src = template.src || template
    const srcPath = path.parse(src)
    if (!src || typeof src !== 'string' || !fs.existsSync(src)) {
      // eslint-disable-next-line no-console
      console.warn('[Nuxt] invalid template', template)
      return
    }
    // Generate unique and human readable dst filename
    const dst = template.fileName ||
      (path.basename(srcPath.dir) + '.' + srcPath.name + '.' + hash(src) + '.' + srcPath.ext)
    // Add to templates list
    const templateObj = {
      src,
      dst,
      options: template.options
    }
    this.options.build.templates.push(templateObj)
    // Watch template for changes
    this.addWatch(src)
    return templateObj
  }

  addWatch (pattern) {
    this.options.build.watch.push(pattern)
  }

  addPlugin (template) {
    const {dst} = this.addTemplate(template)
    // Add to nuxt plugins
    this.options.plugins.push({
      src: '~/.nuxt/' + dst,
      ssr: template.ssr,
      injectAs: template.injectAs
    })
  }

  addServerMiddleware (middleware) {
    this.options.serverMiddlewares.push(middleware)
  }

  extendBuild (fn) {
    this.options.build.extend = chainFn(this.options.build.extend, fn)
  }

  extendRoutes (fn) {
    this.options.router.extendRoutes = chainFn(this.options.router.extendRoutes, fn)
  }

  installModule (moduleOpts) {
    if (!moduleOpts) {
      return
    }
    // Allows passing runtime options to each module
    const options = moduleOpts.options || {}
    let src = moduleOpts.src || moduleOpts
    // Resolve module
    let module
    try {
      if (typeof src === 'string') {
        // Using ~ shorthand modules are resolved from project srcDir
        if (src.indexOf('~') === 0) {
          src = path.resolve(this.options.srcDir, src.substr(1))
        }
        // eslint-disable-next-line no-eval
        module = eval('require')(src)
      }
    } catch (e) {
      // eslint-disable-next-line no-console
      console.error('[Nuxt] Unable to resolve module', src)
      // eslint-disable-next-line no-console
      console.error(e)
      return
    }
    // Validate module
    if (!(module instanceof Function)) {
      // eslint-disable-next-line no-console
      console.error('[Nuxt] Module should be a function', module)
    }
    // Call module with `this` context and pass options
    return new Promise((resolve, reject) => {
      const result = module.call(this, options, err => {
        if (err) {
          return reject(err)
        }
        resolve(module)
      })
      if (result && result.then instanceof Function) {
        return result.then(resolve)
      }
    })
  }
}

export default Module
<commit_msg>[module] remove extra dot in generated template names<commit_after>'use strict'

import path from 'path'
import fs from 'fs'
import {uniq} from 'lodash'
import hash from 'hash-sum'
import {chainFn} from './utils'

class Module {
  constructor (nuxt) {
    this.nuxt = nuxt
    this.options = nuxt.options
  }

  addVendor (vendor) {
    if (!vendor) {
      return
    }
    this.options.build.vendor = uniq(this.options.build.vendor.concat(vendor))
  }

  addTemplate (template) {
    if (!template) {
      return
    }
    // Validate & parse source
    const src = template.src || template
    const srcPath = path.parse(src)
    if (!src || typeof src !== 'string' || !fs.existsSync(src)) {
      // eslint-disable-next-line no-console
      console.warn('[Nuxt] invalid template', template)
      return
    }
    // Generate unique and human readable dst filename
    const dst = template.fileName ||
      (path.basename(srcPath.dir) + '.' + srcPath.name + '.' + hash(src) + srcPath.ext)
    // Add to templates list
    const templateObj = {
      src,
      dst,
      options: template.options
    }
    this.options.build.templates.push(templateObj)
    // Watch template for changes
    this.addWatch(src)
    return templateObj
  }

  addWatch (pattern) {
    this.options.build.watch.push(pattern)
  }

  addPlugin (template) {
    const {dst} = this.addTemplate(template)
    // Add to nuxt plugins
    this.options.plugins.push({
      src: '~/.nuxt/' + dst,
      ssr: template.ssr,
      injectAs: template.injectAs
    })
  }

  addServerMiddleware (middleware) {
    this.options.serverMiddlewares.push(middleware)
  }

  extendBuild (fn) {
    this.options.build.extend = chainFn(this.options.build.extend, fn)
  }

  extendRoutes (fn) {
    this.options.router.extendRoutes = chainFn(this.options.router.extendRoutes, fn)
  }

  installModule (moduleOpts) {
    if (!moduleOpts) {
      return
    }
    // Allows passing runtime options to each module
    const options = moduleOpts.options || {}
    let src = moduleOpts.src || moduleOpts
    // Resolve module
    let module
    try {
      if (typeof src === 'string') {
        // Using ~ shorthand modules are resolved from project srcDir
        if (src.indexOf('~') === 0) {
          src = path.resolve(this.options.srcDir, src.substr(1))
        }
        // eslint-disable-next-line no-eval
        module = eval('require')(src)
      }
    } catch (e) {
      // eslint-disable-next-line no-console
      console.error('[Nuxt] Unable to resolve module', src)
      // eslint-disable-next-line no-console
      console.error(e)
      return
    }
    // Validate module
    if (!(module instanceof Function)) {
      // eslint-disable-next-line no-console
      console.error('[Nuxt] Module should be a function', module)
    }
    // Call module with `this` context and pass options
    return new Promise((resolve, reject) => {
      const result = module.call(this, options, err => {
        if (err) {
          return reject(err)
        }
        resolve(module)
      })
      if (result && result.then instanceof Function) {
        return result.then(resolve)
      }
    })
  }
}

export default Module
<|endoftext|>


<commit_before>'use strict'
var fs = require('fs'),
    path = require('path'),
    util = require('util'),
    chalk = require('chalk')

var UglifyJS = require('uglify-js')

var helper = require('./helper')

function Module(meta, options) {
    this.meta = meta
    this.options = options
    this.factory = null
    this.result = null
    this.remainDeps = []
    this.depsMods = []

    this.fetch()
}

helper.extend(Module.prototype, {
    id2uri: function(id) {
        var self = this
        var uri = null
        self.options.paths.some(function(modulePath) {
            uri = helper.ensureExtname(path.join(modulePath, id), self.options.parsers)
            if (fs.existsSync(uri)) {
                return true
            } else {
                uri = null
            }
        })
        return uri
    },
    resolve: function() {
        var self = this
        var deps = []
        var remainDeps = this.remainDeps = []

        if (!this.meta.deps) {
            return deps
        }

        this.meta.deps.forEach(function(id) {
            var topId = helper.topId(id, self.meta.id)

            // 不允许循环引用
            if (topId === self.meta.id) {
                return
            }

            var exclude = false
            if (self.options.exclude) {
                if (helper.isFunction(self.options.exclude)) {
                    exclude = self.options.exclude(topId, self)

                } else if (helper.isArray(self.options.exclude)) {
                    exclude = self.options.exclude.indexOf(topId) > -1
                }

                if (exclude === null) {
                    return
                }
            }

            if (exclude === true || (id.charAt(0) !== '.' && !self.options.all)) {
                remainDeps.push(topId)
                return
            }

            deps.push(topId)
        })

        this.remainDeps = remainDeps
        return deps
    },
    parseMeta: function(ast) {
        var hasMeta = false
        var meta = {
            id: undefined,
            deps: []
        }

        ast.walk(new UglifyJS.TreeWalker(function(node) {
            if (node instanceof UglifyJS.AST_Call && node.expression.name === 'define') {
                hasMeta = true
                getMeta(node)
                return true
            }
        }))

        function getMeta(node) {
            if (node.args.length === 1) {
                getRequires(node.args[0])

            } else if (node.args.length === 3) {
                if (node.args[0] instanceof UglifyJS.AST_String) {
                    meta.id = node.args[0].getValue()
                }

                if (node.args[1] instanceof UglifyJS.AST_Array) {
                    meta.deps = node.args[1].elements.map(function(id) {
                        if (id instanceof UglifyJS.AST_String) {
                            return id.getValue()
                        }
                    })
                }
            }
        }

        function getRequires(factory) {
            if (factory instanceof UglifyJS.AST_Function) {
                factory.walk(new UglifyJS.TreeWalker(function(node) {
                    if (node instanceof UglifyJS.AST_Call && node.expression.name === 'require') {
                        if (node.args && node.args.length === 1) {
                            var id = node.args[0]
                            if (id instanceof UglifyJS.AST_String) {
                                id = id.getValue()
                                meta.deps.indexOf(id) === -1 && meta.deps.push(id)
                            }
                        }
                    }
                }))
            }
        }

        return hasMeta && meta
    },
    minify: function(ast) {
        if (typeof ast === 'string') {
            ast = UglifyJS.parse(ast)
            ast.figure_out_scope()
        }

        if (this.options.minify) {
            ast = ast.transform(UglifyJS.Compressor({
                warnings: false
            }))
            ast.figure_out_scope()
            ast.compute_char_frequency()
            ast.mangle_names()
        }

        var uglifyOptions = this.options.minify ? this.options.uglify : {
            beautify: true
        }
        return ast.print_to_string(uglifyOptions)
    },
    fetch: function() {
        var ext = path.extname(this.meta.uri)
        var parser = this.options.parsers[ext]

        if (parser) {
            this.meta.factory = parser(this.meta)
        }

        if (this.meta.factory === null) {
            throw new Error(util.format('not found mod %s', chalk.yellow(this.meta.uri)))
            return null
        }

        var ast = UglifyJS.parse(this.meta.factory)
        ast.figure_out_scope()

        var meta = this.parseMeta(ast)

        // 目前只支持匿名模块，具名模块不会执行 transport concat
        // 非模块直接压缩
        if (meta && meta.id === undefined && meta.deps) {
            this.meta.deps = meta.deps
            this.ast = ast
            this.load()
            this.transport()
            this.concat()
        } else {
            this.result = this.factory = this.minify(ast)
        }
    },
    load: function() {
        var self = this
        var mods = []

        var deps = this.resolve()

        var notFoundDeps = []

        deps.forEach(function(id) {
            var meta = {
                id: id,
                uri: self.id2uri(id)
            }

            if (meta.uri) {
                var mod = Module.get(meta, self.options)
                if (mod.factory) {
                    mods.push(mod)
                }
            } else {
                self.remainDeps.push(id)
                notFoundDeps.push(id)
            }
        })

        if (notFoundDeps.length) {
            helper.log(self.options.log, util.format('module %s not found deps %s', chalk.cyan(this.meta.id), chalk.red('[' + notFoundDeps.join(', ') + ']')))
        }

        this.depsMods = mods
    },
    transport: function() {
        var self = this
        var ast = this.ast

        this.depsMods.forEach(function(mod) {
            self.remainDeps = self.remainDeps.concat(mod.remainDeps)
        })

        this.remainDeps = helper.unique(this.remainDeps)

        ast.transform(new UglifyJS.TreeTransformer(function(node) {
            if (node instanceof UglifyJS.AST_Call && node.expression.name === 'define' && node.expression.thedef.global) {
                if (node.args.length === 1) {
                    return transport(node)
                }
            }
        }))

        function transport(node) {
            var args = []
            var factory = node.args[0]

            args.push(new UglifyJS.AST_String({
                value: self.meta.id
            }))

            args.push(new UglifyJS.AST_Array({
                elements: self.remainDeps.map(function(id) {
                    return new UglifyJS.AST_String({
                        value: id
                    })
                })
            }))

            args.push(factory)

            node.args = args
            return node
        }

        this.factory = this.minify(ast)
    },
    concat: function() {
        var cache = {}
        cache[this.meta.id] = true

        var depsFactory = []

        function recursiveFindDepsFactory(mods) {
            mods.forEach(function(mod) {
                if (!cache[mod.meta.id]) {

                    cache[mod.meta.id] = true
                    depsFactory.push(mod.factory)

                    if (mod.depsMods.length) {
                        recursiveFindDepsFactory(mod.depsMods)
                    }
                }
            })
        }

        recursiveFindDepsFactory(this.depsMods)

        this.result = [this.factory].concat(depsFactory).join(this.options.footer)
    }
})

helper.extend(Module, {
    cache: {},
    get: function(meta, options) {
        var mod = Module.cache[meta.uri]
        if (!mod) {
            mod = Module.cache[meta.uri] = new Module(meta, options)
        }
        return mod
    }
})

module.exports = Module
<commit_msg>optimize find define expression and predefine meta.factory<commit_after>'use strict'
var fs = require('fs'),
    path = require('path'),
    util = require('util'),
    chalk = require('chalk')

var UglifyJS = require('uglify-js')

var helper = require('./helper')

function Module(meta, options) {
    this.meta = meta
    this.options = options
    this.factory = null
    this.result = null
    this.remainDeps = []
    this.depsMods = []

    this.fetch()
}

helper.extend(Module.prototype, {
    id2uri: function(id) {
        var self = this
        var uri = null
        self.options.paths.some(function(modulePath) {
            uri = helper.ensureExtname(path.join(modulePath, id), self.options.parsers)
            if (fs.existsSync(uri)) {
                return true
            } else {
                uri = null
            }
        })
        return uri
    },
    resolve: function() {
        var self = this
        var deps = []
        var remainDeps = this.remainDeps = []

        if (!this.meta.deps) {
            return deps
        }

        this.meta.deps.forEach(function(id) {
            var topId = helper.topId(id, self.meta.id)

            // 不允许循环引用
            if (topId === self.meta.id) {
                return
            }

            var exclude = false
            if (self.options.exclude) {
                if (helper.isFunction(self.options.exclude)) {
                    exclude = self.options.exclude(topId, self)

                } else if (helper.isArray(self.options.exclude)) {
                    exclude = self.options.exclude.indexOf(topId) > -1
                }

                if (exclude === null) {
                    return
                }
            }

            if (exclude === true || (id.charAt(0) !== '.' && !self.options.all)) {
                remainDeps.push(topId)
                return
            }

            deps.push(topId)
        })

        this.remainDeps = remainDeps
        return deps
    },
    parseMeta: function(ast) {
        var hasMeta = false
        var meta = {
            id: undefined,
            deps: []
        }

        ast.walk(new UglifyJS.TreeWalker(function(node) {
            if (node instanceof UglifyJS.AST_Call && node.expression.name === 'define' && node.expression.thedef.global) {
                hasMeta = true
                getMeta(node)
                return true
            }
        }))

        function getMeta(node) {
            if (node.args.length === 1) {
                getRequires(node.args[0])

            } else if (node.args.length === 3) {
                if (node.args[0] instanceof UglifyJS.AST_String) {
                    meta.id = node.args[0].getValue()
                }

                if (node.args[1] instanceof UglifyJS.AST_Array) {
                    meta.deps = node.args[1].elements.map(function(id) {
                        if (id instanceof UglifyJS.AST_String) {
                            return id.getValue()
                        }
                    })
                }
            }
        }

        function getRequires(factory) {
            if (factory instanceof UglifyJS.AST_Function) {
                factory.walk(new UglifyJS.TreeWalker(function(node) {
                    if (node instanceof UglifyJS.AST_Call && node.expression.name === 'require') {
                        if (node.args && node.args.length === 1) {
                            var id = node.args[0]
                            if (id instanceof UglifyJS.AST_String) {
                                id = id.getValue()
                                meta.deps.indexOf(id) === -1 && meta.deps.push(id)
                            }
                        }
                    }
                }))
            }
        }

        return hasMeta && meta
    },
    minify: function(ast) {
        if (this.options.minify) {
            ast = ast.transform(UglifyJS.Compressor({
                warnings: false
            }))
            ast.figure_out_scope()
            ast.compute_char_frequency()
            ast.mangle_names()
        }

        var uglifyOptions = this.options.minify ? this.options.uglify : {
            beautify: true
        }
        return ast.print_to_string(uglifyOptions)
    },
    fetch: function() {

        if (this.meta.factory === undefined) {
            var ext = path.extname(this.meta.uri)
            var parser = this.options.parsers[ext]

            if (parser) {
                this.meta.factory = parser(this.meta)
            }
        }

        if (this.meta.factory === null) {
            helper.log(this.options.log, util.format('not found mod %s', chalk.yellow(this.meta.uri)))
            return
        }

        var ast = UglifyJS.parse(this.meta.factory)
        ast.figure_out_scope()

        var meta = this.parseMeta(ast)

        // 目前只支持匿名模块，具名模块不会执行 transport concat
        // 非模块直接压缩
        if (meta && meta.id === undefined && meta.deps) {
            this.meta.deps = meta.deps
            this.ast = ast
            this.load()
            this.transport()
            this.concat()
        } else {
            this.result = this.factory = this.minify(ast)
        }
    },
    load: function() {
        var self = this
        var mods = []

        var deps = this.resolve()

        var notFoundDeps = []

        deps.forEach(function(id) {
            var meta = {
                id: id,
                uri: self.id2uri(id)
            }

            if (meta.uri) {
                var mod = Module.get(meta, self.options)
                if (mod.factory) {
                    mods.push(mod)
                }
            } else {
                self.remainDeps.push(id)
                notFoundDeps.push(id)
            }
        })

        if (notFoundDeps.length) {
            helper.log(this.options.log, util.format('module %s not found deps %s', chalk.cyan(this.meta.id), chalk.red('[' + notFoundDeps.join(', ') + ']')))
        }

        this.depsMods = mods
    },
    transport: function() {
        var self = this
        var ast = this.ast

        this.depsMods.forEach(function(mod) {
            self.remainDeps = self.remainDeps.concat(mod.remainDeps)
        })

        this.remainDeps = helper.unique(this.remainDeps)

        ast.transform(new UglifyJS.TreeTransformer(function(node) {
            if (node instanceof UglifyJS.AST_Call && node.expression.name === 'define' && node.expression.thedef.global) {
                if (node.args.length === 1) {
                    return transport(node)
                }
            }
        }))

        function transport(node) {
            var args = []
            var factory = node.args[0]

            args.push(new UglifyJS.AST_String({
                value: self.meta.id
            }))

            args.push(new UglifyJS.AST_Array({
                elements: self.remainDeps.map(function(id) {
                    return new UglifyJS.AST_String({
                        value: id
                    })
                })
            }))

            args.push(factory)

            node.args = args
            return node
        }

        this.factory = this.minify(ast)
    },
    concat: function() {
        var cache = {}
        cache[this.meta.id] = true

        var depsFactory = []

        function recursiveFindDepsFactory(mods) {
            mods.forEach(function(mod) {
                if (!cache[mod.meta.id]) {

                    cache[mod.meta.id] = true
                    depsFactory.push(mod.factory)

                    if (mod.depsMods.length) {
                        recursiveFindDepsFactory(mod.depsMods)
                    }
                }
            })
        }

        recursiveFindDepsFactory(this.depsMods)

        this.result = [this.factory].concat(depsFactory).join(this.options.footer)
    }
})

helper.extend(Module, {
    cache: {},
    get: function(meta, options) {
        var mod = Module.cache[meta.uri]
        if (!mod) {
            mod = Module.cache[meta.uri] = new Module(meta, options)
        }
        return mod
    }
})

module.exports = Module
<|endoftext|>