<commit_before>/* Downloads the latest translations from Transifex */

var request = require('request').defaults({ maxSockets: 1 }),
    yaml = require('js-yaml'),
    fs = require('fs'),
    stringify = require('json-stable-stringify'),
    _ = require('lodash');

var resources = ['core', 'presets'];
var outdir = './dist/locales/';
var api = 'https://www.transifex.com/api/2/';
var project = api + 'project/id-editor/';

/*
 * Transifex oddly doesn't allow anonymous downloading
 *
 * auth is stored in transifex.auth in a json object:
 *  {
 *      "user": "username",
 *      "pass": "password"
 *  }
 *  */

var auth = JSON.parse(fs.readFileSync('./transifex.auth', 'utf8'));

var sourceCore = yaml.load(fs.readFileSync('./data/core.yaml', 'utf8')),
    sourcePresets = yaml.load(fs.readFileSync('./data/presets.yaml', 'utf8'));

asyncMap(resources, getResource, function(err, locales) {
    if (err) return console.log(err);

    var locale = _.merge(sourceCore, sourcePresets),
        dataLocales = {};

    locales.forEach(function(l) {
        locale = _.merge(locale, l);
    });

    asyncMap(Object.keys(locale),
        function(code, done) {
            if (code === 'en' || _.isEmpty(locale[code])) {
                done();
            } else {
                var obj = {};
                obj[code] = locale[code];
                fs.writeFileSync(outdir + code + '.json', JSON.stringify(obj, null, 4));
                getLanguageInfo(code, function(err, info) {
                    dataLocales[code] = { rtl: info && info.rtl };
                    done();
                });
            }
        }, function(err) {
            if (!err) {
                fs.writeFileSync('data/locales.json', stringify({ dataLocales: dataLocales }, { space: 4 }));
            }
        }
    );
});

function getResource(resource, callback) {
    resource = project + 'resource/' + resource + '/';
    getLanguages(resource, function(err, codes) {
        if (err) return callback(err);

        asyncMap(codes, getLanguage(resource), function(err, results) {
            if (err) return callback(err);

            var locale = {};
            results.forEach(function(result, i) {
                locale[codes[i]] = result;
            });

            callback(null, locale);
        });
    });
}

function getLanguage(resource) {
    return function(code, callback) {
        code = code.replace(/-/g, '_');
        var url = resource + 'translation/' + code;
        if (code === 'vi') url += '?mode=reviewed';
        request.get(url, { auth : auth }, function(err, resp, body) {
            if (err) return callback(err);
            console.log(resp.statusCode + ': ' + url);
            callback(null, yaml.load(JSON.parse(body).content)[code]);
        });
    };
}

function getLanguageInfo(code, callback) {
    code = code.replace(/-/g, '_');
    var url = api + 'language/' + code;
    request.get(url, { auth : auth }, function(err, resp, body) {
        if (err) return callback(err);
        console.log(resp.statusCode + ': ' + url);
        callback(null, JSON.parse(body));
    });
}

function getLanguages(resource, callback) {
    var url = resource + '?details';
    request.get(url, { auth: auth },
        function(err, resp, body) {
        if (err) return callback(err);
        console.log(resp.statusCode + ': ' + url);
        callback(null, JSON.parse(body).available_languages.map(function(d) {
            return d.code.replace(/_/g, '-');
        }).filter(function(d) {
            return d !== 'en';
        }));
    });
}

function asyncMap(inputs, func, callback) {
    setTimeout(function() {
        var remaining = inputs.length,
            results = [],
            error;

        inputs.forEach(function(d, i) {
            func(d, function done(err, data) {
                if (err) error = err;
                results[i] = data;
                remaining --;
                if (!remaining) callback(error, results);
            });
        });
    }, 300);
}
<commit_msg>Download and merge imagery resource from Transifex<commit_after>/* Downloads the latest translations from Transifex */

var request = require('request').defaults({ maxSockets: 1 }),
    yaml = require('js-yaml'),
    fs = require('fs'),
    stringify = require('json-stable-stringify'),
    _ = require('lodash');

var resources = ['core', 'presets', 'imagery'];
var outdir = './dist/locales/';
var api = 'https://www.transifex.com/api/2/';
var project = api + 'project/id-editor/';

/*
 * Transifex oddly doesn't allow anonymous downloading
 *
 * auth is stored in transifex.auth in a json object:
 *  {
 *      "user": "username",
 *      "pass": "password"
 *  }
 *  */

var auth = JSON.parse(fs.readFileSync('./transifex.auth', 'utf8'));

var sourceCore = yaml.load(fs.readFileSync('./data/core.yaml', 'utf8')),
    sourcePresets = yaml.load(fs.readFileSync('./data/presets.yaml', 'utf8')),
    sourceImagery = JSON.parse(fs.readFileSync('./node_modules/editor-layer-index/i18n/en.json', 'utf8'));

asyncMap(resources, getResource, function(err, locales) {
    if (err) return console.log(err);

    var locale = _.merge(sourceCore, sourcePresets, {en: sourceImagery}),
        dataLocales = {};

    locales.forEach(function(l) {
        locale = _.merge(locale, l);
    });

    asyncMap(Object.keys(locale),
        function(code, done) {
            if (code === 'en' || _.isEmpty(locale[code])) {
                done();
            } else {
                var obj = {};
                obj[code] = locale[code];
                fs.writeFileSync(outdir + code + '.json', JSON.stringify(obj, null, 4));
                getLanguageInfo(code, function(err, info) {
                    dataLocales[code] = { rtl: info && info.rtl };
                    done();
                });
            }
        }, function(err) {
            if (!err) {
                fs.writeFileSync('data/locales.json', stringify({ dataLocales: dataLocales }, { space: 4 }));
            }
        }
    );
});

function getResource(resource, callback) {
    resource = project + 'resource/' + resource + '/';
    getLanguages(resource, function(err, codes) {
        if (err) return callback(err);

        asyncMap(codes, getLanguage(resource), function(err, results) {
            if (err) return callback(err);

            var locale = {};
            results.forEach(function(result, i) {
                locale[codes[i]] = result;
            });

            callback(null, locale);
        });
    });
}

function getLanguage(resource) {
    return function(code, callback) {
        code = code.replace(/-/g, '_');
        var url = resource + 'translation/' + code;
        if (code === 'vi') url += '?mode=reviewed';
        request.get(url, { auth : auth }, function(err, resp, body) {
            if (err) return callback(err);
            console.log(resp.statusCode + ': ' + url);
            callback(null, yaml.load(JSON.parse(body).content)[code]);
        });
    };
}

function getLanguageInfo(code, callback) {
    code = code.replace(/-/g, '_');
    var url = api + 'language/' + code;
    request.get(url, { auth : auth }, function(err, resp, body) {
        if (err) return callback(err);
        console.log(resp.statusCode + ': ' + url);
        callback(null, JSON.parse(body));
    });
}

function getLanguages(resource, callback) {
    var url = resource + '?details';
    request.get(url, { auth: auth },
        function(err, resp, body) {
        if (err) return callback(err);
        console.log(resp.statusCode + ': ' + url);
        callback(null, JSON.parse(body).available_languages.map(function(d) {
            return d.code.replace(/_/g, '-');
        }).filter(function(d) {
            return d !== 'en';
        }));
    });
}

function asyncMap(inputs, func, callback) {
    setTimeout(function() {
        var remaining = inputs.length,
            results = [],
            error;

        inputs.forEach(function(d, i) {
            func(d, function done(err, data) {
                if (err) error = err;
                results[i] = data;
                remaining --;
                if (!remaining) callback(error, results);
            });
        });
    }, 300);
}
<|endoftext|>


<commit_before>export const getChartConfig = (contract_info) => {
    const start_epoch = contract_info.date_start;
    const end_epoch   = getEndSpotTime(contract_info) || contract_info.date_expiry;
    const granularity = calculateGranularity(end_epoch - start_epoch);

    return {
        granularity,
        chart_type: granularity ? 'candle' : 'mountain',
        end_epoch,
        start_epoch,
    };
};

const hour_to_granularity_map = [
    [1      , 0],
    [2      , 120],
    [6      , 600],
    [24     , 900],
    [5 * 24 , 3600],
    [30 * 24, 14400],
];
export const calculateGranularity = (duration) =>
    (hour_to_granularity_map.find(m => duration <= m[0] * 3600) || [null, 86400])[1];

export const getDisplayStatus = (contract_info) => {
    let status = 'purchased';
    if (isEnded(contract_info)) {
        status = contract_info.profit >= 0 ? 'won' : 'lost';
    }
    return status;
};

export const getEndSpot = (contract_info) => (
    isUserSold(contract_info) ? contract_info.sell_spot : contract_info.exit_tick
);

export const getEndSpotTime = (contract_info, is_return_string) => {
    const end_spot_time = isUserSold(contract_info) ? contract_info.sell_spot_time : contract_info.exit_tick_time;
    if (is_return_string) {
        return end_spot_time;
    }
    return +end_spot_time;
};

export const getFinalPrice = (contract_info) => (
    +(contract_info.sell_price || contract_info.bid_price)
);

export const getIndicativePrice = (contract_info) => (
    getFinalPrice(contract_info) && isEnded(contract_info) ?
        getFinalPrice(contract_info) :
        (+contract_info.bid_price || null)
);

export const getLastTickFromTickStream = (tick_stream = []) => (
    tick_stream[tick_stream.length - 1] || {}
);

export const isEnded = (contract_info) => !!(
    (contract_info.status && contract_info.status !== 'open') ||
    contract_info.is_expired        ||
    contract_info.is_settleable
);

export const isSoldBeforeStart = (contract_info) => (
    contract_info.sell_time && +contract_info.sell_time < +contract_info.date_start
);

export const isStarted = (contract_info) => (
    !contract_info.is_forward_starting || contract_info.current_spot_time > contract_info.date_start
);

export const isUserSold = (contract_info) => (
    contract_info.status === 'sold'
);

export const isValidToSell = (contract_info) => (
    !isEnded(contract_info) && !isUserSold(contract_info) && +contract_info.is_valid_to_sell === 1
);
<commit_msg>revert getEndSpotTime<commit_after>export const getChartConfig = (contract_info) => {
    const start_epoch = contract_info.date_start;
    const end_epoch   = getEndSpotTime(contract_info) || contract_info.date_expiry;
    const granularity = calculateGranularity(end_epoch - start_epoch);

    return {
        granularity,
        chart_type: granularity ? 'candle' : 'mountain',
        end_epoch,
        start_epoch,
    };
};

const hour_to_granularity_map = [
    [1      , 0],
    [2      , 120],
    [6      , 600],
    [24     , 900],
    [5 * 24 , 3600],
    [30 * 24, 14400],
];
export const calculateGranularity = (duration) =>
    (hour_to_granularity_map.find(m => duration <= m[0] * 3600) || [null, 86400])[1];

export const getDisplayStatus = (contract_info) => {
    let status = 'purchased';
    if (isEnded(contract_info)) {
        status = contract_info.profit >= 0 ? 'won' : 'lost';
    }
    return status;
};

export const getEndSpot = (contract_info) => (
    isUserSold(contract_info) ? contract_info.sell_spot : contract_info.exit_tick
);

export const getEndSpotTime = (contract_info) => (
    isUserSold(contract_info) ? +contract_info.sell_spot_time : +contract_info.exit_tick_time
);

export const getFinalPrice = (contract_info) => (
    +(contract_info.sell_price || contract_info.bid_price)
);

export const getIndicativePrice = (contract_info) => (
    getFinalPrice(contract_info) && isEnded(contract_info) ?
        getFinalPrice(contract_info) :
        (+contract_info.bid_price || null)
);

export const getLastTickFromTickStream = (tick_stream = []) => (
    tick_stream[tick_stream.length - 1] || {}
);

export const isEnded = (contract_info) => !!(
    (contract_info.status && contract_info.status !== 'open') ||
    contract_info.is_expired        ||
    contract_info.is_settleable
);

export const isSoldBeforeStart = (contract_info) => (
    contract_info.sell_time && +contract_info.sell_time < +contract_info.date_start
);

export const isStarted = (contract_info) => (
    !contract_info.is_forward_starting || contract_info.current_spot_time > contract_info.date_start
);

export const isUserSold = (contract_info) => (
    contract_info.status === 'sold'
);

export const isValidToSell = (contract_info) => (
    !isEnded(contract_info) && !isUserSold(contract_info) && +contract_info.is_valid_to_sell === 1
);
<|endoftext|>


<commit_before>var util = require('util');
var HyperdeckCore = require('./hyperdeck-core.js');

var Hyperdeck = function(config) {

  // call constructor of HyperdeckCore
  HyperdeckCore.call(this, config);

  this.makeRequest('notify: remote: true');
  this.makeRequest('notify: transport: true');
  this.makeRequest('notify: slot: true');
  this.makeRequest('notify: configuration: true');

  // add Easy Access commands
  this.play = function(speed) {
    var commandString;
    if (Math.abs(speed) <= 1600) {
      commandString = 'play: speed: ' + speed;
    } else {
      if (speed) {
        throw new Error('Speed value invalid or out of range');
      } else {
        commandString = 'play';
      }
    }
    return this.makeRequest(commandString);
  };

  this.stop = function() {
    return this.makeRequest('stop');
  };

  this.record = function() {
    return this.makeRequest('record');
  };

  this.goTo = function(timecode) {
    return this.makeRequest('goto: timecode: ' + timecode);
  };

  this.slotInfo = function (id) {
    if (id === 0 || id === 1 || id === 2){ //TO DO find if it's 0-1 or 1-2
      return this.makeRequest('slot info: slot id: ' + id);
    } else{
      if (!id){
        return this.makeRequest('slot info');
      }
      throw new Error('Slot ID Value out of range');
    }
  };

  this.transportInfo = function(){
    return this.makeRequest('transport info');
  };

  this.clipsGet = function(){
    return this.makeRequest('clips get');
  };

  this.slotSelect = function(id){
    return this.makeRequest('slot select: slot id: ' + id);
  };

  this.format = function(format){
    return this.makeRequest('format: prepare: ' + format).then(function(response){
      if (response.code !== 216 || response.text !== 'format ready' || !response.rawData) {
        throw new Error('Unexpected response.');
      }
      var token = response.rawData;
      return this.makeRequest('format: confirm: ' + token);
    });
  };
};

// make this class extend HyperdeckCore
// https://nodejs.org/docs/latest/api/util.html#util_util_inherits_constructor_superconstructor
util.inherits(Hyperdeck, HyperdeckCore);

module.exports = Hyperdeck;
<commit_msg>hyperdeck.format fix context scoping<commit_after>var util = require('util');
var HyperdeckCore = require('./hyperdeck-core.js');

var Hyperdeck = function(config) {

  // call constructor of HyperdeckCore
  HyperdeckCore.call(this, config);

  this.makeRequest('notify: remote: true');
  this.makeRequest('notify: transport: true');
  this.makeRequest('notify: slot: true');
  this.makeRequest('notify: configuration: true');

  // add Easy Access commands
  this.play = function(speed) {
    var commandString;
    if (Math.abs(speed) <= 1600) {
      commandString = 'play: speed: ' + speed;
    } else {
      if (speed) {
        throw new Error('Speed value invalid or out of range');
      } else {
        commandString = 'play';
      }
    }
    return this.makeRequest(commandString);
  };

  this.stop = function() {
    return this.makeRequest('stop');
  };

  this.record = function() {
    return this.makeRequest('record');
  };

  this.goTo = function(timecode) {
    return this.makeRequest('goto: timecode: ' + timecode);
  };

  this.slotInfo = function (id) {
    if (id === 0 || id === 1 || id === 2){ //TO DO find if it's 0-1 or 1-2
      return this.makeRequest('slot info: slot id: ' + id);
    } else{
      if (!id){
        return this.makeRequest('slot info');
      }
      throw new Error('Slot ID Value out of range');
    }
  };

  this.transportInfo = function(){
    return this.makeRequest('transport info');
  };

  this.clipsGet = function(){
    return this.makeRequest('clips get');
  };

  this.slotSelect = function(id){
    return this.makeRequest('slot select: slot id: ' + id);
  };

  this.format = function(format){
    return this.makeRequest('format: prepare: ' + format).then((response)=>{
      if (response.code !== 216 || response.text !== 'format ready' || !response.rawData) {
        throw new Error('Unexpected response.');
      }
      var token = response.rawData;
      return this.makeRequest('format: confirm: ' + token);
    });
  };
};

// make this class extend HyperdeckCore
// https://nodejs.org/docs/latest/api/util.html#util_util_inherits_constructor_superconstructor
util.inherits(Hyperdeck, HyperdeckCore);

module.exports = Hyperdeck;
<|endoftext|>


<commit_before>import '../styles/HiGlassApp.css';
import React from 'react';
import ReactDOM from 'react-dom';
import {MultiViewContainer} from './MultiViewContainer.jsx';
import {HiGlassInput} from './HiGlassInput.jsx';
import {Button, Panel, FormGroup, ControlLabel, FormControl, SafeAnchor} from 'react-bootstrap';

export class HiGlassApp extends React.Component {
    constructor(props) {
        super(props);

    this.state = {
        //viewConfig : []

        viewConfig : JSON.parse(this.props.viewConfigString),
        inputOpen: false
    }

    this.updateLinkedViews(this.state.viewConfig);

    }

    updateLinkedViews(viewConfig) {
        for (let i = 0; i < viewConfig.length; i++) {
            if (typeof viewConfig[i].zoomLock ==  'undefined')
                viewConfig[i].zoomDispatch = d3.dispatch('zoom', 'zoomend')
            else {
                let zoomLock = viewConfig[i].zoomLock;
                if (typeof viewConfig[zoomLock].zoomDispatch == 'undefined') {
                    console.log('WARNING: view requests zoom lock to another view with an undefined zoomDispatch:', zoomLock);
                    viewConfig[i].zoomDispatch = d3.dispatch('zoom', 'zoomend')
                } else {
                    viewConfig[i].zoomDispatch = viewConfig[zoomLock].zoomDispatch;
                }
            }
        }

    }

    handleNewConfig(configText) {
        let viewConfig = JSON.parse(configText);
        this.updateLinkedViews(viewConfig);

        this.setState(
         {
             viewConfig : viewConfig
         });

    };

    render() {
        /*
        let divStyle = {"paddingLeft": "20px",
                        "paddingRight": "20px"}
        */
       let divStyle = {};

        let toolbarStyle = {"position": "relative",
                       "top": "-1px"};

        return (

                <Panel
                    ref='displayPanel'
                    className="higlass-display"
                    >
                    <MultiViewContainer viewConfig={this.state.viewConfig}
                    />
                </Panel>
                <HiGlassInput currentConfig={this.defaultViewString}
                        onNewConfig={this.handleNewConfig.bind(this)}
                        />

        );
    }
}

<commit_msg>Clean up the default track layout view<commit_after>import '../styles/HiGlassApp.css';
import React from 'react';
import ReactDOM from 'react-dom';
import {MultiViewContainer} from './MultiViewContainer.jsx';
import {HiGlassInput} from './HiGlassInput.jsx';
import {Button, Panel, FormGroup, ControlLabel, FormControl, SafeAnchor} from 'react-bootstrap';

export class HiGlassApp extends React.Component {
    constructor(props) {
        super(props);

    this.defaultViewString = JSON.stringify(JSON.parse(this.props.viewConfigString), null, 2);

    this.state = {
        //viewConfig : []

        viewConfig : JSON.parse(this.props.viewConfigString),
        inputOpen: false
    }

    this.updateLinkedViews(this.state.viewConfig);

    }

    updateLinkedViews(viewConfig) {
        for (let i = 0; i < viewConfig.length; i++) {
            if (typeof viewConfig[i].zoomLock ==  'undefined')
                viewConfig[i].zoomDispatch = d3.dispatch('zoom', 'zoomend')
            else {
                let zoomLock = viewConfig[i].zoomLock;
                if (typeof viewConfig[zoomLock].zoomDispatch == 'undefined') {
                    console.log('WARNING: view requests zoom lock to another view with an undefined zoomDispatch:', zoomLock);
                    viewConfig[i].zoomDispatch = d3.dispatch('zoom', 'zoomend')
                } else {
                    viewConfig[i].zoomDispatch = viewConfig[zoomLock].zoomDispatch;
                }
            }
        }

    }

    handleNewConfig(configText) {
        let viewConfig = JSON.parse(configText);
        this.updateLinkedViews(viewConfig);

        this.setState(
         {
             viewConfig : viewConfig
         });

    };

    render() {
        /*
        let divStyle = {"paddingLeft": "20px",
                        "paddingRight": "20px"}
        */
       let divStyle = {};

        let toolbarStyle = {"position": "relative",
                       "top": "-1px"};

        return (

                <Panel
                    ref='displayPanel'
                    className="higlass-display"
                    >
                    <MultiViewContainer viewConfig={this.state.viewConfig}
                    />
                </Panel>
                <HiGlassInput currentConfig={this.defaultViewString}
                        onNewConfig={this.handleNewConfig.bind(this)}
                        />

        );
    }
}

<|endoftext|>


<commit_before>//     Zepto.js
//     (c) 2010-2013 Thomas Fuchs
//     Zepto.js may be freely distributed under the MIT license.

;(function($){
  function detect(ua){
    var os = this.os = {}, browser = this.browser = {},
      webkit = ua.match(/WebKit\/([\d.]+)/),
      android = ua.match(/(Android)\s+([\d.]+)/),
      ipad = ua.match(/(iPad).*OS\s([\d_]+)/),
      ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/),
      iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/),
      webos = ua.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),
      touchpad = webos && ua.match(/TouchPad/),
      kindle = ua.match(/Kindle\/([\d.]+)/),
      silk = ua.match(/Silk\/([\d._]+)/),
      blackberry = ua.match(/(BlackBerry).*Version\/([\d.]+)/),
      bb10 = ua.match(/(BB10).*Version\/([\d.]+)/),
      rimtabletos = ua.match(/(RIM\sTablet\sOS)\s([\d.]+)/),
      playbook = ua.match(/PlayBook/),
      chrome = ua.match(/Chrome\/([\d.]+)/) || ua.match(/CriOS\/([\d.]+)/),
      firefox = ua.match(/Firefox\/([\d.]+)/),
      ie = ua.match(/MSIE ([\d.]+)/),
      safari = webkit && ua.match(/Mobile\//) && !chrome,
      webview = ua.match(/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/) && !chrome

    // Todo: clean this up with a better OS/browser seperation:
    // - discern (more) between multiple browsers on android
    // - decide if kindle fire in silk mode is android or not
    // - Firefox on Android doesn't specify the Android version
    // - possibly devide in os, device and browser hashes

    if (browser.webkit = !!webkit) browser.version = webkit[1]

    if (android) os.android = true, os.version = android[2]
    if (iphone && !ipod) os.ios = os.iphone = true, os.version = iphone[2].replace(/_/g, '.')
    if (ipad) os.ios = os.ipad = true, os.version = ipad[2].replace(/_/g, '.')
    if (ipod) os.ios = os.ipod = true, os.version = ipod[3] ? ipod[3].replace(/_/g, '.') : null
    if (webos) os.webos = true, os.version = webos[2]
    if (touchpad) os.touchpad = true
    if (blackberry) os.blackberry = true, os.version = blackberry[2]
    if (bb10) os.bb10 = true, os.version = bb10[2]
    if (rimtabletos) os.rimtabletos = true, os.version = rimtabletos[2]
    if (playbook) browser.playbook = true
    if (kindle) os.kindle = true, os.version = kindle[1]
    if (silk) browser.silk = true, browser.version = silk[1]
    if (!silk && os.android && ua.match(/Kindle Fire/)) browser.silk = true
    if (chrome) browser.chrome = true, browser.version = chrome[1]
    if (firefox) browser.firefox = true, browser.version = firefox[1]
    if (ie) browser.ie = true, browser.version = ie[1]
    if (safari && (ua.match(/Safari/) || !!os.ios)) browser.safari = true
    if (webview) browser.webview = true

    os.tablet = !!(ipad || playbook || (android && !ua.match(/Mobile/)) || (firefox && ua.match(/Tablet/)))
    os.phone  = !!(!os.tablet && !os.ipod && (android || iphone || webos || blackberry || bb10 ||
      (chrome && ua.match(/Android/)) || (chrome && ua.match(/CriOS\/([\d.]+)/)) || (firefox && ua.match(/Mobile/))))
  }

  detect.call($, navigator.userAgent)
  // make available to unit tests
  $.__detect = detect

})(Zepto)
<commit_msg>Updated detect.js to include IE Support<commit_after>//     Zepto.js
//     (c) 2010-2013 Thomas Fuchs
//     Zepto.js may be freely distributed under the MIT license.

;(function($){
  function detect(ua){
    var os = this.os = {}, browser = this.browser = {},
      webkit = ua.match(/WebKit\/([\d.]+)/),
      android = ua.match(/(Android)\s+([\d.]+)/),
      ipad = ua.match(/(iPad).*OS\s([\d_]+)/),
      ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/),
      iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/),
      webos = ua.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),
      touchpad = webos && ua.match(/TouchPad/),
      kindle = ua.match(/Kindle\/([\d.]+)/),
      silk = ua.match(/Silk\/([\d._]+)/),
      blackberry = ua.match(/(BlackBerry).*Version\/([\d.]+)/),
      bb10 = ua.match(/(BB10).*Version\/([\d.]+)/),
      rimtabletos = ua.match(/(RIM\sTablet\sOS)\s([\d.]+)/),
      playbook = ua.match(/PlayBook/),
      chrome = ua.match(/Chrome\/([\d.]+)/) || ua.match(/CriOS\/([\d.]+)/),
      firefox = ua.match(/Firefox\/([\d.]+)/),
      ie = ua.match(/MSIE ([\d.]+)/),
      safari = webkit && ua.match(/Mobile\//) && !chrome,
      webview = ua.match(/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/) && !chrome,
      ie = ua.match(/MSIE ([\d.]+)/)

    // Todo: clean this up with a better OS/browser seperation:
    // - discern (more) between multiple browsers on android
    // - decide if kindle fire in silk mode is android or not
    // - Firefox on Android doesn't specify the Android version
    // - possibly devide in os, device and browser hashes

    if (browser.webkit = !!webkit) browser.version = webkit[1]

    if (android) os.android = true, os.version = android[2]
    if (iphone && !ipod) os.ios = os.iphone = true, os.version = iphone[2].replace(/_/g, '.')
    if (ipad) os.ios = os.ipad = true, os.version = ipad[2].replace(/_/g, '.')
    if (ipod) os.ios = os.ipod = true, os.version = ipod[3] ? ipod[3].replace(/_/g, '.') : null
    if (webos) os.webos = true, os.version = webos[2]
    if (touchpad) os.touchpad = true
    if (blackberry) os.blackberry = true, os.version = blackberry[2]
    if (bb10) os.bb10 = true, os.version = bb10[2]
    if (rimtabletos) os.rimtabletos = true, os.version = rimtabletos[2]
    if (playbook) browser.playbook = true
    if (kindle) os.kindle = true, os.version = kindle[1]
    if (silk) browser.silk = true, browser.version = silk[1]
    if (!silk && os.android && ua.match(/Kindle Fire/)) browser.silk = true
    if (chrome) browser.chrome = true, browser.version = chrome[1]
    if (firefox) browser.firefox = true, browser.version = firefox[1]
    if (ie) browser.ie = true, browser.version = ie[1]
    if (safari && (ua.match(/Safari/) || !!os.ios)) browser.safari = true
    if (webview) browser.webview = true
    if (ie) browser.ie = true, browser.verson = ie[1]

    os.tablet = !!(ipad || playbook || (android && !ua.match(/Mobile/)) || (firefox && ua.match(/Tablet/)))
    os.phone  = !!(!os.tablet && !os.ipod && (android || iphone || webos || blackberry || bb10 ||
      (chrome && ua.match(/Android/)) || (chrome && ua.match(/CriOS\/([\d.]+)/)) || (firefox && ua.match(/Mobile/))))
  }

  detect.call($, navigator.userAgent)
  // make available to unit tests
  $.__detect = detect

})(Zepto)
<|endoftext|>


<commit_before>define(['lib/knockout', 'scripts/Book', 'scripts/Link', 'scripts/Skill'],
  function(ko, Book, Link, Skill){
  'use strict';
  window.jiathis_config = {
    url: window.location.href,
    summary: '快来试试。',
    title: '我是 Level 1 Web Developer #Web技能树#',
    showClose: true,
    shortUrl: true,
    hideMore: false
  };

  var TalentTree = function(_e){
    var e = _e || {};
    var self = this;

    var asciiOffset = 96;
    var hashDelimeter = '_';

    var numPortraits = 1;

    self.isOpen = ko.observable(true);
    self.open = function () {
      self.isOpen(true);
    };
    self.close = function () {
      self.isOpen(false);
    };
    self.toggle = function () {
      self.isOpen(!self.isOpen());
    };

    //Mega skill list population
    self.skills = ko.observableArray(ko.utils.arrayMap(e.skills, function (item) {
      return new Skill(item);
    }));

    function getSkillById(id) {
      return ko.utils.arrayFirst(self.skills(), function (item) {
        return item.id === id;
      });
    }

    //Wire up dependency references
    ko.utils.arrayForEach(e.skills, function (item) {
      if (item.dependsOn) {
        var dependent = getSkillById(item.id);
        ko.utils.arrayForEach(item.dependsOn, function (dependencyId) {
          var dependency = getSkillById(dependencyId);
          dependent.dependencies.push(dependency);
          dependency.dependents.push(dependent);
        });
      }
    });

    //Avatar properties
    self.avatarName = ko.observable('Name');
    //level = total of all points spent
    self.level = ko.computed(function () {
      var totalSkillPoints = 0;
      ko.utils.arrayForEach(self.skills(), function (skill) {
        totalSkillPoints += skill.points();
      });
	    var totalLevel = totalSkillPoints + 1;
      window.jiathis_config.title = '@phodal 我是Level ' + totalLevel + ' Web Developer #Web技能树#';
      return totalLevel;
    });
    self.noPointsSpent = ko.computed(function () {
      return !Boolean(ko.utils.arrayFirst(self.skills(), function (skill) {
        return (skill.points() > 0);
      }));
    });
    self.stats = ko.computed(function () {
      //set some defaults
      var totals = {
        'Charisma': 9
        , 'Dexterity': 9
        , 'Fortitude': 9
        , 'Intellect': 9
        , 'Strength': 9
        , 'Wisdom': 9
      };
      //get all the skill name/value pairs and add/create them, using the stat name as the index
      ko.utils.arrayForEach(self.skills(), function (skill) {
        var p = skill.points();
        if (p > 0) {
          ko.utils.arrayForEach(skill.stats, function (stat) {
            var total = totals[stat.title] || 0;
            total += stat.value * p; //multiply the stat value by the points spent on the skill
            totals[stat.title] = total;
          });
        }
      });
      //Translate into a view-friendly array
      var result = [];
      for (var statName in totals) {
        result.push({
          title: statName
          , value: totals[statName]
        });
      }
      return result;
    });
    //String of unique characteristics, comma delimeted
    self.talentSummary = ko.computed(function () {
      var a = [];
      ko.utils.arrayForEach(self.skills(), function (skill) {
        if (skill.hasPoints()) {
          a = a.concat(skill.talents);
        }
      });
      window.jiathis_config.summary = '我获得的称号有: ' + a.join(', ');
      return a.join(', ');
    });
    //Portrait stuff
    self.portrait = ko.observable(Math.ceil(Math.random() * numPortraits));
    self.portraitURL = ko.computed(function () {
      return 'app/images/portraits/portrait-' + self.portrait() + '.jpg';
    });
    self.choosePreviousPortrait = function () {
      var n = self.portrait() - 1;
      if (n < 1) {
        n = numPortraits;
      }
      self.portrait(n);
    };
    self.chooseNextPortrait = function () {
      var n = self.portrait() + 1;
      if (n > numPortraits) {
        n = 1;
      }
      self.portrait(n);
    };

    //Utility functions
    self.newbMode = function () {
      ko.utils.arrayForEach(self.skills(), function (skill) {
        skill.points(0);
      });
    };
    self.godMode = function () {
      ko.utils.arrayForEach(self.skills(), function (skill) {
        skill.points(skill.maxPoints);
      });
    };

    //Hash functions
    self.hash = ko.computed(function () {
      var a = [];
      //compile a flat list of skill ids and values
      ko.utils.arrayForEach(self.skills(), function (skill) {
        if (skill.hasPoints()) {
          a.push(String.fromCharCode(skill.id + asciiOffset)); //convert skill id to letter of the alphabet
          if (skill.hasMultiplePoints()) {
            a.push(skill.points());
          } //only include points if they are > 1
        }
      });
      return ['', a.join(''), self.portrait(), self.avatarName()].join(hashDelimeter);
    });

    //update the address bar when the hash changes
    function useLastHash() {
      if (lastHash) {
        self.newbMode();
        var hashParts = lastHash.split(hashDelimeter);
        if (hashParts[2]) {
          self.portrait(Number(hashParts[2]));
        } //use the segment after the second delimeter as the portrait index
        if (hashParts[3]) {
          self.avatarName(hashParts[3]);
        } //use the segment after the third delimeter as the avatar name
        var s = hashParts[1]; //use the segment after the first delimeter as the skill hash
        var pairs = [];
        //break the hash back down into skill/value pairs, one character at a time
        var hashCharacters = s.split('');
        for (var i = 0; i < hashCharacters.length; i++) {
          if (!Number(hashCharacters[i])) { //if the current character is not a number,
            var skill = getSkillById(hashCharacters[i].charCodeAt(0) - asciiOffset); //convert the character to a skill id and look it up
            if (skill) {
              var points = Number(hashCharacters[i + 1]) || 1; //default to 1 point if the number is not specified next
              pairs.push({
                skill: skill
                , points: points
              });
            }
          }
        }
        //cycle through the whole list, adding points where possible, until the list is depleted
        var pointsWereAllocated = true; //flag
        var handlePair = function (pair) {
          if (!pair.wasAllocated && pair.skill.canAddPoints()) { //only add points once, and only where possible
            pair.skill.points(Math.min(pair.skill.maxPoints, pair.points)); //don't add more points than allowed
            pair.wasAllocated = true; //don't add this one again
            pointsWereAllocated = true;
          }
        };
        while (pointsWereAllocated) {
          pointsWereAllocated = false; //assume the list is depleted by default
          ko.utils.arrayForEach(pairs, handlePair);
        }
        do_update_hash = true;
      }
    }

    function updateHash(s) {
      window.location.hash = s || newHash;
    }

    var lastHash, usehash_timeout, newHash, update_hash_timeout, do_update_hash = true;
    self.useHash = function (hash) {
      lastHash = hash;
      clearTimeout(usehash_timeout);
      usehash_timeout = setTimeout(useLastHash, 50);
    };
    self.hash.subscribe(function (newValue) {
      if (do_update_hash) {
        newHash = newValue;
        clearTimeout(update_hash_timeout);
        update_hash_timeout = setTimeout(updateHash, 50);
      }
    });

    window.onhashchange = function () {
      self.useHash(window.location.hash.substr(1));
    };

    //Launch
    var current_hash = window.location.hash.substr(1);
    self.isOpen(current_hash !== ''); //If there is a hash, open the skill tree by default
    self.useHash(current_hash);

    self.clear = function(){
      window.location = '';
    };
    self.noAvatarName = ko.computed(function () {
      return !Boolean(self.avatarName() !== 'Name');
    });
    self.canShare = ko.computed(function () {
      return Boolean(self.avatarName() !== 'Name');
    });
  };

  return TalentTree;
});
<commit_msg>fix url problem<commit_after>define(['lib/knockout', 'scripts/Book', 'scripts/Link', 'scripts/Skill'],
  function(ko, Book, Link, Skill){
  'use strict';
  window.jiathis_config = {
    url: window.location.href,
    summary: '快来试试。',
    title: '我是 Level 1 Web Developer #Web技能树#',
    showClose: true,
    shortUrl: true,
    hideMore: false
  };

  var TalentTree = function(_e){
    var e = _e || {};
    var self = this;

    var asciiOffset = 96;
    var hashDelimeter = '_';

    var numPortraits = 1;

    self.isOpen = ko.observable(true);
    self.open = function () {
      self.isOpen(true);
    };
    self.close = function () {
      self.isOpen(false);
    };
    self.toggle = function () {
      self.isOpen(!self.isOpen());
    };

    //Mega skill list population
    self.skills = ko.observableArray(ko.utils.arrayMap(e.skills, function (item) {
      return new Skill(item);
    }));

    function getSkillById(id) {
      return ko.utils.arrayFirst(self.skills(), function (item) {
        return item.id === id;
      });
    }

    //Wire up dependency references
    ko.utils.arrayForEach(e.skills, function (item) {
      if (item.dependsOn) {
        var dependent = getSkillById(item.id);
        ko.utils.arrayForEach(item.dependsOn, function (dependencyId) {
          var dependency = getSkillById(dependencyId);
          dependent.dependencies.push(dependency);
          dependency.dependents.push(dependent);
        });
      }
    });

    //Avatar properties
    self.avatarName = ko.observable('Name');
    //level = total of all points spent
    self.level = ko.computed(function () {
      var totalSkillPoints = 0;
      ko.utils.arrayForEach(self.skills(), function (skill) {
        totalSkillPoints += skill.points();
      });
	    var totalLevel = totalSkillPoints + 1;
      window.jiathis_config.title = '@phodal 我是Level ' + totalLevel + ' Web Developer #Web技能树#';
      return totalLevel;
    });
    self.noPointsSpent = ko.computed(function () {
      return !Boolean(ko.utils.arrayFirst(self.skills(), function (skill) {
        return (skill.points() > 0);
      }));
    });
    self.stats = ko.computed(function () {
      //set some defaults
      var totals = {
        'Charisma': 9
        , 'Dexterity': 9
        , 'Fortitude': 9
        , 'Intellect': 9
        , 'Strength': 9
        , 'Wisdom': 9
      };
      //get all the skill name/value pairs and add/create them, using the stat name as the index
      ko.utils.arrayForEach(self.skills(), function (skill) {
        var p = skill.points();
        if (p > 0) {
          ko.utils.arrayForEach(skill.stats, function (stat) {
            var total = totals[stat.title] || 0;
            total += stat.value * p; //multiply the stat value by the points spent on the skill
            totals[stat.title] = total;
          });
        }
      });
      //Translate into a view-friendly array
      var result = [];
      for (var statName in totals) {
        result.push({
          title: statName
          , value: totals[statName]
        });
      }
      return result;
    });
    //String of unique characteristics, comma delimeted
    self.talentSummary = ko.computed(function () {
      var a = [];
      ko.utils.arrayForEach(self.skills(), function (skill) {
        if (skill.hasPoints()) {
          a = a.concat(skill.talents);
        }
      });
      window.jiathis_config.summary = '我获得的称号有: ' + a.join(', ');
      return a.join(', ');
    });
    //Portrait stuff
    self.portrait = ko.observable(Math.ceil(Math.random() * numPortraits));
    self.portraitURL = ko.computed(function () {
      return 'app/images/portraits/portrait-' + self.portrait() + '.jpg';
    });
    self.choosePreviousPortrait = function () {
      var n = self.portrait() - 1;
      if (n < 1) {
        n = numPortraits;
      }
      self.portrait(n);
    };
    self.chooseNextPortrait = function () {
      var n = self.portrait() + 1;
      if (n > numPortraits) {
        n = 1;
      }
      self.portrait(n);
    };

    //Utility functions
    self.newbMode = function () {
      ko.utils.arrayForEach(self.skills(), function (skill) {
        skill.points(0);
      });
    };
    self.godMode = function () {
      ko.utils.arrayForEach(self.skills(), function (skill) {
        skill.points(skill.maxPoints);
      });
    };

    //Hash functions
    self.hash = ko.computed(function () {
      var a = [];
      //compile a flat list of skill ids and values
      ko.utils.arrayForEach(self.skills(), function (skill) {
        if (skill.hasPoints()) {
          a.push(String.fromCharCode(skill.id + asciiOffset)); //convert skill id to letter of the alphabet
          if (skill.hasMultiplePoints()) {
            a.push(skill.points());
          } //only include points if they are > 1
        }
      });
      return ['', a.join(''), self.portrait(), self.avatarName()].join(hashDelimeter);
    });

    //update the address bar when the hash changes
    function useLastHash() {
      if (lastHash) {
        self.newbMode();
        var hashParts = lastHash.split(hashDelimeter);
        if (hashParts[2]) {
          self.portrait(Number(hashParts[2]));
        } //use the segment after the second delimeter as the portrait index
        if (hashParts[3]) {
          self.avatarName(hashParts[3]);
        } //use the segment after the third delimeter as the avatar name
        var s = hashParts[1]; //use the segment after the first delimeter as the skill hash
        var pairs = [];
        //break the hash back down into skill/value pairs, one character at a time
        var hashCharacters = s.split('');
        for (var i = 0; i < hashCharacters.length; i++) {
          if (!Number(hashCharacters[i])) { //if the current character is not a number,
            var skill = getSkillById(hashCharacters[i].charCodeAt(0) - asciiOffset); //convert the character to a skill id and look it up
            if (skill) {
              var points = Number(hashCharacters[i + 1]) || 1; //default to 1 point if the number is not specified next
              pairs.push({
                skill: skill
                , points: points
              });
            }
          }
        }
        //cycle through the whole list, adding points where possible, until the list is depleted
        var pointsWereAllocated = true; //flag
        var handlePair = function (pair) {
          if (!pair.wasAllocated && pair.skill.canAddPoints()) { //only add points once, and only where possible
            pair.skill.points(Math.min(pair.skill.maxPoints, pair.points)); //don't add more points than allowed
            pair.wasAllocated = true; //don't add this one again
            pointsWereAllocated = true;
          }
        };
        while (pointsWereAllocated) {
          pointsWereAllocated = false; //assume the list is depleted by default
          ko.utils.arrayForEach(pairs, handlePair);
        }
        do_update_hash = true;
      }
    }

    function updateHash(s) {
      window.location.hash = s || newHash;
      window.jiathis_config.url = window.location.href;
    }

    var lastHash, usehash_timeout, newHash, update_hash_timeout, do_update_hash = true;
    self.useHash = function (hash) {
      lastHash = hash;
      clearTimeout(usehash_timeout);
      usehash_timeout = setTimeout(useLastHash, 50);
    };
    self.hash.subscribe(function (newValue) {
      if (do_update_hash) {
        newHash = newValue;
        clearTimeout(update_hash_timeout);
        update_hash_timeout = setTimeout(updateHash, 50);
      }
    });

    window.onhashchange = function () {
      self.useHash(window.location.hash.substr(1));
    };

    //Launch
    var current_hash = window.location.hash.substr(1);
    self.isOpen(current_hash !== ''); //If there is a hash, open the skill tree by default
    self.useHash(current_hash);

    self.clear = function(){
      window.location = '';
    };
    self.noAvatarName = ko.computed(function () {
      return !Boolean(self.avatarName() !== 'Name');
    });
    self.canShare = ko.computed(function () {
      return Boolean(self.avatarName() !== 'Name');
    });
  };

  return TalentTree;
});
<|endoftext|>


<commit_before><commit_msg>apply 'hlsearchstyle' to frame documents<commit_after><|endoftext|>


<commit_before>const adb         = require(`adbkit`),
    consolePrefix = require(`console-prefix`);

/**
 * A device that can be communicated with via ADB.
 */
class Device {
    /**
     * Constructs a Device that will communicate with the specified device
     * using the specified client.
     *
     * @private
     * @param {Client} client The adbkit client that will be used to communicate
     * with the device. See {@link https://github.com/openstf/adbkit#client adbkit.Client}.
     * @param {AdbkitDevice} device The object used by adbkit to represent the
     * device. See {@link https://github.com/openstf/adbkit#clientlistdevicescallback adbkit.Client.listDevices}.
     */
    constructor (client, device) {
        this.client = client;
        this.id = device.id;
    }

    /**
     * Retrieves the phone number of the device.
     *
     * This function uses {@link http://adbshell.com/commands/adb-shell-dumpstate dumpstate},
     * which may not be reliable a reliable method of retrieving the phone
     * number, depending on your device model. For that reason this function
     * should be considered experimental, and should only be used if you know
     * that dumpstate can be used to retrieve your device's phone number.
     *
     * Note that the promise will take a few minutes to resolve, due to the
     * size of dumpstate's output.
     *
     * @return {Promise<String>} A promise that resolves with the device's
     * phone number.
     */
    getPhoneNumber () {
        return this.shell(`dumpstate`).
            then(output => {
                const none        = 0,
                    afterAreaCode = 3,
                    afterOffice   = 7,
                    phone         = Array.from(/mMdn=(\d{10})/.exec(output)[1]);

                phone.splice(afterAreaCode, none, `-`);
                phone.splice(afterOffice,   none, `-`);

                return this.phoneNumber = phone.join(``);
            });
    }

    /**
     * Installs the specified apk file onto the device.
     *
     * @param {String|Stream} apk The apk file to install. See
     * {@link https://github.com/openstf/adbkit#clientinstallserial-apk-callback adbkit.Client.install}.
     * @return {Promise<Boolean>} A promise that resolves with true when the app
     * is successfully installed.
     */
    install (apk) {
        return this.client.install(this.id, apk);
    }

    /**
     * Runs the specified shell command on the device. See the official
     * {@link https://developer.android.com/studio/command-line/shell.html ADB shell documentation}
     * for valid commands.
     *
     * @param {String} command The command to run.
     * @return {Promise<String>} A promise that resolves with the string output
     * of the command when the command finishes.
     */
    shell (command) {
        return this.client.shell(this.id, command).
            then(adb.util.readAll).
            then(output => String(output));
    }

    /**
     * Uninstalls the specified package from the device.
     *
     * @param {String} pkg The name of the package to uninstall.
     * @return {Promise<Boolean>} A promise that resolves with true when the
     * package is finished uninstalling.
     */
    uninstall (pkg) {
        const id = this.id;

        consolePrefix(id).log(`uninstalling ${pkg}`);

        return this.client.uninstall(id, pkg);
    }
}

module.exports = Device;<commit_msg>Mark private Device constructor with #jsdoc-ignore<commit_after>const adb         = require(`adbkit`),
    consolePrefix = require(`console-prefix`);

/**
 * A device that can be communicated with via ADB.
 */
class Device {
    /**
     * Constructs a Device that will communicate with the specified device
     * using the specified client.
     *
     * #jsdoc-ignore
     *
     * @private
     * @param {Client} client The adbkit client that will be used to communicate
     * with the device. See {@link https://github.com/openstf/adbkit#client adbkit.Client}.
     * @param {AdbkitDevice} device The object used by adbkit to represent the
     * device. See {@link https://github.com/openstf/adbkit#clientlistdevicescallback adbkit.Client.listDevices}.
     */
    constructor (client, device) {
        this.client = client;
        this.id = device.id;
    }

    /**
     * Retrieves the phone number of the device.
     *
     * This function uses {@link http://adbshell.com/commands/adb-shell-dumpstate dumpstate},
     * which may not be reliable a reliable method of retrieving the phone
     * number, depending on your device model. For that reason this function
     * should be considered experimental, and should only be used if you know
     * that dumpstate can be used to retrieve your device's phone number.
     *
     * Note that the promise will take a few minutes to resolve, due to the
     * size of dumpstate's output.
     *
     * @return {Promise<String>} A promise that resolves with the device's
     * phone number.
     */
    getPhoneNumber () {
        return this.shell(`dumpstate`).
            then(output => {
                const none        = 0,
                    afterAreaCode = 3,
                    afterOffice   = 7,
                    phone         = Array.from(/mMdn=(\d{10})/.exec(output)[1]);

                phone.splice(afterAreaCode, none, `-`);
                phone.splice(afterOffice,   none, `-`);

                return this.phoneNumber = phone.join(``);
            });
    }

    /**
     * Installs the specified apk file onto the device.
     *
     * @param {String|Stream} apk The apk file to install. See
     * {@link https://github.com/openstf/adbkit#clientinstallserial-apk-callback adbkit.Client.install}.
     * @return {Promise<Boolean>} A promise that resolves with true when the app
     * is successfully installed.
     */
    install (apk) {
        return this.client.install(this.id, apk);
    }

    /**
     * Runs the specified shell command on the device. See the official
     * {@link https://developer.android.com/studio/command-line/shell.html ADB shell documentation}
     * for valid commands.
     *
     * @param {String} command The command to run.
     * @return {Promise<String>} A promise that resolves with the string output
     * of the command when the command finishes.
     */
    shell (command) {
        return this.client.shell(this.id, command).
            then(adb.util.readAll).
            then(output => String(output));
    }

    /**
     * Uninstalls the specified package from the device.
     *
     * @param {String} pkg The name of the package to uninstall.
     * @return {Promise<Boolean>} A promise that resolves with true when the
     * package is finished uninstalling.
     */
    uninstall (pkg) {
        const id = this.id;

        consolePrefix(id).log(`uninstalling ${pkg}`);

        return this.client.uninstall(id, pkg);
    }
}

module.exports = Device;<|endoftext|>


<commit_before>define(function (require) {

    "use strict";

    var $           = require('jquery'),
        Backbone    = require('backbone'),
        ShellView   = require('app/views/Shell'),
        HomeView    = require('app/views/Home'),

        $body = $('body'),
        shellView = new ShellView({el: $body}).render(),
        $content = $("#content", shellView.el),
        homeView = new HomeView({el: $content});

    return Backbone.Router.extend({

        routes: {
            "": "home",
            "contact": "contact",
            "about": "about",
        },

        home: function () {
            console.log("On home view");
            homeView.delegateEvents(); // delegate events when the view is recycled
            homeView.render();
            shellView.selectMenuItem('home-menu');
        },

        contact: function () {
            require(["app/views/Contact"], function (ContactView) {
                var view = new ContactView({el: $content});
                view.render();
                shellView.selectMenuItem('contact-menu');
            });
        },

        about: function () {
            require(["app/views/About"], function (AboutView) {
                var view = new AboutView({el: $content});
                view.render();
                shellView.selectMenuItem('about-menu');
            });
        },

    });

});
<commit_msg>Removing delegateEvents b/c it's loaded by default<commit_after>define(function (require) {

    "use strict";

    var $           = require('jquery'),
        Backbone    = require('backbone'),
        ShellView   = require('app/views/Shell'),
        HomeView    = require('app/views/Home'),

        $body = $('body'),
        shellView = new ShellView({el: $body}).render(),
        $content = $("#content", shellView.el),
        homeView = new HomeView({el: $content});

    return Backbone.Router.extend({

        routes: {
            "": "home",
            "contact": "contact",
            "about": "about",
        },

        home: function () {
            console.log("On home view");
            homeView.render();
            shellView.selectMenuItem('home-menu');
        },

        contact: function () {
            require(["app/views/Contact"], function (ContactView) {
                var view = new ContactView({el: $content});
                view.render();
                shellView.selectMenuItem('contact-menu');
            });
        },

        about: function () {
            require(["app/views/About"], function (AboutView) {
                var view = new AboutView({el: $content});
                view.render();
                shellView.selectMenuItem('about-menu');
            });
        },

    });

});
<|endoftext|>


<commit_before>import { prepareGraphQlBody } from './adapter/graphql/gqlQuery'
import { prepareElasticsearchQueryBody } from './adapter/api/elasticsearchQuery'
import { currentStoreView, prepareStoreView } from '../multistore'
import hash from 'object-hash'
import config from 'config'
import fetch from 'isomorphic-fetch'
import handleGqlResult from './adapter/graphql/handleGqlResult'
import handleEsResult from './adapter/api/handleEsResult'

function isOnline () {
  if (typeof navigator !== 'undefined') {
    return navigator.onLine
  } else {
    return true // SSR
  }
}

/**
 * Execute GraphQl query
 */
function searchGql (Query) {
  const gqlQueryBody = prepareGraphQlBody(Query)

  const storeView = (Query.store === null) ? currentStoreView() : prepareStoreView(Query.store, config)

  if (storeView.storeId === undefined || storeView.storeId == null || !Query.type) {
    throw new Error('Store and Query.type are required arguments for executing Graphql query')
  }

  let urlGql = config.server.protocol + '://' + config.graphql.host + ':' + config.graphql.port + '/graphql'
  urlGql = urlGql + '/' + encodeURIComponent(storeView.storeId) + '/'

  return fetch(urlGql, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    },
    body: gqlQueryBody
  })
    .then(resp => {
      return resp.json()
    })
}

/**
 * Execute ElasticSearch query
 */
function searchES (Query) {
  const buildURLQuery = obj => Object.entries(obj).map(pair => pair.map(encodeURIComponent).join('=')).join('&')

  let ElasticsearchQueryBody = prepareElasticsearchQueryBody(Query.searchQuery)

  const storeView = (Query.store === null) ? currentStoreView() : prepareStoreView(Query.store, config)

  Query.index = storeView.elasticsearch.index

  let url = storeView.elasticsearch.host
  if (!url.startsWith('http')) {
    url = 'http://' + url
  }
  const httpQuery = {
    'size': Query.size,
    'from': Query.from,
    'sort': Query.sort
  }
  if (Query._sourceExclude) {
    httpQuery._source_exclude = Query._sourceExclude.join(',')
  }
  if (Query._sourceInclude) {
    httpQuery._source_include = Query._sourceInclude.join(',')
  }
  if (Query.q) {
    httpQuery.q = Query.q
  }

  if (!Query.index || !Query.type) {
    throw new Error('Query.index and Query.type are required arguments for executing ElasticSearch query')
  }

  url = url + '/' + encodeURIComponent(Query.index) + '/' + encodeURIComponent(Query.type) + '/_search'
  url = url + '?' + buildURLQuery(httpQuery)

  return fetch(url, { method: 'POST',
    mode: 'cors',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(ElasticsearchQueryBody)
  }).then(resp => { return resp.json() })
}

/**
 * Execute search query
 */
/* function search (Query) {
  if ((config.server.api === 'graphql' || Query.searchQuery.getSearchText() !== '') && Query.type === 'product') {
    return searchGql(Query)
  } else {
    return searchES(Query)
  }
}
*/
/**
 * Helper function to handle ElasticSearch Results
 * @param {Object} resp result from ES call
 * @param {Int} start pagination data
 * @param {Int} size pagination data
 */
/* function _handleResult (resp, start = 0, size = 50) {
  console.log(resp)
  if (config.server.api === 'graphql' || resp.hasOwnProperty('data')) {
    return _handleGqlResult(resp, start, size)
    // return _handleEsResult(resp.data.products, start, size)
  } else {
    return _handleEsResult(resp, start, size)
  }
}
*/

/**
 * Search ElasticSearch catalog of products using simple text query
 * Use bodybuilder to build the query, aggregations etc: http://bodybuilder.js.org/
 * @param {Object} searchQuery query object
 * @param {Int} start start index
 * @param {Int} size page size
 * @return {Promise}
 */
export function quickSearchByQueryObj ({ searchQuery, start = 0, size = 50, entityType = 'product', sort = '', storeCode = null, excludeFields = null, includeFields = null }) {
  size = parseInt(size)
  if (size <= 0) size = 50
  if (start < 0) start = 0

  return new Promise((resolve, reject) => {
    const Query = {
      store: storeCode, // TODO: add grouped prodduct and bundled product support
      type: entityType,
      searchQuery: searchQuery,
      size: size,
      from: start,
      sort: sort
    }

    if (excludeFields) Query._sourceExclude = excludeFields
    if (includeFields) Query._sourceInclude = includeFields
    const cache = global.$VS.db.elasticCacheCollection // switch to appcache?
    const cacheKey = hash(Query)
    let servedFromCache = false
    const benchmarkTime = new Date()
    cache.getItem(cacheKey, (err, res) => {
      if (err) {
        console.log(err)
      }

      if (res !== null) {
        res.cache = true
        res.noresults = false
        res.offline = !isOnline() // TODO: refactor it to checking ES heartbit
        resolve(res)
        console.debug('Result from cache for ' + cacheKey + ' (' + entityType + '), ms=' + (new Date().getTime() - benchmarkTime.getTime()))
        servedFromCache = true
      } else {
        if (!isOnline()) {
          console.debug('No results and offline ' + cacheKey + ' (' + entityType + '), ms=' + (new Date().getTime() - benchmarkTime.getTime()))

          res = {
            items: [],
            total: 0,
            start: 0,
            perPage: 0,
            aggregations: {},
            offline: true,
            cache: true,
            noresults: true
          }

          servedFromCache = true
          resolve(res)
        }
      }
    }).catch((err) => { console.error('Cannot read cache for ' + cacheKey + ', ' + err) })

    if ((config.server.api === 'graphql' || Query.searchQuery.getSearchText() !== '') && Query.type === 'product') {
      // Use test graphql for simple product search
      searchGql(Query).then(function (resp) { // we're always trying to populate cache - when online
        const res = handleGqlResult(resp, Query.type, start, size)
        cache.setItem(cacheKey, res).catch((err) => { console.error('Cannot store cache for ' + cacheKey + ', ' + err) })
        if (!servedFromCache) { // if navigator onLine == false means ES is unreachable and probably this will return false; sometimes returned false faster than indexedDb cache returns result ...
          console.debug('Result from ES for ' + cacheKey + ' (' + entityType + '),  ms=' + (new Date().getTime() - benchmarkTime.getTime()))
          res.cache = false
          res.noresults = false
          res.offline = false
          resolve(res)
        }
      }).catch(function (err) {
        // reject(err)
        console.error(err)
      })
    } else {
      // Use test graphql for simple product search
      searchES(Query).then(function (resp) { // we're always trying to populate cache - when online
        const res = handleEsResult(resp, start, size)
        cache.setItem(cacheKey, res).catch((err) => { console.error('Cannot store cache for ' + cacheKey + ', ' + err) })
        if (!servedFromCache) { // if navigator onLine == false means ES is unreachable and probably this will return false; sometimes returned false faster than indexedDb cache returns result ...
          console.debug('Result from ES for ' + cacheKey + ' (' + entityType + '),  ms=' + (new Date().getTime() - benchmarkTime.getTime()))
          res.cache = false
          res.noresults = false
          res.offline = false
          resolve(res)
        }
      }).catch(function (err) {
        // reject(err)
        console.error(err)
      })
    }
  })
}
<commit_msg>MTH-250 temporary add checking for attribute type<commit_after>import { prepareGraphQlBody } from './adapter/graphql/gqlQuery'
import { prepareElasticsearchQueryBody } from './adapter/api/elasticsearchQuery'
import { currentStoreView, prepareStoreView } from '../multistore'
import hash from 'object-hash'
import config from 'config'
import fetch from 'isomorphic-fetch'
import handleGqlResult from './adapter/graphql/handleGqlResult'
import handleEsResult from './adapter/api/handleEsResult'

function isOnline () {
  if (typeof navigator !== 'undefined') {
    return navigator.onLine
  } else {
    return true // SSR
  }
}

/**
 * Execute GraphQl query
 */
function searchGql (Query) {
  const gqlQueryBody = prepareGraphQlBody(Query)

  const storeView = (Query.store === null) ? currentStoreView() : prepareStoreView(Query.store, config)

  if (storeView.storeId === undefined || storeView.storeId == null || !Query.type) {
    throw new Error('Store and Query.type are required arguments for executing Graphql query')
  }

  let urlGql = config.server.protocol + '://' + config.graphql.host + ':' + config.graphql.port + '/graphql'
  urlGql = urlGql + '/' + encodeURIComponent(storeView.storeId) + '/'

  return fetch(urlGql, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    },
    body: gqlQueryBody
  })
    .then(resp => {
      return resp.json()
    })
}

/**
 * Execute ElasticSearch query
 */
function searchES (Query) {
  const buildURLQuery = obj => Object.entries(obj).map(pair => pair.map(encodeURIComponent).join('=')).join('&')

  let ElasticsearchQueryBody = prepareElasticsearchQueryBody(Query.searchQuery)

  const storeView = (Query.store === null) ? currentStoreView() : prepareStoreView(Query.store, config)

  Query.index = storeView.elasticsearch.index

  let url = storeView.elasticsearch.host
  if (!url.startsWith('http')) {
    url = 'http://' + url
  }
  const httpQuery = {
    'size': Query.size,
    'from': Query.from,
    'sort': Query.sort
  }
  if (Query._sourceExclude) {
    httpQuery._source_exclude = Query._sourceExclude.join(',')
  }
  if (Query._sourceInclude) {
    httpQuery._source_include = Query._sourceInclude.join(',')
  }
  if (Query.q) {
    httpQuery.q = Query.q
  }

  if (!Query.index || !Query.type) {
    throw new Error('Query.index and Query.type are required arguments for executing ElasticSearch query')
  }

  url = url + '/' + encodeURIComponent(Query.index) + '/' + encodeURIComponent(Query.type) + '/_search'
  url = url + '?' + buildURLQuery(httpQuery)

  return fetch(url, { method: 'POST',
    mode: 'cors',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(ElasticsearchQueryBody)
  }).then(resp => { return resp.json() })
}

/**
 * Execute search query
 */
/* function search (Query) {
  if ((config.server.api === 'graphql' || Query.searchQuery.getSearchText() !== '') && Query.type === 'product') {
    return searchGql(Query)
  } else {
    return searchES(Query)
  }
}
*/
/**
 * Helper function to handle ElasticSearch Results
 * @param {Object} resp result from ES call
 * @param {Int} start pagination data
 * @param {Int} size pagination data
 */
/* function _handleResult (resp, start = 0, size = 50) {
  console.log(resp)
  if (config.server.api === 'graphql' || resp.hasOwnProperty('data')) {
    return _handleGqlResult(resp, start, size)
    // return _handleEsResult(resp.data.products, start, size)
  } else {
    return _handleEsResult(resp, start, size)
  }
}
*/

/**
 * Search ElasticSearch catalog of products using simple text query
 * Use bodybuilder to build the query, aggregations etc: http://bodybuilder.js.org/
 * @param {Object} searchQuery query object
 * @param {Int} start start index
 * @param {Int} size page size
 * @return {Promise}
 */
export function quickSearchByQueryObj ({ searchQuery, start = 0, size = 50, entityType = 'product', sort = '', storeCode = null, excludeFields = null, includeFields = null }) {
  size = parseInt(size)
  if (size <= 0) size = 50
  if (start < 0) start = 0

  return new Promise((resolve, reject) => {
    const Query = {
      store: storeCode, // TODO: add grouped prodduct and bundled product support
      type: entityType,
      searchQuery: searchQuery,
      size: size,
      from: start,
      sort: sort
    }

    if (excludeFields) Query._sourceExclude = excludeFields
    if (includeFields) Query._sourceInclude = includeFields
    const cache = global.$VS.db.elasticCacheCollection // switch to appcache?
    const cacheKey = hash(Query)
    let servedFromCache = false
    const benchmarkTime = new Date()
    cache.getItem(cacheKey, (err, res) => {
      if (err) {
        console.log(err)
      }

      if (res !== null) {
        res.cache = true
        res.noresults = false
        res.offline = !isOnline() // TODO: refactor it to checking ES heartbit
        resolve(res)
        console.debug('Result from cache for ' + cacheKey + ' (' + entityType + '), ms=' + (new Date().getTime() - benchmarkTime.getTime()))
        servedFromCache = true
      } else {
        if (!isOnline()) {
          console.debug('No results and offline ' + cacheKey + ' (' + entityType + '), ms=' + (new Date().getTime() - benchmarkTime.getTime()))

          res = {
            items: [],
            total: 0,
            start: 0,
            perPage: 0,
            aggregations: {},
            offline: true,
            cache: true,
            noresults: true
          }

          servedFromCache = true
          resolve(res)
        }
      }
    }).catch((err) => { console.error('Cannot read cache for ' + cacheKey + ', ' + err) })

    if ((config.server.api === 'graphql' || Query.searchQuery.getSearchText() !== '') && (Query.type === 'product' || Query.type === 'attribute')) {
      // Use test graphql for simple product search
      searchGql(Query).then(function (resp) { // we're always trying to populate cache - when online
        const res = handleGqlResult(resp, Query.type, start, size)
        cache.setItem(cacheKey, res).catch((err) => { console.error('Cannot store cache for ' + cacheKey + ', ' + err) })
        if (!servedFromCache) { // if navigator onLine == false means ES is unreachable and probably this will return false; sometimes returned false faster than indexedDb cache returns result ...
          console.debug('Result from ES for ' + cacheKey + ' (' + entityType + '),  ms=' + (new Date().getTime() - benchmarkTime.getTime()))
          res.cache = false
          res.noresults = false
          res.offline = false
          resolve(res)
        }
      }).catch(function (err) {
        // reject(err)
        console.error(err)
      })
    } else {
      // Use test graphql for simple product search
      searchES(Query).then(function (resp) { // we're always trying to populate cache - when online
        const res = handleEsResult(resp, start, size)
        cache.setItem(cacheKey, res).catch((err) => { console.error('Cannot store cache for ' + cacheKey + ', ' + err) })
        if (!servedFromCache) { // if navigator onLine == false means ES is unreachable and probably this will return false; sometimes returned false faster than indexedDb cache returns result ...
          console.debug('Result from ES for ' + cacheKey + ' (' + entityType + '),  ms=' + (new Date().getTime() - benchmarkTime.getTime()))
          res.cache = false
          res.noresults = false
          res.offline = false
          resolve(res)
        }
      }).catch(function (err) {
        // reject(err)
        console.error(err)
      })
    }
  })
}
<|endoftext|>


<commit_before><commit_msg>Code Quiz: Initialize attempts' status in AttemptStore constructor function.<commit_after><|endoftext|>


<commit_before>#!/usr/bin/env sh
':' //; exec "$(command -v nodejs || command -v node)" "$0" "$@"

/** @license
 * This file is part of the Game Closure SDK.
 *
 * The Game Closure SDK is free software: you can redistribute it and/or modify
 * it under the terms of the Mozilla Public License v. 2.0 as published by
 * Mozilla.
 *
 * The Game Closure SDK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * Mozilla Public License v. 2.0 for more details.
 *
 * You should have received a copy of the Mozilla Public License v. 2.0
 * along with the Game Closure SDK.  If not, see <http://mozilla.org/MPL/2.0/>.
 */

process.title = 'devkit';

/**
 * Command line interface.
 */

/* jshint -W020 */
require = require('jsio');
/* jshint +W020 */

require('./globals');

var logging = require('./util/logging');
var logger = logging.get('devkit');

/**
 * Module API.
 */

// Command line invocation.
if (require.main === module) {
  main();
}

function main () {
  var commands = require('./commands');
  var argv = commands.argv;
  var args = argv._;

  var name;

  if (argv.help) {
    name = 'help';
  } else if (commands.has(args[0])) {
    name = args.shift();
  } else if (argv.version) {
    name = 'version';
  } else if (argv.info) {
    name = 'info';
  } else {
    name = 'help';
  }

  var command = commands.get(name);
  command.exec(name, args);
}
<commit_msg>don't overwrite require, don't import logging unnecessarily<commit_after>#!/usr/bin/env sh
':' //; exec "$(command -v nodejs || command -v node)" "$0" "$@"

/** @license
 * This file is part of the Game Closure SDK.
 *
 * The Game Closure SDK is free software: you can redistribute it and/or modify
 * it under the terms of the Mozilla Public License v. 2.0 as published by
 * Mozilla.
 *
 * The Game Closure SDK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * Mozilla Public License v. 2.0 for more details.
 *
 * You should have received a copy of the Mozilla Public License v. 2.0
 * along with the Game Closure SDK.  If not, see <http://mozilla.org/MPL/2.0/>.
 */

process.title = 'devkit';

/**
 * Command line interface.
 */

/* jshint -W020 */
require('jsio');
/* jshint +W020 */

require('./globals');

/**
 * Module API.
 */

// Command line invocation.
if (require.main === module) {
  main();
}

function main () {
  var commands = require('./commands');
  var argv = commands.argv;
  var args = argv._;

  var name;

  if (argv.help) {
    name = 'help';
  } else if (commands.has(args[0])) {
    name = args.shift();
  } else if (argv.version) {
    name = 'version';
  } else if (argv.info) {
    name = 'info';
  } else {
    name = 'help';
  }

  var command = commands.get(name);
  command.exec(name, args);
}
<|endoftext|>


<commit_before>#!/usr/bin/env node
/**
 * Copyright 2018 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const URL = require('url');
const debug = require('debug');
const pptr = require('..');
const browserFetcher = pptr.createBrowserFetcher();
const path = require('path');
const fs = require('fs');
const {fork} = require('child_process');

const COLOR_RESET = '\x1b[0m';
const COLOR_RED = '\x1b[31m';
const COLOR_GREEN = '\x1b[32m';
const COLOR_YELLOW = '\x1b[33m';

const argv = require('minimist')(process.argv.slice(2), {});

const help = `
Usage:
  node bisect.js --good <revision> --bad <revision> <script>

Parameters:
  --good    revision that is known to be GOOD
  --bad     revision that is known to be BAD
  <script>  path to the script that returns non-zero code for BAD revisions and 0 for good

Example:
  node utils/bisect.js --good 577361 --bad 599821 simple.js
`;

if (argv.h || argv.help) {
  console.log(help);
  process.exit(0);
}

if (typeof argv.good !== 'number') {
  console.log(COLOR_RED + 'ERROR: expected --good argument to be a number' + COLOR_RESET);
  console.log(help);
  process.exit(1);
}

if (typeof argv.bad !== 'number') {
  console.log(COLOR_RED + 'ERROR: expected --bad argument to be a number' + COLOR_RESET);
  console.log(help);
  process.exit(1);
}

const scriptPath = path.resolve(argv._[0]);
if (!fs.existsSync(scriptPath)) {
  console.log(COLOR_RED + 'ERROR: Expected to be given a path to a script to run' + COLOR_RESET);
  console.log(help);
  process.exit(1);
}

(async(scriptPath, good, bad) => {
  const span = Math.abs(good - bad);
  console.log(`Bisecting ${COLOR_YELLOW}${span}${COLOR_RESET} revisions in ${COLOR_YELLOW}~${span.toString(2).length}${COLOR_RESET} iterations`);

  while (true) {
    const middle = Math.round((good + bad) / 2);
    const revision = await findDownloadableRevision(middle, good, bad);
    if (!revision || revision === good || revision === bad)
      break;
    let info = browserFetcher.revisionInfo(revision);
    const shouldRemove = !info.local;
    info = await downloadRevision(revision);
    const exitCode = await runScript(scriptPath, info);
    if (shouldRemove)
      await browserFetcher.remove(revision);
    let outcome;
    if (exitCode) {
      bad = revision;
      outcome = COLOR_RED + 'BAD' + COLOR_RESET;
    } else {
      good = revision;
      outcome = COLOR_GREEN + 'GOOD' + COLOR_RESET;
    }
    const span = Math.abs(good - bad);
    console.log(`- ${COLOR_YELLOW}r${revision}${COLOR_RESET} was ${outcome}. Bisecting [${good}, ${bad}] - ${COLOR_YELLOW}${span}${COLOR_RESET} revisions and ${COLOR_YELLOW}~${span.toString(2).length}${COLOR_RESET} iterations`);
  }

  const [goodSha, badSha] = await Promise.all([
    revisionToSha(good),
    revisionToSha(bad),
  ]);
  console.log(`RANGE: https://chromium.googlesource.com/chromium/src/+log/${goodSha}..${badSha}`);
})(scriptPath, argv.good, argv.bad);

function runScript(scriptPath, revisionInfo) {
  const log = debug('bisect:runscript');
  log('Running script');
  const child = fork(scriptPath, [], {
    stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
    env: {
      PUPPETEER_EXECUTABLE_PATH: revisionInfo.executablePath,
    },
  });
  return new Promise((resolve, reject) => {
    child.on('error', err => reject(err));
    child.on('exit', code => resolve(code));
  });
}

async function downloadRevision(revision) {
  const log = debug('bisect:download');
  log(`Downloading ${revision}`);
  let progressBar = null;
  let lastDownloadedBytes = 0;
  return await browserFetcher.download(revision, (downloadedBytes, totalBytes) => {
    if (!progressBar) {
      const ProgressBar = require('progress');
      progressBar = new ProgressBar(`- downloading Chromium r${revision} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `, {
        complete: '=',
        incomplete: ' ',
        width: 20,
        total: totalBytes,
      });
    }
    const delta = downloadedBytes - lastDownloadedBytes;
    lastDownloadedBytes = downloadedBytes;
    progressBar.tick(delta);
  });
  function toMegabytes(bytes) {
    const mb = bytes / 1024 / 1024;
    return `${Math.round(mb * 10) / 10} Mb`;
  }
}

async function findDownloadableRevision(rev, from, to) {
  const log = debug('bisect:findrev');
  const min = Math.min(from, to);
  const max = Math.max(from, to);
  log(`Looking around ${rev} from [${min}, ${max}]`);
  if (await browserFetcher.canDownload(rev))
    return rev;
  let down = rev;
  let up = rev;
  while (min <= down || up <= max) {
    const [downOk, upOk] = await Promise.all([
      down > min ? probe(--down) : Promise.resolve(false),
      up < max ? probe(++up) : Promise.resolve(false),
    ]);
    if (downOk)
      return down;
    if (upOk)
      return up;
  }
  return null;

  async function probe(rev) {
    const result = await browserFetcher.canDownload(rev);
    log(`  ${rev} - ${result ? 'OK' : 'missing'}`);
    return result;
  }
}

async function revisionToSha(revision) {
  const json = await fetchJSON('https://cr-rev.appspot.com/_ah/api/crrev/v1/redirect/' + revision);
  return json.git_sha;
}

function fetchJSON(url) {
  return new Promise((resolve, reject) => {
    const agent = url.startsWith('https://') ? require('https') : require('http');
    const options = URL.parse(url);
    options.method = 'GET';
    options.headers = {
      'Content-Type': 'application/json'
    };
    const req = agent.request(options, function(res) {
      let result = '';
      res.setEncoding('utf8');
      res.on('data', chunk => result += chunk);
      res.on('end', () => resolve(JSON.parse(result)));
    });
    req.on('error', err => reject(err));
    req.end();
  });
}
<commit_msg>chore(bisect): properly output bisect ranges + output styling (#3523)<commit_after>#!/usr/bin/env node
/**
 * Copyright 2018 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const URL = require('url');
const debug = require('debug');
const pptr = require('..');
const browserFetcher = pptr.createBrowserFetcher();
const path = require('path');
const fs = require('fs');
const {fork} = require('child_process');

const COLOR_RESET = '\x1b[0m';
const COLOR_RED = '\x1b[31m';
const COLOR_GREEN = '\x1b[32m';
const COLOR_YELLOW = '\x1b[33m';

const argv = require('minimist')(process.argv.slice(2), {});

const help = `
Usage:
  node bisect.js --good <revision> --bad <revision> <script>

Parameters:
  --good    revision that is known to be GOOD
  --bad     revision that is known to be BAD
  <script>  path to the script that returns non-zero code for BAD revisions and 0 for good

Example:
  node utils/bisect.js --good 577361 --bad 599821 simple.js
`;

if (argv.h || argv.help) {
  console.log(help);
  process.exit(0);
}

if (typeof argv.good !== 'number') {
  console.log(COLOR_RED + 'ERROR: expected --good argument to be a number' + COLOR_RESET);
  console.log(help);
  process.exit(1);
}

if (typeof argv.bad !== 'number') {
  console.log(COLOR_RED + 'ERROR: expected --bad argument to be a number' + COLOR_RESET);
  console.log(help);
  process.exit(1);
}

const scriptPath = path.resolve(argv._[0]);
if (!fs.existsSync(scriptPath)) {
  console.log(COLOR_RED + 'ERROR: Expected to be given a path to a script to run' + COLOR_RESET);
  console.log(help);
  process.exit(1);
}

(async(scriptPath, good, bad) => {
  const span = Math.abs(good - bad);
  console.log(`Bisecting ${COLOR_YELLOW}${span}${COLOR_RESET} revisions in ${COLOR_YELLOW}~${span.toString(2).length}${COLOR_RESET} iterations`);

  while (true) {
    const middle = Math.round((good + bad) / 2);
    const revision = await findDownloadableRevision(middle, good, bad);
    if (!revision || revision === good || revision === bad)
      break;
    let info = browserFetcher.revisionInfo(revision);
    const shouldRemove = !info.local;
    info = await downloadRevision(revision);
    const exitCode = await runScript(scriptPath, info);
    if (shouldRemove)
      await browserFetcher.remove(revision);
    let outcome;
    if (exitCode) {
      bad = revision;
      outcome = COLOR_RED + 'BAD' + COLOR_RESET;
    } else {
      good = revision;
      outcome = COLOR_GREEN + 'GOOD' + COLOR_RESET;
    }
    const span = Math.abs(good - bad);
    let fromText = '';
    let toText = '';
    if (good < bad) {
      fromText = COLOR_GREEN + good + COLOR_RESET;
      toText = COLOR_RED + bad + COLOR_RESET;
    } else {
      fromText = COLOR_RED + bad + COLOR_RESET;
      toText = COLOR_GREEN + good + COLOR_RESET;
    }
    console.log(`- ${COLOR_YELLOW}r${revision}${COLOR_RESET} was ${outcome}. Bisecting [${fromText}, ${toText}] - ${COLOR_YELLOW}${span}${COLOR_RESET} revisions and ${COLOR_YELLOW}~${span.toString(2).length}${COLOR_RESET} iterations`);
  }

  const [fromSha, toSha] = await Promise.all([
    revisionToSha(Math.min(good, bad)),
    revisionToSha(Math.max(good, bad)),
  ]);
  console.log(`RANGE: https://chromium.googlesource.com/chromium/src/+log/${fromSha}..${toSha}`);
})(scriptPath, argv.good, argv.bad);

function runScript(scriptPath, revisionInfo) {
  const log = debug('bisect:runscript');
  log('Running script');
  const child = fork(scriptPath, [], {
    stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
    env: {
      PUPPETEER_EXECUTABLE_PATH: revisionInfo.executablePath,
    },
  });
  return new Promise((resolve, reject) => {
    child.on('error', err => reject(err));
    child.on('exit', code => resolve(code));
  });
}

async function downloadRevision(revision) {
  const log = debug('bisect:download');
  log(`Downloading ${revision}`);
  let progressBar = null;
  let lastDownloadedBytes = 0;
  return await browserFetcher.download(revision, (downloadedBytes, totalBytes) => {
    if (!progressBar) {
      const ProgressBar = require('progress');
      progressBar = new ProgressBar(`- downloading Chromium r${revision} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `, {
        complete: '=',
        incomplete: ' ',
        width: 20,
        total: totalBytes,
      });
    }
    const delta = downloadedBytes - lastDownloadedBytes;
    lastDownloadedBytes = downloadedBytes;
    progressBar.tick(delta);
  });
  function toMegabytes(bytes) {
    const mb = bytes / 1024 / 1024;
    return `${Math.round(mb * 10) / 10} Mb`;
  }
}

async function findDownloadableRevision(rev, from, to) {
  const log = debug('bisect:findrev');
  const min = Math.min(from, to);
  const max = Math.max(from, to);
  log(`Looking around ${rev} from [${min}, ${max}]`);
  if (await browserFetcher.canDownload(rev))
    return rev;
  let down = rev;
  let up = rev;
  while (min <= down || up <= max) {
    const [downOk, upOk] = await Promise.all([
      down > min ? probe(--down) : Promise.resolve(false),
      up < max ? probe(++up) : Promise.resolve(false),
    ]);
    if (downOk)
      return down;
    if (upOk)
      return up;
  }
  return null;

  async function probe(rev) {
    const result = await browserFetcher.canDownload(rev);
    log(`  ${rev} - ${result ? 'OK' : 'missing'}`);
    return result;
  }
}

async function revisionToSha(revision) {
  const json = await fetchJSON('https://cr-rev.appspot.com/_ah/api/crrev/v1/redirect/' + revision);
  return json.git_sha;
}

function fetchJSON(url) {
  return new Promise((resolve, reject) => {
    const agent = url.startsWith('https://') ? require('https') : require('http');
    const options = URL.parse(url);
    options.method = 'GET';
    options.headers = {
      'Content-Type': 'application/json'
    };
    const req = agent.request(options, function(res) {
      let result = '';
      res.setEncoding('utf8');
      res.on('data', chunk => result += chunk);
      res.on('end', () => resolve(JSON.parse(result)));
    });
    req.on('error', err => reject(err));
    req.end();
  });
}
<|endoftext|>