<commit_before>/**
 * The most minimal `task` implementation and its responsibilties.
 *
 * An implementation of `bionode-watermill` shall expose a user facing function
 * called `task` which takes two parameters:
 * - the first, `props`, is a plain object describing properties of the task
 * - the second, `operationCreator`, is a function which receives one parameter:
 *   a plain object called `opsProps` which describes the properties of the
 *   operation. `opsProps` **must not be referentially equal to `props`**.
 * - returns a function `invocableTask` which will accept a callback `cb` and a
 *   context `ctx`. the callback shall be called on completion or error of the
 *   `operation`.
 */

const _ = require('lodash')
const Promise = require('bluebird')

const { defaultContext } = require('../lib/constants/default-task-state.js')
const { generateUid } = require('./lifecycle')
const { createTask } = require('./reducers/tasks.js')

const hash = require('./utils/hash.js')
const stringify = require('json-stable-stringify')

/**
 * The task factory function.
 *
 * Internally:
 * @param dispatch {Function} redux store.dispatch passed from above
 *
 * User facing:
 * @param props {Object} properties of this task
 * @param operationCreator {Function} function to be called with props
 * @returns {Function}
 */
const task = (dispatch) => (props, operationCreator, instanceUid = null) => {
  // Type checking on params
  if (!_.isPlainObject(props) || !_.isFunction(operationCreator)) {
    throw new Error('incorrect parameter types: task(props, operationCreator) where props is a plain object and operationCreator is a function')
  }

  // First thing we need to do is create a `uid` for this task
  // `hashes` is an object with input, output, params keys
  // The `uid` is a hash of the `input`, `output`, and `params` hashes
  let { uid, hashes } = generateUid(props)
  // console.log('params for uid before invoked', uid, props.params)

  // Returns an invocable task that can be passed a callback (or resolved as a
  // promise) and/or context
  // const invocableTask = (cb = _.noop, ctx = defaultContext) => {
  const invocableTask = (cb = _.noop, ctx = defaultContext) => {
    // const self = this
    return new Promise((resolve, reject) => {
      // Take trajectory from context
      const { trajectory } = ctx
      // Make a new context for the task we are about to create using it
      const context = { trajectory }

      // used for merkle tree like tasks with child nodes adding to hash based
      // on their hash.
      // TODO some instances seem to return undefined (not sure which ones)
      if (context.trajectory !== undefined) {
        const childHashes = Object.keys(context).reduce(
          (sum, key) => Object.assign({}, sum, {[key]: hash(stringify(context[key]))}),
          {}
        )
        uid = hash(childHashes.trajectory + uid)
      }

      // This kicks off the task lifecycle saga. Lifecycle saga will continue from
      // point just after this action has update the store.
      dispatch(createTask({
        uid: instanceUid ? instanceUid : uid,
        hashes,
        props,
        context,
        // For saga but not reducer
        operationCreator,
        taskResolve: resolve,
        taskReject: reject
      }))
    }).asCallback(cb)
  }
  // const invocableTask = (cb = _.noop, ctx = defaultContext) => new Promise((resolve, reject) => {

  // Attach info to returned invocable task
  // Used to let join() know if it should stream across tasks
  // Could be used for a dry run using just glob patterns

  // const obj = {
  //   instanceUid: null
  // }
  // invocableTask = invocableTask.bind(obj)

  invocableTask.info = Object.assign({
    uid: instanceUid ? instanceUid : uid,
    props,
    operationCreator
  })
  // invocableTask.setInstanceUid = (instanceUid) => {
  //   obj.instanceUid = instanceUid
  //   invocableTask.info.uid = instanceUid
  // }

  return invocableTask
}

module.exports = task
<commit_msg>made reducer match merkle tree task creation<commit_after>/**
 * The most minimal `task` implementation and its responsibilties.
 *
 * An implementation of `bionode-watermill` shall expose a user facing function
 * called `task` which takes two parameters:
 * - the first, `props`, is a plain object describing properties of the task
 * - the second, `operationCreator`, is a function which receives one parameter:
 *   a plain object called `opsProps` which describes the properties of the
 *   operation. `opsProps` **must not be referentially equal to `props`**.
 * - returns a function `invocableTask` which will accept a callback `cb` and a
 *   context `ctx`. the callback shall be called on completion or error of the
 *   `operation`.
 */

const _ = require('lodash')
const Promise = require('bluebird')

const { defaultContext } = require('../lib/constants/default-task-state.js')
const { generateUid } = require('./lifecycle')
const { createTask } = require('./reducers/tasks.js')

const hash = require('./utils/hash.js')
const stringify = require('json-stable-stringify')

/**
 * The task factory function.
 *
 * Internally:
 * @param dispatch {Function} redux store.dispatch passed from above
 *
 * User facing:
 * @param props {Object} properties of this task
 * @param operationCreator {Function} function to be called with props
 * @returns {Function}
 */
const task = (dispatch) => (props, operationCreator, instanceUid = null, ctx = defaultContext) => {
  // Type checking on params
  if (!_.isPlainObject(props) || !_.isFunction(operationCreator)) {
    throw new Error('incorrect parameter types: task(props, operationCreator) where props is a plain object and operationCreator is a function')
  }

  // First thing we need to do is create a `uid` for this task
  // `hashes` is an object with input, output, params keys
  // The `uid` is a hash of the `input`, `output`, and `params` hashes
  let { uid, hashes } = generateUid(props)
  // console.log('params for uid before invoked', uid, props.params)

  const { trajectory } = ctx
  // Make a new context for the task we are about to create using it
  const context = { trajectory }

  // used for merkle tree like tasks with child nodes adding to hash based
  // on their hash.
  // // TODO some instances seem to return undefined (not sure which ones)
  const childHashes = Object.keys(context).reduce(
    (sum, key) => Object.assign({}, sum, {[key]: hash(stringify(context[key]))}),
    {}
  )
  uid = hash(childHashes.trajectory + uid)

  // Returns an invocable task that can be passed a callback (or resolved as a
  // promise) and/or context
  // const invocableTask = (cb = _.noop, ctx = defaultContext) => {
  const invocableTask = (cb = _.noop, ctx = defaultContext) => {
    // const self = this
    return new Promise((resolve, reject) => {
      // Take trajectory from context
      const { trajectory } = ctx
      // Make a new context for the task we are about to create using it
      const context = { trajectory }

      // This kicks off the task lifecycle saga. Lifecycle saga will continue from
      // point just after this action has update the store.
      dispatch(createTask({
        uid: instanceUid ? instanceUid : uid,
        hashes,
        props,
        context,
        // For saga but not reducer
        operationCreator,
        taskResolve: resolve,
        taskReject: reject
      }))
    }).asCallback(cb)
  }
  // const invocableTask = (cb = _.noop, ctx = defaultContext) => new Promise((resolve, reject) => {

  // Attach info to returned invocable task
  // Used to let join() know if it should stream across tasks
  // Could be used for a dry run using just glob patterns

  // const obj = {
  //   instanceUid: null
  // }
  // invocableTask = invocableTask.bind(obj)

  invocableTask.info = Object.assign({
    uid: instanceUid ? instanceUid : uid,
    props,
    operationCreator
  })
  // invocableTask.setInstanceUid = (instanceUid) => {
  //   obj.instanceUid = instanceUid
  //   invocableTask.info.uid = instanceUid
  // }

  return invocableTask
}

module.exports = task
<|endoftext|>


<commit_before>
var Phantom = require('phantom')
  , http = require('http')
  , cache_manager = require('cache-manager')
  , cache;

if (process.argv[2] != null && (process.argv[2] == '-c' || process.argv[2] == '--cache')) {
    console.log('Prerender started with caching turned ON');
    cache = cache_manager.caching({
        store: 'memory', max: 100, ttl: 60/*seconds*/
    });
}

var testCache = function(url, cb) {
    if (!cache) {
        cb(null, null);
    }
    else {
	    cache.get(url, cb);
    }
}

Phantom.create({
    binary: require('phantomjs').path
}, function(phantom) {

    http.createServer(function (req, res) {
        console.log('getting', req.url);

	    testCache(req.url.substr(1), function (err, result) {
	        if (!err && result != null) {
    	        res.writeHead(200, {'Content-Type': 'text/html'});
    	        res.end(result);
	        } else {
                //hack to restart phantom if it crashes...for now
                var timeoutID = setTimeout(function() {
                    console.log('restarting phantom due to timeout');
                    Phantom.create(function(ph){
                        phantom = ph;
                        phantom.createPage(onPageCreate);
                    });
                }, 5000);

                var onPageCreate = function(page) {
                    clearTimeout(timeoutID); //wont get hit if phantom has crashed

                    page.open(req.url.substr(1), function (status) {
                        if ('fail' === status) {
                            res.writeHead(404);
                            res.end();
                            page.close();
                        } else {
                            setTimeout(function(){
                                page.evaluate(function () {
                                    return document && document.getElementsByTagName('html')[0].outerHTML
                                }, function(documentHTML) {
                                    var matches = documentHTML.match(/<script(?:.*?)>(?:[\S\s]*?)<\/script>/g);

                                    for( var i = 0; matches && i < matches.length; i++) {
                                        documentHTML = documentHTML.replace(matches[i], '');
                                    }
                                    cache.set(req.url.substr(1), documentHTML);
                                    res.writeHead(200, {'Content-Type': 'text/html'});
                                    res.end(documentHTML);
                                    page.close();
                                });
                            }, 50);
                        };
                    });
                };

                phantom.createPage(onPageCreate);
            }
        });

    }).listen(process.env.PORT || 3000);
    console.log('Server running on port ' + (process.env.PORT || 3000));
});<commit_msg>added if check for when caching is turned off<commit_after>
var Phantom = require('phantom')
  , http = require('http')
  , cache_manager = require('cache-manager')
  , cache;

if (process.argv[2] != null && (process.argv[2] == '-c' || process.argv[2] == '--cache')) {
    console.log('Prerender started with caching turned ON');
    cache = cache_manager.caching({
        store: 'memory', max: 100, ttl: 60/*seconds*/
    });
}

var testCache = function(url, cb) {
    if (!cache) {
        cb(null, null);
    }
    else {
	    cache.get(url, cb);
    }
}

Phantom.create({
    binary: require('phantomjs').path
}, function(phantom) {

    http.createServer(function (req, res) {
        console.log('getting', req.url);

	    testCache(req.url.substr(1), function (err, result) {
	        if (!err && result != null) {
    	        res.writeHead(200, {'Content-Type': 'text/html'});
    	        res.end(result);
	        } else {
                //hack to restart phantom if it crashes...for now
                var timeoutID = setTimeout(function() {
                    console.log('restarting phantom due to timeout');
                    Phantom.create(function(ph){
                        phantom = ph;
                        phantom.createPage(onPageCreate);
                    });
                }, 5000);

                var onPageCreate = function(page) {
                    clearTimeout(timeoutID); //wont get hit if phantom has crashed

                    page.open(req.url.substr(1), function (status) {
                        if ('fail' === status) {
                            res.writeHead(404);
                            res.end();
                            page.close();
                        } else {
                            setTimeout(function(){
                                page.evaluate(function () {
                                    return document && document.getElementsByTagName('html')[0].outerHTML
                                }, function(documentHTML) {
                                    var matches = documentHTML.match(/<script(?:.*?)>(?:[\S\s]*?)<\/script>/g);

                                    for( var i = 0; matches && i < matches.length; i++) {
                                        documentHTML = documentHTML.replace(matches[i], '');
                                    }
                                    if(cache) cache.set(req.url.substr(1), documentHTML);
                                    res.writeHead(200, {'Content-Type': 'text/html'});
                                    res.end(documentHTML);
                                    page.close();
                                });
                            }, 50);
                        };
                    });
                };

                phantom.createPage(onPageCreate);
            }
        });

    }).listen(process.env.PORT || 3000);
    console.log('Server running on port ' + (process.env.PORT || 3000));
});<|endoftext|>


<commit_before>// Module to control application life.
var app = require('app');

// Module to create native browser window.
var BrowserWindow = require('browser-window');

var mainWindow = null;

// Quit when all windows are closed.
app.on('window-all-closed', function () {
  if (process.platform != 'darwin') {
    app.quit();
  }
});

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', function () {
  // Create the browser window.
  mainWindow = new BrowserWindow({ width: 800, height: 600 });

  // and load the index.html of the app.
  mainWindow.loadUrl('file://' + __dirname + '/index.html');

  // Open the devtools.
  // mainWindow.openDevTools();

  // Emitted when the window is closed.
  mainWindow.on('closed', function () {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null;
  });
});<commit_msg>change size window<commit_after>// Module to control application life.
var app = require('app');

// Module to create native browser window.
var BrowserWindow = require('browser-window');

var mainWindow = null;

// Quit when all windows are closed.
app.on('window-all-closed', function () {
  if (process.platform != 'darwin') {
    app.quit();
  }
});

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', function () {
  // Create the browser window.
  mainWindow = new BrowserWindow({ width: 610, height: 190 });

  // and load the index.html of the app.
  mainWindow.loadUrl('file://' + __dirname + '/index.html');

  // Open the devtools.
  // mainWindow.openDevTools();

  // Emitted when the window is closed.
  mainWindow.on('closed', function () {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null;
  });
});<|endoftext|>


<commit_before>var htmlparser = require("htmlparser2");
var entities = require("entities");

// References to the full namespace to they get packaged for device builds
var references = [
  Ti.UI.AttributedString
];

var ns = Ti.UI;

if (parseInt(Ti.version.split('.')[0], 10) < 4) {
  references.push(Ti.UI.iOS.AttributedString);
  ns = Ti.UI.iOS;
}

var ios = Ti.Platform.name === 'iPhone OS';

// Custom matcher is a function to enable parsing of extra node types.
// @return Object
// @property parameters Object
// @property continue Boolean
// @example
//   function(node, parameters, outerFont, offset, length, ns) {
//     if (node.type === 'tag' && node.name && node.name === 'h1') {
//       parameters.attributes.unshift({
//         type: ns.ATTRIBUTE_FOREGROUND_COLOR,
//         value: Alloy.CFG.h1Color,
//         range: [offset, length]
//       });
//       parameters.attributes.unshift({
//         type: ns.ATTRIBUTE_FONT,
//         value: {
//           fontSize: Alloy.CFG.h1FontSize,
//           fontFamily: Alloy.CFG.h1FontFamily
//         },
//         range: [offset, length]
//       });
//     }
//     return {
//       parameters: parameters,
//       continue: true
//     };
//   }
function walker(node, parameters, outerFont, customMatcher) {

  if (node.type === 'text') {
    parameters.text += entities.decodeHTML(node.data);

  } else if (node.type === 'tag' && node.children) {
    var innerFont;

    // clone font property from wrapping tags
    if (outerFont) {
      innerFont = {};
      outerFont.fontWeight && (innerFont.fontWeight = outerFont.fontWeight);
      outerFont.fontFamily && (innerFont.fontFamily = outerFont.fontFamily);
      outerFont.fontSize && (innerFont.fontSize = outerFont.fontSize);
    }

    // override font properties from this tag
    if (node.name === 'strong' || node.name === 'b') {
      innerFont || (innerFont = {});
      innerFont.fontWeight = 'bold';

    } else if (node.name === 'font' && node.attribs) {

      if (node.attribs.face) {
        innerFont || (innerFont = {});
        innerFont.fontFamily = node.attribs.face;
      }

      if (node.attribs.size) {
        innerFont || (innerFont = {});
        innerFont.fontSize = node.attribs.size;
      }
    }

    // save length before children
    var offset = parameters.text.length;

    // walk children
    node.children.forEach(function onEach(child) {
      parameters = walker(child, parameters, innerFont, customMatcher);
    });

    // calculate length of (grant)children text nodes
    var length = parameters.text.length - offset;

    if (typeof customMatcher === 'function') {
      var customMatch = customMatcher(node, parameters, outerFont, offset, length, ns);

      if (customMatch.parameters === undefined || customMatch.continue === undefined) {
        throw new Error('customMatcher should return an object with parameters and continue properties defined');
      }

      parameters = customMatch.parameters;

      if (customMatch.continue === false) {
        return parameters;
      }
    }

    // only apply attributes if we wrap text
    if (length > 0) {

      if (node.name === 'a' && node.attribs && node.attribs.href) {
        parameters.attributes.unshift({
          type: ns.ATTRIBUTE_LINK,
          value: node.attribs.href,
          range: [offset, length]
        });

      } else if (node.name === 'u') {
        parameters.attributes.unshift({
          type: ns.ATTRIBUTE_UNDERLINES_STYLE,
          value: ios ? ns.ATTRIBUTE_UNDERLINE_STYLE_SINGLE : undefined,
          range: [offset, length]
        });

      } else if (ios && (node.name === 'i' || node.name === 'em')) {
        parameters.attributes.unshift({
          type: ns.ATTRIBUTE_OBLIQUENESS,
          value: 0.25,
          range: [offset, length]
        });

      } else if (node.name === 'strike' || node.name === 'del' || node.name === 's') {
        parameters.attributes.unshift({
          type: ns.ATTRIBUTE_STRIKETHROUGH_STYLE,
          value: ios ? ns.ATTRIBUTE_UNDERLINE_STYLE_SINGLE : undefined,
          range: [offset, length]
        });

      } else if (ios && node.name === 'effect') {
        parameters.attributes.unshift({
          type: ns.ATTRIBUTE_TEXT_EFFECT,
          value: ns.ATTRIBUTE_LETTERPRESS_STYLE,
          range: [offset, length]
        });

      } else if (ios && node.name === 'kern' && node.attribs && node.attribs.value) {
        parameters.attributes.unshift({
          type: ns.ATTRIBUTE_KERN,
          value: node.attribs.value,
          range: [offset, length]
        });

      } else if (ios && node.name === 'expansion' && node.attribs && node.attribs.value) {
        parameters.attributes.unshift({
          type: ns.ATTRIBUTE_EXPANSION,
          value: node.attribs.value,
          range: [offset, length]
        });

      } else if (node.name === 'font' && node.attribs && node.attribs.color) {
        parameters.attributes.unshift({
          type: ns.ATTRIBUTE_FOREGROUND_COLOR,
          value: node.attribs.color,
          range: [offset, length]
        });
      }
    }

    // if we have a font to set
    if (innerFont) {
      parameters.attributes.unshift({
        type: ns.ATTRIBUTE_FONT,
        value: innerFont,
        range: [offset, length]
      });
    }
  }

  return parameters;
}

module.exports = function(html, callback, customMatcher) {
  var parser = new htmlparser.Parser(new htmlparser.DomHandler(function(error, dom) {

    if (error) {
      callback(error);

    } else {

      var parameters = walker({
        type: 'tag',
        children: dom
      }, {
        text: '',
        attributes: []
    }, null, customMatcher);

      var attr = ns.createAttributedString(parameters);

      callback(null, attr);
    }
  }));

  // remove newlines
  html = html.replace(/[\r\n]+/gm, ' ').replace(/\s+/g, ' ');

  // replace
 with newlines
  html = html.replace(/
]*>/gm, '\n');

  parser.parseComplete(html);

};
<commit_msg>fix check for iOS to support iOS 10<commit_after>var htmlparser = require("htmlparser2");
var entities = require("entities");

// References to the full namespace to they get packaged for device builds
var references = [
  Ti.UI.AttributedString
];

var ns = Ti.UI;

if (parseInt(Ti.version.split('.')[0], 10) < 4) {
  references.push(Ti.UI.iOS.AttributedString);
  ns = Ti.UI.iOS;
}

var ios = Ti.Platform.name === 'iPhone OS' || Ti.Platform.name === 'iOS';

// Custom matcher is a function to enable parsing of extra node types.
// @return Object
// @property parameters Object
// @property continue Boolean
// @example
//   function(node, parameters, outerFont, offset, length, ns) {
//     if (node.type === 'tag' && node.name && node.name === 'h1') {
//       parameters.attributes.unshift({
//         type: ns.ATTRIBUTE_FOREGROUND_COLOR,
//         value: Alloy.CFG.h1Color,
//         range: [offset, length]
//       });
//       parameters.attributes.unshift({
//         type: ns.ATTRIBUTE_FONT,
//         value: {
//           fontSize: Alloy.CFG.h1FontSize,
//           fontFamily: Alloy.CFG.h1FontFamily
//         },
//         range: [offset, length]
//       });
//     }
//     return {
//       parameters: parameters,
//       continue: true
//     };
//   }
function walker(node, parameters, outerFont, customMatcher) {

  if (node.type === 'text') {
    parameters.text += entities.decodeHTML(node.data);

  } else if (node.type === 'tag' && node.children) {
    var innerFont;

    // clone font property from wrapping tags
    if (outerFont) {
      innerFont = {};
      outerFont.fontWeight && (innerFont.fontWeight = outerFont.fontWeight);
      outerFont.fontFamily && (innerFont.fontFamily = outerFont.fontFamily);
      outerFont.fontSize && (innerFont.fontSize = outerFont.fontSize);
    }

    // override font properties from this tag
    if (node.name === 'strong' || node.name === 'b') {
      innerFont || (innerFont = {});
      innerFont.fontWeight = 'bold';

    } else if (node.name === 'font' && node.attribs) {

      if (node.attribs.face) {
        innerFont || (innerFont = {});
        innerFont.fontFamily = node.attribs.face;
      }

      if (node.attribs.size) {
        innerFont || (innerFont = {});
        innerFont.fontSize = node.attribs.size;
      }
    }

    // save length before children
    var offset = parameters.text.length;

    // walk children
    node.children.forEach(function onEach(child) {
      parameters = walker(child, parameters, innerFont, customMatcher);
    });

    // calculate length of (grant)children text nodes
    var length = parameters.text.length - offset;

    if (typeof customMatcher === 'function') {
      var customMatch = customMatcher(node, parameters, outerFont, offset, length, ns);

      if (customMatch.parameters === undefined || customMatch.continue === undefined) {
        throw new Error('customMatcher should return an object with parameters and continue properties defined');
      }

      parameters = customMatch.parameters;

      if (customMatch.continue === false) {
        return parameters;
      }
    }

    // only apply attributes if we wrap text
    if (length > 0) {

      if (node.name === 'a' && node.attribs && node.attribs.href) {
        parameters.attributes.unshift({
          type: ns.ATTRIBUTE_LINK,
          value: node.attribs.href,
          range: [offset, length]
        });

      } else if (node.name === 'u') {
        parameters.attributes.unshift({
          type: ns.ATTRIBUTE_UNDERLINES_STYLE,
          value: ios ? ns.ATTRIBUTE_UNDERLINE_STYLE_SINGLE : undefined,
          range: [offset, length]
        });

      } else if (ios && (node.name === 'i' || node.name === 'em')) {
        parameters.attributes.unshift({
          type: ns.ATTRIBUTE_OBLIQUENESS,
          value: 0.25,
          range: [offset, length]
        });

      } else if (node.name === 'strike' || node.name === 'del' || node.name === 's') {
        parameters.attributes.unshift({
          type: ns.ATTRIBUTE_STRIKETHROUGH_STYLE,
          value: ios ? ns.ATTRIBUTE_UNDERLINE_STYLE_SINGLE : undefined,
          range: [offset, length]
        });

      } else if (ios && node.name === 'effect') {
        parameters.attributes.unshift({
          type: ns.ATTRIBUTE_TEXT_EFFECT,
          value: ns.ATTRIBUTE_LETTERPRESS_STYLE,
          range: [offset, length]
        });

      } else if (ios && node.name === 'kern' && node.attribs && node.attribs.value) {
        parameters.attributes.unshift({
          type: ns.ATTRIBUTE_KERN,
          value: node.attribs.value,
          range: [offset, length]
        });

      } else if (ios && node.name === 'expansion' && node.attribs && node.attribs.value) {
        parameters.attributes.unshift({
          type: ns.ATTRIBUTE_EXPANSION,
          value: node.attribs.value,
          range: [offset, length]
        });

      } else if (node.name === 'font' && node.attribs && node.attribs.color) {
        parameters.attributes.unshift({
          type: ns.ATTRIBUTE_FOREGROUND_COLOR,
          value: node.attribs.color,
          range: [offset, length]
        });
      }
    }

    // if we have a font to set
    if (innerFont) {
      parameters.attributes.unshift({
        type: ns.ATTRIBUTE_FONT,
        value: innerFont,
        range: [offset, length]
      });
    }
  }

  return parameters;
}

module.exports = function(html, callback, customMatcher) {
  var parser = new htmlparser.Parser(new htmlparser.DomHandler(function(error, dom) {

    if (error) {
      callback(error);

    } else {

      var parameters = walker({
        type: 'tag',
        children: dom
      }, {
        text: '',
        attributes: []
    }, null, customMatcher);

      var attr = ns.createAttributedString(parameters);

      callback(null, attr);
    }
  }));

  // remove newlines
  html = html.replace(/[\r\n]+/gm, ' ').replace(/\s+/g, ' ');

  // replace
 with newlines
  html = html.replace(/
]*>/gm, '\n');

  parser.parseComplete(html);

};
<|endoftext|>


<commit_before>import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import iView from 'iview'
import axios from 'axios'
import VueI18n from 'vue-i18n'
import numeral from 'numeral'

import 'iview/dist/styles/iview.css'
import en_US from 'iview/dist/locale/en-US'
import zh_CN from 'iview/dist/locale/zh-CN'
import http from './common/http'
import { getInitConfig } from './common/native'

Vue.use(VueI18n)
Vue.use(iView)

Vue.config.productionTip = false

// Setting i18n
const i18n = new VueI18n({
  locale: 'zh-CN',
  messages: {
    'en-US': Object.assign(require('./i18n/en-US').default, en_US),
    'zh-CN': Object.assign(require('./i18n/zh-CN').default, zh_CN)
  }
})

Vue.prototype.$noSpinHttp = axios.create()
Vue.prototype.$http = http.build()
Vue.prototype.$http.interceptors.response.use(
  response => {
    return response
  },
  error => {
    if (!error.response) {
      Vue.prototype.$Message.error(i18n.t('alert.refused'))
    } else if (error.response.status == 400) {
      let i18nKey =
        'alert["' +
        new URL(error.config.url).pathname +
        '"]' +
        '.' +
        error.config.method +
        '.' +
        error.response.data.code
      Vue.prototype.$Message.error(i18n.t(i18nKey))
    } else if (error.response.status == 404) {
      Vue.prototype.$Message.error(i18n.t('alert.notFound'))
    } else if (error.response.status == 504) {
      Vue.prototype.$Message.error(i18n.t('alert.timeout'))
    } else {
      Vue.prototype.$Message.error(i18n.t('alert.error'))
    }
    return Promise.reject(error)
  }
)

Vue.prototype.$numeral = numeral
Date.prototype.format = function(fmt) {
  var o = {
    'M+': this.getMonth() + 1, // Month
    'd+': this.getDate(), // Day
    'h+': this.getHours(), // Hour
    'm+': this.getMinutes(), // Minute
    's+': this.getSeconds(), // Second
    'q+': Math.floor((this.getMonth() + 3) / 3), // Quarter
    S: this.getMilliseconds() // Millisecond
  }
  if (/(y+)/.test(fmt)) {
    fmt = fmt.replace(
      RegExp.$1,
      (this.getFullYear() + '').substr(4 - RegExp.$1.length)
    )
  }
  for (var k in o) {
    if (new RegExp('(' + k + ')').test(fmt)) {
      fmt = fmt.replace(
        RegExp.$1,
        RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)
      )
    }
  }
  return fmt
}

Promise.prototype.finally = function(callback) {
  let P = this.constructor
  return this.then(
    value => P.resolve(callback()).then(() => value),
    reason =>
      P.resolve(callback()).then(() => {
        throw reason
      })
  )
}

// Change the page according to the routing changes title
router.beforeEach((to, from, next) => {
  if (to.meta.title) {
    document.title = `Proxyee Down-${to.meta.title}`
  }
  next()
})

// Get client configuration information
getInitConfig().then(result => {
  Vue.prototype.$config = result
  // Set default language
  i18n.locale = result.locale
  new Vue({
    router,
    store,
    i18n,
    render: h => h(App)
  }).$mount('#app')
})
<commit_msg>添加繁体字支持<commit_after>import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import iView from 'iview'
import axios from 'axios'
import VueI18n from 'vue-i18n'
import numeral from 'numeral'

import 'iview/dist/styles/iview.css'
import en_US from 'iview/dist/locale/en-US'
import zh_CN from 'iview/dist/locale/zh-CN'
import zh_TW from 'iview/dist/locale/zh-TW'
import http from './common/http'
import { getInitConfig } from './common/native'

Vue.use(VueI18n)
Vue.use(iView)

Vue.config.productionTip = false

// Setting i18n
const i18n = new VueI18n({
  locale: 'zh-CN',
  messages: {
    'en-US': Object.assign(require('./i18n/en-US').default, en_US),
    'zh-CN': Object.assign(require('./i18n/zh-CN').default, zh_CN),
    'zh-TW': Object.assign(require('./i18n/zh-TW').default, zh_TW)
  }
})

Vue.prototype.$noSpinHttp = axios.create()
Vue.prototype.$http = http.build()
Vue.prototype.$http.interceptors.response.use(
  response => {
    return response
  },
  error => {
    if (!error.response) {
      Vue.prototype.$Message.error(i18n.t('alert.refused'))
    } else if (error.response.status == 400) {
      let i18nKey =
        'alert["' +
        new URL(error.config.url).pathname +
        '"]' +
        '.' +
        error.config.method +
        '.' +
        error.response.data.code
      Vue.prototype.$Message.error(i18n.t(i18nKey))
    } else if (error.response.status == 404) {
      Vue.prototype.$Message.error(i18n.t('alert.notFound'))
    } else if (error.response.status == 504) {
      Vue.prototype.$Message.error(i18n.t('alert.timeout'))
    } else {
      Vue.prototype.$Message.error(i18n.t('alert.error'))
    }
    return Promise.reject(error)
  }
)

Vue.prototype.$numeral = numeral
Date.prototype.format = function(fmt) {
  var o = {
    'M+': this.getMonth() + 1, // Month
    'd+': this.getDate(), // Day
    'h+': this.getHours(), // Hour
    'm+': this.getMinutes(), // Minute
    's+': this.getSeconds(), // Second
    'q+': Math.floor((this.getMonth() + 3) / 3), // Quarter
    S: this.getMilliseconds() // Millisecond
  }
  if (/(y+)/.test(fmt)) {
    fmt = fmt.replace(
      RegExp.$1,
      (this.getFullYear() + '').substr(4 - RegExp.$1.length)
    )
  }
  for (var k in o) {
    if (new RegExp('(' + k + ')').test(fmt)) {
      fmt = fmt.replace(
        RegExp.$1,
        RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)
      )
    }
  }
  return fmt
}

Promise.prototype.finally = function(callback) {
  let P = this.constructor
  return this.then(
    value => P.resolve(callback()).then(() => value),
    reason =>
      P.resolve(callback()).then(() => {
        throw reason
      })
  )
}

// Change the page according to the routing changes title
router.beforeEach((to, from, next) => {
  if (to.meta.title) {
    document.title = `Proxyee Down-${to.meta.title}`
  }
  next()
})

// Get client configuration information
getInitConfig().then(result => {
  Vue.prototype.$config = result
  // Set default language
  i18n.locale = result.locale
  new Vue({
    router,
    store,
    i18n,
    render: h => h(App)
  }).$mount('#app')
})
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react';
import {Observable, helpers} from 'rx';
import assign from 'object-assign';
import invariant from 'invariant';

export default {create: Container, fromPromise: Observable.fromPromise, fromStore};

function Container(Component, options) {
	// fragments can return Promise, Observer, Subscription
	// Component :: ReactClass | { pending, success, failure }
	// Promise :: { then }
	// Observer :: { subscribe -> unsubscribe }
	// Subscription :: { getState, subscribe -> { dispose } }
	const {
		fragments = {},
		shouldContainerUpdate = () => true,
		initialVariables = {},
	} = options;

	const componentEnum = enumerate(Component);

	return React.createClass({
		displayName: `${componentEnum.success.displayName || componentEnum.success.name}Container`,

		propTypes: {
			variables: PropTypes.object,
		},

		getInitialState() {
			return {
				status: 'pending',
				fragments: {},
				error: null,
			};
		},

		fetchFragment(fragment, variables, name) {
			const fragmentContainer = fragment(variables);
			const observable = fromEverything(fragmentContainer);

			return observable.map(data => ({[name]: data}));
		},

		fetch(newVariables) {
			const variables = assign({}, initialVariables, newVariables);

			const streams = Object.keys(fragments)
				.map(key => this.fetchFragment(fragments[key], variables, key));

			return Observable.combineLatest(streams)
				.subscribe(
					results => this.setState({
						status: 'success',
						fragments: results.reduce((a, b) => assign(a, b), {}),
					}),
					error => this.setState({
						status: 'failure',
						error: {type: 'FETCH_FAILED', error},
					})
				);
		},

		waitForUpdates() {
			this.setState({status: 'pending', error: null});
			this.disposable.dispose();
		},

		refetch() {
			this.waitForUpdates();
			this.disposable = this.fetch(this.props.variables);
		},

		componentWillMount() {
			this.disposable = this.fetch(this.props.variables);
		},

		componentWillUnmount() {
			this.disposable.dispose();
		},

		componentWillReceiveProps(nextProps) {
			if (shouldContainerUpdate.call(this, nextProps)) {
				this.waitForUpdates();
				this.disposable = this.fetch(nextProps.variables);
			}
		},

		render() {
			const {fragments, error, status} = this.state;
			const onRetry = () => this.refetch();

			switch (status) {
			case 'success':
				return React.createElement(componentEnum.success, assign(fragments, this.props));
			case 'failure':
				return React.createElement(componentEnum.failure, assign({error, onRetry}, this.props));
			case 'pending':
				// falls through
			default:
				return React.createElement(componentEnum.pending, this.props);
			}
		},
	});
}

function EmptyComponent() {
	return null;
}

function enumerate(target) {
	const isEnumerableComponent = typeof target === 'object';

	if (isEnumerableComponent) {
		invariant(isReactComponentEnum(target), 'Success, Failure and Pending should be React components');
		invariant(hasSuccessPoint(target), 'Success component should be specified');
	}

	return isEnumerableComponent ? target : {success: target, pending: EmptyComponent, failure: EmptyComponent};
}

function isReactComponentEnum(target) {
	return ['success', 'failure', 'pending']
		.filter(type => target.hasOwnProperty(type))
		.every(type => typeof target[type] === 'function');
}

function hasSuccessPoint(target) {
	return typeof target.success === 'function';
}

function fromEverything(object) {
	if (helpers.isPromise(object)) {
		return Observable.fromPromise(object);
	}

	// assume that fragmentContainer is Observable by default
	return object;
}

function fromStore(store) {
	return Observable.create(observer => {
		const unsubscribe = store.subscribe(() => observer.onNext(store.getState()));
		return {dispose() { unsubscribe() }};
	}).startWith(store.getState());
}
<commit_msg>used separate methods for different statuses<commit_after>import React, {PropTypes} from 'react';
import {Observable, helpers} from 'rx';
import assign from 'object-assign';
import invariant from 'invariant';

export default {create: Container, fromPromise: Observable.fromPromise, fromStore};

function Container(Component, options) {
	// fragments can return Promise, Observer, Subscription
	// Component :: ReactClass | { pending, success, failure }
	// Promise :: { then }
	// Observer :: { subscribe -> unsubscribe }
	// Subscription :: { getState, subscribe -> { dispose } }
	const {
		fragments = {},
		shouldContainerUpdate = () => true,
		initialVariables = {},
	} = options;

	const componentEnum = enumerate(Component);

	return React.createClass({
		displayName: `${componentEnum.success.displayName || componentEnum.success.name}Container`,

		propTypes: {
			variables: PropTypes.object,
		},

		getInitialState() {
			return {
				status: 'pending',
				fragments: {},
				error: null,
			};
		},

		fetchFragment(fragment, variables, name) {
			const fragmentContainer = fragment(variables);
			const observable = fromEverything(fragmentContainer);

			return observable.map(data => ({[name]: data}));
		},

		success(results) {
			this.setState({
				status: 'success',
				fragments: results.reduce((a, b) => assign(a, b), {}),
			});
		},

		failure(error) {
			this.setState({
				status: 'failure',
				error: {type: 'FETCH_FAILED', error},
			});
		},

		pending() {
			this.setState({
				status: 'pending',
				error: null,
			});

			this.disposable.dispose();
		},

		fetch(newVariables) {
			const variables = assign({}, initialVariables, newVariables);

			const streams = Object.keys(fragments)
				.map(key => this.fetchFragment(fragments[key], variables, key));

			return Observable.combineLatest(streams)
				.subscribe(
					results => this.success(results),
					error => this.failure(error)
				);
		},

		refetch() {
			this.pending();
			this.disposable = this.fetch(this.props.variables);
		},

		componentWillMount() {
			this.disposable = this.fetch(this.props.variables);
		},

		componentWillUnmount() {
			this.disposable.dispose();
		},

		componentWillReceiveProps(nextProps) {
			if (shouldContainerUpdate.call(this, nextProps)) {
				this.pending();
				this.disposable = this.fetch(nextProps.variables);
			}
		},

		render() {
			const {fragments, error, status} = this.state;
			const onRetry = () => this.refetch();

			switch (status) {
			case 'success':
				return React.createElement(componentEnum.success, assign(fragments, this.props));
			case 'failure':
				return React.createElement(componentEnum.failure, assign({error, onRetry}, this.props));
			case 'pending':
				// falls through
			default:
				// TODO: pass onCancel
				return React.createElement(componentEnum.pending, this.props);
			}
		},
	});
}

function EmptyComponent() {
	return null;
}

function enumerate(target) {
	const isEnumerableComponent = typeof target === 'object';

	if (isEnumerableComponent) {
		invariant(isReactComponentEnum(target), 'Success, Failure and Pending should be React components');
		invariant(hasSuccessPoint(target), 'Success component should be specified');
	}

	return isEnumerableComponent ? target : {success: target, pending: EmptyComponent, failure: EmptyComponent};
}

function isReactComponentEnum(target) {
	return ['success', 'failure', 'pending']
		.filter(type => target.hasOwnProperty(type))
		.every(type => typeof target[type] === 'function');
}

function hasSuccessPoint(target) {
	return typeof target.success === 'function';
}

function fromEverything(object) {
	if (helpers.isPromise(object)) {
		return Observable.fromPromise(object);
	}

	// assume that fragmentContainer is Observable by default
	return object;
}

function fromStore(store) {
	return Observable.create(observer => {
		const unsubscribe = store.subscribe(() => observer.onNext(store.getState()));
		return {dispose() { unsubscribe() }};
	}).startWith(store.getState());
}
<|endoftext|>


<commit_before>var exec        = require('child_process').exec;
var colors      = require("colors");
var request     = require("request");
var rl          = require("readline");
var formatter   = require("./formatter");

function handleInput(question, options, callback) {
    var readline = require('readline');

    var rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });

    rl.question(question + " ", function (answer) {
        rl.close();
        if (answer === 'g')
            getGames();
        else if (answer === 'f')
            getFeatured();
        else if (answer === 'm')
            showMainMenu();
        else
            callback(options[parseInt(answer)][0]);
    });
}

function getFeatured() {
    request({
        url: "https://api.twitch.tv/kraken/streams/featured"
    }, function(err, response, body) {
        var json = JSON.parse(body);
        var featured = json.featured;
        var streamers = [];
        for (var i = 0; i < featured.length; i++) {
            var stream = featured[i].stream;
            var channel = stream.channel;
            var channel_name = channel.display_name;
            var channel_status = channel.status.slice(0, 40);
            var viewers = stream.viewers;

            streamers[i] = [channel_name, channel_status, viewers];
        }
        var col = [colors.red, colors.lightgrey, colors.blue, colors.yellow];
        formatter.table(streamers, {"colors":col, "delimeter_color":colors.red});
        openStreams(streamers);
    });

}

function getGames() {
    var games = [];

    request({
        url: "https://api.twitch.tv/kraken/games/top?limit=25&offset=0",
    }, function (err, response, body) {
        var json = JSON.parse(body);
        var top = json.top;
        for (var i = 0; i < top.length; i++) {
            var info = top[i];
            var game_info = info.game;
            var game_name = game_info.name;
            var game_viewers = info.viewers;

            games[i] = [game_name, game_viewers];
        }
        var col = [colors.red, colors.lightgrey, colors.yellow];
        formatter.table(games, {"colors":col, "delimeter_color":colors.red});
        openGame(games);
    });
}

function getStreamsForGame(gamename) {
    request({
        url: "https://api.twitch.tv/kraken/streams?game="+gamename
    }, function(err, response, body) {
        var json = JSON.parse(body);
        var streams = json.streams;
        var streamers = [];
        for (var i = 0; i < streams.length; i++) {
            var channel = streams[i].channel;
            var channel_name = channel.display_name;
            var channel_status = channel.status.slice(0, 40);
            var viewers = streams[i].viewers;

            streamers[i] = [channel_name, channel_status, viewers];
        }
        var col = [colors.red, colors.lightgrey, colors.blue, colors.yellow];
        formatter.table(streamers, {"colors":col, "delimeter_color":colors.red});
        openStreams(streamers);
    });
}

function showMainMenu() {
    var games = "Games (g)";
    var featured = "Featured (f)";

    var col = [colors.red, colors.lightgrey];
    var options = [[games], [featured]];
    formatter.table(options, {colors: col});
    handleInput("Open Menu (m)?", options, function(answer) {
        if (answer === games)
            getGames();
        else if (answer === featured)
            getFeatured();
        else
            showMainMenu();
    });
}

function openStreams(streams) {
    cb = function(stream) {
        exec('mpv --fs http://www.twitch.tv/' + stream, function(err, out, code) {
            handleInput("Open stream number?", streams, cb);
        });
    };
    handleInput("Open stream number?", streams, cb);
}

function openGame(games) {
    handleInput("Open game number?", games, getStreamsForGame);
}

showMainMenu();
<commit_msg>Changed from child_process.exec => child_process.spawn. This fixes a bug where the stream crashed after a short amount of time<commit_after>var spawn       = require('child_process').spawn;
var colors      = require("colors");
var request     = require("request");
var rl          = require("readline");
var formatter   = require("./formatter");

function handleInput(question, options, callback) {
    var readline = require('readline');

    var rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });

    rl.question(question + " ", function (answer) {
        rl.close();
        if (answer === 'g')
            getGames();
        else if (answer === 'f')
            getFeatured();
        else if (answer === 'm')
            showMainMenu();
        else
            callback(options[parseInt(answer)][0]);
    });
}

function getFeatured() {
    request({
        url: "https://api.twitch.tv/kraken/streams/featured"
    }, function(err, response, body) {
        var json = JSON.parse(body);
        var featured = json.featured;
        var streamers = [];
        for (var i = 0; i < featured.length; i++) {
            var stream = featured[i].stream;
            var channel = stream.channel;
            var channel_name = channel.display_name;
            var channel_status = channel.status.slice(0, 40);
            var viewers = stream.viewers;

            streamers[i] = [channel_name, channel_status, viewers];
        }
        var col = [colors.red, colors.lightgrey, colors.blue, colors.yellow];
        formatter.table(streamers, {"colors":col, "delimeter_color":colors.red});
        openStreams(streamers);
    });

}

function getGames() {
    var games = [];

    request({
        url: "https://api.twitch.tv/kraken/games/top?limit=25&offset=0",
    }, function (err, response, body) {
        var json = JSON.parse(body);
        var top = json.top;
        for (var i = 0; i < top.length; i++) {
            var info = top[i];
            var game_info = info.game;
            var game_name = game_info.name;
            var game_viewers = info.viewers;

            games[i] = [game_name, game_viewers];
        }
        var col = [colors.red, colors.lightgrey, colors.yellow];
        formatter.table(games, {"colors":col, "delimeter_color":colors.red});
        openGame(games);
    });
}

function getStreamsForGame(gamename) {
    request({
        url: "https://api.twitch.tv/kraken/streams?game="+gamename
    }, function(err, response, body) {
        var json = JSON.parse(body);
        var streams = json.streams;
        var streamers = [];
        for (var i = 0; i < streams.length; i++) {
            var channel = streams[i].channel;
            var channel_name = channel.display_name;
            var channel_status = channel.status.slice(0, 40);
            var viewers = streams[i].viewers;

            streamers[i] = [channel_name, channel_status, viewers];
        }
        var col = [colors.red, colors.lightgrey, colors.blue, colors.yellow];
        formatter.table(streamers, {"colors":col, "delimeter_color":colors.red});
        openStreams(streamers);
    });
}

function showMainMenu() {
    var games = "Games (g)";
    var featured = "Featured (f)";

    var col = [colors.red, colors.lightgrey];
    var options = [[games], [featured]];
    formatter.table(options, {colors: col});
    handleInput("Open Menu (m)?", options, function(answer) {
        if (answer === games)
            getGames();
        else if (answer === featured)
            getFeatured();
        else
            showMainMenu();
    });
}

function openStreams(streams) {
    cb = function(stream) {
        const mpv = spawn('mpv', ['--fs', 'http://www.twitch.tv/' + stream]);
        mpv.on('close', (code) => {
            handleInput("Open stream number?", streams, cb);
        });
    };
    handleInput("Open stream number?", streams, cb);
}

function openGame(games) {
    handleInput("Open game number?", games, getStreamsForGame);
}

showMainMenu();
<|endoftext|>


<commit_before>'use strict';

var utils = require('loader-utils');
var sass = require('node-sass');
var path = require('path');
var os = require('os');
var fs = require('fs');
var async = require('async');

// A typical sass error looks like this
var SassError = {
    message: 'invalid property name',
    column: 14,
    line: 1,
    file: 'stdin',
    status: 1
};

// libsass uses this precedence when importing files without extension
var extPrecedence = ['.scss', '.sass', '.css'];
var matchCss = /\.css$/;

// This queue makes sure node-sass leaves one thread available for executing
// fs tasks when running the custom importer code.
// This can be removed as soon as node-sass implements a fix for this.
var threadPoolSize = process.env.UV_THREADPOOL_SIZE || 4;
var asyncSassJobQueue = async.queue(sass.render, threadPoolSize - 1);

/**
 * The sass-loader makes node-sass available to webpack modules.
 *
 * @param {string} content
 * @returns {string}
 */
module.exports = function (content) {
    var callback = this.async();
    var isSync = typeof callback !== 'function';
    var self = this;
    var resourcePath = this.resourcePath;
    var result;
    var opt;

    /**
     * Enhances the sass error with additional information about what actually went wrong.
     *
     * @param {SassError} err
     */
    function formatSassError(err) {
        var msg = err.message;

        if (err.file === 'stdin') {
            err.file = resourcePath;
        }
        // node-sass returns UNIX-style paths
        err.file = path.normalize(err.file);

        // The 'Current dir' hint of node-sass does not help us, we're providing
        // additional information by reading the err.file property
        msg = msg.replace(/\s*Current dir:\s*/, '');

        err.message = getFileExcerptIfPossible(err) +
            msg.charAt(0).toUpperCase() + msg.slice(1) + os.EOL +
            '      in ' + err.file + ' (line ' + err.line + ', column ' + err.column + ')';

        // Instruct webpack to hide the JS stack from the console
        // Usually you're only interested in the SASS stack in this case.
        err.hideStack = true;
    }

    /**
     * Returns an importer that uses webpack's resolving algorithm.
     *
     * It's important that the returned function has the correct number of arguments
     * (based on whether the call is sync or async) because otherwise node-sass doesn't exit.
     *
     * @returns {function}
     */
    function getWebpackImporter() {
        if (isSync) {
            return function syncWebpackImporter(url, fileContext) {
                var dirContext;
                var request;

                // node-sass returns UNIX-style paths
                fileContext = path.normalize(fileContext);
                request = utils.urlToRequest(url, opt.root);
                dirContext = fileToDirContext(fileContext);

                return resolveSync(dirContext, url, getImportsToResolve(request));
            };
        }
        return function asyncWebpackImporter(url, fileContext, done) {
            var dirContext;
            var request;

            // node-sass returns UNIX-style paths
            fileContext = path.normalize(fileContext);
            request = utils.urlToRequest(url, opt.root);
            dirContext = fileToDirContext(fileContext);

            resolve(dirContext, url, getImportsToResolve(request), done);
        };
    }

    /**
     * Tries to resolve the first url of importsToResolve. If that resolve fails, the next url is tried.
     * If all imports fail, the import is passed to libsass which also take includePaths into account.
     *
     * @param {string} dirContext
     * @param {string} originalImport
     * @param {Array} importsToResolve
     * @returns {object}
     */
    function resolveSync(dirContext, originalImport, importsToResolve) {
        var importToResolve = importsToResolve.shift();
        var resolvedFilename;

        if (!importToResolve) {
            // No import possibilities left. Let's pass that one back to libsass...
            return {
                file: originalImport
            };
        }

        try {
            resolvedFilename = self.resolveSync(dirContext, importToResolve);
            // Add the resolvedFilename as dependency. Although we're also using stats.includedFiles, this might come
            // in handy when an error occurs. In this case, we don't get stats.includedFiles from node-sass.
            addNormalizedDependency(resolvedFilename);
            // By removing the CSS file extension, we trigger node-sass to include the CSS file instead of just linking it.
            resolvedFilename = resolvedFilename.replace(matchCss, '');
            return {
                file: resolvedFilename
            };
        } catch (err) {
            return resolveSync(dirContext, originalImport, importsToResolve);
        }
    }

    /**
     * Tries to resolve the first url of importsToResolve. If that resolve fails, the next url is tried.
     * If all imports fail, the import is passed to libsass which also take includePaths into account.
     *
     * @param {string} dirContext
     * @param {string} originalImport
     * @param {Array} importsToResolve
     * @param {function} done
     */
    function resolve(dirContext, originalImport, importsToResolve, done) {
        var importToResolve = importsToResolve.shift();

        if (!importToResolve) {
            // No import possibilities left. Let's pass that one back to libsass...
            done({
                file: originalImport
            });
            return;
        }

        self.resolve(dirContext, importToResolve, function onWebpackResolve(err, resolvedFilename) {
            if (err) {
                resolve(dirContext, originalImport, importsToResolve, done);
                return;
            }
            // Add the resolvedFilename as dependency. Although we're also using stats.includedFiles, this might come
            // in handy when an error occurs. In this case, we don't get stats.includedFiles from node-sass.
            addNormalizedDependency(resolvedFilename);
            // By removing the CSS file extension, we trigger node-sass to include the CSS file instead of just linking it.
            resolvedFilename = resolvedFilename.replace(matchCss, '');

            // Use self.loadModule() before calling done() to make imported files available to
            // other webpack tools like postLoaders etc.?

            done({
                file: resolvedFilename.replace(matchCss, '')
            });
        });
    }

    function fileToDirContext(fileContext) {
        // The first file is 'stdin' when we're using the data option
        if (fileContext === 'stdin') {
            fileContext = resourcePath;
        }
        return path.dirname(fileContext);
    }

    // When files have been imported via the includePaths-option, these files need to be
    // introduced to webpack in order to make them watchable.
    function addIncludedFilesToWebpack(includedFiles) {
        includedFiles.forEach(addNormalizedDependency);
    }

    function addNormalizedDependency(file) {
        // node-sass returns UNIX-style paths
        self.dependency(path.normalize(file));
    }

    this.cacheable();

    opt = utils.parseQuery(this.query);
    opt.data = content;

    // Skip empty files, otherwise it will stop webpack, see issue #21
    if (opt.data.trim() === '') {
        return isSync ? content : callback(null, content);
    }

    // opt.outputStyle
    if (!opt.outputStyle && this.minimize) {
        opt.outputStyle = 'compressed';
    }

    // opt.sourceMap
    // Not using the `this.sourceMap` flag because css source maps are different
    // @see https://github.com/webpack/css-loader/pull/40
    if (opt.sourceMap) {
        // deliberately overriding the sourceMap option
        // this value is (currently) ignored by libsass when using the data input instead of file input
        // however, it is still necessary for correct relative paths in result.map.sources
        opt.sourceMap = this.options.output.path + '/sass.map';
        opt.omitSourceMapUrl = true;

        // If sourceMapContents option is not set, set it to true otherwise maps will be empty/null
        // when exported by webpack-extract-text-plugin.
        if ('sourceMapContents' in opt === false) {
            opt.sourceMapContents = true;
        }
    }

    // indentedSyntax is a boolean flag
    opt.indentedSyntax = Boolean(opt.indentedSyntax);

    opt.importer = getWebpackImporter();

    // start the actual rendering
    if (isSync) {
        try {
            result = sass.renderSync(opt);
            addIncludedFilesToWebpack(result.stats.includedFiles);
            return result.css.toString();
        } catch (err) {
            formatSassError(err);
            err.file && this.dependency(err.file);
            throw err;
        }
    }

    asyncSassJobQueue.push(opt, function onRender(err, result) {
        if (err) {
            formatSassError(err);
            err.file && self.dependency(err.file);
            callback(err);
            return;
        }

        if (result.map && result.map !== '{}') {
            result.map = JSON.parse(result.map);
            result.map.file = resourcePath;
            // The first source is 'stdin' according to libsass because we've used the data input
            // Now let's override that value with the correct relative path
            result.map.sources[0] = path.relative(self.options.output.path, resourcePath);
        } else {
            result.map = null;
        }

        addIncludedFilesToWebpack(result.stats.includedFiles);
        callback(null, result.css.toString(), result.map);
    });
};

/**
 * Tries to get an excerpt of the file where the error happened.
 * Uses err.line and err.column.
 *
 * Returns an empty string if the excerpt could not be retrieved.
 *
 * @param {SassError} err
 * @returns {string}
 */
function getFileExcerptIfPossible(err) {
    var content;

    try {
        content = fs.readFileSync(err.file, 'utf8');

        return os.EOL +
            content.split(os.EOL)[err.line - 1] + os.EOL +
            new Array(err.column - 1).join(' ') + '^' + os.EOL +
            '      ';
    } catch (err) {
        // If anything goes wrong here, we don't want any errors to be reported to the user
        return '';
    }
}

/**
 * When libsass tries to resolve an import, it uses this "funny" algorithm:
 *
 * - Imports with no file extension:
 *   - Prefer modules starting with '_'
 *   - File extension precedence: .scss, .sass, .css
 * - Imports with file extension:
 *   - If the file is a CSS-file, do not include it all, but just link it via @import url()
 *   - The exact file name must match (no auto-resolving of '_'-modules)
 *
 * Since the sass-loader uses webpack to resolve the modules, we need to simulate that algorithm. This function
 * returns an array of import paths to try.
 *
 * @param {string} originalImport
 * @returns {Array}
 */
function getImportsToResolve(originalImport) {
    var ext = path.extname(originalImport);
    var basename = path.basename(originalImport);
    var dirname = path.dirname(originalImport);
    var startsWithUnderscore = basename.charAt(0) === '_';
    var paths = [];

    function add(file) {
        // No path.sep required here, because imports inside SASS are usually with /
        paths.push(dirname + '/' + file);
    }

    if (originalImport.charAt(0) !== '.') {
        // If true: originalImport is a module import like 'bootstrap-sass...'
        if (dirname === '.') {
            // If true: originalImport is just a module import without a path like 'bootstrap-sass'
            // In this case we don't do that auto-resolving dance at all.
            return [originalImport];
        }
    }
    if (ext) {
        if (ext === '.scss' || ext === '.sass') {
            add(basename);
        }/* else {
            Leave unknown extensions (like .css) untouched
        }*/
    } else {
        if (!startsWithUnderscore) {
            // Prefer modules starting with '_' first
            extPrecedence.forEach(function (ext) {
                add('_' + basename + ext);
            });
        }
        extPrecedence.forEach(function (ext) {
            add(basename + ext);
        });
    }

    return paths;
}
<commit_msg>Prevent error when the node-sass error does not contain a file<commit_after>'use strict';

var utils = require('loader-utils');
var sass = require('node-sass');
var path = require('path');
var os = require('os');
var fs = require('fs');
var async = require('async');

// A typical sass error looks like this
var SassError = {
    message: 'invalid property name',
    column: 14,
    line: 1,
    file: 'stdin',
    status: 1
};

// libsass uses this precedence when importing files without extension
var extPrecedence = ['.scss', '.sass', '.css'];
var matchCss = /\.css$/;

// This queue makes sure node-sass leaves one thread available for executing
// fs tasks when running the custom importer code.
// This can be removed as soon as node-sass implements a fix for this.
var threadPoolSize = process.env.UV_THREADPOOL_SIZE || 4;
var asyncSassJobQueue = async.queue(sass.render, threadPoolSize - 1);

/**
 * The sass-loader makes node-sass available to webpack modules.
 *
 * @param {string} content
 * @returns {string}
 */
module.exports = function (content) {
    var callback = this.async();
    var isSync = typeof callback !== 'function';
    var self = this;
    var resourcePath = this.resourcePath;
    var result;
    var opt;

    /**
     * Enhances the sass error with additional information about what actually went wrong.
     *
     * @param {SassError} err
     */
    function formatSassError(err) {
        // Instruct webpack to hide the JS stack from the console
        // Usually you're only interested in the SASS stack in this case.
        err.hideStack = true;

        // The file property is missing in rare cases.
        // No improvement in the error is possible.
        if (!err.file) {
            return;
        }

        var msg = err.message;

        if (err.file === 'stdin') {
            err.file = resourcePath;
        }
        // node-sass returns UNIX-style paths
        err.file = path.normalize(err.file);

        // The 'Current dir' hint of node-sass does not help us, we're providing
        // additional information by reading the err.file property
        msg = msg.replace(/\s*Current dir:\s*/, '');

        err.message = getFileExcerptIfPossible(err) +
            msg.charAt(0).toUpperCase() + msg.slice(1) + os.EOL +
            '      in ' + err.file + ' (line ' + err.line + ', column ' + err.column + ')';
    }

    /**
     * Returns an importer that uses webpack's resolving algorithm.
     *
     * It's important that the returned function has the correct number of arguments
     * (based on whether the call is sync or async) because otherwise node-sass doesn't exit.
     *
     * @returns {function}
     */
    function getWebpackImporter() {
        if (isSync) {
            return function syncWebpackImporter(url, fileContext) {
                var dirContext;
                var request;

                // node-sass returns UNIX-style paths
                fileContext = path.normalize(fileContext);
                request = utils.urlToRequest(url, opt.root);
                dirContext = fileToDirContext(fileContext);

                return resolveSync(dirContext, url, getImportsToResolve(request));
            };
        }
        return function asyncWebpackImporter(url, fileContext, done) {
            var dirContext;
            var request;

            // node-sass returns UNIX-style paths
            fileContext = path.normalize(fileContext);
            request = utils.urlToRequest(url, opt.root);
            dirContext = fileToDirContext(fileContext);

            resolve(dirContext, url, getImportsToResolve(request), done);
        };
    }

    /**
     * Tries to resolve the first url of importsToResolve. If that resolve fails, the next url is tried.
     * If all imports fail, the import is passed to libsass which also take includePaths into account.
     *
     * @param {string} dirContext
     * @param {string} originalImport
     * @param {Array} importsToResolve
     * @returns {object}
     */
    function resolveSync(dirContext, originalImport, importsToResolve) {
        var importToResolve = importsToResolve.shift();
        var resolvedFilename;

        if (!importToResolve) {
            // No import possibilities left. Let's pass that one back to libsass...
            return {
                file: originalImport
            };
        }

        try {
            resolvedFilename = self.resolveSync(dirContext, importToResolve);
            // Add the resolvedFilename as dependency. Although we're also using stats.includedFiles, this might come
            // in handy when an error occurs. In this case, we don't get stats.includedFiles from node-sass.
            addNormalizedDependency(resolvedFilename);
            // By removing the CSS file extension, we trigger node-sass to include the CSS file instead of just linking it.
            resolvedFilename = resolvedFilename.replace(matchCss, '');
            return {
                file: resolvedFilename
            };
        } catch (err) {
            return resolveSync(dirContext, originalImport, importsToResolve);
        }
    }

    /**
     * Tries to resolve the first url of importsToResolve. If that resolve fails, the next url is tried.
     * If all imports fail, the import is passed to libsass which also take includePaths into account.
     *
     * @param {string} dirContext
     * @param {string} originalImport
     * @param {Array} importsToResolve
     * @param {function} done
     */
    function resolve(dirContext, originalImport, importsToResolve, done) {
        var importToResolve = importsToResolve.shift();

        if (!importToResolve) {
            // No import possibilities left. Let's pass that one back to libsass...
            done({
                file: originalImport
            });
            return;
        }

        self.resolve(dirContext, importToResolve, function onWebpackResolve(err, resolvedFilename) {
            if (err) {
                resolve(dirContext, originalImport, importsToResolve, done);
                return;
            }
            // Add the resolvedFilename as dependency. Although we're also using stats.includedFiles, this might come
            // in handy when an error occurs. In this case, we don't get stats.includedFiles from node-sass.
            addNormalizedDependency(resolvedFilename);
            // By removing the CSS file extension, we trigger node-sass to include the CSS file instead of just linking it.
            resolvedFilename = resolvedFilename.replace(matchCss, '');

            // Use self.loadModule() before calling done() to make imported files available to
            // other webpack tools like postLoaders etc.?

            done({
                file: resolvedFilename.replace(matchCss, '')
            });
        });
    }

    function fileToDirContext(fileContext) {
        // The first file is 'stdin' when we're using the data option
        if (fileContext === 'stdin') {
            fileContext = resourcePath;
        }
        return path.dirname(fileContext);
    }

    // When files have been imported via the includePaths-option, these files need to be
    // introduced to webpack in order to make them watchable.
    function addIncludedFilesToWebpack(includedFiles) {
        includedFiles.forEach(addNormalizedDependency);
    }

    function addNormalizedDependency(file) {
        // node-sass returns UNIX-style paths
        self.dependency(path.normalize(file));
    }

    this.cacheable();

    opt = utils.parseQuery(this.query);
    opt.data = content;

    // Skip empty files, otherwise it will stop webpack, see issue #21
    if (opt.data.trim() === '') {
        return isSync ? content : callback(null, content);
    }

    // opt.outputStyle
    if (!opt.outputStyle && this.minimize) {
        opt.outputStyle = 'compressed';
    }

    // opt.sourceMap
    // Not using the `this.sourceMap` flag because css source maps are different
    // @see https://github.com/webpack/css-loader/pull/40
    if (opt.sourceMap) {
        // deliberately overriding the sourceMap option
        // this value is (currently) ignored by libsass when using the data input instead of file input
        // however, it is still necessary for correct relative paths in result.map.sources
        opt.sourceMap = this.options.output.path + '/sass.map';
        opt.omitSourceMapUrl = true;

        // If sourceMapContents option is not set, set it to true otherwise maps will be empty/null
        // when exported by webpack-extract-text-plugin.
        if ('sourceMapContents' in opt === false) {
            opt.sourceMapContents = true;
        }
    }

    // indentedSyntax is a boolean flag
    opt.indentedSyntax = Boolean(opt.indentedSyntax);

    opt.importer = getWebpackImporter();

    // start the actual rendering
    if (isSync) {
        try {
            result = sass.renderSync(opt);
            addIncludedFilesToWebpack(result.stats.includedFiles);
            return result.css.toString();
        } catch (err) {
            formatSassError(err);
            err.file && this.dependency(err.file);
            throw err;
        }
    }

    asyncSassJobQueue.push(opt, function onRender(err, result) {
        if (err) {
            formatSassError(err);
            err.file && self.dependency(err.file);
            callback(err);
            return;
        }

        if (result.map && result.map !== '{}') {
            result.map = JSON.parse(result.map);
            result.map.file = resourcePath;
            // The first source is 'stdin' according to libsass because we've used the data input
            // Now let's override that value with the correct relative path
            result.map.sources[0] = path.relative(self.options.output.path, resourcePath);
        } else {
            result.map = null;
        }

        addIncludedFilesToWebpack(result.stats.includedFiles);
        callback(null, result.css.toString(), result.map);
    });
};

/**
 * Tries to get an excerpt of the file where the error happened.
 * Uses err.line and err.column.
 *
 * Returns an empty string if the excerpt could not be retrieved.
 *
 * @param {SassError} err
 * @returns {string}
 */
function getFileExcerptIfPossible(err) {
    var content;

    try {
        content = fs.readFileSync(err.file, 'utf8');

        return os.EOL +
            content.split(os.EOL)[err.line - 1] + os.EOL +
            new Array(err.column - 1).join(' ') + '^' + os.EOL +
            '      ';
    } catch (err) {
        // If anything goes wrong here, we don't want any errors to be reported to the user
        return '';
    }
}

/**
 * When libsass tries to resolve an import, it uses this "funny" algorithm:
 *
 * - Imports with no file extension:
 *   - Prefer modules starting with '_'
 *   - File extension precedence: .scss, .sass, .css
 * - Imports with file extension:
 *   - If the file is a CSS-file, do not include it all, but just link it via @import url()
 *   - The exact file name must match (no auto-resolving of '_'-modules)
 *
 * Since the sass-loader uses webpack to resolve the modules, we need to simulate that algorithm. This function
 * returns an array of import paths to try.
 *
 * @param {string} originalImport
 * @returns {Array}
 */
function getImportsToResolve(originalImport) {
    var ext = path.extname(originalImport);
    var basename = path.basename(originalImport);
    var dirname = path.dirname(originalImport);
    var startsWithUnderscore = basename.charAt(0) === '_';
    var paths = [];

    function add(file) {
        // No path.sep required here, because imports inside SASS are usually with /
        paths.push(dirname + '/' + file);
    }

    if (originalImport.charAt(0) !== '.') {
        // If true: originalImport is a module import like 'bootstrap-sass...'
        if (dirname === '.') {
            // If true: originalImport is just a module import without a path like 'bootstrap-sass'
            // In this case we don't do that auto-resolving dance at all.
            return [originalImport];
        }
    }
    if (ext) {
        if (ext === '.scss' || ext === '.sass') {
            add(basename);
        }/* else {
            Leave unknown extensions (like .css) untouched
        }*/
    } else {
        if (!startsWithUnderscore) {
            // Prefer modules starting with '_' first
            extPrecedence.forEach(function (ext) {
                add('_' + basename + ext);
            });
        }
        extPrecedence.forEach(function (ext) {
            add(basename + ext);
        });
    }

    return paths;
}
<|endoftext|>


<commit_before>var es = require('event-stream');
var gutil = require('gulp-util');
var path = require('path');
var ect = require('ect');

module.exports = function (opt) {
  opt = opt || {};
  if (!opt.ext) opt.ext = '.ect';
  if (!opt.outExt) opt.outExt = '.html';

  return es.map(function (file, callback) {
    try {
      var filePath = file.base;
      var fileName = gutil.replaceExtension(path.basename(file.path), "");
      var html = ect({
        root: filePath,
        ext: opt.ext
      });
      html.render(fileName, function (error, html) {
        file.contents = new Buffer(html);
        file.path = gutil.replaceExtension(file.path, opt.outExt);
      });
    } catch (e) {
      console.warn('Error gulp-ect: ' + e.message);
    }
    callback(null, file);
  });
};<commit_msg>Use gulp-util.log instead of console.warn<commit_after>var es = require('event-stream');
var gutil = require('gulp-util');
var path = require('path');
var ect = require('ect');

module.exports = function (opt) {
  opt = opt || {};
  if (!opt.ext) opt.ext = '.ect';
  if (!opt.outExt) opt.outExt = '.html';

  return es.map(function (file, callback) {
    try {
      var filePath = file.base;
      var fileName = gutil.replaceExtension(path.basename(file.path), "");
      var html = ect({
        root: filePath,
        ext: opt.ext
      });
      html.render(fileName, function (error, html) {
        file.contents = new Buffer(html);
        file.path = gutil.replaceExtension(file.path, opt.outExt);
      });
    } catch (e) {
      gutil.log(gutil.colors.red('Error gulp-ect: ' + e.message));
    }
    callback(null, file);
  });
};
<|endoftext|>


<commit_before>'use strict';

/**
 * Make multiple console beep sounds
 * @param {number} [i=1] - Number of beeps
 * @param {number} [t=500] - Milliseconds between beeps
 */
exports = module.exports = function beep(i, t) {
  if (isNaN(i)) i = 1
  if (isNaN(t)) t = 500

  while (i-- > 0) setTimeout(beepNow, t*i)
}

function beepNow() { process.stdout.write('\x07') }
<commit_msg>fix exports style<commit_after>'use strict';

/**
 * Make multiple console beep sounds
 * @param {number} [i=1] - Number of beeps
 * @param {number} [t=500] - Milliseconds between beeps
 */
module.exports = function (i, t) {
  if (isNaN(i)) i = 1
  if (isNaN(t)) t = 500

  while (i-- > 0) setTimeout(beepNow, t*i)
}

function beepNow() { process.stdout.write('\x07') }
<|endoftext|>


<commit_before>/**
# Test
Copyright ©2016-2017 Tonio Loewald

A very simple test library with support for async tests.

To test something:

    Test(() => ... [, 'description']).shouldBe(expected_value);

For example:

    Test(() => 1 + 1, 'math still works').shouldBe(2);

## Async Tests

Just return a promise:

    Test(() => new Promise((resolve) => resolve(3))).shouldBe(3);

Or sometimes:

    Test(() => ... [, 'description']).shouldNotBe(wrong_value);

To setup visible reporting:

    Test.setReportContainer(element);

To set an error callback:

    Test.onError = () => ...;

To get a list of tests (which will include descriptions and statuses):

    Test.tests();

To reset the list of tests:

    Test.reset();

~~~~
Test(() => 3).shouldBe(3);
Test(() => new Promise((resolve) => resolve(3))).shouldBe(3);
Test(() => 17).shouldNotBe(3);
Test(() => {throw 'ouch'}).shouldThrow();
Test(() => new Promise((resolve) => {throw 'ouch'})).shouldThrow();
Test(() => 17).shouldBe(3);
~~~~
*/
/* jshint latedef:false */
/* globals module, console */

'use strict';
var report_container;
const tests = [];

function quote(x) {
  const json = JSON.stringify(x);
  if (typeof x === 'string') {
    return '"' + x + '"';
  } else if (typeof x === 'number') {
    return x;
  } else if (x instanceof Array) {
    return json.length < 50 ? json : '[...]';
  } else if (typeof x === 'object') {
    return json.length < 50 ? json : '{...}';
  } else {
    return x;
  }
}

class _Test {
  constructor (fn, description) {
    if (typeof fn === 'function') {
      this.fn = fn;
      const source = fn.toString().replace(/^\(\)\s*=>\s*/, '');
      this.description = description ? `${description} — ${source}` : source;
      this.status = 'incomplete';
      this.report_container = report_container;
      this.threw_error = false;
      this.onError = Test.onError;
      tests.push(this);
      this.run();
    } else {
      throw `Test expects a function, got ${fn}`;
    }
  }

  shouldBe(value) {
    if (this._test) {
      throw 'cannot set a different expectation';
    }
    this._expected = `expected ${value}`;
    this._test = x => x === value;
  }

  shouldBeJSON(value) {
    if (this._test) {
      throw 'cannot set a different expectation';
    }
    this._expected = `expected ${JSON.stringify(value)}`;
    this._test = x => JSON.stringify(x) === JSON.stringify(value);
  }

  shouldThrow() {
    this._expected = `expected throw`;
    this._test = () => this.threw_error;
  }

  shouldNotBe(value) {
    if (this._test) {
      throw 'cannot set a different expectation';
    }
    this._expected = `did not expect ${value}`;
    this._test = x => x !== value;
  }

  grade(result) {
    if (!this._test(result)) {
      this.failure(result);
    } else {
      this.success(result);
    }
  }

  report(message, _class_) {
    Test.report(message, _class_, this.report_container);
  }

  success(result) {
    this.status = 'success';
    this.report(`${this.description} == ${quote(result)}`, 'success');
  }

  failure(result) {
    this.onError();
    this.status = 'failure';
    this.report(`${this.description} == ${quote(result)}`, 'failure');
  }

  run() {
    let result;
    try {
      result = this.fn();
    } catch (e) {
      this.threw_error = true;
    }
    if (result instanceof Promise) {
      result.
      then(result => this.grade(result)).
      catch(() => {
        this.threw_error = true;
        this.grade();
      });
    } else {
      setTimeout(() => this.grade(result));
    }
  }
}

function Test(fn, description) {
  return new _Test(fn, description);
}

Test.onError = () => {};

Test.tests = () => tests;

Test.reset = () => tests.splice(0);

Test.setReportContainer = elt => report_container = elt;

function trigger(event_type, target) {
  var targets;
  if (typeof target === 'string') {
    targets = document.querySelectorAll(target);
  } else if (!target.length) {
    targets = [target];
  }
  if (targets.length && !(targets instanceof Array)) {
    targets = [].slice.apply(targets);
  }
  targets.forEach(target => {
    target.dispatchEvent(
        new Event(event_type, {bubbles: true, view: window}));
  });
}

Test.trigger = trigger;

function report(message, _class_, target) {
  target = target || report_container;
  if (target) {
    var p = document.createElement('p');
    if (_class_) {
      p.classList.add(_class_);
    }
    p.textContent = message;
    target.appendChild(p);
  } else {
    console.error(message);
  }
}

Test.report = report;

module.exports = Test;
<commit_msg>new — improved support for async tests (test results will be ordered consistently based on when the test was triggered, not when it completes. new — .pending state for async tests that have not returned.<commit_after>/**
# Test
Copyright ©2016-2017 Tonio Loewald

A very simple test library with support for async tests.

To test something:

    Test(() => ... [, 'description']).shouldBe(expected_value);

For example:

    Test(() => 1 + 1, 'math still works').shouldBe(2);

By default, the `description` will be the text of the test method.

## Async Tests

Just return a promise:

    Test(() => new Promise(resolve => resolve(3))).shouldBe(3);

Or sometimes:

    Test(() => ... [, 'description']).shouldNotBe(wrong_value);

To setup visible reporting:

    Test.setReportContainer(element);

To set an error callback:

    Test.onError = () => ...;

To get a list of tests (which will include descriptions and statuses):

    Test.tests();

To reset the list of tests:

    Test.reset();

~~~~
Test(() => 3).shouldBe(3);
Test(() => new Promise(resolve => resolve(3))).shouldBe(3);
Test(() => new Promise(resolve => {
  setTimeout(() => resolve(3), 1000);
})).shouldBe(3)
Test(() => 17).shouldNotBe(3);
Test(() => {throw 'ouch'}).shouldThrow();
Test(() => new Promise((resolve) => {throw 'ouch'})).shouldThrow();
Test(() => 17).shouldBe(3);
~~~~
*/
/* jshint latedef:false */
/* globals module, console */

'use strict';
var report_container;
const tests = [];

const make_report_item = description => {
  if (report_container) {
    const p = document.createElement('p');
    p.classList.add('pending');
    p.textContent = description;
    report_container.appendChild(p);
    return p;
  }
};

function quote(x) {
  const json = JSON.stringify(x);
  if (typeof x === 'string') {
    return '"' + x + '"';
  } else if (typeof x === 'number') {
    return x;
  } else if (x instanceof Array) {
    return json.length < 50 ? json : '[...]';
  } else if (typeof x === 'object') {
    return json.length < 50 ? json : '{...}';
  } else {
    return x;
  }
}

class _Test {
  constructor (fn, description) {
    if (typeof fn === 'function') {
      this.fn = fn;
      const source = fn.toString().replace(/^\(\)\s*=>\s*/, '');
      this.description = description ? `${description} — ${source}` : source;
      this.status = 'incomplete';
      this.report_container = make_report_item(this.description);
      this.threw_error = false;
      this.onError = Test.onError;
      tests.push(this);
      this.run();
    } else {
      throw `Test expects a function, got ${fn}`;
    }
  }

  shouldBe(value) {
    if (this._test) {
      throw 'cannot set a different expectation';
    }
    this._expected = `expected ${value}`;
    this._test = x => x === value;
  }

  shouldBeJSON(value) {
    if (this._test) {
      throw 'cannot set a different expectation';
    }
    this._expected = `expected ${JSON.stringify(value)}`;
    this._test = x => JSON.stringify(x) === JSON.stringify(value);
  }

  shouldThrow() {
    this._expected = `expected throw`;
    this._test = () => this.threw_error;
  }

  shouldNotBe(value) {
    if (this._test) {
      throw 'cannot set a different expectation';
    }
    this._expected = `did not expect ${value}`;
    this._test = x => x !== value;
  }

  grade(result) {
    if (!this._test(result)) {
      this.failure(result);
    } else {
      this.success(result);
    }
  }

  report(message, _class_) {
    Test.report(message, _class_, this.report_container);
  }

  success(result) {
    this.status = 'success';
    this.report(`${this.description} == ${quote(result)}`, 'success');
  }

  failure(result) {
    this.onError();
    this.status = 'failure';
    this.report(`${this.description} == ${quote(result)}`, 'failure');
  }

  run() {
    let result;
    try {
      result = this.fn();
    } catch (e) {
      this.threw_error = true;
    }
    if (result instanceof Promise) {
      result.
      then(result => this.grade(result)).
      catch(() => {
        this.threw_error = true;
        this.grade();
      });
    } else {
      setTimeout(() => this.grade(result));
    }
  }
}

function Test(fn, description) {
  return new _Test(fn, description);
}

Test.onError = () => {};

Test.tests = () => tests;

Test.reset = () => tests.splice(0);

Test.setReportContainer = elt => report_container = elt;

function trigger(event_type, target) {
  var targets;
  if (typeof target === 'string') {
    targets = document.querySelectorAll(target);
  } else if (!target.length) {
    targets = [target];
  }
  if (targets.length && !(targets instanceof Array)) {
    targets = [].slice.apply(targets);
  }
  targets.forEach(target => {
    target.dispatchEvent(
        new Event(event_type, {bubbles: true, view: window}));
  });
}

Test.trigger = trigger;

function report(message, _class_, target) {
  target = target || report_container;
  if (target) {
    target.classList.remove('pending');
    target.classList.add(_class_);
    target.textContent = message;
  } else {
    console.error(message);
  }
}

Test.report = report;

module.exports = Test;
<|endoftext|>