<commit_before>var assert = require("assert");
var linesModule = require("./lines");
var fromString = linesModule.fromString;
var Lines = linesModule.Lines;
var concat = linesModule.concat;
var Visitor = require("./visitor").Visitor;
var comparePos = require("./util").comparePos;

exports.add = function(ast, lines) {
    var comments = ast.comments;
    assert.ok(comments instanceof Array);
    delete ast.comments;

    assert.ok(lines instanceof Lines);

    var pt = new PosTracker,
        len = comments.length,
        comment,
        key,
        loc, locs = pt.locs,
        pair,
        sorted = [];

    pt.visit(ast);

    for (var i = 0; i < len; ++i) {
        comment = comments[i];
        Object.defineProperty(comment.loc, "lines", { value: lines });
        pt.getEntry(comment, "end").comment = comment;
    }

    for (key in locs) {
        loc = locs[key];
        pair = key.split(",");

        sorted.push({
            line: +pair[0],
            column: +pair[1],
            startNode: loc.startNode,
            endNode: loc.endNode,
            comment: loc.comment
        });
    }

    sorted.sort(comparePos);

    var pendingComments = [];
    var previousNode;

    function addComment(node, comment) {
        if (node) {
            var comments = node.comments || (node.comments = []);
            comments.push(comment);
        }
    }

    function dumpTrailing() {
        pendingComments.forEach(function(comment) {
            addComment(previousNode, comment);
            comment.trailing = true;
        });

        pendingComments.length = 0;
    }

    sorted.forEach(function(entry) {
        if (entry.endNode) {
            // If we're ending a node with comments still pending, then we
            // need to attach those comments to the previous node before
            // updating the previous node.
            dumpTrailing();
            previousNode = entry.endNode;
        }

        if (entry.comment) {
            pendingComments.push(entry.comment);
        }

        if (entry.startNode) {
            var node = entry.startNode;
            var nodeStartColumn = node.loc.start.column;
            var didAddLeadingComment = false;
            var gapEndLoc = node.loc.start;

            // Iterate backwards through pendingComments, examining the
            // gaps between them. In order to earn the .possiblyLeading
            // status, a comment must be separated from entry.startNode by
            // an unbroken series of whitespace-only gaps.
            for (var i = pendingComments.length - 1; i >= 0; --i) {
                var comment = pendingComments[i];
                var gap = lines.slice(comment.loc.end, gapEndLoc);
                gapEndLoc = comment.loc.start;

                if (gap.isOnlyWhitespace()) {
                    comment.possiblyLeading = true;
                } else {
                    break;
                }
            }

            pendingComments.forEach(function(comment) {
                if (!comment.possiblyLeading) {
                    // If comment.possiblyLeading was not set to true
                    // above, the comment must be a trailing comment.
                    comment.trailing = true;
                    addComment(previousNode, comment);

                } else if (didAddLeadingComment) {
                    // If we previously added a leading comment to this
                    // node, then any subsequent pending comments must
                    // also be leading comments, even if they are indented
                    // more deeply than the node itself.
                    assert.strictEqual(comment.possiblyLeading, true);
                    comment.trailing = false;
                    addComment(node, comment);

                } else if (comment.type === "Line" &&
                           comment.loc.start.column > nodeStartColumn) {
                    // If the comment is a //-style comment and indented
                    // more deeply than the node itself, and we have not
                    // encountered any other leading comments, treat this
                    // comment as a trailing comment and add it to the
                    // previous node.
                    comment.trailing = true;
                    addComment(previousNode, comment);

                } else {
                    // Here we have the first leading comment for this node.
                    comment.trailing = false;
                    addComment(node, comment);
                    didAddLeadingComment = true;
                }
            });

            pendingComments.length = 0;

            // Note: the previous node is the node that started OR ended
            // most recently.
            previousNode = entry.startNode;
        }
    });

    // Provided we have a previous node to add them to, dump any
    // still-pending comments into the last node we came across.
    dumpTrailing();
};

var PosTracker = Visitor.extend({
    init: function() {
        this.locs = {};
    },

    getEntry: function(node, which) {
        var locs = this.locs,
            key = getKey(node, which);
        return key && (locs[key] || (locs[key] = {}));
    },

    onStart: function(node) {
        var entry = this.getEntry(node, "start");
        if (entry && !entry.startNode)
            entry.startNode = node;
    },

    onEnd: function(node) {
        var entry = this.getEntry(node, "end");
        if (entry)
            entry.endNode = node;
    },

    genericVisit: function(node) {
        this.onStart(node);
        this._super(node);
        this.onEnd(node);
    }
});

function getKey(node, which) {
    var loc = node && node.loc,
        pos = loc && loc[which];
    return pos && (pos.line + "," + pos.column);
}

function printLeadingComment(comment) {
    var orig = comment.original;
    var loc = orig && orig.loc;
    var lines = loc && loc.lines;
    var parts = [];

    if (comment.type === "Block") {
        parts.push("/*", comment.value, "*/");
    } else if (comment.type === "Line") {
        parts.push("//", comment.value);
    } else assert.fail(comment.type);

    if (comment.trailing) {
        // When we print trailing comments as leading comments, we don't
        // want to bring any trailing spaces along.
        parts.push("\n");

    } else if (lines instanceof Lines) {
        var trailingSpace = lines.slice(
            loc.end,
            lines.skipSpaces(loc.end)
        );

        if (trailingSpace.length === 1) {
            // If the trailing space contains no newlines, then we want to
            // preserve it exactly as we found it.
            parts.push(trailingSpace);
        } else {
            // If the trailing space contains newlines, then replace it
            // with just that many newlines, with all other spaces removed.
            parts.push(new Array(trailingSpace.length).join("\n"));
        }

    } else {
        parts.push("\n");
    }

    return concat(parts).stripMargin(loc ? loc.start.column : 0);
}

function printTrailingComment(comment) {
    var orig = comment.original;
    var loc = orig && orig.loc;
    var lines = loc && loc.lines;
    var parts = [];

    if (lines instanceof Lines) {
        var fromPos = lines.skipSpaces(loc.start, true) || lines.firstPos();
        var leadingSpace = lines.slice(fromPos, loc.start);

        if (leadingSpace.length === 1) {
            // If the leading space contains no newlines, then we want to
            // preserve it exactly as we found it.
            parts.push(leadingSpace);
        } else {
            // If the leading space contains newlines, then replace it
            // with just that many newlines, sans all other spaces.
            parts.push(new Array(leadingSpace.length).join("\n"));
        }
    }

    if (comment.type === "Block") {
        parts.push("/*", comment.value, "*/");
    } else if (comment.type === "Line") {
        parts.push("//", comment.value, "\n");
    } else assert.fail(comment.type);

    return concat(parts).stripMargin(
        loc ? loc.start.column : 0,
        true // Skip the first line, in case there were leading spaces.
    );
}

exports.printComments = function(comments, innerLines) {
    if (innerLines) {
        assert.ok(innerLines instanceof Lines);
    } else {
        innerLines = fromString("");
    }

    var count = comments ? comments.length : 0;
    if (count === 0) {
        return innerLines;
    }

    var parts = [];
    var leading = [];
    var trailing = [];

    comments.forEach(function(comment) {
        // For now, only /*comments*/ can be trailing comments.
        if (comment.type === "Block" &&
            comment.trailing) {
            trailing.push(comment);
        } else {
            leading.push(comment);
        }
    });

    leading.forEach(function(comment) {
        parts.push(printLeadingComment(comment));
    });

    parts.push(innerLines);

    trailing.forEach(function(comment) {
        parts.push(printTrailingComment(comment));
    });

    return concat(parts);
};
<commit_msg>Improve PosTracker performance by not using the Visitor abstraction.<commit_after>var assert = require("assert");
var types = require("./types");
var isArray = types.builtInTypes.array;
var isObject = types.builtInTypes.object;
var linesModule = require("./lines");
var fromString = linesModule.fromString;
var Lines = linesModule.Lines;
var concat = linesModule.concat;
var comparePos = require("./util").comparePos;

exports.add = function(ast, lines) {
    var comments = ast.comments;
    assert.ok(comments instanceof Array);
    delete ast.comments;

    assert.ok(lines instanceof Lines);

    var pt = new PosTracker,
        len = comments.length,
        comment,
        key,
        loc, locs = pt.locs,
        pair,
        sorted = [];

    pt.visit(ast);

    for (var i = 0; i < len; ++i) {
        comment = comments[i];
        Object.defineProperty(comment.loc, "lines", { value: lines });
        pt.getEntry(comment, "end").comment = comment;
    }

    for (key in locs) {
        loc = locs[key];
        pair = key.split(",");

        sorted.push({
            line: +pair[0],
            column: +pair[1],
            startNode: loc.startNode,
            endNode: loc.endNode,
            comment: loc.comment
        });
    }

    sorted.sort(comparePos);

    var pendingComments = [];
    var previousNode;

    function addComment(node, comment) {
        if (node) {
            var comments = node.comments || (node.comments = []);
            comments.push(comment);
        }
    }

    function dumpTrailing() {
        pendingComments.forEach(function(comment) {
            addComment(previousNode, comment);
            comment.trailing = true;
        });

        pendingComments.length = 0;
    }

    sorted.forEach(function(entry) {
        if (entry.endNode) {
            // If we're ending a node with comments still pending, then we
            // need to attach those comments to the previous node before
            // updating the previous node.
            dumpTrailing();
            previousNode = entry.endNode;
        }

        if (entry.comment) {
            pendingComments.push(entry.comment);
        }

        if (entry.startNode) {
            var node = entry.startNode;
            var nodeStartColumn = node.loc.start.column;
            var didAddLeadingComment = false;
            var gapEndLoc = node.loc.start;

            // Iterate backwards through pendingComments, examining the
            // gaps between them. In order to earn the .possiblyLeading
            // status, a comment must be separated from entry.startNode by
            // an unbroken series of whitespace-only gaps.
            for (var i = pendingComments.length - 1; i >= 0; --i) {
                var comment = pendingComments[i];
                var gap = lines.slice(comment.loc.end, gapEndLoc);
                gapEndLoc = comment.loc.start;

                if (gap.isOnlyWhitespace()) {
                    comment.possiblyLeading = true;
                } else {
                    break;
                }
            }

            pendingComments.forEach(function(comment) {
                if (!comment.possiblyLeading) {
                    // If comment.possiblyLeading was not set to true
                    // above, the comment must be a trailing comment.
                    comment.trailing = true;
                    addComment(previousNode, comment);

                } else if (didAddLeadingComment) {
                    // If we previously added a leading comment to this
                    // node, then any subsequent pending comments must
                    // also be leading comments, even if they are indented
                    // more deeply than the node itself.
                    assert.strictEqual(comment.possiblyLeading, true);
                    comment.trailing = false;
                    addComment(node, comment);

                } else if (comment.type === "Line" &&
                           comment.loc.start.column > nodeStartColumn) {
                    // If the comment is a //-style comment and indented
                    // more deeply than the node itself, and we have not
                    // encountered any other leading comments, treat this
                    // comment as a trailing comment and add it to the
                    // previous node.
                    comment.trailing = true;
                    addComment(previousNode, comment);

                } else {
                    // Here we have the first leading comment for this node.
                    comment.trailing = false;
                    addComment(node, comment);
                    didAddLeadingComment = true;
                }
            });

            pendingComments.length = 0;

            // Note: the previous node is the node that started OR ended
            // most recently.
            previousNode = entry.startNode;
        }
    });

    // Provided we have a previous node to add them to, dump any
    // still-pending comments into the last node we came across.
    dumpTrailing();
};

function PosTracker() {
    assert.ok(this instanceof PosTracker);
    this.locs = {};
}

var PTp = PosTracker.prototype;

PTp.getEntry = function(node, which) {
    var locs = this.locs,
        loc = node && node.loc,
        pos = loc && loc[which],
        key = pos && (pos.line + "," + pos.column);
    return key && (locs[key] || (locs[key] = {}));
};

PTp.visit = function(node) {
    if (isArray.check(node)) {
        node.forEach(this.visit, this);
    } else if (isObject.check(node)) {
        var entry = this.getEntry(node, "start");
        if (entry && !entry.startNode) {
            entry.startNode = node;
        }

        var names = types.getFieldNames(node);
        for (var i = 0, len = names.length; i < len; ++i) {
            this.visit(node[names[i]]);
        }

        if ((entry = this.getEntry(node, "end"))) {
            entry.endNode = node;
        }
    }
};

function printLeadingComment(comment) {
    var orig = comment.original;
    var loc = orig && orig.loc;
    var lines = loc && loc.lines;
    var parts = [];

    if (comment.type === "Block") {
        parts.push("/*", comment.value, "*/");
    } else if (comment.type === "Line") {
        parts.push("//", comment.value);
    } else assert.fail(comment.type);

    if (comment.trailing) {
        // When we print trailing comments as leading comments, we don't
        // want to bring any trailing spaces along.
        parts.push("\n");

    } else if (lines instanceof Lines) {
        var trailingSpace = lines.slice(
            loc.end,
            lines.skipSpaces(loc.end)
        );

        if (trailingSpace.length === 1) {
            // If the trailing space contains no newlines, then we want to
            // preserve it exactly as we found it.
            parts.push(trailingSpace);
        } else {
            // If the trailing space contains newlines, then replace it
            // with just that many newlines, with all other spaces removed.
            parts.push(new Array(trailingSpace.length).join("\n"));
        }

    } else {
        parts.push("\n");
    }

    return concat(parts).stripMargin(loc ? loc.start.column : 0);
}

function printTrailingComment(comment) {
    var orig = comment.original;
    var loc = orig && orig.loc;
    var lines = loc && loc.lines;
    var parts = [];

    if (lines instanceof Lines) {
        var fromPos = lines.skipSpaces(loc.start, true) || lines.firstPos();
        var leadingSpace = lines.slice(fromPos, loc.start);

        if (leadingSpace.length === 1) {
            // If the leading space contains no newlines, then we want to
            // preserve it exactly as we found it.
            parts.push(leadingSpace);
        } else {
            // If the leading space contains newlines, then replace it
            // with just that many newlines, sans all other spaces.
            parts.push(new Array(leadingSpace.length).join("\n"));
        }
    }

    if (comment.type === "Block") {
        parts.push("/*", comment.value, "*/");
    } else if (comment.type === "Line") {
        parts.push("//", comment.value, "\n");
    } else assert.fail(comment.type);

    return concat(parts).stripMargin(
        loc ? loc.start.column : 0,
        true // Skip the first line, in case there were leading spaces.
    );
}

exports.printComments = function(comments, innerLines) {
    if (innerLines) {
        assert.ok(innerLines instanceof Lines);
    } else {
        innerLines = fromString("");
    }

    var count = comments ? comments.length : 0;
    if (count === 0) {
        return innerLines;
    }

    var parts = [];
    var leading = [];
    var trailing = [];

    comments.forEach(function(comment) {
        // For now, only /*comments*/ can be trailing comments.
        if (comment.type === "Block" &&
            comment.trailing) {
            trailing.push(comment);
        } else {
            leading.push(comment);
        }
    });

    leading.forEach(function(comment) {
        parts.push(printLeadingComment(comment));
    });

    parts.push(innerLines);

    trailing.forEach(function(comment) {
        parts.push(printTrailingComment(comment));
    });

    return concat(parts);
};
<|endoftext|>


<commit_before>/*
 * This file uses webpack to compile a template with a child compiler.
 *
 * [TEMPLATE] -> [JAVASCRIPT]
 *
 */
'use strict';
const _ = require('lodash');
const path = require('path');
const NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin');
const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
const LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin');
const LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin');
const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');

/**
 * Compiles the template into a nodejs factory, adds its to the compilation.assets
 * and returns a promise of the result asset object.
 *
 * @param template relative path to the template file
 * @param context path context
 * @param outputFilename the file name
 * @param compilation The webpack compilation object
 *
 * Returns an object:
 * {
 *  hash: {String} - Base64 hash of the file
 *  content: {String} - Javascript executable code of the template
 * }
 *
 */
module.exports.compileTemplate = function compileTemplate (template, context, outputFilename, compilation) {
  // The entry file is just an empty helper as the dynamic template
  // require is added in "loader.js"
  const outputOptions = {
    filename: outputFilename,
    publicPath: compilation.outputOptions.publicPath
  };
  // Store the result of the parent compilation before we start the child compilation
  const assetsBeforeCompilation = _.assign({}, compilation.assets[outputOptions.filename]);
  // Create an additional child compiler which takes the template
  // and turns it into an Node.JS html factory.
  // This allows us to use loaders during the compilation
  const compilerName = getCompilerName(context, outputFilename);
  const childCompiler = compilation.createChildCompiler(compilerName, outputOptions);
  childCompiler.context = context;
  childCompiler.apply(
    new NodeTemplatePlugin(outputOptions),
    new NodeTargetPlugin(),
    new LibraryTemplatePlugin('HTML_WEBPACK_PLUGIN_RESULT', 'var'),
    new SingleEntryPlugin(this.context, template),
    new LoaderTargetPlugin('node')
  );

  // Fix for "Uncaught TypeError: __webpack_require__(...) is not a function"
  // Hot module replacement requires that every child compiler has its own
  // cache. @see https://github.com/ampedandwired/html-webpack-plugin/pull/179

  // Backwards compatible version of: childCompiler.hooks.compilation
  (childCompiler.hooks ? childCompiler.hooks.compilation.tap.bind(childCompiler.hooks.compilation, 'HtmlWebpackPlugin') : childCompiler.plugin.bind(childCompiler, 'compilation'))(compilation => {
    if (compilation.cache) {
      if (!compilation.cache[compilerName]) {
        compilation.cache[compilerName] = {};
      }
      compilation.cache = compilation.cache[compilerName];
    }
  });

  // Compile and return a promise
  return new Promise((resolve, reject) => {
    childCompiler.runAsChild((err, entries, childCompilation) => {
      // Resolve / reject the promise
      if (childCompilation && childCompilation.errors && childCompilation.errors.length) {
        const errorDetails = childCompilation.errors.map(error => error.message + (error.error ? ':\n' + error.error : '')).join('\n');
        reject(new Error('Child compilation failed:\n' + errorDetails));
      } else if (err) {
        reject(err);
      } else {
        // Replace [hash] placeholders in filename
        // In webpack 4 the plugin interface changed, so check for available fns
        const outputName = compilation.mainTemplate.getAssetPath
          ? compilation.mainTemplate.hooks.assetPath.call(outputOptions.filename, {
            hash: childCompilation.hash,
            chunk: entries[0]
          })
          : compilation.mainTemplate.applyPluginsWaterfall(
              'asset-path',
              outputOptions.filename,
            {
              hash: childCompilation.hash,
              chunk: entries[0]
            });

        // Restore the parent compilation to the state like it
        // was before the child compilation
        compilation.assets[outputName] = assetsBeforeCompilation[outputName];
        if (assetsBeforeCompilation[outputName] === undefined) {
          // If it wasn't there - delete it
          delete compilation.assets[outputName];
        }
        resolve({
          // Hash of the template entry point
          hash: entries[0].hash,
          // Output name
          outputName: outputName,
          // Compiled code
          content: childCompilation.assets[outputName].source()
        });
      }
    });
  });
};

/**
 * Returns the child compiler name e.g. 'html-webpack-plugin for "index.html"'
 */
function getCompilerName (context, filename) {
  const absolutePath = path.resolve(context, filename);
  const relativePath = path.relative(context, absolutePath);
  return 'html-webpack-plugin for "' + (absolutePath.length < relativePath.length ? absolutePath : relativePath) + '"';
}
<commit_msg>fix(hooks): Call tapable.apply directly #879<commit_after>/*
 * This file uses webpack to compile a template with a child compiler.
 *
 * [TEMPLATE] -> [JAVASCRIPT]
 *
 */
'use strict';
const _ = require('lodash');
const path = require('path');
const NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin');
const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
const LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin');
const LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin');
const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');

/**
 * Compiles the template into a nodejs factory, adds its to the compilation.assets
 * and returns a promise of the result asset object.
 *
 * @param template relative path to the template file
 * @param context path context
 * @param outputFilename the file name
 * @param compilation The webpack compilation object
 *
 * Returns an object:
 * {
 *  hash: {String} - Base64 hash of the file
 *  content: {String} - Javascript executable code of the template
 * }
 *
 */
module.exports.compileTemplate = function compileTemplate (template, context, outputFilename, compilation) {
  // The entry file is just an empty helper as the dynamic template
  // require is added in "loader.js"
  const outputOptions = {
    filename: outputFilename,
    publicPath: compilation.outputOptions.publicPath
  };
  // Store the result of the parent compilation before we start the child compilation
  const assetsBeforeCompilation = _.assign({}, compilation.assets[outputOptions.filename]);
  // Create an additional child compiler which takes the template
  // and turns it into an Node.JS html factory.
  // This allows us to use loaders during the compilation
  const compilerName = getCompilerName(context, outputFilename);
  const childCompiler = compilation.createChildCompiler(compilerName, outputOptions);
  childCompiler.context = context;
  new NodeTemplatePlugin(outputOptions).apply(childCompiler);
  new NodeTargetPlugin().apply(childCompiler);
  new LibraryTemplatePlugin('HTML_WEBPACK_PLUGIN_RESULT', 'var').apply(childCompiler);
  new SingleEntryPlugin(this.context, template).apply(childCompiler);
  new LoaderTargetPlugin('node').apply(childCompiler);

  // Fix for "Uncaught TypeError: __webpack_require__(...) is not a function"
  // Hot module replacement requires that every child compiler has its own
  // cache. @see https://github.com/ampedandwired/html-webpack-plugin/pull/179

  // Backwards compatible version of: childCompiler.hooks.compilation
  (childCompiler.hooks ? childCompiler.hooks.compilation.tap.bind(childCompiler.hooks.compilation, 'HtmlWebpackPlugin') : childCompiler.plugin.bind(childCompiler, 'compilation'))(compilation => {
    if (compilation.cache) {
      if (!compilation.cache[compilerName]) {
        compilation.cache[compilerName] = {};
      }
      compilation.cache = compilation.cache[compilerName];
    }
  });

  // Compile and return a promise
  return new Promise((resolve, reject) => {
    childCompiler.runAsChild((err, entries, childCompilation) => {
      // Resolve / reject the promise
      if (childCompilation && childCompilation.errors && childCompilation.errors.length) {
        const errorDetails = childCompilation.errors.map(error => error.message + (error.error ? ':\n' + error.error : '')).join('\n');
        reject(new Error('Child compilation failed:\n' + errorDetails));
      } else if (err) {
        reject(err);
      } else {
        // Replace [hash] placeholders in filename
        // In webpack 4 the plugin interface changed, so check for available fns
        const outputName = compilation.mainTemplate.getAssetPath
          ? compilation.mainTemplate.hooks.assetPath.call(outputOptions.filename, {
            hash: childCompilation.hash,
            chunk: entries[0]
          })
          : compilation.mainTemplate.applyPluginsWaterfall(
              'asset-path',
              outputOptions.filename,
            {
              hash: childCompilation.hash,
              chunk: entries[0]
            });

        // Restore the parent compilation to the state like it
        // was before the child compilation
        compilation.assets[outputName] = assetsBeforeCompilation[outputName];
        if (assetsBeforeCompilation[outputName] === undefined) {
          // If it wasn't there - delete it
          delete compilation.assets[outputName];
        }
        resolve({
          // Hash of the template entry point
          hash: entries[0].hash,
          // Output name
          outputName: outputName,
          // Compiled code
          content: childCompilation.assets[outputName].source()
        });
      }
    });
  });
};

/**
 * Returns the child compiler name e.g. 'html-webpack-plugin for "index.html"'
 */
function getCompilerName (context, filename) {
  const absolutePath = path.resolve(context, filename);
  const relativePath = path.relative(context, absolutePath);
  return 'html-webpack-plugin for "' + (absolutePath.length < relativePath.length ? absolutePath : relativePath) + '"';
}
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

// Provides control sap.m.FormattedText.
sap.ui.define([
	'./library',
	'sap/ui/core/library',
	'sap/ui/core/Control',
	'./FormattedTextAnchorGenerator',
	'./FormattedTextRenderer',
	"sap/base/Log",
	"sap/base/security/URLListValidator",
	"sap/base/security/sanitizeHTML"
],
function(
	library,
	coreLibrary,
	Control,
	FormattedTextAnchorGenerator,
	FormattedTextRenderer,
	Log,
	URLListValidator,
	sanitizeHTML0
	) {
		"use strict";

		// shortcut for sap.m.LinkConversion
		var LinkConversion = library.LinkConversion,
			TextDirection = coreLibrary.TextDirection,
			TextAlign = coreLibrary.TextAlign;

		/**
		 * Constructor for a new FormattedText.
		 *
		 * @param {string} [sId] ID for the new control, generated automatically if no ID is given
		 * @param {object} [mSettings] Initial settings for the new control
		 *
		 * @class
		 * The FormattedText control allows the usage of a limited set of tags for inline display of formatted text in HTML format.
		 * @extends sap.ui.core.Control
		 * @version ${version}
		 *
		 * @constructor
		 * @public
		 * @since 1.38.0
		 * @alias sap.m.FormattedText
		 * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
		 */
		var FormattedText = Control.extend("sap.m.FormattedText", /** @lends sap.m.FormattedText.prototype */ {
			metadata: {

				library: "sap.m",
				properties: {
					/**
					 * Text in HTML format.
					 * The following tags are supported:
					 *

					 *
a

					 *
abbr

					 *
bdi

					 *
blockquote

					 *
br

					 *
cite

					 *
code

					 *
em

					 *
h1

					 *
h2

					 *
h3

					 *
h4

					 *
h5

					 *
h6

					 *
p

					 *
pre

					 *
strong

					 *
span

					 *
u

					 *
dl

					 *
dt

					 *
dd

					 *
ul

					 *
ol

					 *
li

					 *

					 *
class, style, dir, and target attributes are allowed.
					 * If target is not set, links open in a new window by default.
					 *
Only safe href attributes can be used. See {@link module:sap/base/security/URLListValidator URLListValidator}.
					 *
					 * Note: Keep in mind that not supported HTML tags and
					 * the content nested inside them are both not rendered by the control.
					 */
					htmlText: {type: "string", group: "Misc", defaultValue: ""},

					/**
					 * Optional width of the control in CSS units.
					 */
					width: {type : "sap.ui.core.CSSSize", group : "Appearance", defaultValue : null},

					/**
					 * Determines whether strings that appear to be links will be converted to HTML anchor tags,
					 * and what are the criteria for recognizing them.
					 * @since 1.45.5
					 */
					convertLinksToAnchorTags: {type: "sap.m.LinkConversion", group: "Behavior", defaultValue: LinkConversion.None},

					/**
					 * Determines the target attribute of the generated HTML anchor tags.
					 *
					 * Note: Applicable only if ConvertLinksToAnchorTags property is used with a value other than sap.m.LinkConversion.None.
					 * Options are the standard values for the target attribute of the HTML anchor tag:
					 * _self, _top, _blank, _parent, _search.
					 * @since 1.45.5
					 */
					convertedLinksDefaultTarget: {type: "string", group: "Behavior", defaultValue: "_blank"},

					/**
					 *  Optional height of the control in CSS units.
					 */
					height: {type : "sap.ui.core.CSSSize", group : "Appearance", defaultValue : null},

					/**
					 * Defines the directionality of the text in the FormattedText, e.g. right-to-left(RTL)
					 * or left-to-right (LTR).
					 *
					 * Note: This functionality if set to the root element. Use the bdi element and
					 * the dir attribute to set explicit direction to an element.
					 *
					 * @since 1.86.0
					 */
					textDirection: { type: "sap.ui.core.TextDirection", group: "Appearance", defaultValue: TextDirection.Inherit },

					/**
					 * Determines the text alignment in the text elements in the FormattedText.
					 *
					 * Note: This functionality if set to the root element. To set explicit alignment to an element
					 * use the style attribute.
					 *
					 * @since 1.86.0
					 */
					textAlign : {type : "sap.ui.core.TextAlign", group : "Appearance", defaultValue : TextAlign.Begin}
				},
				aggregations: {

					/**
					* List of sap.m.Link controls that will be used to replace the placeholders in the text.
					* Placeholders are replaced according to their indexes. The placeholder with index %%0 will be replaced
					* by the first link in the aggregation, etc.
					*/
					controls: {type: "sap.m.Link", multiple: true, singularName: "control"}
				}
			}
		});

		/*
		 * These are the rules for the FormattedText
		 */
		var _defaultRenderingRules = {
			// rules for the allowed attributes
			ATTRIBS: {
				'style' : 1,
				'class' : 1,
				'a::href' : 1,
				'a::target' : 1,
				'dir' : 1
			},
			// rules for the allowed tags
			ELEMENTS: {
				// Text Module Tags
				'a' : {cssClass: 'sapMLnk'},
				'abbr': 1,
				'bdi' : 1,
				'blockquote': 1,
				'br': 1,
				'cite': 1,
				'code': 1,
				'em': 1,
				'h1': {cssClass: 'sapMTitle sapMTitleStyleH1'},
				'h2': {cssClass: 'sapMTitle sapMTitleStyleH2'},
				'h3': {cssClass: 'sapMTitle sapMTitleStyleH3'},
				'h4': {cssClass: 'sapMTitle sapMTitleStyleH4'},
				'h5': {cssClass: 'sapMTitle sapMTitleStyleH5'},
				'h6': {cssClass: 'sapMTitle sapMTitleStyleH6'},
				'p': 1,
				'pre': 1,
				'strong': 1,
				'span': 1,
				'u' : 1,

				// List Module Tags
				'dl': 1,
				'dt': 1,
				'dd': 1,
				'ol': 1,
				'ul': 1,
				'li': 1
			}
		},
		_limitedRenderingRules = {
			ATTRIBS: {
				'a::href' : 1,
				'a::target' : 1
			},
			ELEMENTS: {
				'a' : {cssClass: 'sapMLnk'},
				'br': 1,
				'em': 1,
				'strong': 1,
				'u': 1
			}
		};

		/**
		 * Holds the internal list of allowed and evaluated HTML elements and attributes
		 * @private
		 */
		FormattedText.prototype._renderingRules = _defaultRenderingRules;

		/**
		 * Initialization hook for the FormattedText, which creates a list of rules with allowed tags and attributes.
		 */
		FormattedText.prototype.init = function () {
		};

		/**
		 * Sanitizes attributes on an HTML tag.
		 *
		 * @param {string} tagName An HTML tag name in lower case
		 * @param {array} attribs An array of alternating names and values
		 * @return {array} The sanitized attributes as a list of alternating names and values. Value null removes the attribute.
		 * @private
		 */
		function fnSanitizeAttribs (tagName, attribs) {

			var sWarning;
			var attr,
				value,
				addTarget = tagName === "a";
			// add UI5 specific classes when appropriate
			var cssClass = this._renderingRules.ELEMENTS[tagName].cssClass || "";

			for (var i = 0; i < attribs.length; i += 2) {
				// attribs[i] is the name of the tag's attribute.
				// attribs[i+1] is its corresponding value.
				// (i.e.  -> attribs[i] = "class" | attribs[i+1] = "foo")
				attr = attribs[i];
				value = attribs[i + 1];

				if (!this._renderingRules.ATTRIBS[attr] && !this._renderingRules.ATTRIBS[tagName + "::" + attr]) {
					sWarning = 'FormattedText: <' + tagName + '> with attribute [' + attr + '="' + value + '"] is not allowed';
					Log.warning(sWarning, this);
					// to remove the attribute by the sanitizer, set the value to null
					attribs[i + 1] = null;
					continue;
				}

				// sanitize hrefs
				if (attr == "href") { // a::href
					if (!URLListValidator.validate(value)) {
						Log.warning("FormattedText: incorrect href attribute:" + value, this);
						attribs[i + 1] = "#";
						addTarget = false;
					}
				}
				if (attr == "target") { // a::target already exists
					addTarget = false;
				}

				// add UI5 classes to the user defined
				if (cssClass && attr.toLowerCase() == "class") {
					attribs[i + 1] = cssClass + " " + value;
					cssClass = "";
				}
			}

			if (addTarget) {
				attribs.push("target");
				attribs.push("_blank");
			}

			// add UI5 classes, if not done before
			if (cssClass) {
				attribs.push("class");
				attribs.push(cssClass);
			}

			return attribs;
		}

		function fnPolicy (tagName, attribs) {
			if (this._renderingRules.ELEMENTS[tagName]) {
				return fnSanitizeAttribs.call(this, tagName, attribs);
			} else {
				var sWarning = '<' + tagName + '> is not allowed';
				Log.warning(sWarning, this);
			}
		}

		/**
		 * Sanitizes HTML tags and attributes according to a given policy.
		 *
		 * @param {string} sText The HTML to sanitize
		 * @return {string} The sanitized HTML
		 * @private
		 */
		function sanitizeHTML(sText) {
			return sanitizeHTML0(sText, {
				tagPolicy: fnPolicy.bind(this),
				uriRewriter: function (sUrl) {
					// by default, we use the URLListValidator to check the URLs
					if (URLListValidator.validate(sUrl)) {
						return sUrl;
					}
				}
			});
		}

		// prohibit a new window from accessing window.opener.location
		function openExternalLink (oEvent) {
			var newWindow = window.open();
			newWindow.opener = null;
			newWindow.location = oEvent.currentTarget.href;
			oEvent.preventDefault();
		}

		FormattedText.prototype.onAfterRendering = function () {
			this.$().find('a:not([target="_self"]):not([target="_parent"]):not([target="_top"])').on("click", openExternalLink);
		};

		FormattedText.prototype.onBeforeRendering = function () {
			this.$().find('a:not([target="_self"]):not([target="_parent"]):not([target="_top"])').off("click", openExternalLink);
		};

		FormattedText.prototype._getDisplayHtml = function (){
			var sText = this.getHtmlText(),
				sAutoGenerateLinkTags = this.getConvertLinksToAnchorTags();

			if (sAutoGenerateLinkTags === LinkConversion.None) {
				return sText;
			}

			sText = FormattedTextAnchorGenerator.generateAnchors(sText, sAutoGenerateLinkTags, this.getConvertedLinksDefaultTarget());

			return sanitizeHTML.call(this, sText);
		};

		/**
		 * Defines the HTML text to be displayed.
		 * @param {string} sText HTML text as a string
		 * @return {this} this for chaining
		 * @public
		 */
		FormattedText.prototype.setHtmlText = function (sText) {
			return this.setProperty("htmlText", sanitizeHTML.call(this, sText));
		};

		/**
		 * Sets should a limited list of rendering rules be used instead of the default one. This limited list
		 * will evaluate only a small subset of the default HTML elements and attributes.
		 * @param {boolean} bLimit Should the control use the limited list
		 * @private
		 * @ui5-restricted sap.m.MessageStrip
		 */
		FormattedText.prototype._setUseLimitedRenderingRules = function (bLimit) {
			this._renderingRules = bLimit ? _limitedRenderingRules : _defaultRenderingRules;
		};

		FormattedText.prototype.getFocusDomRef = function () {
			return this.getDomRef() && this.getDomRef().querySelector("a");
		};

		return FormattedText;
	});<commit_msg>[INTERNAL] sap.m.FormattedText: More generic module used for links<commit_after>/*!
 * ${copyright}
 */

// Provides control sap.m.FormattedText.
sap.ui.define([
	'./library',
	'sap/ui/core/library',
	'sap/ui/core/Control',
	'./FormattedTextAnchorGenerator',
	'./FormattedTextRenderer',
	"sap/base/Log",
	"sap/base/security/URLListValidator",
	"sap/base/security/sanitizeHTML",
	"sap/ui/util/openWindow"
],
function(
	library,
	coreLibrary,
	Control,
	FormattedTextAnchorGenerator,
	FormattedTextRenderer,
	Log,
	URLListValidator,
	sanitizeHTML0,
	openWindow
	) {
		"use strict";

		// shortcut for sap.m.LinkConversion
		var LinkConversion = library.LinkConversion,
			TextDirection = coreLibrary.TextDirection,
			TextAlign = coreLibrary.TextAlign;

		/**
		 * Constructor for a new FormattedText.
		 *
		 * @param {string} [sId] ID for the new control, generated automatically if no ID is given
		 * @param {object} [mSettings] Initial settings for the new control
		 *
		 * @class
		 * The FormattedText control allows the usage of a limited set of tags for inline display of formatted text in HTML format.
		 * @extends sap.ui.core.Control
		 * @version ${version}
		 *
		 * @constructor
		 * @public
		 * @since 1.38.0
		 * @alias sap.m.FormattedText
		 * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
		 */
		var FormattedText = Control.extend("sap.m.FormattedText", /** @lends sap.m.FormattedText.prototype */ {
			metadata: {

				library: "sap.m",
				properties: {
					/**
					 * Text in HTML format.
					 * The following tags are supported:
					 *

					 *
a

					 *
abbr

					 *
bdi

					 *
blockquote

					 *
br

					 *
cite

					 *
code

					 *
em

					 *
h1

					 *
h2

					 *
h3

					 *
h4

					 *
h5

					 *
h6

					 *
p

					 *
pre

					 *
strong

					 *
span

					 *
u

					 *
dl

					 *
dt

					 *
dd

					 *
ul

					 *
ol

					 *
li

					 *

					 *
class, style, dir, and target attributes are allowed.
					 * If target is not set, links open in a new window by default.
					 *
Only safe href attributes can be used. See {@link module:sap/base/security/URLListValidator URLListValidator}.
					 *
					 * Note: Keep in mind that not supported HTML tags and
					 * the content nested inside them are both not rendered by the control.
					 */
					htmlText: {type: "string", group: "Misc", defaultValue: ""},

					/**
					 * Optional width of the control in CSS units.
					 */
					width: {type : "sap.ui.core.CSSSize", group : "Appearance", defaultValue : null},

					/**
					 * Determines whether strings that appear to be links will be converted to HTML anchor tags,
					 * and what are the criteria for recognizing them.
					 * @since 1.45.5
					 */
					convertLinksToAnchorTags: {type: "sap.m.LinkConversion", group: "Behavior", defaultValue: LinkConversion.None},

					/**
					 * Determines the target attribute of the generated HTML anchor tags.
					 *
					 * Note: Applicable only if ConvertLinksToAnchorTags property is used with a value other than sap.m.LinkConversion.None.
					 * Options are the standard values for the target attribute of the HTML anchor tag:
					 * _self, _top, _blank, _parent, _search.
					 * @since 1.45.5
					 */
					convertedLinksDefaultTarget: {type: "string", group: "Behavior", defaultValue: "_blank"},

					/**
					 *  Optional height of the control in CSS units.
					 */
					height: {type : "sap.ui.core.CSSSize", group : "Appearance", defaultValue : null},

					/**
					 * Defines the directionality of the text in the FormattedText, e.g. right-to-left(RTL)
					 * or left-to-right (LTR).
					 *
					 * Note: This functionality if set to the root element. Use the bdi element and
					 * the dir attribute to set explicit direction to an element.
					 *
					 * @since 1.86.0
					 */
					textDirection: { type: "sap.ui.core.TextDirection", group: "Appearance", defaultValue: TextDirection.Inherit },

					/**
					 * Determines the text alignment in the text elements in the FormattedText.
					 *
					 * Note: This functionality if set to the root element. To set explicit alignment to an element
					 * use the style attribute.
					 *
					 * @since 1.86.0
					 */
					textAlign : {type : "sap.ui.core.TextAlign", group : "Appearance", defaultValue : TextAlign.Begin}
				},
				aggregations: {

					/**
					* List of sap.m.Link controls that will be used to replace the placeholders in the text.
					* Placeholders are replaced according to their indexes. The placeholder with index %%0 will be replaced
					* by the first link in the aggregation, etc.
					*/
					controls: {type: "sap.m.Link", multiple: true, singularName: "control"}
				}
			}
		});

		/*
		 * These are the rules for the FormattedText
		 */
		var _defaultRenderingRules = {
			// rules for the allowed attributes
			ATTRIBS: {
				'style' : 1,
				'class' : 1,
				'a::href' : 1,
				'a::target' : 1,
				'dir' : 1
			},
			// rules for the allowed tags
			ELEMENTS: {
				// Text Module Tags
				'a' : {cssClass: 'sapMLnk'},
				'abbr': 1,
				'bdi' : 1,
				'blockquote': 1,
				'br': 1,
				'cite': 1,
				'code': 1,
				'em': 1,
				'h1': {cssClass: 'sapMTitle sapMTitleStyleH1'},
				'h2': {cssClass: 'sapMTitle sapMTitleStyleH2'},
				'h3': {cssClass: 'sapMTitle sapMTitleStyleH3'},
				'h4': {cssClass: 'sapMTitle sapMTitleStyleH4'},
				'h5': {cssClass: 'sapMTitle sapMTitleStyleH5'},
				'h6': {cssClass: 'sapMTitle sapMTitleStyleH6'},
				'p': 1,
				'pre': 1,
				'strong': 1,
				'span': 1,
				'u' : 1,

				// List Module Tags
				'dl': 1,
				'dt': 1,
				'dd': 1,
				'ol': 1,
				'ul': 1,
				'li': 1
			}
		},
		_limitedRenderingRules = {
			ATTRIBS: {
				'a::href' : 1,
				'a::target' : 1
			},
			ELEMENTS: {
				'a' : {cssClass: 'sapMLnk'},
				'br': 1,
				'em': 1,
				'strong': 1,
				'u': 1
			}
		};

		/**
		 * Holds the internal list of allowed and evaluated HTML elements and attributes
		 * @private
		 */
		FormattedText.prototype._renderingRules = _defaultRenderingRules;

		/**
		 * Initialization hook for the FormattedText, which creates a list of rules with allowed tags and attributes.
		 */
		FormattedText.prototype.init = function () {
		};

		/**
		 * Sanitizes attributes on an HTML tag.
		 *
		 * @param {string} tagName An HTML tag name in lower case
		 * @param {array} attribs An array of alternating names and values
		 * @return {array} The sanitized attributes as a list of alternating names and values. Value null removes the attribute.
		 * @private
		 */
		function fnSanitizeAttribs (tagName, attribs) {

			var sWarning;
			var attr,
				value,
				addTarget = tagName === "a";
			// add UI5 specific classes when appropriate
			var cssClass = this._renderingRules.ELEMENTS[tagName].cssClass || "";

			for (var i = 0; i < attribs.length; i += 2) {
				// attribs[i] is the name of the tag's attribute.
				// attribs[i+1] is its corresponding value.
				// (i.e.  -> attribs[i] = "class" | attribs[i+1] = "foo")
				attr = attribs[i];
				value = attribs[i + 1];

				if (!this._renderingRules.ATTRIBS[attr] && !this._renderingRules.ATTRIBS[tagName + "::" + attr]) {
					sWarning = 'FormattedText: <' + tagName + '> with attribute [' + attr + '="' + value + '"] is not allowed';
					Log.warning(sWarning, this);
					// to remove the attribute by the sanitizer, set the value to null
					attribs[i + 1] = null;
					continue;
				}

				// sanitize hrefs
				if (attr == "href") { // a::href
					if (!URLListValidator.validate(value)) {
						Log.warning("FormattedText: incorrect href attribute:" + value, this);
						attribs[i + 1] = "#";
						addTarget = false;
					}
				}
				if (attr == "target") { // a::target already exists
					addTarget = false;
				}

				// add UI5 classes to the user defined
				if (cssClass && attr.toLowerCase() == "class") {
					attribs[i + 1] = cssClass + " " + value;
					cssClass = "";
				}
			}

			if (addTarget) {
				attribs.push("target");
				attribs.push("_blank");
			}

			// add UI5 classes, if not done before
			if (cssClass) {
				attribs.push("class");
				attribs.push(cssClass);
			}

			return attribs;
		}

		function fnPolicy (tagName, attribs) {
			if (this._renderingRules.ELEMENTS[tagName]) {
				return fnSanitizeAttribs.call(this, tagName, attribs);
			} else {
				var sWarning = '<' + tagName + '> is not allowed';
				Log.warning(sWarning, this);
			}
		}

		/**
		 * Sanitizes HTML tags and attributes according to a given policy.
		 *
		 * @param {string} sText The HTML to sanitize
		 * @return {string} The sanitized HTML
		 * @private
		 */
		function sanitizeHTML(sText) {
			return sanitizeHTML0(sText, {
				tagPolicy: fnPolicy.bind(this),
				uriRewriter: function (sUrl) {
					// by default, we use the URLListValidator to check the URLs
					if (URLListValidator.validate(sUrl)) {
						return sUrl;
					}
				}
			});
		}

		// prohibit a new window from accessing window.opener.location
		function openExternalLink (oEvent) {
			oEvent.preventDefault();
			openWindow(oEvent.currentTarget.href);
		}

		FormattedText.prototype.onAfterRendering = function () {
			this.$().find('a:not([target="_self"]):not([target="_parent"]):not([target="_top"])').on("click", openExternalLink);
		};

		FormattedText.prototype.onBeforeRendering = function () {
			this.$().find('a:not([target="_self"]):not([target="_parent"]):not([target="_top"])').off("click", openExternalLink);
		};

		FormattedText.prototype._getDisplayHtml = function (){
			var sText = this.getHtmlText(),
				sAutoGenerateLinkTags = this.getConvertLinksToAnchorTags();

			if (sAutoGenerateLinkTags === LinkConversion.None) {
				return sText;
			}

			sText = FormattedTextAnchorGenerator.generateAnchors(sText, sAutoGenerateLinkTags, this.getConvertedLinksDefaultTarget());

			return sanitizeHTML.call(this, sText);
		};

		/**
		 * Defines the HTML text to be displayed.
		 * @param {string} sText HTML text as a string
		 * @return {this} this for chaining
		 * @public
		 */
		FormattedText.prototype.setHtmlText = function (sText) {
			return this.setProperty("htmlText", sanitizeHTML.call(this, sText));
		};

		/**
		 * Sets should a limited list of rendering rules be used instead of the default one. This limited list
		 * will evaluate only a small subset of the default HTML elements and attributes.
		 * @param {boolean} bLimit Should the control use the limited list
		 * @private
		 * @ui5-restricted sap.m.MessageStrip
		 */
		FormattedText.prototype._setUseLimitedRenderingRules = function (bLimit) {
			this._renderingRules = bLimit ? _limitedRenderingRules : _defaultRenderingRules;
		};

		FormattedText.prototype.getFocusDomRef = function () {
			return this.getDomRef() && this.getDomRef().querySelector("a");
		};

		return FormattedText;
	});<|endoftext|>


<commit_before>/**
 *
 * wechaty: Wechat for Bot. and for human who talk to bot/robot
 *
 * Class Io
 * http://www.wechaty.io
 *
 * Licenst: ISC
 * https://github.com/zixia/wechaty
 *
 */
const EventEmitter  = require('events')
const WebSocket     = require('ws')
const co            = require('co')

const log           = require('./npmlog-env')
const Contact       = require('./contact')

class Io {

  constructor({
    wechaty = null
    , token = null
    , endpoint = 'wss://api.wechaty.io/v0/websocket'
    , protocol = 'io|0.0.1'
  }) {
    if (!wechaty || !token) {
      throw new Error('Io must has wechaty & token set')
    }
    this.wechaty  = wechaty
    this.token    = token
    this.endpoint = endpoint

    this.protocol = protocol + '|' + wechaty.uuid
    log.verbose('Io', 'instantiated with endpoint[%s], token[%s], protocol[%s], uuid[%s]', endpoint, token, protocol, this.uuid)
  }

  toString() { return 'Class Io(' + this.token + ')'}

  connected() { return this.ws && this.ws.readyState === WebSocket.OPEN }

  init() {
    log.verbose('Io', 'init()')

    return co.call(this, function* () {
      yield this.initEventHook()
      yield this.initWebSocket()

      return this
    }).catch(e => {
      log.warn('Io', 'init() exception: %s', e.message)
      throw e
    })
  }

  initWebSocket() {
    log.verbose('Io', 'initWebSocket()')
    // const auth = 'Basic ' + new Buffer(this.token + ':X').toString('base64')
    const auth = 'Token ' + this.token
    const headers = { 'Authorization': auth }

    const ws = this.ws = new WebSocket(this.endpoint, this.protocol, { headers })

    ws.on('open', function open() {
      if (this.protocol !== ws.protocol) {
        log.error('Io', 'initWebSocket() require protocol[%s] failed', this.protocol)
        // XXX deal with error?
      }
      log.verbose('Io', 'initWebSocket() connected with protocol [%s]', ws.protocol)

      // FIXME: how to keep alive???
      ws._socket.setKeepAlive(true, 100)

      this.reconnectTimeout = null

      const initEvent = {
        name: 'sys'
        , payload: 'Wechaty version ' + this.wechaty.version() + ` with UUID: ${this.uuid}`
      }
      this.send(initEvent)

    }.bind(this))

    ws.on('message', (data, flags) => {
      log.silly('Io', 'initWebSocket() ws.on(message): %s', data)
      // flags.binary will be set if a binary data is received.
      // flags.masked will be set if the data was masked.

      const ioEvent = {
        name: 'raw'
        , payload: data
      }

      try {
        const obj = JSON.parse(data)
        ioEvent.name    = obj.name
        ioEvent.payload = obj.payload
      } catch (e) {
        log.verbose('Io', 'on(message) recv a non IoEvent data[%s]', data)
      }

      switch (ioEvent.name) {
        case 'botie':
          const payload = ioEvent.payload
          if (payload.onMessage) {
            const script = payload.script
            const fn = eval(script)
            if (typeof fn === 'function') {
              this.onMessage = fn
            } else {
              log.warn('Io', 'server pushed function is invalid')
            }
          }
          break

        case 'reset':
          log.verbose('Io', 'on(reset): %s', ioEvent.payload)
          this.wechaty.reset()
          break

        case 'shutdown':
          log.warn('Io', 'on(shutdown): %s', ioEvent.payload)
          process.exit(0)
          break

        case 'update':
          log.verbose('Io', 'on(report): %s', ioEvent.payload)
          const user = this.wechaty.user()
          if (user) {
            const loginEvent = {
              name:       'login'
              , payload:  user.obj
            }
            this.send(loginEvent)
          }

          const scan = this.wechaty && this.wechaty.puppet && this.wechaty.puppet.scan
          if (scan) {
            const scanEvent = {
              name: 'scan'
              , payload: scan
            }
            this.send(scanEvent)
          }

          break

        case 'sys':
          // do nothing
          break

        default:
          log.warn('Io', 'UNKNOWN on(%s): %s', ioEvent.name, ioEvent.payload)
          break
      }
    })

    ws.on('error', e => {
      log.warn('Io', 'initWebSocket() error event[%s]', e.message)
      this.wechaty.emit('error', e)

      // when `error`, there must have already a `close` event
      // we should not call this.reconnect() again
      //
      // this.close()
      // this.reconnect()
    })
    .on('close', (code, message) => {
      log.warn('Io', 'initWebSocket() close event[%d: %s]', code, message)
      ws.close()
      this.reconnect()
    })

    return Promise.resolve()
  }

  reconnect() {
    if (this.connected()) {
      log.warn('Io', 'reconnect() on a already connected io')
      return
    }
    if (this.reconnectTimer) {
      log.warn('Io', 'reconnect() on a already re-connecting io')
      return
    }

    if (!this.reconnectTimeout) {
      this.reconnectTimeout = 1
    } else if (this.reconnectTimeout < 10000) {
      this.reconnectTimeout *= 3
    }

    log.warn('Io', 'reconnect() will reconnect after %d s', Math.floor(this.reconnectTimeout/1000))
    this.reconnectTimer = setTimeout(_ => {
      this.reconnectTimer = null
      this.initWebSocket()
    }, this.reconnectTimeout)
  }

  initEventHook() {
    const wechaty = this.wechaty

    wechaty.on('message', this.ioMessage)

    const hookEvents = [
      'scan'
      , 'login'
      , 'logout'
      , 'heartbeat'
      , 'error'
    ]
    hookEvents.map(event => {
      wechaty.on(event, data => {
        const ioEvent = {
          name:       event
          , payload:  data
        }

        switch (event) {
          case 'login':
          case 'logout':
            if (data instanceof Contact) {
              ioEvent.payload = data.obj
            }
            break

          case 'error':
            ioEvent.payload = data.toString()
            break

          case 'heartbeat':
            ioEvent.payload = {
              uuid: this.uuid
              , data: data
            }
            break

          default:
            break
        }

        this.send(ioEvent)
      })
    })

    // wechaty.on('message', m => {
    //   const text = (m.room() ? '['+m.room().name()+']' : '')
    //               + '<'+m.from().name()+'>'
    //               + ':' + m.toStringDigest()
    //   const messageEvent = {
    //     name:       'message'
    //     , payload:  text
    //   }
    //   this.send(messageEvent)
    // })

    return Promise.resolve()
  }

  send(ioEvent) {
    log.silly('Io', 'send(%s: %s)', ioEvent.name, ioEvent.payload)

    if (!this.connected()) {
      log.verbose('Io', 'send() without a connected websocket, dropped(ToBeQueue)')
      return
    }

    this.ws.send(
      JSON.stringify(
        ioEvent
      )
    )
  }

  close() {
    this.ws.close()
    // TODO: remove listener for this.wechaty.on(message )
  }

  /**
   *
   * Prepare to be overwriten by server setting
   *
   */
  ioMessage(m) {
    log.verbose('Io', 'ioMessage() is a nop function before be overwriten from cloud')
  }

}

/**
 * Expose `Wechaty`.
 */
module.exports = Io.default = Io.Io = Io
<commit_msg>fix uuid<commit_after>/**
 *
 * wechaty: Wechat for Bot. and for human who talk to bot/robot
 *
 * Class Io
 * http://www.wechaty.io
 *
 * Licenst: ISC
 * https://github.com/zixia/wechaty
 *
 */
const EventEmitter  = require('events')
const WebSocket     = require('ws')
const co            = require('co')

const log           = require('./npmlog-env')
const Contact       = require('./contact')

class Io {

  constructor({
    wechaty = null
    , token = null
    , endpoint = 'wss://api.wechaty.io/v0/websocket'
    , protocol = 'io|0.0.1'
  }) {
    if (!wechaty || !token) {
      throw new Error('Io must has wechaty & token set')
    }
    this.wechaty  = wechaty
    this.uuid     = wechaty.uuid

    this.token    = token
    this.endpoint = endpoint

    this.protocol = protocol + '|' + wechaty.uuid
    log.verbose('Io', 'instantiated with endpoint[%s], token[%s], protocol[%s], uuid[%s]', endpoint, token, protocol, this.uuid)
  }

  toString() { return 'Class Io(' + this.token + ')'}

  connected() { return this.ws && this.ws.readyState === WebSocket.OPEN }

  init() {
    log.verbose('Io', 'init()')

    return co.call(this, function* () {
      yield this.initEventHook()
      yield this.initWebSocket()

      return this
    }).catch(e => {
      log.warn('Io', 'init() exception: %s', e.message)
      throw e
    })
  }

  initWebSocket() {
    log.verbose('Io', 'initWebSocket()')
    // const auth = 'Basic ' + new Buffer(this.token + ':X').toString('base64')
    const auth = 'Token ' + this.token
    const headers = { 'Authorization': auth }

    const ws = this.ws = new WebSocket(this.endpoint, this.protocol, { headers })

    ws.on('open', function open() {
      if (this.protocol !== ws.protocol) {
        log.error('Io', 'initWebSocket() require protocol[%s] failed', this.protocol)
        // XXX deal with error?
      }
      log.verbose('Io', 'initWebSocket() connected with protocol [%s]', ws.protocol)

      // FIXME: how to keep alive???
      ws._socket.setKeepAlive(true, 100)

      this.reconnectTimeout = null

      const initEvent = {
        name: 'sys'
        , payload: 'Wechaty version ' + this.wechaty.version() + ` with UUID: ${this.uuid}`
      }
      this.send(initEvent)

    }.bind(this))

    ws.on('message', (data, flags) => {
      log.silly('Io', 'initWebSocket() ws.on(message): %s', data)
      // flags.binary will be set if a binary data is received.
      // flags.masked will be set if the data was masked.

      const ioEvent = {
        name: 'raw'
        , payload: data
      }

      try {
        const obj = JSON.parse(data)
        ioEvent.name    = obj.name
        ioEvent.payload = obj.payload
      } catch (e) {
        log.verbose('Io', 'on(message) recv a non IoEvent data[%s]', data)
      }

      switch (ioEvent.name) {
        case 'botie':
          const payload = ioEvent.payload
          if (payload.onMessage) {
            const script = payload.script
            const fn = eval(script)
            if (typeof fn === 'function') {
              this.onMessage = fn
            } else {
              log.warn('Io', 'server pushed function is invalid')
            }
          }
          break

        case 'reset':
          log.verbose('Io', 'on(reset): %s', ioEvent.payload)
          this.wechaty.reset()
          break

        case 'shutdown':
          log.warn('Io', 'on(shutdown): %s', ioEvent.payload)
          process.exit(0)
          break

        case 'update':
          log.verbose('Io', 'on(report): %s', ioEvent.payload)
          const user = this.wechaty.user()
          if (user) {
            const loginEvent = {
              name:       'login'
              , payload:  user.obj
            }
            this.send(loginEvent)
          }

          const scan = this.wechaty && this.wechaty.puppet && this.wechaty.puppet.scan
          if (scan) {
            const scanEvent = {
              name: 'scan'
              , payload: scan
            }
            this.send(scanEvent)
          }

          break

        case 'sys':
          // do nothing
          break

        default:
          log.warn('Io', 'UNKNOWN on(%s): %s', ioEvent.name, ioEvent.payload)
          break
      }
    })

    ws.on('error', e => {
      log.warn('Io', 'initWebSocket() error event[%s]', e.message)
      this.wechaty.emit('error', e)

      // when `error`, there must have already a `close` event
      // we should not call this.reconnect() again
      //
      // this.close()
      // this.reconnect()
    })
    .on('close', (code, message) => {
      log.warn('Io', 'initWebSocket() close event[%d: %s]', code, message)
      ws.close()
      this.reconnect()
    })

    return Promise.resolve()
  }

  reconnect() {
    if (this.connected()) {
      log.warn('Io', 'reconnect() on a already connected io')
      return
    }
    if (this.reconnectTimer) {
      log.warn('Io', 'reconnect() on a already re-connecting io')
      return
    }

    if (!this.reconnectTimeout) {
      this.reconnectTimeout = 1
    } else if (this.reconnectTimeout < 10000) {
      this.reconnectTimeout *= 3
    }

    log.warn('Io', 'reconnect() will reconnect after %d s', Math.floor(this.reconnectTimeout/1000))
    this.reconnectTimer = setTimeout(_ => {
      this.reconnectTimer = null
      this.initWebSocket()
    }, this.reconnectTimeout)
  }

  initEventHook() {
    const wechaty = this.wechaty

    wechaty.on('message', this.ioMessage)

    const hookEvents = [
      'scan'
      , 'login'
      , 'logout'
      , 'heartbeat'
      , 'error'
    ]
    hookEvents.map(event => {
      wechaty.on(event, data => {
        const ioEvent = {
          name:       event
          , payload:  data
        }

        switch (event) {
          case 'login':
          case 'logout':
            if (data instanceof Contact) {
              ioEvent.payload = data.obj
            }
            break

          case 'error':
            ioEvent.payload = data.toString()
            break

          case 'heartbeat':
            ioEvent.payload = {
              uuid: this.uuid
              , data: data
            }
            break

          default:
            break
        }

        this.send(ioEvent)
      })
    })

    // wechaty.on('message', m => {
    //   const text = (m.room() ? '['+m.room().name()+']' : '')
    //               + '<'+m.from().name()+'>'
    //               + ':' + m.toStringDigest()
    //   const messageEvent = {
    //     name:       'message'
    //     , payload:  text
    //   }
    //   this.send(messageEvent)
    // })

    return Promise.resolve()
  }

  send(ioEvent) {
    log.silly('Io', 'send(%s: %s)', ioEvent.name, ioEvent.payload)

    if (!this.connected()) {
      log.verbose('Io', 'send() without a connected websocket, dropped(ToBeQueue)')
      return
    }

    this.ws.send(
      JSON.stringify(
        ioEvent
      )
    )
  }

  close() {
    this.ws.close()
    // TODO: remove listener for this.wechaty.on(message )
  }

  /**
   *
   * Prepare to be overwriten by server setting
   *
   */
  ioMessage(m) {
    log.verbose('Io', 'ioMessage() is a nop function before be overwriten from cloud')
  }

}

/**
 * Expose `Wechaty`.
 */
module.exports = Io.default = Io.Io = Io
<|endoftext|>


<commit_before>'use strict';

var tsc = require('./tsc');
var fs = require('fs');
var path = require('path');
var util = require('util');
var _ = require('lodash');
var async = require('async');
var byline = require('byline');
var temp = require('temp');
var rimraf = require('rimraf');
var through = require('through2');
var fsSrc = require('vinyl-fs').src;
var EventEmitter = require('events').EventEmitter;

function Compiler(sourceFiles, options) {
    EventEmitter.call(this);

    this.sourceFiles = sourceFiles || [];

    var defaultModule = options && options.target === 'ES6' ? null : 'commonjs';

    this.options = _.extend({
        tscPath:           null,
        tscSearch:         null,
        module:            defaultModule,
        target:            'ES3',
        out:               null,
        outDir:            null,
        mapRoot:           null,
        sourceRoot:        null,
        allowbool:         false,
        allowimportmodule: false,
        declaration:       false,
        noImplicitAny:     false,
        noResolve:         false,
        removeComments:    false,
        sourceMap:         false,
        suppressImplicitAnyIndexErrors: false,
        tmpDir:            '',
        noLib:             false,
        keepTree:          true,
        pathFilter:        null,
        safe:              false,
        emitDecoratorMetadata: false,
        experimentalDecorators: false,
        additionalTscParameters: []
    }, options);
    this.options.sourceMap = this.options.sourceMap || this.options.sourcemap;
    delete this.options.sourcemap;

    this.tscOptions = {
        path:   this.options.tscPath,
        search: this.options.tscSearch
    };

    this.tempDestination = null;
    this.tscArgumentsFile = null;
    this.treeKeeperFile  = null;
}
util.inherits(Compiler, EventEmitter);

Compiler.prototype.buildTscArguments = function (version) {
  var args = [];
  if (version === undefined) {
      version = "1.5";
  }

  if (this.options.module)            args.push('--module',     this.options.module.toLowerCase());
  if (this.options.target)            args.push('--target',     this.options.target.toUpperCase());
  if (this.options.mapRoot)           args.push('--mapRoot',    this.options.mapRoot);
  if (this.options.sourceRoot)        args.push('--sourceRoot', this.options.sourceRoot);
  if (this.options.allowbool)         args.push('--allowbool');
  if (this.options.allowimportmodule) args.push('--allowimportmodule');
  if (this.options.suppressImplicitAnyIndexErrors
    && (version == "1.5" || version == "1.6.2" || version == "1.6.3")) args.push('--suppressImplicitAnyIndexErrors');
  if (this.options.declaration)       args.push('--declaration');
  if (this.options.noImplicitAny)     args.push('--noImplicitAny');
  if (this.options.noResolve)         args.push('--noResolve');
  if (this.options.removeComments)    args.push('--removeComments');
  if (this.options.sourceMap)         args.push('--sourcemap');
  if (this.options.noLib)             args.push('--noLib');
  if (this.options.jsx
    && (version == "1.6.2" || version == "1.6.3")) args.push('--jsx');
  if (this.options.emitDecoratorMetadata)    args.push('--emitDecoratorMetadata');
  if (this.options.experimentalDecorators)    args.push('--experimentalDecorators');
  if (this.options.additionalTscParameters)   this.options.additionalTscParameters.forEach(function (param) { args.push(param); });

  if (this.tempDestination) {
    args.push('--outDir', this.tempDestination);
    if (this.options.out) {
      args.push('--out', path.resolve(this.tempDestination, this.options.out));
    }
  } else if (this.options.out) {
    args.push('--out', this.options.out);
  }

  this.sourceFiles.forEach(function (f) { args.push(f.path); });
  if (this.treeKeeperFile) {
    args.push(this.treeKeeperFile);
  }

  return args;
};

Compiler.prototype.getVersion = function (callback) {
  return tsc.version(this.tscOptions, callback);
};

Compiler.prototype.compile = function (callback) {
  var _this = this;
  var checkAborted = this.checkAborted.bind(this);

  this.emit('start');
  Compiler._start(this);

  async.waterfall([
    checkAborted,
    this.makeTempDestinationDir.bind(this),
    checkAborted,
    this.makeTreeKeeperFile.bind(this),
    checkAborted,
    this.prepareTscArgumentsFile.bind(this),
    checkAborted,
    this.runTsc.bind(this),
    checkAborted
  ], function (err) {
    if (err && _this.options.safe) {
      finish(err);
    } else {
      _this.processOutputFiles(function (err2) {
        finish(err || err2);
      });
    }
  });

  function finish(err) {
    _this.cleanup();
    _this.emit('end');
    callback(err);
  }
};

Compiler.prototype.checkAborted = function (callback) {
  if (Compiler.isAborted()) {
    callback(new Error('aborted'));
  } else {
    callback(null);
  }
};

Compiler.prototype.makeTempDestinationDir = function (callback) {
  var _this = this;
  temp.track();
  temp.mkdir({ dir: path.resolve(process.cwd(), this.options.tmpDir), prefix: 'gulp-tsc-tmp-' }, function (err, dirPath) {
    if (err) return callback(err);
    _this.tempDestination = dirPath;

    callback(null);
  });
};

Compiler.prototype.makeTreeKeeperFile = function (callback) {
  if (!this.options.keepTree || this.options.out || this.sourceFiles.length === 0) {
    return callback(null);
  }

  var _this = this;
  temp.open({ dir: this.sourceFiles[0].base, prefix: '.gulp-tsc-tmp-', suffix: '.ts' }, function (err, file) {
    if (err) {
      return callback(new Error(
        'Failed to create a temporary file on source directory: ' + (err.message || err) + ', ' +
        'To skip creating it specify { keepTree: false } to your gulp-tsc.'
      ));
    }

    _this.treeKeeperFile = file.path;
    try {
      fs.writeSync(file.fd, '// This is a temporary file by gulp-tsc for keeping directory tree.\n');
      fs.closeSync(file.fd);
    } catch (e) {
      return callback(e);
    }
    callback(null);
  });
};

Compiler.prototype.prepareTscArgumentsFile = function(callback) {
    this.getVersion(function (version) {
        var tscArguments = this.buildTscArguments(version);
        var content = '"' + tscArguments.join('"\n"') + '"';
        this.tscArgumentsFile = path.join(this.tempDestination, 'tscArguments');
        fs.writeFile(this.tscArgumentsFile, content, callback);
    }.bind(this));
};

Compiler.prototype.runTsc = function (callback) {
  var _this = this;
  var proc = tsc.exec(['@' + this.tscArgumentsFile], this.tscOptions);
  var stdout = byline(proc.stdout);
  var stderr = byline(proc.stderr);

  proc.on('exit', function (code) {
    if (code !== 0) {
      callback(new Error('tsc command has exited with code:' + code));
    } else {
      callback(null);
    }
  })
  proc.on('error', function (err) {
    _this.emit('error', err);
  });
  stdout.on('data', function (chunk) {
    _this.emit('stdout', chunk);
  });
  stderr.on('data', function (chunk) {
    _this.emit('stderr', chunk);
  });

  return proc;
};

Compiler.prototype.processOutputFiles = function (callback) {
  var _this = this;
  var options = { cwd: this.tempDestination, cwdbase: true };
  var patterns = ['**/*{.js,.js.map,.d.ts}'];
  if (this.treeKeeperFile) {
    patterns.push('!**/' + path.basename(this.treeKeeperFile, '.ts') + '.*');
  }

  var stream = fsSrc(patterns, options);
  stream = stream.pipe(this.fixOutputFilePath());
  if (this.options.sourceMap && !this.options.sourceRoot) {
    stream = stream.pipe(this.fixSourcemapPath());
  }
  if (this.options.declaration && this.options.outDir) {
    stream = stream.pipe(this.fixReferencePath());
  }
  stream.on('data', function (file) {
    _this.emit('data', file);
  });
  stream.on('error', function (err) {
    callback(err);
  });
  stream.on('end', function () {
    callback();
  });
};

Compiler.prototype.fixOutputFilePath = function () {
  var filter = this.options.pathFilter && this.filterOutput.bind(this);
  var outDir;
  if (this.options.outDir) {
    outDir = path.resolve(process.cwd(), this.options.outDir);
  } else {
    outDir = this.tempDestination;
  }

  return through.obj(function (file, encoding, done) {
    file.originalPath = file.path;
    file.path = path.resolve(outDir, file.relative);
    file.cwd = file.base = outDir;
    if (filter) {
      try {
        file = filter(file);
      } catch (e) {
        return done(e);
      }
    }
    if (file) this.push(file);
    done();
  });
};

Compiler.prototype.filterOutput = function (file) {
  if (!this.options.pathFilter) return file;

  var filter = this.options.pathFilter;
  if (_.isFunction(filter)) {
    var ret = filter(file.relative, file);
    if (ret === true || _.isUndefined(ret)) {
      return file;
    } else if (ret === false) {
      return null;
    } else if (_.isString(ret)) {
      file.path = path.resolve(file.base, ret);
      return file;
    } else if (_.isObject(ret) && ret.path) {
      return ret;
    } else {
      throw new Error('Unknown return value from pathFilter function');
    }
  } else if (_.isPlainObject(filter)) {
    _.forOwn(filter, function (val, key) {
      if (_.isString(key) && _.isString(val)) {
        var src = path.normalize(key) + path.sep;
        if (file.relative.substr(0, src.length) === src) {
          file.path = path.resolve(file.base, val, file.relative.substr(src.length) || '.');
          return false;
        }
      }
    });
    return file;
  } else {
    throw new Error('Unknown type for pathFilter');
  }
};

Compiler.prototype.fixSourcemapPath = function () {
  return through.obj(function (file, encoding, done) {
    if (!file.isBuffer() || !/\.js\.map/.test(file.path)) {
      this.push(file);
      return done();
    }

    var map = JSON.parse(file.contents);
    if (map['sources'] && map['sources'].length > 0) {
      map['sources'] = map['sources'].map(function (sourcePath) {
        sourcePath = path.resolve(path.dirname(file.originalPath), sourcePath);
        sourcePath = path.relative(path.dirname(file.path), sourcePath);
        if (path.sep == '\\') sourcePath = sourcePath.replace(/\\/g, '/');
        return sourcePath;
      });
      file.contents = new Buffer(JSON.stringify(map));
    }
    this.push(file);
    done();
  });
};

Compiler.prototype.fixReferencePath = function () {
  return through.obj(function (file, encoding, done) {
    if (!file.isBuffer() || !/\.d\.ts/.test(file.path)) {
      this.push(file);
      return done();
    }

    var newContent = file.contents.toString().replace(
      /(\/\/\/\s*<reference\s*path\s*=\s*)(["'])(.+?)\2/g,
      function (entire, prefix, quote, refPath) {
        refPath = path.resolve(path.dirname(file.originalPath), refPath);
        refPath = path.relative(path.dirname(file.path), refPath);
        if (path.sep == '\\') refPath = refPath.replace(/\\/g, '/');
        return prefix + quote + refPath + quote;
      }
    );
    file.contents = new Buffer(newContent);
    this.push(file);
    done();
  });
};

Compiler.prototype.cleanup = function () {
  try { rimraf.sync(this.tempDestination); } catch(e) {}
  try { fs.unlinkSync(this.treeKeeperFile); } catch(e) {}
};

Compiler.running = 0;
Compiler.aborted = false;
Compiler.abortCallbacks = [];

Compiler.abortAll = function (callback) {
  Compiler.aborted = true;
  callback && Compiler.abortCallbacks.push(callback);
  if (Compiler.running == 0) {
    Compiler._allAborted();
  }
};

Compiler.isAborted = function () {
  return Compiler.aborted;
};

Compiler._start = function (compiler) {
  Compiler.running++;
  compiler.once('end', function () {
    Compiler.running--;
    if (Compiler.running == 0 && Compiler.aborted) {
      Compiler._allAborted();
    }
  });
};

Compiler._allAborted = function () {
  var callbacks = Compiler.abortCallbacks;
  Compiler.aborted = false;
  Compiler.abortCallbacks = [];
  callbacks.forEach(function (fn) {
    fn.call(null);
  });
};

module.exports = Compiler;
<commit_msg>Fix version detection error<commit_after>'use strict';

var tsc = require('./tsc');
var fs = require('fs');
var path = require('path');
var util = require('util');
var _ = require('lodash');
var async = require('async');
var byline = require('byline');
var temp = require('temp');
var rimraf = require('rimraf');
var through = require('through2');
var fsSrc = require('vinyl-fs').src;
var EventEmitter = require('events').EventEmitter;

function Compiler(sourceFiles, options) {
    EventEmitter.call(this);

    this.sourceFiles = sourceFiles || [];

    var defaultModule = options && options.target === 'ES6' ? null : 'commonjs';

    this.options = _.extend({
        tscPath:           null,
        tscSearch:         null,
        module:            defaultModule,
        target:            'ES3',
        out:               null,
        outDir:            null,
        mapRoot:           null,
        sourceRoot:        null,
        allowbool:         false,
        allowimportmodule: false,
        declaration:       false,
        noImplicitAny:     false,
        noResolve:         false,
        removeComments:    false,
        sourceMap:         false,
        suppressImplicitAnyIndexErrors: false,
        tmpDir:            '',
        noLib:             false,
        keepTree:          true,
        pathFilter:        null,
        safe:              false,
        emitDecoratorMetadata: false,
        experimentalDecorators: false,
        additionalTscParameters: []
    }, options);
    this.options.sourceMap = this.options.sourceMap || this.options.sourcemap;
    delete this.options.sourcemap;

    this.tscOptions = {
        path:   this.options.tscPath,
        search: this.options.tscSearch
    };

    this.tempDestination = null;
    this.tscArgumentsFile = null;
    this.treeKeeperFile  = null;
}
util.inherits(Compiler, EventEmitter);

Compiler.prototype.buildTscArguments = function (version) {
  var args = [];
  if (version === undefined) {
      version = "1.5";
  }

  if (this.options.module)            args.push('--module',     this.options.module.toLowerCase());
  if (this.options.target)            args.push('--target',     this.options.target.toUpperCase());
  if (this.options.mapRoot)           args.push('--mapRoot',    this.options.mapRoot);
  if (this.options.sourceRoot)        args.push('--sourceRoot', this.options.sourceRoot);
  if (this.options.allowbool)         args.push('--allowbool');
  if (this.options.allowimportmodule) args.push('--allowimportmodule');
  if (this.options.suppressImplicitAnyIndexErrors
    && (version == "1.5" || version == "1.6.2" || version == "1.6.3")) args.push('--suppressImplicitAnyIndexErrors');
  if (this.options.declaration)       args.push('--declaration');
  if (this.options.noImplicitAny)     args.push('--noImplicitAny');
  if (this.options.noResolve)         args.push('--noResolve');
  if (this.options.removeComments)    args.push('--removeComments');
  if (this.options.sourceMap)         args.push('--sourcemap');
  if (this.options.noLib)             args.push('--noLib');
  if (this.options.jsx
    && (version == "1.6.2" || version == "1.6.3")) args.push('--jsx');
  if (this.options.emitDecoratorMetadata)    args.push('--emitDecoratorMetadata');
  if (this.options.experimentalDecorators)    args.push('--experimentalDecorators');
  if (this.options.additionalTscParameters)   this.options.additionalTscParameters.forEach(function (param) { args.push(param); });

  if (this.tempDestination) {
    args.push('--outDir', this.tempDestination);
    if (this.options.out) {
      args.push('--out', path.resolve(this.tempDestination, this.options.out));
    }
  } else if (this.options.out) {
    args.push('--out', this.options.out);
  }

  this.sourceFiles.forEach(function (f) { args.push(f.path); });
  if (this.treeKeeperFile) {
    args.push(this.treeKeeperFile);
  }

  return args;
};

Compiler.prototype.getVersion = function (callback) {
  return tsc.version(this.tscOptions, callback);
};

Compiler.prototype.compile = function (callback) {
  var _this = this;
  var checkAborted = this.checkAborted.bind(this);

  this.emit('start');
  Compiler._start(this);

  async.waterfall([
    checkAborted,
    this.makeTempDestinationDir.bind(this),
    checkAborted,
    this.makeTreeKeeperFile.bind(this),
    checkAborted,
    this.prepareTscArgumentsFile.bind(this),
    checkAborted,
    this.runTsc.bind(this),
    checkAborted
  ], function (err) {
    if (err && _this.options.safe) {
      finish(err);
    } else {
      _this.processOutputFiles(function (err2) {
        finish(err || err2);
      });
    }
  });

  function finish(err) {
    _this.cleanup();
    _this.emit('end');
    callback(err);
  }
};

Compiler.prototype.checkAborted = function (callback) {
  if (Compiler.isAborted()) {
    callback(new Error('aborted'));
  } else {
    callback(null);
  }
};

Compiler.prototype.makeTempDestinationDir = function (callback) {
  var _this = this;
  temp.track();
  temp.mkdir({ dir: path.resolve(process.cwd(), this.options.tmpDir), prefix: 'gulp-tsc-tmp-' }, function (err, dirPath) {
    if (err) return callback(err);
    _this.tempDestination = dirPath;

    callback(null);
  });
};

Compiler.prototype.makeTreeKeeperFile = function (callback) {
  if (!this.options.keepTree || this.options.out || this.sourceFiles.length === 0) {
    return callback(null);
  }

  var _this = this;
  temp.open({ dir: this.sourceFiles[0].base, prefix: '.gulp-tsc-tmp-', suffix: '.ts' }, function (err, file) {
    if (err) {
      return callback(new Error(
        'Failed to create a temporary file on source directory: ' + (err.message || err) + ', ' +
        'To skip creating it specify { keepTree: false } to your gulp-tsc.'
      ));
    }

    _this.treeKeeperFile = file.path;
    try {
      fs.writeSync(file.fd, '// This is a temporary file by gulp-tsc for keeping directory tree.\n');
      fs.closeSync(file.fd);
    } catch (e) {
      return callback(e);
    }
    callback(null);
  });
};

Compiler.prototype.prepareTscArgumentsFile = function(callback) {
    this.getVersion(function (error, version) {
        var tscArguments = this.buildTscArguments(version);
        var content = '"' + tscArguments.join('"\n"') + '"';
        this.tscArgumentsFile = path.join(this.tempDestination, 'tscArguments');
        fs.writeFile(this.tscArgumentsFile, content, callback);
    }.bind(this));
};

Compiler.prototype.runTsc = function (callback) {
  var _this = this;
  var proc = tsc.exec(['@' + this.tscArgumentsFile], this.tscOptions);
  var stdout = byline(proc.stdout);
  var stderr = byline(proc.stderr);

  proc.on('exit', function (code) {
    if (code !== 0) {
      callback(new Error('tsc command has exited with code:' + code));
    } else {
      callback(null);
    }
  })
  proc.on('error', function (err) {
    _this.emit('error', err);
  });
  stdout.on('data', function (chunk) {
    _this.emit('stdout', chunk);
  });
  stderr.on('data', function (chunk) {
    _this.emit('stderr', chunk);
  });

  return proc;
};

Compiler.prototype.processOutputFiles = function (callback) {
  var _this = this;
  var options = { cwd: this.tempDestination, cwdbase: true };
  var patterns = ['**/*{.js,.js.map,.d.ts}'];
  if (this.treeKeeperFile) {
    patterns.push('!**/' + path.basename(this.treeKeeperFile, '.ts') + '.*');
  }

  var stream = fsSrc(patterns, options);
  stream = stream.pipe(this.fixOutputFilePath());
  if (this.options.sourceMap && !this.options.sourceRoot) {
    stream = stream.pipe(this.fixSourcemapPath());
  }
  if (this.options.declaration && this.options.outDir) {
    stream = stream.pipe(this.fixReferencePath());
  }
  stream.on('data', function (file) {
    _this.emit('data', file);
  });
  stream.on('error', function (err) {
    callback(err);
  });
  stream.on('end', function () {
    callback();
  });
};

Compiler.prototype.fixOutputFilePath = function () {
  var filter = this.options.pathFilter && this.filterOutput.bind(this);
  var outDir;
  if (this.options.outDir) {
    outDir = path.resolve(process.cwd(), this.options.outDir);
  } else {
    outDir = this.tempDestination;
  }

  return through.obj(function (file, encoding, done) {
    file.originalPath = file.path;
    file.path = path.resolve(outDir, file.relative);
    file.cwd = file.base = outDir;
    if (filter) {
      try {
        file = filter(file);
      } catch (e) {
        return done(e);
      }
    }
    if (file) this.push(file);
    done();
  });
};

Compiler.prototype.filterOutput = function (file) {
  if (!this.options.pathFilter) return file;

  var filter = this.options.pathFilter;
  if (_.isFunction(filter)) {
    var ret = filter(file.relative, file);
    if (ret === true || _.isUndefined(ret)) {
      return file;
    } else if (ret === false) {
      return null;
    } else if (_.isString(ret)) {
      file.path = path.resolve(file.base, ret);
      return file;
    } else if (_.isObject(ret) && ret.path) {
      return ret;
    } else {
      throw new Error('Unknown return value from pathFilter function');
    }
  } else if (_.isPlainObject(filter)) {
    _.forOwn(filter, function (val, key) {
      if (_.isString(key) && _.isString(val)) {
        var src = path.normalize(key) + path.sep;
        if (file.relative.substr(0, src.length) === src) {
          file.path = path.resolve(file.base, val, file.relative.substr(src.length) || '.');
          return false;
        }
      }
    });
    return file;
  } else {
    throw new Error('Unknown type for pathFilter');
  }
};

Compiler.prototype.fixSourcemapPath = function () {
  return through.obj(function (file, encoding, done) {
    if (!file.isBuffer() || !/\.js\.map/.test(file.path)) {
      this.push(file);
      return done();
    }

    var map = JSON.parse(file.contents);
    if (map['sources'] && map['sources'].length > 0) {
      map['sources'] = map['sources'].map(function (sourcePath) {
        sourcePath = path.resolve(path.dirname(file.originalPath), sourcePath);
        sourcePath = path.relative(path.dirname(file.path), sourcePath);
        if (path.sep == '\\') sourcePath = sourcePath.replace(/\\/g, '/');
        return sourcePath;
      });
      file.contents = new Buffer(JSON.stringify(map));
    }
    this.push(file);
    done();
  });
};

Compiler.prototype.fixReferencePath = function () {
  return through.obj(function (file, encoding, done) {
    if (!file.isBuffer() || !/\.d\.ts/.test(file.path)) {
      this.push(file);
      return done();
    }

    var newContent = file.contents.toString().replace(
      /(\/\/\/\s*<reference\s*path\s*=\s*)(["'])(.+?)\2/g,
      function (entire, prefix, quote, refPath) {
        refPath = path.resolve(path.dirname(file.originalPath), refPath);
        refPath = path.relative(path.dirname(file.path), refPath);
        if (path.sep == '\\') refPath = refPath.replace(/\\/g, '/');
        return prefix + quote + refPath + quote;
      }
    );
    file.contents = new Buffer(newContent);
    this.push(file);
    done();
  });
};

Compiler.prototype.cleanup = function () {
  try { rimraf.sync(this.tempDestination); } catch(e) {}
  try { fs.unlinkSync(this.treeKeeperFile); } catch(e) {}
};

Compiler.running = 0;
Compiler.aborted = false;
Compiler.abortCallbacks = [];

Compiler.abortAll = function (callback) {
  Compiler.aborted = true;
  callback && Compiler.abortCallbacks.push(callback);
  if (Compiler.running == 0) {
    Compiler._allAborted();
  }
};

Compiler.isAborted = function () {
  return Compiler.aborted;
};

Compiler._start = function (compiler) {
  Compiler.running++;
  compiler.once('end', function () {
    Compiler.running--;
    if (Compiler.running == 0 && Compiler.aborted) {
      Compiler._allAborted();
    }
  });
};

Compiler._allAborted = function () {
  var callbacks = Compiler.abortCallbacks;
  Compiler.aborted = false;
  Compiler.abortCallbacks = [];
  callbacks.forEach(function (fn) {
    fn.call(null);
  });
};

module.exports = Compiler;
<|endoftext|>


<commit_before>'use strict';

import React, {
  Alert,
  Component,
  Dimensions,
  Text,
  Image,
  View,
} from 'react-native';

import Button from '../components/button';
import ButtonStyles from '../styles/button-styles';
import TextStyles from '../styles/text-styles';
import TitleBar from '../components/title-bar';

let database = new Firebase("poopapp1.firebaseio.com");

class ChangeProfilePicture extends Component {
  constructor(props) {
    super(props);
    this.state = {
      profilePic: ''
    };
  }

  componentDidMount() {
    this.getProfilePicture();
  }

  render() {
    return (
      <View style = {{flex: 1}}>
        <TitleBar
          hasBack = {"true"}
          navigator = {this.props.navigator}
          text = "Change Your Profile Picture"
        />

        <Text style = {{marginTop: 14, marginLeft: 20}}>
          Current Profile Picture
        </Text>

        <View style = {{padding: 8, alignItems: 'center'}}>
          <Image
            source = {{uri: this.state.profilePic}}
            style = {{
              height: Dimensions.get("window").width / 4,
              width: Dimensions.get("window").width / 4,
            }}>
          </Image>
        </View>

        <Button
          buttonStyles = {ButtonStyles.transparentButton}
          buttonTextStyles = {ButtonStyles.blackButtonText}
          onPress = {this.getNewProfilePicture.bind(this)}
          text = "New Profile Picture"
          underlayColor = {'gray'}
        />
        <Button
          buttonStyles = {ButtonStyles.transparentButton}
          buttonTextStyles = {ButtonStyles.blackButtonText}
          onPress = {this.changeProfilePicture.bind(this)}
          text = "Submit"
          underlayColor = {'gray'}
        />
      </View>
    );
  }

  getProfilePicture() {
    database.child("users/" + database.getAuth().uid + "/profilePic").once("value",
      (snapshot) => {
        this.setState({
          profilePic: snapshot.val().uri
        });
      }
    );
  }

  getNewProfilePicture() {

  }

  changeProfilePicture() {

  }
}

module.exports = ChangeProfilePicture;
<commit_msg>Implemented change profile pic<commit_after>'use strict';

import React, {
  Alert,
  Component,
  Dimensions,
  Text,
  Image,
  View,
} from 'react-native';

import Button from '../components/button';
import ButtonStyles from '../styles/button-styles';
import TextStyles from '../styles/text-styles';
import TitleBar from '../components/title-bar';

var ImagePickerManager = require('NativeModules').ImagePickerManager;

let database = new Firebase("poopapp1.firebaseio.com");

class ChangeProfilePicture extends Component {
  constructor(props) {
    super(props);
    this.state = {
      profilePic: '',
      image: '',
    };
  }

  componentDidMount() {
    this.getProfilePicture();
  }

  render() {
    return (
      <View style = {{flex: 1}}>
        <TitleBar
          hasBack = {"true"}
          navigator = {this.props.navigator}
          text = "Change Your Profile Picture"
        />

        <Text style = {{marginTop: 14, marginLeft: 20}}>
          Current Profile Picture
        </Text>

        <View style = {{padding: 8, alignItems: 'center'}}>
          <Image
            source = {{uri: this.state.profilePic}}
            style = {{
              height: Dimensions.get("window").width / 4,
              width: Dimensions.get("window").width / 4,
            }}>
          </Image>
        </View>

        <Button
          buttonStyles = {ButtonStyles.transparentButton}
          buttonTextStyles = {ButtonStyles.blackButtonText}
          onPress = {this.getNewProfilePicture.bind(this)}
          text = "New Profile Picture"
          underlayColor = {'gray'}
        />
        <Button
          buttonStyles = {ButtonStyles.transparentButton}
          buttonTextStyles = {ButtonStyles.blackButtonText}
          onPress = {this.changeProfilePicture.bind(this)}
          text = "Submit"
          underlayColor = {'gray'}
        />
      </View>
    );
  }

  getProfilePicture() {
    database.child("users/" + database.getAuth().uid + "/profilePic").once("value",
      (snapshot) => {
        this.setState({
          profilePic: snapshot.val().uri
        });
      }
    );
  }

  getNewProfilePicture() {
    ImagePickerManager.showImagePicker(options, (response) => {
      console.log('Response = ', response);
      if(response.didCancel) {
        console.log('User cancelled image picker');
      } else if(response.error) {
        console.log('ImagePickerManager Error: ', response.error);
      } else {
        // You can display the image using either data:
        const source = {uri: 'data:image/jpeg;base64,' + response.data, isStatic: true};
        this.setState({
          profilePic: source.uri,
          image: source,
        });
      }
    });
  }

  changeProfilePicture() {
    var ref = database.child("users");
    ref.child(database.getAuth().uid).update({
      profilePic: this.state.image,
    });
  }
}

const options = {
  allowsEditing: false, // Built in functionality to resize, reposition the image after selection
  angle: 0, // android only, photos only
  cameraType: 'front', // 'front' or 'back'
  cancelButtonTitle: 'Cancel',
  chooseFromLibraryButtonTitle: 'Choose from Library...', // specify null or empty string to remove this button
  durationLimit: 10, // video recording max time in seconds
  maxHeight: 370, // photos only
  maxWidth: 370, // photos only
  mediaType: 'photo', // 'photo' or 'video'
  noData: false, // photos only - disables the base64 `data` field from being generated (greatly improves performance on large photos)
  quality: 1, // 0 to 1, photos only
  takePhotoButtonTitle: 'Take Photo...', // specify null or empty string to remove this button
  title: 'Select Avatar', // specify null or empty string to remove the title
  videoQuality: 'high', // 'low', 'medium', or 'high'
};

module.exports = ChangeProfilePicture;
<|endoftext|>