<commit_before>var CACHENAME = 'shapeclear';
var VERSION = 'v0.7.0';
var myPath = location.origin + location.pathname;
myPath = myPath.match(/(.*\/)/)[1];

self.addEventListener('fetch', function(event) {
    if (event.request) {
        var url = event.request.url;
        if (url.startsWith(myPath + 'cache/')) {
            cacheSettings(url.substring(myPath.length + 6), event);
        }
        else if (!url.startsWith(myPath) && url.indexOf('?') !== -1) {
            // don't cache hit counter!!
            event.respondWith(fetch(event.request));
        }
        else {
            event.respondWith(fromCache(event.request, event));
        }
    }
});

function cacheSettings(url, event) {
    if (url === 'delete') {
        event.waitUntil(preLoad());
        event.respondWith(new Response('ok'));
        console.log('cache cleared');
    }
    else if (url === 'version') {
        event.respondWith(new Response(VERSION));
    }
    else {
        event.respondWith(noPage()); // need to get a promise
    }
}

addEventListener('install', function (event) {
    event.waitUntil(preLoad());
    skipWaiting();
});

addEventListener('activate', function (event) {
    event.waitUntil(clients.claim());
});

// with help from https://serviceworke.rs/strategy-cache-and-update_service-worker_doc.html
function preLoad() {
    var mustDownload = [
      'js/swLoader.js',
      'js/GameLoader.js',
      'js/alertbox.js',
      'css/fullscreen.css',
      'css/alertbox.css',
      'game.html',
      'index.html',
      '',
      '404.html',
      'config.html',
      'LICENSE'
    ];
    for (var i = 0; i < mustDownload.length; i++) {
        mustDownload[i] = myPath + mustDownload[i];
    }
    var c, names = [], must = new Set(mustDownload);
    return caches.open(CACHENAME).then(function (cache) {
        c = cache;
        return c.keys().then(function (keys) {
            names = keys;
            return cache.addAll(mustDownload);
        });
    }).then(function () {
        names.forEach(function (name) {
            if (!must.has(name.url)) {
                console.log("delete " + name.url);
                c['delete'](name);
            }
        });
        console.log('preloaded');
    })['catch'](function (x) {
        console.log(x);
    });
}

function fromCache(req, event) {
    var nocache = false;
    var c;
    return caches.open(CACHENAME).then(function (cache) {
        c = cache;
        // manually strip search string to make Chrome faster
        var i = req.url.indexOf('?');
        if (i !== -1) req = req.url.substring(0, i);
        return c.match(req).then(function (result) {
            if (result) {
                // update cache
                if (event) event.waitUntil(updateCache(req, cache));
                return result;
            }
            nocache = true;
            return Promise.reject("cache miss");
        });
    })['catch'](function (x) {
        if (!nocache) {
            console.log(x);
        }
        return fetch(req);
    }).then(function (result) {
        // add to cache
        if (nocache && event && result.ok) {
            event.waitUntil(c.put(req, result.clone()));
        }
        return result;
    })['catch'](noPage);
}

function updateCache(req, cache) {
    return fetch(req).then(function (result) {
        if (result.ok) {
            return cache.put(req, result);
        }
    })['catch'](function (x) {
        console.log(x);
    });
}

function noPage() {
    return caches.match('404.html').then(function (result) {
        if (result) return new Response(result.body, {status: 404});
        return new Response(
          '<!DOCTYPE html>\n' +
          '<meta charset="UTF-8">\n' +
          '<meta name="viewport" content="width=device-width, initial-scale=1.0">\n' +
          '<title>File not found</title>\n' +
          '
File not found
'
        , {status: 404, headers: {'Content-Type': 'text/html'}}
        );
    });
}
<commit_msg>service worker can disable cache<commit_after>var CACHENAME = 'shapeclear';
var VERSION = 'v0.7.0';
var myPath = location.origin + location.pathname;
var cacheEnableTest =
    caches.open(CACHENAME).then(function (cache) {
        return cache.match(myPath + "cache/no");
    }).then(function (result) {
        return result == null;
    })['catch'](function () {
        return true;
    });

myPath = myPath.match(/(.*\/)/)[1];

self.addEventListener('fetch', function(event) {
    if (event.request) {
        var url = event.request.url;
        if (url.startsWith(myPath + 'cache/')) {
            cacheSettings(url.substring(myPath.length + 6), event);
        }
        else if (!url.startsWith(myPath) && url.indexOf('?') !== -1) {
            // don't cache hit counter!!
            event.respondWith(fetch(event.request));
        }
        else {
            event.respondWith(fromCache(event.request, event));
        }
    }
});

function cacheSettings(url, event) {
    if (url === 'delete') {
        event.waitUntil(preLoad());
        event.respondWith(new Response('ok'));
        console.log('cache cleared');
    }
    else if (url === 'version') {
        event.respondWith(new Response(VERSION));
    }
    else if (url === "disable") {
        event.waitUntil(caches.open(CACHENAME).then(function (c) {
            c.put(new Request("cache/no"), new Response('yes', {'status':200}));
        }));
        cacheEnableTest = Promise.resolve(false);
        event.respondWith(new Response('ok'));
    }
    else if (url === "enable") {
        event.waitUntil(caches.open(CACHENAME).then(function (c) {
            c.delete(new Request("cache/no"));
        }));
        cacheEnableTest = Promise.resolve(true);
        event.respondWith(new Response('ok'));
    }
    else {
        event.respondWith(noPage()); // need to get a promise
    }
}

addEventListener('install', function (event) {
    event.waitUntil(preLoad());
    skipWaiting();
});

addEventListener('activate', function (event) {
    event.waitUntil(clients.claim());
});

// with help from https://serviceworke.rs/strategy-cache-and-update_service-worker_doc.html
function preLoad() {
    var mustDownload = [
      'js/swLoader.js',
      'js/GameLoader.js',
      'js/alertbox.js',
      'css/fullscreen.css',
      'css/alertbox.css',
      'game.html',
      'index.html',
      '',
      '404.html',
      'config.html',
      'LICENSE'
    ];
    for (var i = 0; i < mustDownload.length; i++) {
        mustDownload[i] = myPath + mustDownload[i];
    }
    var c, names = [], must = new Set(mustDownload);
    return caches.open(CACHENAME).then(function (cache) {
        c = cache;
        return c.keys().then(function (keys) {
            names = keys;
            return cache.addAll(mustDownload);
        });
    }).then(function () {
        names.forEach(function (name) {
            if (!must.has(name.url)) {
                console.log("delete " + name.url);
                c['delete'](name);
            }
        });
        console.log('preloaded');
    })['catch'](function (x) {
        console.log(x);
    });
}

function fromCache(req, event) {
    var nocache = false;
    var c;
    return cacheEnableTest.then(function (yes) {
        if (!yes) return Promise.reject("cache disabled");
        return caches.open(CACHENAME);
    }).then(function (cache) {
        c = cache;
        // manually strip search string to make Chrome faster
        var i = req.url.indexOf('?');
        if (i !== -1) req = req.url.substring(0, i);
        return c.match(req).then(function (result) {
            if (result) {
                // update cache
                if (event) event.waitUntil(updateCache(req, cache));
                return result;
            }
            nocache = true;
            return Promise.reject("cache miss");
        });
    })['catch'](function (x) {
        if (!nocache) {
            console.log(x);
        }
        return fetch(req);
    }).then(function (result) {
        // add to cache
        if (nocache && event && result.ok) {
            event.waitUntil(c.put(req, result.clone()));
        }
        return result;
    })['catch'](noPage);
}

function updateCache(req, cache) {
    return fetch(req).then(function (result) {
        if (result.ok) {
            return cache.put(req, result);
        }
    })['catch'](function (x) {
        console.log(x);
    });
}

function noPage() {
    return caches.match('404.html').then(function (result) {
        if (result) return new Response(result.body, {status: 404});
        return new Response(
          '<!DOCTYPE html>\n' +
          '<meta charset="UTF-8">\n' +
          '<meta name="viewport" content="width=device-width, initial-scale=1.0">\n' +
          '<title>File not found</title>\n' +
          '
File not found
'
        , {status: 404, headers: {'Content-Type': 'text/html'}}
        );
    });
}
<|endoftext|>


<commit_before>/*
  Copyright 2017 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 FILES = [
  'index.html'
];

const CACHE = 'v1.8';

self.addEventListener('install', (event) => {
  console.log('Service worker:', event);
  event.waitUntil(installHandler(event));
});

self.addEventListener('activate', (event) => {
  console.log('Service worker:', event);
  window.clients.claim();
});

self.addEventListener('fetch', (event) => {
  console.log('Service worker:', event);
  event.respondWith(fetchHandler(event.request));
});

async function installHandler(event) {
  const cache = await caches.open(CACHE);
  cache.addAll(FILES);
  self.skipWaiting();
}

async function fetchHandler(request) {
  const cache = await caches.open(CACHE);
  const cacheResult = await cache.match(request);
  if (cacheResult) {
    return cacheResult;
  }
  const fetchResult = await fetch(request);
  if (fetchResult.ok) {
    cache.put(request, fetchResult.clone());
  }
  return fetchResult;
}
<commit_msg>Updated sw.js<commit_after>/*
  Copyright 2017 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 FILES = [
  'index.html'
];

const CACHE = 'v1.9';

self.addEventListener('install', (event) => {
  console.log('Service worker:', event);
  event.waitUntil(installHandler(event));
});

self.addEventListener('activate', (event) => {
  console.log('Service worker:', event);
  window.clients.claim();
});

self.addEventListener('fetch', (event) => {
  console.log('Service worker:', event);
  event.respondWith(fetchHandler(event.request));
});

async function installHandler(event) {
  const cache = await caches.open(CACHE);
  cache.addAll(FILES);
  self.skipWaiting();
}

async function fetchHandler(request) {
  const cache = await caches.open(CACHE);
  const cacheResult = await cache.match(request);
  if (cacheResult) {
    return cacheResult;
  }
  const fetchResult = await fetch(request);
  if (fetchResult.ok) {
    cache.put(request, fetchResult.clone());
  }
  return fetchResult;
}
<|endoftext|>


<commit_before>/*!
 * ui.js
 * Copyright Mathias Bynens <http://mths.be/>
 * Modified by John-David Dalton <http://allyoucanleet.com/>
 * Available under MIT license <http://mths.be/mit>
 */

(function(global, document) {

  var RUN_TEXT = {
    'RUNNING' :   'Stop tests',
    'RUN_AGAIN' : 'Run tests again',
    'STOPPED' :   'Run tests'
  },

  /*--------------------------------------------------------------------------*/

  // private cache
  cache = {
    'counter' : 0,
    'errors' : [],
    'trash' : createElement('div')
  };

  // shortcut(s)
  function $(id) {
    return document.getElementById(id);
  }

  function createElement(tag) {
    return document.createElement(tag);
  }

  // pretty print for numbers
  function formatNumber(number) {
    var comma = ',',
        string = String(Math.max(0, Math.abs(number).toFixed(0))),
        length = string.length,
        end = /^\d{4,}$/.test(string) ? length % 3 : 0;
    return (end ? string.slice(0, end) + comma : '') + string.slice(end).replace(/(\d{3})(?=\d)/g, '$1' + comma);
  }

  // grabs the test from the ui object that matches the id
  function getTestById(id) {
    var test,
        i = 0,
        result = null,
        tests = ui.tests;
    while (test = tests[i++]) {
      if (test.id == id) {
        result = test;
        break;
      }
    }
    return result;
  }

  // element className utility
  function hasClass(element, className) {
    return (' ' + element.className + ' ').indexOf(' ' + className + ' ') > -1;
  }

  // a cross-browser Array#indexOf solution
  function indexOf(array, value) {
    var i = -1,
        length = this.length,
        result = i;
    if (typeof array.indexOf == 'function') {
      return array.indexOf(value);
    }
    while (++i < length) {
      if (i in array && array[i] === value) {
        result = i;
        break;
      }
    }
    return result;
  }

  // like Array#join but for key-value pairs of an object
  function join(object, delimit1, delimit2) {
    var key,
        pairs = [];
    for (key in object) {
      pairs.push(key + delimit1 + object[key]);
    }
    return pairs.join(delimit2);
  }

  // appends or clears error log
  function logError(text) {
    var table,
        el = $('error-info');

    if (!el) {
      table = $('test-table');
      el = createElement('div');
      el.id = 'error-info';
      table.parentNode.insertBefore(el, table.nextSibling);
    }
    if (text === false) {
      el.className = el.innerHTML = '';
      cache.errors = [];
    }
    else {
      text || (text = '');
      if (indexOf(cache.errors, text) < 0) {
        cache.errors.push(text);
        el.className = 'show';
        el.innerHTML += text;
      }
    }
  }

  // sets the status text
  function logStatus(text) {
    var el = $('status');
    if (el) {
      el.innerHTML = text || '';
    }
  }

  // Browserscope makes us work for it
  function postBrowserscope(me) {
    var idoc,
        item,
        i = 0,
        body = document.body,
        id = 'browserscope_',
        elIframe = $(id + cache.counter),
        result = { };

    // populate result object
    while (item = me.tests[i++]) {
      if (item.count) {
        result[(item.name.match(/[a-z0-9]+/ig) || [item.id]).join(' ')] = item.hz;
      }
    }
    // remove old beacon
    if (elIframe) {
      cache.trash.appendChild(elIframe);
      cache.trash.innerHTML = '';
    }
    // create new beacon
    id += ++cache.counter;
    try {
      elIframe = createElement('');
    } catch(e) {
      (elIframe = createElement('iframe')).name = id;
    }
    // inject beacon
    elIframe.id = id;
    elIframe.style.display = 'none';
    body.insertBefore(elIframe, body.firstChild);

    // perform inception :3
    if (ui._bTestKey) {
      ui._bTestResults = result;
      idoc = global.frames[id].document;
      idoc.write('


<commit_before>function getStoryUrl() {
    if (window.location.hash.length > 1) {
        return window.location.hash.substr(1) + '.json';
    }
    return "model.json";
}

function tellStory(modelUrl) {
    window.storyModel = new MarkovModel();

    $.getJSON(modelUrl, function(data) {
        window.storyModel.states = data;
        window.storyModel.startIteration();
        tellNextWord();
    })
}

function tellNextWord() {
    var decision = window.storyModel.next();
    var aniElem = makeAnimationElement(decision);
    animateDecision(decision, aniElem);
    for (var i=0; i < decision.taken.length; i++) {
        addToStory(decision.taken[i]);
    }
    var lastToken = decision.taken[decision.taken.length - 1];
    //if (lastToken != '\n' && lastToken != '.') {
    //    console.log("Last token was '" + lastToken + '", scheduling next word');
    //    setTimeout(tellNextWord, 1000);
    //}
    setTimeout(tellNextWord, 1000);
}

// Add a new word to the animation area. Remove old words as needed.
function makeAnimationElement(decision) {
    var elem = $('

').appendTo("#animarkov-inner");
    var placeholder = createDecisionOptionElem(decision.taken, ['placeholder'])
    placeholder.classList.add("placeholder")
    elem.append(placeholder);
    while ($("#animarkov-inner").width() > $("#thestory").width()) {
        $("#animarkov-inner > .decision:first-child").remove()
    }
    return elem;
}

function animateDecision(decision, anchor) {
    // create all the elements for the options
    var optionElems = [];
    optionElems.push(createDecisionOptionElem(decision.taken));
    optionElems[0].taken = true;
    for (var i=0; i < decision.options.length; i++) {
        if (decision.options[i] == decision.taken) {
            continue;
        }
        if (optionElems.length >= 7) {
            break;
        }
        var elem = createDecisionOptionElem(decision.options[i]);
        elem.taken = false;
        optionElems.push(elem);
    }
    shuffle(optionElems);

    // Show and fan-out the possible options
    var takenIdx = null;
    var takenPos = 0;
    for (var i=0; i < optionElems.length; i++) {
        var elem = optionElems[i];
        anchor.append(elem);
        elem.style.transform = "translateY(" + (elem.offsetHeight * (i - optionElems.length/2)) + "px)";
        if (elem.taken) {
            takenIdx = i;
            takenPos = elem.offsetHeight * (i - optionElems.length/2);
        }
    }

    // Move them so that the taken option is at the right height
    setTimeout(function() {
        anchor.get(0).style.transform = "translateY(" + (-takenPos) + "px)";
    }, 500)

    // Fade in the taken option, fade out the others
    setTimeout(function() {
        for (var i=0; i < optionElems.length; i++) {
            var elem = optionElems[i];
            elem.style.color = (i === takenIdx) ? "#000000" : "#fafafa";
        }
    }, 800)
}

function addToStory(token) {
    var story = $("#thestory > p:last-child")
    if (token === '\n') {
        $('

').appendTo("#thestory")
    } else if (isPunctuation(token)) {
        story.text(story.text() + token)
    } else {
        story.text(story.text() + " " + token)
    }
}

function createDecisionOptionElem(tokens) {
	var elem = document.createElement('SPAN');
	elem.textContent = tokensToText(tokens);
	elem.classList.add('decision-option');
    elem.isPunctuation = isPunctuation(tokens[0]);
    if (elem.isPunctuation) {
        elem.classList.add("punctuation");
    }
	return elem;
}

function tokensToText(tokens) {
    var result = [];
    for (var i=0; i < tokens.length; i++) {
        var t = tokens[i];
        if (t == '\n') {
            result.push("\u21B5");
        } else if (isPunctuation(t)) {
            result.push(t);
        } else {
            result.push(" ");
            result.push(t);
        }
    }
    return result.join('');
}

function shuffle(a) {
    var j, x, i;
    for (i = a.length; i; i--) {
        j = Math.floor(Math.random() * i);
        x = a[i - 1];
        a[i - 1] = a[j];
        a[j] = x;
    }
}

function isPunctuation(token) {
    return ",.-!?:&".indexOf(token) !== -1;
}

<commit_msg>Double animation speed for words without options<commit_after>function getStoryUrl() {
    if (window.location.hash.length > 1) {
        return window.location.hash.substr(1) + '.json';
    }
    return "model.json";
}

function tellStory(modelUrl) {
    window.storyModel = new MarkovModel();

    $.getJSON(modelUrl, function(data) {
        window.storyModel.states = data;
        window.storyModel.startIteration();
        tellNextWord();
    })
}

function tellNextWord() {
    var decision = window.storyModel.next();
    var aniElem = makeAnimationElement(decision);
    animateDecision(decision, aniElem);
    for (var i=0; i < decision.taken.length; i++) {
        addToStory(decision.taken[i]);
    }
    var lastToken = decision.taken[decision.taken.length - 1];
    //if (lastToken != '\n' && lastToken != '.') {
    //    console.log("Last token was '" + lastToken + '", scheduling next word');
    //    setTimeout(tellNextWord, 1000);
    //}
    if (decision.options.length == 1) {
        setTimeout(tellNextWord, 500);
    } else {
        setTimeout(tellNextWord, 1000);
    }
}

// Add a new word to the animation area. Remove old words as needed.
function makeAnimationElement(decision) {
    var elem = $('

').appendTo("#animarkov-inner");
    var placeholder = createDecisionOptionElem(decision.taken, ['placeholder'])
    placeholder.classList.add("placeholder")
    elem.append(placeholder);
    while ($("#animarkov-inner").width() > $("#thestory").width()) {
        $("#animarkov-inner > .decision:first-child").remove()
    }
    return elem;
}

function animateDecision(decision, anchor) {
    // create all the elements for the options
    var optionElems = [];
    optionElems.push(createDecisionOptionElem(decision.taken));
    optionElems[0].taken = true;
    for (var i=0; i < decision.options.length; i++) {
        if (decision.options[i] == decision.taken) {
            continue;
        }
        if (optionElems.length >= 7) {
            break;
        }
        var elem = createDecisionOptionElem(decision.options[i]);
        elem.taken = false;
        optionElems.push(elem);
    }
    shuffle(optionElems);

    // Show and fan-out the possible options
    var takenIdx = null;
    var takenPos = 0;
    for (var i=0; i < optionElems.length; i++) {
        var elem = optionElems[i];
        anchor.append(elem);
        elem.style.transform = "translateY(" + (elem.offsetHeight * (i - optionElems.length/2)) + "px)";
        if (elem.taken) {
            takenIdx = i;
            takenPos = elem.offsetHeight * (i - optionElems.length/2);
        }
    }

    // Move them so that the taken option is at the right height
    setTimeout(function() {
        anchor.get(0).style.transform = "translateY(" + (-takenPos) + "px)";
    }, 500)

    // Fade in the taken option, fade out the others
    setTimeout(function() {
        for (var i=0; i < optionElems.length; i++) {
            var elem = optionElems[i];
            elem.style.color = (i === takenIdx) ? "#000000" : "#fafafa";
        }
    }, 800)
}

function addToStory(token) {
    var story = $("#thestory > p:last-child")
    if (token === '\n') {
        $('

').appendTo("#thestory")
    } else if (isPunctuation(token)) {
        story.text(story.text() + token)
    } else {
        story.text(story.text() + " " + token)
    }
}

function createDecisionOptionElem(tokens) {
	var elem = document.createElement('SPAN');
	elem.textContent = tokensToText(tokens);
	elem.classList.add('decision-option');
    elem.isPunctuation = isPunctuation(tokens[0]);
    if (elem.isPunctuation) {
        elem.classList.add("punctuation");
    }
	return elem;
}

function tokensToText(tokens) {
    var result = [];
    for (var i=0; i < tokens.length; i++) {
        var t = tokens[i];
        if (t == '\n') {
            result.push("\u21B5");
        } else if (isPunctuation(t)) {
            result.push(t);
        } else {
            result.push(" ");
            result.push(t);
        }
    }
    return result.join('');
}

function shuffle(a) {
    var j, x, i;
    for (i = a.length; i; i--) {
        j = Math.floor(Math.random() * i);
        x = a[i - 1];
        a[i - 1] = a[j];
        a[j] = x;
    }
}

function isPunctuation(token) {
    return ",.-!?:&".indexOf(token) !== -1;
}

<|endoftext|>


<commit_before>/*!
 * ui.js
 * Copyright Mathias Bynens <http://mths.be/>
 * Modified by John-David Dalton <http://allyoucanleet.com/>
 * Available under MIT license <http://mths.be/mit>
 */

(function(global, document) {

  var RUN_TEXT = {
    'RUNNING' :   'Stop tests',
    'RUN_AGAIN' : 'Run tests again',
    'STOPPED' :   'Run tests'
  },

  /*--------------------------------------------------------------------------*/

  // private cache
  cache = {
    'elements' : {},
    'errors' : [],
    'trash' : document.createElement('div')
  };

  // GEBID shortcut
  function $(id) {
    return cache.elements[id] || (cache.elements[id] = document.getElementById(id));
  }

  // adds external JavaScript files to the page
  function addScript(src) {
    var script = document.createElement('script'),
     s = document.getElementsByTagName('script')[0];

    script.async = 1;
    script.src = src;
    s.parentNode.insertBefore(script, s);
  }

  // pretty print for numbers
  function formatNumber(number) {
    var comma = ',',
        string = String(Math.max(0, Math.abs(number).toFixed(0))),
        length = string.length,
        end = /^\d{4,}$/.test(string) ? length % 3 : 0;
    return (end ? string.slice(0, end) + comma : '') + string.slice(end).replace(/(\d{3})(?=\d)/g, '$1' + comma);
  }

  // grabs the test from the ui object that matches the id
  function getTestById(id) {
    var test,
        result = null,
        tests = ui.tests,
        i = 0;
    while (test = tests[i++]) {
      if (test.id == id) {
        result = test;
        break;
      }
    }
    return result;
  }

  // element className utility
  function hasClass(element, className) {
    return (' ' + element.className + ' ').indexOf(' ' + className + ' ') > -1;
  }

  // a cross-browser Array#indexOf solution
  function indexOf(array, value) {
    if (typeof array.indexOf == 'function') {
      return array.indexOf(value);
    }
    var i = -1,
        length = this.length,
        result = -1;
    while (++i < length) {
      if (i in array && array[i] === value) {
        result = i;
        break;
      }
    }
    return result;
  }

  // like Array#join but for key-value pairs of an object
  function join(object, delimit1, delimit2) {
    var key,
        pairs = [];
    for (key in object) {
      pairs.push(key + delimit1 + object[key]);
    }
    return pairs.join(delimit2);
  }

  // appends or clears error log
  function logError(text) {
    var table,
        el = $('error-info');

    if (!el) {
      table = $('test-table');
      el = document.createElement('div');
      el.id = 'error-info';
      table.parentNode.insertBefore(el, table.nextSibling);
    }
    if (text === false) {
      el.className = el.innerHTML = '';
      cache.errors = [];
    }
    else {
      text || (text = '');
      if (indexOf(cache.errors, text) < 0) {
        cache.errors.push(text);
        el.className = 'show';
        el.innerHTML += text;
      }
    }
  }

  // sets the status text
  function logStatus(text) {
    var el = $('status');
    if (el) {
      el.innerHTML = text || '';
    }
  }

  /*--------------------------------------------------------------------------*/

  function onClick() {
    ui.runTest(getTestById(this.id.split('-')[1]));
  }

  function onCycle(test) {
    onStart(test);
    ui.renderTest(test);
  }

  function onHashChange() {
    ui.parseHash();
    if (typeof global.init == 'function') {
      init();
    }
  }

  function onKeyUp(e) {
    // treat hitting ENTER while focused on a test title as if it were clicked
    e || (e = global.event);
    if (13 == e.keyCode) {
      onClick.call(this);
    }
  }

  function onLoad() {
    $('run').onclick = onRun;
    $('user-agent').innerHTML = Benchmark.getPlatform();
    ($('question') || { }).value = 'no';

    // auto-run tests when the URL has #run
    if ('run' == location.hash.slice(1, 3)) {
      onRun();
    }
    if (typeof global.init == 'function') {
      init();
    }
  }

  function onRun() {
    ui[$('run').innerHTML == RUN_TEXT.RUNNING ? 'stop' : 'runAll']();
  }

  function onStart(test) {
    logStatus(test.name + ' × ' + formatNumber(test.count));
  }

  function onStop() {
    logStatus('Done. Ready to run tests again.');
    ui.currentTest = null;
    nextTest(ui);
  }

  /*--------------------------------------------------------------------------*/

  function addTest(name, id, fn) {
    var me = this,
        elTitle = $('title-' + id),
        test = new Benchmark(fn);

    elTitle.tabIndex = 0;
    elTitle.title = 'Click to run this test again';
    elTitle.onclick = onClick;
    elTitle.onkeyup = onKeyUp;

    test.id = id;
    test.name = name;
    test.onCycle = onCycle;
    test.onStart = onStart;
    test.onStop = onStop;

    me.tests.push(test);
    me.elResults.push($('results-' + id));
    me.renderTest(test);
  }

  function parseHash() {
    var pair,
        hashes = location.hash.slice(1).split('&'),
        length = hashes.length,
        params = this.params = { };

    if (hashes[0]) {
      while (length--) {
        pair = hashes[length].split('=');
        params[pair[0]] = pair[1];
      }
    }
  }

  function renderTest(test) {
    var hz,
        cell = $('results-' + test.id);

    if (test.error) {
      cell.innerHTML = 'Error';
      if (!hasClass(cell, 'error')) {
        cell.className += ' error';
      }
      logError('
' + test.error + '.<\/p>

' + join(test.error, ': ', '<\/li>
') + '<\/li><\/ul>');
    }
    else {
      if (test.running) {
        cell.innerHTML = 'running…';
      }
      else if (indexOf(this.queue, test) > -1) {
        cell.innerHTML = 'pending…';
      }
      else if (test.count) {
        hz = Math.round(1 / test.period);
        cell.innerHTML = hz !== Infinity ? formatNumber(hz) : '∞';
        cell.title = 'Looped ' + formatNumber(test.count) + ' times in ' + test.time + ' seconds';
      }
      else {
        cell.innerHTML = 'ready';
      }
    }
  }

  function runAll(e) {
    e || (e = global.event);
    var i = -1,
        me = this,
        reversed = e && e.shiftKey,
        length = me.tests.length;

    $('run').innerHTML = RUN_TEXT.RUNNING;
    while (++i < length) {
      me.runTest(me.tests[reversed ? (length - i - 1) : i]);
    }
  }

  function runTest(test) {
    var elResult,
        i = 0,
        me = this,
     elResults = me.elResults;

    if (indexOf(me.queue, test) < 0) {
      // clear error log
      logError(false);

      // reset result classNames
      while (elResult = elResults[i++]) {
        if (!hasClass(elResult, 'error')) {
          elResult.className = 'results';
        }
      }
      me.queue.push(test);
      me.renderTest(test);
      nextTest(me);
    }
  }

  function stop() {
    var me = this;
    $('run').innerHTML = RUN_TEXT.STOPPED;

    while (me.queue.length) {
      me.renderTest(me.queue.shift());
    }
  }

  function nextTest(me) {
    var elIframe,
        elResult,
        elSpan,
        first,
        hz,
        id,
        item,
        last,
        length,
        percent,
        test,
        text,
        i = 0,
        results = [];

    if (me.currentTest) {
      // do nothing when running another test
    }
    else if (test = me.queue.shift()) {
      // run the next test from the queue
      me.currentTest = test;
      test.run();
    }
    else {
      // populate Browserscope's result object
      _bTestResults = { };
      while (item = me.tests[i++]) {
        if (item.count) {
          id = item.id;
          hz = item.hz;
          results.push({ 'id': id, 'hz': hz });
          _bTestResults[(item.name.match(/[a-z0-9]+/ig) || [id]).join(' ')] = hz;
        }
      }

      // print results
      length = results.length;
      if (length > 1) {
        // sort descending by hz (highest hz / fastest first)
        results.sort(function(a, b) { return b.hz - a.hz; });

        i = 0;
        first = results[0];
        last = results[length - 1];

        while (item = results[i++]) {
          elResult = $('results-' + item.id);
          elSpan = elResult.getElementsByTagName('span')[0];

          percent = (1 - item.hz / first.hz) * 100;
          text = item == first ? 'fastest' : Math.floor(percent) + '% slower';

          if (elSpan) {
            elSpan.innerHTML = text;
          } else {
            elResult.innerHTML += ' ' + text + '<\/span>';
          }
        }
        // all tests are finished
        $('run').innerHTML = RUN_TEXT.RUN_AGAIN;

        // mark fastest
        $('results-' + first.id).className += ' fastest';

        // mark slowest
        $('results-' + last.id).className += ' slowest';

        // beacon results to Browserscope (_bTestKey is defined elsewhere)
        if (global._bTestKey) {
          // remove previous beacon
          elIframe = $('browserscope');
          if (elIframe) {
            cache.trash.appendChild(elIframe);
            cache.trash.innerHTML = cache.elements.browserscope = '';
          }
          addScript('//www.browserscope.org/user/beacon/' + _bTestKey);
        }
      }
    }
  }

  /*--------------------------------------------------------------------------*/

  // expose
  global.ui = {
    // HTML elements that will hold the results
    'elResults' : [],

    // parsed query parameters of the current page URL
    'params' : {},

    // queue of tests that need to run
    'queue' : [],

    // list of all tests that have been registered with benchmark.test
    'tests' : [],

    // create a new test
    'addTest' : addTest,

    // parse query params into ui.params[] hash
    'parseHash' : parseHash,

    // (re)render the results for a specific test
    'renderTest' : renderTest,

    // add all tests to the run queue
    'runAll' : runAll,

    // add a test to the run queue
    'runTest' : runTest,

    // remove and render all tests from the run queue
    'stop' : stop
  };

  /*--------------------------------------------------------------------------*/

  // signal JavaScript detected
  document.documentElement.className = 'js';

  // don't let users alert / confirm / prompt / open new windows
  global.alert = global.confirm = global.prompt = global.open = Benchmark.noop;

  // re-parse query params when hash changes
  global.onhashchange = onHashChange;

  // bootstrap onload
  global.onload = onLoad;

  // for Browserscope, don't rename!
  global._bTestResults = {};

  // parse location hash string
  ui.parseHash();

  // customize calibration test
  (function(cal) {
    cal.name = 'Calibrating loop';
    cal.onCycle = cal.onStart = onStart;
  }(Benchmark.CALIBRATION));

  // optimized asynchronous Google Analytics snippet based on
  // http://mathiasbynens.be/notes/async-analytics-snippet
  global._gaq = [['_setAccount', 'UA-6065217-40'], ['_trackPageview']];
  addScript('//www.google-analytics.com/ga.js');

}(this, document));<commit_msg>benchmark.js: Fix NaN percents displayed in the UI and avoid marking all identical results as slower/faster. [jddalton]<commit_after>/*!
 * ui.js
 * Copyright Mathias Bynens <http://mths.be/>
 * Modified by John-David Dalton <http://allyoucanleet.com/>
 * Available under MIT license <http://mths.be/mit>
 */

(function(global, document) {

  var RUN_TEXT = {
    'RUNNING' :   'Stop tests',
    'RUN_AGAIN' : 'Run tests again',
    'STOPPED' :   'Run tests'
  },

  /*--------------------------------------------------------------------------*/

  // private cache
  cache = {
    'elements' : {},
    'errors' : [],
    'trash' : document.createElement('div')
  };

  // GEBID shortcut
  function $(id) {
    return cache.elements[id] || (cache.elements[id] = document.getElementById(id));
  }

  // adds external JavaScript files to the page
  function addScript(src) {
    var script = document.createElement('script'),
     s = document.getElementsByTagName('script')[0];

    script.async = 1;
    script.src = src;
    s.parentNode.insertBefore(script, s);
  }

  // pretty print for numbers
  function formatNumber(number) {
    var comma = ',',
        string = String(Math.max(0, Math.abs(number).toFixed(0))),
        length = string.length,
        end = /^\d{4,}$/.test(string) ? length % 3 : 0;
    return (end ? string.slice(0, end) + comma : '') + string.slice(end).replace(/(\d{3})(?=\d)/g, '$1' + comma);
  }

  // grabs the test from the ui object that matches the id
  function getTestById(id) {
    var test,
        result = null,
        tests = ui.tests,
        i = 0;
    while (test = tests[i++]) {
      if (test.id == id) {
        result = test;
        break;
      }
    }
    return result;
  }

  // element className utility
  function hasClass(element, className) {
    return (' ' + element.className + ' ').indexOf(' ' + className + ' ') > -1;
  }

  // a cross-browser Array#indexOf solution
  function indexOf(array, value) {
    if (typeof array.indexOf == 'function') {
      return array.indexOf(value);
    }
    var i = -1,
        length = this.length,
        result = -1;
    while (++i < length) {
      if (i in array && array[i] === value) {
        result = i;
        break;
      }
    }
    return result;
  }

  // like Array#join but for key-value pairs of an object
  function join(object, delimit1, delimit2) {
    var key,
        pairs = [];
    for (key in object) {
      pairs.push(key + delimit1 + object[key]);
    }
    return pairs.join(delimit2);
  }

  // appends or clears error log
  function logError(text) {
    var table,
        el = $('error-info');

    if (!el) {
      table = $('test-table');
      el = document.createElement('div');
      el.id = 'error-info';
      table.parentNode.insertBefore(el, table.nextSibling);
    }
    if (text === false) {
      el.className = el.innerHTML = '';
      cache.errors = [];
    }
    else {
      text || (text = '');
      if (indexOf(cache.errors, text) < 0) {
        cache.errors.push(text);
        el.className = 'show';
        el.innerHTML += text;
      }
    }
  }

  // sets the status text
  function logStatus(text) {
    var el = $('status');
    if (el) {
      el.innerHTML = text || '';
    }
  }

  /*--------------------------------------------------------------------------*/

  function onClick() {
    ui.runTest(getTestById(this.id.split('-')[1]));
  }

  function onCycle(test) {
    onStart(test);
    ui.renderTest(test);
  }

  function onHashChange() {
    ui.parseHash();
    if (typeof global.init == 'function') {
      init();
    }
  }

  function onKeyUp(e) {
    // treat hitting ENTER while focused on a test title as if it were clicked
    e || (e = global.event);
    if (13 == e.keyCode) {
      onClick.call(this);
    }
  }

  function onLoad() {
    $('run').onclick = onRun;
    $('user-agent').innerHTML = Benchmark.getPlatform();
    ($('question') || { }).value = 'no';

    // auto-run tests when the URL has #run
    if ('run' == location.hash.slice(1, 3)) {
      onRun();
    }
    if (typeof global.init == 'function') {
      init();
    }
  }

  function onRun() {
    ui[$('run').innerHTML == RUN_TEXT.RUNNING ? 'stop' : 'runAll']();
  }

  function onStart(test) {
    logStatus(test.name + ' × ' + formatNumber(test.count));
  }

  function onStop() {
    logStatus('Done. Ready to run tests again.');
    ui.currentTest = null;
    nextTest(ui);
  }

  /*--------------------------------------------------------------------------*/

  function addTest(name, id, fn) {
    var me = this,
        elTitle = $('title-' + id),
        test = new Benchmark(fn);

    elTitle.tabIndex = 0;
    elTitle.title = 'Click to run this test again';
    elTitle.onclick = onClick;
    elTitle.onkeyup = onKeyUp;

    test.id = id;
    test.name = name;
    test.onCycle = onCycle;
    test.onStart = onStart;
    test.onStop = onStop;

    me.tests.push(test);
    me.elResults.push($('results-' + id));
    me.renderTest(test);
  }

  function parseHash() {
    var pair,
        hashes = location.hash.slice(1).split('&'),
        length = hashes.length,
        params = this.params = { };

    if (hashes[0]) {
      while (length--) {
        pair = hashes[length].split('=');
        params[pair[0]] = pair[1];
      }
    }
  }

  function renderTest(test) {
    var hz,
        cell = $('results-' + test.id);

    if (test.error) {
      cell.innerHTML = 'Error';
      if (!hasClass(cell, 'error')) {
        cell.className += ' error';
      }
      logError('
' + test.error + '.<\/p>

' + join(test.error, ': ', '<\/li>
') + '<\/li><\/ul>');
    }
    else {
      if (test.running) {
        cell.innerHTML = 'running…';
      }
      else if (indexOf(this.queue, test) > -1) {
        cell.innerHTML = 'pending…';
      }
      else if (test.count) {
        hz = Math.round(1 / test.period);
        cell.innerHTML = hz !== Infinity ? formatNumber(hz) : '∞';
        cell.title = 'Looped ' + formatNumber(test.count) + ' times in ' + test.time + ' seconds';
      }
      else {
        cell.innerHTML = 'ready';
      }
    }
  }

  function runAll(e) {
    e || (e = global.event);
    var i = -1,
        me = this,
        reversed = e && e.shiftKey,
        length = me.tests.length;

    $('run').innerHTML = RUN_TEXT.RUNNING;
    while (++i < length) {
      me.runTest(me.tests[reversed ? (length - i - 1) : i]);
    }
  }

  function runTest(test) {
    var elResult,
        i = 0,
        me = this,
     elResults = me.elResults;

    if (indexOf(me.queue, test) < 0) {
      // clear error log
      logError(false);

      // reset result classNames
      while (elResult = elResults[i++]) {
        if (!hasClass(elResult, 'error')) {
          elResult.className = 'results';
        }
      }
      me.queue.push(test);
      me.renderTest(test);
      nextTest(me);
    }
  }

  function stop() {
    var me = this;
    $('run').innerHTML = RUN_TEXT.STOPPED;

    while (me.queue.length) {
      me.renderTest(me.queue.shift());
    }
  }

  function nextTest(me) {
    var elIframe,
        elResult,
        elSpan,
        first,
        hz,
        id,
        item,
        last,
        length,
        percent,
        test,
        text,
        i = 0,
        results = [];

    if (me.currentTest) {
      // do nothing when running another test
    }
    else if (test = me.queue.shift()) {
      // run the next test from the queue
      me.currentTest = test;
      test.run();
    }
    else {
      // populate Browserscope's result object
      _bTestResults = { };
      while (item = me.tests[i++]) {
        if (item.count) {
          id = item.id;
          hz = item.hz;
          results.push({ 'id': id, 'hz': hz });
          _bTestResults[(item.name.match(/[a-z0-9]+/ig) || [id]).join(' ')] = hz;
        }
      }

      // print results
      length = results.length;
      if (length > 1) {
        // sort descending by hz (highest hz / fastest first)
        results.sort(function(a, b) { return b.hz - a.hz; });

        i = 0;
        first = results[0];
        last = results[length - 1];

        if (first.hz != last.hz) {
          while (item = results[i++]) {
            elResult = $('results-' + item.id);
            elSpan = elResult.getElementsByTagName('span')[0];

            percent = (1 - item.hz / first.hz) * 100 || 0;
            text = item == first ? 'fastest' : Math.floor(percent) + '% slower';

            if (elSpan) {
              elSpan.innerHTML = text;
            } else {
              elResult.innerHTML += ' ' + text + '<\/span>';
            }
          }
          // mark fastest
          $('results-' + first.id).className += ' fastest';

          // mark slowest
          $('results-' + last.id).className += ' slowest';
        }
        // all tests are finished
        $('run').innerHTML = RUN_TEXT.RUN_AGAIN;

        // beacon results to Browserscope (_bTestKey is defined elsewhere)
        if (global._bTestKey) {
          // remove previous beacon
          elIframe = $('browserscope');
          if (elIframe) {
            cache.trash.appendChild(elIframe);
            cache.trash.innerHTML = cache.elements.browserscope = '';
          }
          addScript('//www.browserscope.org/user/beacon/' + _bTestKey);
        }
      }
    }
  }

  /*--------------------------------------------------------------------------*/

  // expose
  global.ui = {
    // HTML elements that will hold the results
    'elResults' : [],

    // parsed query parameters of the current page URL
    'params' : {},

    // queue of tests that need to run
    'queue' : [],

    // list of all tests that have been registered with benchmark.test
    'tests' : [],

    // create a new test
    'addTest' : addTest,

    // parse query params into ui.params[] hash
    'parseHash' : parseHash,

    // (re)render the results for a specific test
    'renderTest' : renderTest,

    // add all tests to the run queue
    'runAll' : runAll,

    // add a test to the run queue
    'runTest' : runTest,

    // remove and render all tests from the run queue
    'stop' : stop
  };

  /*--------------------------------------------------------------------------*/

  // signal JavaScript detected
  document.documentElement.className = 'js';

  // don't let users alert / confirm / prompt / open new windows
  global.alert = global.confirm = global.prompt = global.open = Benchmark.noop;

  // re-parse query params when hash changes
  global.onhashchange = onHashChange;

  // bootstrap onload
  global.onload = onLoad;

  // for Browserscope, don't rename!
  global._bTestResults = {};

  // parse location hash string
  ui.parseHash();

  // customize calibration test
  (function(cal) {
    cal.name = 'Calibrating loop';
    cal.onCycle = cal.onStart = onStart;
  }(Benchmark.CALIBRATION));

  // optimized asynchronous Google Analytics snippet based on
  // http://mathiasbynens.be/notes/async-analytics-snippet
  global._gaq = [['_setAccount', 'UA-6065217-40'], ['_trackPageview']];
  addScript('//www.google-analytics.com/ga.js');

}(this, document));<|endoftext|>


<commit_before>#!/usr/bin/env node

var Q        = require('q');
var events   = require('events');
var https    = require('https');
var libapi   = require('./api');
var libdata  = require('./data');
var entities = require('entities');
var termMenu = require('terminal-menu-2');
var exec     = require('child_process').exec;
var spawn    = require('child_process').spawn;

var OPTIONS  = require('./getopts');
var COLUMNS  = process.stdout.columns || 80;
var ROWS     = process.stdout.rows || 24;
var colMax   = COLUMNS - 4;
var rowMax   = ROWS - 2;

var MENU, RUNNING = {}, ITEMS = [];
var INDICATOR_ON = ' ◉ ', INDICATOR_OFF = ' ◯ ';

Q().
then(function() {
  ITEMS = libdata.readCache();
  makeMenu();
}).
then(function() {
  return libdata.checkConf();
}).
then(function(cookie) {
  if (!MENU) process.stdout.write('Retrieving list of videos ... ');
  var pagesNeeded = Math.ceil(rowMax / libapi.subscription.itemsPerPage);
  return libapi.subscription.get(serial(pagesNeeded), cookie);
}).
then(function(data) {
  if (arrayEquals(ITEMS, data)) return;
  libdata.createCache(data);
  ITEMS = data;
  makeMenu();
}).
catch(function(err) {
  error(err);
});

// events

var ytEvents = new events.EventEmitter();

ytEvents.on('start', function(index) {
  var url = ITEMS[index].url;
  if (MENU.items[index].label.slice(0, 3) === INDICATOR_ON) {
    return killall(RUNNING[url].pid);
  }
  MENU.items[index].label = INDICATOR_ON + MENU.items[index].label.slice(3);
  MENU._drawRow(index);
  // use --player-no-close to prevent video player exiting too early
  // vlc's --play-and-exit option does not work in Mac OS X
  // so we use verbose stderr to see when the video really ends
  var livestreamer = spawn('livestreamer', [
    '--player-no-close',
    url, OPTIONS.f,
    '--player-args', '--video-on-top {filename} --verbose 2',
    '--verbose-player'
  ]);
  livestreamer.stderr.on('data', function(chunk) {
    if (chunk.toString().indexOf('finished input') > -1) {
      if (!RUNNING[url]) return;
      killall(RUNNING[url].pid);
    }
  });
  livestreamer.on('error', function(e) {
    error('Make sure you have access to command `livestreamer`.');
  });
  livestreamer.on('exit', function() {
    ytEvents.emit('end', url);
  });
  RUNNING[url] = livestreamer;
});

ytEvents.on('end', function(url) {
  var index = -1;
  for (var i = 0; i < ITEMS.length; i++) {
    if (ITEMS[i].url === url) {
      index = i;
      break;
    }
  }
  if (index > -1) {
    MENU.items[index].label = INDICATOR_OFF + MENU.items[index].label.slice(3);
    MENU._drawRow(index);
  }
  delete RUNNING[url];
});

function error(err) {
  if (MENU) {
    MENU.reset();
    MENU.close();
  }
  console.error(err.stack ? err.stack : err);
  process.kill();
}

// serial(2) = [1, 2]  //  serial(5) = [1, 2, 3, 4, 5]
function serial(n) {
  return Array.apply(undefined, { length: n }).map(Function.call, function(i) {
    return i + 1;
  });
}

function killall(pid) {
  if (!pid) return;
  exec('pgrep -P ' + pid, function(error, stdout, stderr) {
    if (stdout) {
      var pids = stdout.trim().split('\n');
      pids.forEach(function(pid) {
        if (pid) process.kill(pid, 'SIGKILL');
      });
    }
    process.kill(pid, 'SIGKILL');
  });
}

function arrayEquals(a, b) {
  return JSON.stringify(a) === JSON.stringify(b);
}

function slice(str, len) {
  str = entities.decodeHTML(str);
  return str.slice(0, len - (str.match(/[^\u0000-\u00ff]/g) || '').length);
}

function pad(n) {
  return (n < 10 ? '0' : '') + n;
}

function makeMenu() {
  if (Object.prototype.toString.call(ITEMS) !== '[object Array]') return;
  if (ITEMS.length === 0) return;

  var selected = -1;
  if (MENU) {
    selected = MENU.selected;
    MENU.reset();
    MENU.close();
  }
  MENU = termMenu({ width: colMax, fg: OPTIONS.fg, bg: OPTIONS.bg });
  MENU.reset();
  MENU.write('');
  var indLen = 1; // indicator length is 2 in `slice`, so distract 1
  for (var i = 0; i < Math.min(rowMax, ITEMS.length); i++) {
    var indicator = INDICATOR_OFF;
    if (RUNNING.hasOwnProperty(ITEMS[i].url)) indicator = INDICATOR_ON;
    var title = ITEMS[i].title;
    var duration = ITEMS[i].duration || '';
    if (duration && duration.length === 4) duration = '0' + duration;
    if (duration) duration = '[' + duration + '] ';
    var text = indicator + pad(i + 1) + '. ' + duration + title;
    MENU.add(slice(text, colMax + indLen));
  }
  if (selected > -1) MENU.selected = selected;
  MENU.on('select', function (label, index) {
    ytEvents.emit('start', index);
  });
  MENU.createStream().pipe(process.stdout);
}
<commit_msg>add a details page;<commit_after>#!/usr/bin/env node

var Q        = require('q');
var events   = require('events');
var https    = require('https');
var libapi   = require('./api');
var libdata  = require('./data');
var entities = require('entities');
var termMenu = require('terminal-menu-2');
var exec     = require('child_process').exec;
var spawn    = require('child_process').spawn;

var OPTIONS  = require('./getopts');
var COLUMNS  = process.stdout.columns || 80;
var ROWS     = process.stdout.rows || 24;
var colMax   = COLUMNS - 4;
var rowMax   = ROWS - 2;

var MENU, RUNNING = {}, ITEMS = [];
var INDICATOR_ON = ' ◉ ', INDICATOR_OFF = ' ◯ ';

Q().
then(function() {
  ITEMS = libdata.readCache();
  makeMenu();
}).
then(function() {
  return libdata.checkConf();
}).
then(function(cookie) {
  if (!MENU) process.stdout.write('Retrieving list of videos ... ');
  var pagesNeeded = Math.ceil(rowMax / libapi.subscription.itemsPerPage);
  return libapi.subscription.get(serial(pagesNeeded), cookie);
}).
then(function(data) {
  if (arrayEquals(ITEMS, data)) return;
  libdata.createCache(data);
  ITEMS = data;
  makeMenu();
}).
catch(function(err) {
  error(err);
});

// events

var ytEvents = new events.EventEmitter();

ytEvents.on('start', function(index) {
  var url = ITEMS[index].url;
  if (MENU.items[index].label.slice(0, 3) === INDICATOR_ON) {
    return killall(RUNNING[url].pid);
  }
  MENU.items[index].label = INDICATOR_ON + MENU.items[index].label.slice(3);
  MENU._drawRow(index);
  // use --player-no-close to prevent video player exiting too early
  // vlc's --play-and-exit option does not work in Mac OS X
  // so we use verbose stderr to see when the video really ends
  var livestreamer = spawn('livestreamer', [
    '--player-no-close',
    url, OPTIONS.f,
    '--player-args', '--video-on-top {filename} --verbose 2',
    '--verbose-player'
  ]);
  livestreamer.stderr.on('data', function(chunk) {
    if (chunk.toString().indexOf('finished input') > -1) {
      if (!RUNNING[url]) return;
      killall(RUNNING[url].pid);
    }
  });
  livestreamer.on('error', function(e) {
    error('Make sure you have access to command `livestreamer`.');
  });
  livestreamer.on('exit', function() {
    ytEvents.emit('end', url);
  });
  RUNNING[url] = livestreamer;
});

ytEvents.on('end', function(url) {
  var index = -1;
  for (var i = 0; i < ITEMS.length; i++) {
    if (ITEMS[i].url === url) {
      index = i;
      break;
    }
  }
  if (index > -1) {
    MENU.items[index].label = INDICATOR_OFF + MENU.items[index].label.slice(3);
    MENU._drawRow(index);
  }
  delete RUNNING[url];
});

function error(err) {
  if (MENU) {
    MENU.reset();
    MENU.close();
  }
  console.error(err.stack ? err.stack : err);
  process.kill();
}

// serial(2) = [1, 2]  //  serial(5) = [1, 2, 3, 4, 5]
function serial(n) {
  return Array.apply(undefined, { length: n }).map(Function.call, function(i) {
    return i + 1;
  });
}

function killall(pid) {
  if (!pid) return;
  exec('pgrep -P ' + pid, function(error, stdout, stderr) {
    if (stdout) {
      var pids = stdout.trim().split('\n');
      pids.forEach(function(pid) {
        if (pid) process.kill(pid, 'SIGKILL');
      });
    }
    process.kill(pid, 'SIGKILL');
  });
}

function arrayEquals(a, b) {
  return JSON.stringify(a) === JSON.stringify(b);
}

// split('一a二b三c四五六七', 3) = ["一a", "二b", "三c", "四", "五", "六", "七"]
function split(str, len) {
  if (len < 1) len = 1;
  var chunks = [];
  while (str) {
    var s = '', c = 0;
    for (var i = 0; str[i] && i < len - c; i++) {
      if (/[^\u0000-\u00ff]/.test(str[i])) c++;
      if (i + 1 <= len - c) s += str[i];
    }
    chunks.push(s);
    str = str.slice(s.length);
  }
  return chunks;
}

function slice(str, len) {
  str = entities.decodeHTML(str);
  return str.slice(0, len - (str.match(/[^\u0000-\u00ff]/g) || '').length);
}

function pad(n) {
  return (n < 10 ? '0' : '') + n;
}

function makeMenu() {
  if (Object.prototype.toString.call(ITEMS) !== '[object Array]') return;
  if (ITEMS.length === 0) return;

  var selected = -1;
  if (MENU) {
    selected = MENU.selected;
    MENU.reset();
    MENU.close();
  }
  MENU = termMenu({ width: colMax, fg: OPTIONS.fg, bg: OPTIONS.bg });
  MENU.reset();
  MENU.write('');
  var indLen = 1; // indicator length is 2 in `slice`, so distract 1
  for (var i = 0; i < Math.min(rowMax, ITEMS.length); i++) {
    var indicator = INDICATOR_OFF;
    if (RUNNING.hasOwnProperty(ITEMS[i].url)) indicator = INDICATOR_ON;
    var title = ITEMS[i].title;
    var duration = ITEMS[i].duration || '';
    if (duration && duration.length === 4) duration = '0' + duration;
    if (duration) duration = '[' + duration + '] ';
    var text = indicator + pad(i + 1) + '. ' + duration + title;
    MENU.add(slice(text, colMax + indLen));
  }
  if (selected > -1) MENU.selected = selected;
  MENU.on('select', function (label, index) {
    ytEvents.emit('start', index);
  });
  MENU.createStream().pipe(process.stdout);
}

function makeDetailsPage() {
  if (MENU) {
    selected = MENU.selected;
    MENU.reset();
    MENU.close();
  }
  var ITEM = ITEMS[selected];
  MENU = termMenu({ width: colMax, fg: OPTIONS.fg, bg: OPTIONS.bg });
  MENU.detailsPage = true;
  MENU.reset();
  MENU.write('Title:\n');
  split(ITEM.title, colMax - 2).forEach(function(line) {
    MENU.write(line + '\n');
  });
  MENU.write('\n');
  var username = ITEM.username;
  if (ITEM.verified) username += ' (verified)'
  MENU.write('By: ' + username + '\n');
  MENU.write('\n');
  MENU.write('Description:\n');
  split(ITEM.description, colMax - 2).forEach(function(line) {
    MENU.write(line + '\n');
  });
  MENU.write('\n');
  MENU.add('URL:   ' + ITEM.url);
  MENU.write('\n');
  MENU.write(ITEM.time + ' - ' + ITEM.views + '\n');
  MENU.createStream().pipe(process.stdout);
}

process.stdin.on('data', function(buf) {
  if (!MENU) return;
  var codes = [].join.call(buf, '.');
  if (MENU.detailsPage && codes === '27.91.68') {             // left
    MENU.reset();
    MENU.close();
    makeMenu();
  } else if (!MENU.detailsPage && codes === '27.91.67') {      // right
    makeDetailsPage();
  }
});
<|endoftext|>


<commit_before>//
//
Ext.define('CustomApp', {
    extend: 'Rally.app.App',
    componentCls: 'app',
    items: [ { xtype : 'container', layout : { type : 'table',columns : 2 },items : [
            { xtype : 'container', itemId: 'iterationDropDown', columnWidth: 2 },
            { xtype : 'rallycheckboxfield', itemId : 'flatCheckBox', fieldLabel : 'Show only "Flat"', columnWidth : 1,value : false }
        ] },
        { xtype: 'container', itemId: 'chart1', columnWidth: 1 }
    ],

    addIterationDropDown : function() {

        var timeboxScope = this.getContext().getTimeboxScope();
        if(timeboxScope) {
            var record = timeboxScope.getRecord();
            var name = record.get('Name');

            this.gIteration = record.data;
            this._onIterationSelect();
        } else {
            // add the iteration dropdown selector
            this.down("#iterationDropDown").add( {
                xtype: 'rallyiterationcombobox',
                itemId : 'iterationSelector',
                listeners: {
                        select: this._onIterationSelect,
                        ready:  this._onIterationSelect,
                        scope: this
                }
            });
        }

    },

    _onIterationSelect : function() {

        if (_.isUndefined( this.getContext().getTimeboxScope())) {
            var value =  this.down('#iterationSelector').getRecord();
            this.gIteration = value.data;
        }

        var iterationId = this.gIteration.ObjectID;

        Ext.create('Rally.data.lookback.SnapshotStore', {
            autoLoad : true,
            limit: Infinity,
            listeners: {
                load: this._onIterationSnapShotData,
                scope : this
            },
            fetch: ['ObjectID','Name', 'Priority','ScheduleState', 'PlanEstimate','TaskEstimateTotal','TaskRemainingTotal', '_UnformattedID','Blocked' ],
            hydrate: ['ScheduleState'],
            filters: [
                {
                    property: '_TypeHierarchy',
                    operator: 'in',
                    value: ['Defect','HierarchicalRequirement']
                },
                {
                    property: 'Iteration',
                    operator: 'in',
                    value: iterationId
                }
            ]
        });

    },

    _onIterationSnapShotData : function(store,data,success) {

        var lumenize = window.parent.Rally.data.lookback.Lumenize;
        var snapShotData = _.map(data,function(d){return d.data});
        var metrics = [];
        var metricsAfterSummary = [];
        var hcConfig = [ { name : "label" }];
        _.each(
            _.uniq(snapShotData, function (e) { return e["_UnformattedID"];}),
            function (item) {
                var id = item["_UnformattedID"];

                var metric1 = {
                    as : "S" + id.toString()+"Remaining",
                    f : 'filteredSum',
                    field : 'TaskRemainingTotal',
                    filterField : '_UnformattedID',
                    filterValues : [id]
                };
                var metric2 = {
                    as : "S" + id.toString()+"Total",
                    f : 'filteredSum',
                    field : 'TaskEstimateTotal',
                    filterField : '_UnformattedID',
                    filterValues : [id]
                }
                var summary =
                {
                    as: "S" + id.toString(),
                    f: function (row, index, summaryMetrics, seriesData) {
                        var t = row["S"+id.toString()+"Total"];
                        var r = row["S"+id.toString()+"Remaining"];
                        return t > 0 ? ((t-r)/t)*100 : 0;
                    }
                }
                metricsAfterSummary.push(summary);

                metrics.push(metric1);
                metrics.push(metric2);

                var hc = {
                    name : "S" + id.toString(), comment : item["Name"]
                };

                if (item["Blocked"]==true)
                    hc.dashStyle = "ShortDot";

                hcConfig.push( hc );

            }
        );

        var config = {
          deriveFieldsOnInput: [],
          metrics: metrics,
          summaryMetricsConfig: [],
          deriveFieldsAfterSummary: metricsAfterSummary,
          granularity: lumenize.Time.DAY,
          tz: 'America/New_York',
          holidays: [],
          workDays: 'Monday,Tuesday,Wednesday,Thursday,Friday'
        };

        // release start and end dates
        var startOnISOString = new lumenize.Time(this.gIteration.StartDate).getISOStringInTZ(config.tz)
        var upToDateISOString = new lumenize.Time(this.gIteration.EndDate).getISOStringInTZ(config.tz)

        calculator = new lumenize.TimeSeriesCalculator(config);
        calculator.addSnapshots(snapShotData, startOnISOString, upToDateISOString);

        var hc = lumenize.arrayOfMaps_To_HighChartsSeries(calculator.getResults().seriesData, hcConfig);
        this.ghc = hc;
        this._showChart(hc);
    },

    // return 'flat' if the % complete has been static for > 2 days, but not if it's zero or 100 (complete).
    isFlat : function(series,todayIndex) {
        var flat = false;
        if (todayIndex != -1 && todayIndex >= 2 ) {
            if (series.data[todayIndex] != 0 && series.data[todayIndex] != 100 )
                if (series.data[todayIndex] == series.data[todayIndex-1] && series.data[todayIndex] == series.data[todayIndex-2])
                    flat = true;
        }
        return flat;
    },

    _showChart : function() {
        var that = this;
        var series = this.ghc;
        var chart = this.down("#chart1");
        chart.removeAll();
        // get checkbox value
        var flat = (this.down("#flatCheckBox").getValue());

        // remove nulls from chart
        series[1].data = _.map(series[1].data, function(d) { return _.isNull(d) ? 0 : d; });

        // find index into series for today
        var today = new Date();
        var todayIndex = -1;
        _.each( series[0].data, function(x,i) {
            var dt = new Date(Date.parse(x));

            if (todayIndex == -1 && dt > today)
                todayIndex = i;
        });

        // set values for future dates to null
        _.each ( series, function(s,i) {
            if (i>0) {
                s.data = _.map(s.data,function(d,x) {

                    return ((todayIndex > 0 && x > todayIndex) ? null :  d);
                });
            }
        });

        var newSeries = [];
        if (flat) {
            newSeries = _.filter( series.slice(1,series.length), function(s) { return (that.isFlat(s,todayIndex));} );
        } else {
            newSeries = series.slice(1,series.length);
        }

        var extChart = Ext.create('Rally.ui.chart.Chart', {
         chartData: {
            categories : series[0].data,
            series : newSeries
         },
          chartConfig : {

                chart: {

                },
                title: {
                text: '',
                x: -20 //center
                },
                tooltip: {
                    formatter: function() {

                        return this.series.name + ':' + this.series.options.comment + '
 The value for '+ this.x +
                    ' is '+ Math.round(this.y) +'';
                    }
                },
                legend: {
                            align: 'center',
                            verticalAlign: 'bottom'
                },
                plotOptions : {

                 line : {
                    zIndex : 1,
                    tooltip : {
                        valueSuffix : ' Percent'
                    }

                 }
                },
                 yAxis: {
                    min : 0,
                    max : 100,
                    title: {
                        text: '% Complete by Task Hours'
                    },
                    plotLines: [{
                        value: 0,
                        width: 1,
                        color: '#808080'
                    }]
                },
            }
        });
        chart.add(extChart);
        var p = Ext.get(chart.id);
        var elems = p.query("div.x-mask");
        _.each(elems, function(e) { e.remove(); });
        var elems = p.query("div.x-mask-msg");
        _.each(elems, function(e) { e.remove(); });
    },

    launch: function() {

        var that = this;
        this.addIterationDropDown();
        this.down("#flatCheckBox").on("change",function(){that._showChart();});

    }
});
<commit_msg>fixed the prefixes<commit_after>//
//

Ext.define('CustomApp', {
    extend: 'Rally.app.App',
    componentCls: 'app',
    items: [ { xtype : 'container', layout : { type : 'table',columns : 2 },items : [
            { xtype : 'container', itemId: 'iterationDropDown', columnWidth: 2 },
            { xtype : 'rallycheckboxfield', itemId : 'flatCheckBox', fieldLabel : 'Show only "Flat"', columnWidth : 1,value : false }
        ] },
        { xtype: 'container', itemId: 'chart1', columnWidth: 1 }
    ],

    addIterationDropDown : function() {

        var timeboxScope = this.getContext().getTimeboxScope();
        if(timeboxScope) {
            var record = timeboxScope.getRecord();
            var name = record.get('Name');

            this.gIteration = record.data;
            this._onIterationSelect();
        } else {
            // add the iteration dropdown selector
            this.down("#iterationDropDown").add( {
                xtype: 'rallyiterationcombobox',
                itemId : 'iterationSelector',
                listeners: {
                        select: this._onIterationSelect,
                        ready:  this._onIterationSelect,
                        scope: this
                }
            });
        }

    },

    _onIterationSelect : function() {

        if (_.isUndefined( this.getContext().getTimeboxScope())) {
            var value =  this.down('#iterationSelector').getRecord();
            this.gIteration = value.data;
        }

        var iterationId = this.gIteration.ObjectID;

        Ext.create('Rally.data.lookback.SnapshotStore', {
            autoLoad : true,
            limit: Infinity,
            listeners: {
                load: this._onIterationSnapShotData,
                scope : this
            },
            fetch: ['ObjectID','Name', 'Priority','ScheduleState', 'PlanEstimate','TaskEstimateTotal','TaskRemainingTotal', '_UnformattedID','Blocked','_Type','_TypeHierarchy' ],
            hydrate: ['ScheduleState','_TypeHierarchy'],
            filters: [
                {
                    property: '_TypeHierarchy',
                    operator: 'in',
                    value: ['Defect','HierarchicalRequirement']
                },
                {
                    property: 'Iteration',
                    operator: 'in',
                    value: iterationId
                }
            ]
        });

    },

    _getFormattedID : function (item) {
        var type = _.last(item["_TypeHierarchy"]);
        var prefix = _.find( this.types, function (t) {
            var tname = t.get("Name").replace(/\s+/g, ''); // remove the space
            return tname == type;
        });
        return prefix.get("IDPrefix") + item["_UnformattedID"];
    },

    _onIterationSnapShotData : function(store,data,success) {

        var that = this;
        var lumenize = window.parent.Rally.data.lookback.Lumenize;
        var snapShotData = _.map(data,function(d){return d.data});
        var metrics = [];
        var metricsAfterSummary = [];
        var hcConfig = [ { name : "label" }];
        _.each(
            _.uniq(snapShotData, function (e) { return that._getFormattedID(e);}),
            function (item) {
                var id = item["_UnformattedID"];
                var fid = that._getFormattedID(item);

                var metric1 = {
                    as : fid+"Remaining",
                    f : 'filteredSum',
                    field : 'TaskRemainingTotal',
                    filterField : '_UnformattedID',
                    filterValues : [id]
                };
                var metric2 = {
                    as : fid+"Total",
                    f : 'filteredSum',
                    field : 'TaskEstimateTotal',
                    filterField : '_UnformattedID',
                    filterValues : [id]
                }
                var summary =
                {
                    as: fid,
                    f: function (row, index, summaryMetrics, seriesData) {
                        var t = row[fid+"Total"];
                        var r = row[fid+"Remaining"];
                        return t > 0 ? ((t-r)/t)*100 : 0;
                    }
                }
                metricsAfterSummary.push(summary);

                metrics.push(metric1);
                metrics.push(metric2);

                var hc = {
                    name : fid, comment : item["Name"]
                };

                if (item["Blocked"]==true)
                    hc.dashStyle = "ShortDot";

                hcConfig.push( hc );

            }
        );

        var config = {
          deriveFieldsOnInput: [],
          metrics: metrics,
          summaryMetricsConfig: [],
          deriveFieldsAfterSummary: metricsAfterSummary,
          granularity: lumenize.Time.DAY,
          tz: 'America/New_York',
          holidays: [],
          workDays: 'Monday,Tuesday,Wednesday,Thursday,Friday'
        };

        // release start and end dates
        var startOnISOString = new lumenize.Time(this.gIteration.StartDate).getISOStringInTZ(config.tz)
        var upToDateISOString = new lumenize.Time(this.gIteration.EndDate).getISOStringInTZ(config.tz)

        calculator = new lumenize.TimeSeriesCalculator(config);
        calculator.addSnapshots(snapShotData, startOnISOString, upToDateISOString);

        var hc = lumenize.arrayOfMaps_To_HighChartsSeries(calculator.getResults().seriesData, hcConfig);
        this.ghc = hc;
        this._showChart(hc);
    },

    // return 'flat' if the % complete has been static for > 2 days, but not if it's zero or 100 (complete).
    isFlat : function(series,todayIndex) {
        var flat = false;
        if (todayIndex != -1 && todayIndex >= 2 ) {
            if (series.data[todayIndex] != 0 && series.data[todayIndex] != 100 )
                if (series.data[todayIndex] == series.data[todayIndex-1] && series.data[todayIndex] == series.data[todayIndex-2])
                    flat = true;
        }
        return flat;
    },

    _showChart : function() {
        var that = this;
        var series = this.ghc;
        var chart = this.down("#chart1");
        chart.removeAll();
        // get checkbox value
        var flat = (this.down("#flatCheckBox").getValue());

        // remove nulls from chart
        series[1].data = _.map(series[1].data, function(d) { return _.isNull(d) ? 0 : d; });

        // find index into series for today
        var today = new Date();
        var todayIndex = -1;
        _.each( series[0].data, function(x,i) {
            var dt = new Date(Date.parse(x));

            if (todayIndex == -1 && dt > today)
                todayIndex = i;
        });

        // set values for future dates to null
        _.each ( series, function(s,i) {
            if (i>0) {
                s.data = _.map(s.data,function(d,x) {

                    return ((todayIndex > 0 && x > todayIndex) ? null :  d);
                });
            }
        });

        var newSeries = [];
        if (flat) {
            newSeries = _.filter( series.slice(1,series.length), function(s) { return (that.isFlat(s,todayIndex));} );
        } else {
            newSeries = series.slice(1,series.length);
        }

        var extChart = Ext.create('Rally.ui.chart.Chart', {
         chartData: {
            categories : series[0].data,
            series : newSeries
         },
          chartConfig : {

                chart: {

                },
                title: {
                text: '',
                x: -20 //center
                },
                tooltip: {
                    formatter: function() {

                        return this.series.name + ':' + this.series.options.comment + '
 The value for '+ this.x +
                    ' is '+ Math.round(this.y) +'';
                    }
                },
                legend: {
                            align: 'center',
                            verticalAlign: 'bottom'
                },
                plotOptions : {

                 line : {
                    zIndex : 1,
                    tooltip : {
                        valueSuffix : ' Percent'
                    }

                 }
                },
                 yAxis: {
                    min : 0,
                    max : 100,
                    title: {
                        text: '% Complete by Task Hours'
                    },
                    plotLines: [{
                        value: 0,
                        width: 1,
                        color: '#808080'
                    }]
                },
            }
        });
        chart.add(extChart);
        var p = Ext.get(chart.id);
        var elems = p.query("div.x-mask");
        _.each(elems, function(e) { e.remove(); });
        var elems = p.query("div.x-mask-msg");
        _.each(elems, function(e) { e.remove(); });
    },

    getTypePrefixes : function() {

        var filter = Ext.create('Rally.data.QueryFilter', {
            property: 'Name',
            operator: '=',
            value: 'Defect'
        });

        var andedTogetherFilter = filter.or(Ext.create('Rally.data.QueryFilter', {
            property: 'Name',
            operator: '=',
            value: 'Hierarchical Requirement'
        }));

        var typeStore = Ext.create('Rally.data.WsapiDataStore', {
            autoLoad: true,
            model: 'TypeDefinition',
            fetch: ['Name', 'IDPrefix' ],
            filters: [
                andedTogetherFilter
            ],
            listeners: {
                load: function(store, recs ) {
                    this.types = recs;
                },
                scope : this
            }
        });
    },

    launch : function() {

        var that = this;
        this.addIterationDropDown();
        this.getTypePrefixes();
        this.down("#flatCheckBox").on("change",function(){that._showChart();});

    }
});
<|endoftext|>


<commit_before>Ext.define('CustomApp', {
    extend: 'Rally.app.App',
    componentCls: 'app',
    items:{ html:'App SDK 2.0rc3 Docs'},
    launch: function() {
        //add some comments to the app
    }
});
<commit_msg>S38 a few more changes<commit_after>Ext.define('CustomApp', {
    extend: 'Rally.app.App',
    componentCls: 'app',
    items:{ html:'App SDK 2.0rc3 Docs'},
    launch: function() {
        //another change
    }
});
<|endoftext|>


<commit_before>(function () {
let pen;

pen = function (el, ops = {}) {
  if (!(this instanceof pen)) {return new pen(...arguments)}
  if (el instanceof pen) {return el}
  this.el = el;
  pen.start(this);
}
pen.cc = (x) => x.replace(/[_\- ](\w)/g, (whole, letter) => letter.toUpperCase());
pen.slice = (x) => ([]).slice.call(x);
pen.type = ((function () {
  let cls2Typ = {},
  names = ['Boolean','Number','String','Function','Array','Date','RegExp','Undefined','Null','Error','Symbol','Promise','NamedNodeMap','Map','NodeList','DOMTokenList','DOMStringMap','CSSStyleDeclaration'];
  for (let i = 0, len = names.length; i < len; i++) cls2Typ[`[object ${names[i]}]`] = names[i].toLowerCase();
  return (obj) => cls2Typ[({}).toString.call(obj)] || 'object';
})());
pen.empty = (arg) => {
  switch (pen.type(arg)) {
    case 'array':
    case 'string':
      return arg.length === 0;
    case 'object':
      if (Object.keys != null) {
        return Object.keys(arg).length === 0 && arg.constructor === Object;
      } else {
        for (let prop in arg) {if (arg.hasOwnProperty(prop)) {return false}}
        return JSON.stringify(arg) === JSON.stringify({});
      }
      break;
    case 'regexp':
      return arg.source.length === 0;
  }
}
pen.random = (x) => x[Math.floor(Math.random() * x.length)];
pen.check = (reg, flg) => (pen.type(reg) === 'string' ? new RegExp(reg, (flg||'gi')) : reg);
pen.parse = (str) => {
  let data = {},
  res = str.match(/([^\n ]*?)=(['"]([^\n'"]*?)['"]|(true|false))/gi);
  if (res == null || (pen.empty(res))) {return null}
  for (let i = 0, len = res.length, lock; i < len; i++) {
    lock = res[i];
    if (lock.includes('=')) {
      data[(lock.substr(0, lock.search(/=/)))] = (lock.substr(lock.search(/=/)+1)).replace(/["']/g, '');
    }
  }
  return (!pen.empty(data) ? data : null);
}
pen.fracture = (it, propz, props, nm) => {
  let pz = pen.type(it.el[propz]), res, prop;
  for (prop in props) {
    res = nm != null ? `${nm}-${prop}` : prop;
    if (pen.type(props[prop]) === 'object') {
      pen.fracture(it, propz, props[prop], res);
    } else {
      pz === 'function' ? it.el[propz](res, props[prop]) : it.el[propz][res] = props[prop];
    }
  }
  return it;
}
pen.parseEl = (str) => {
  let stTag, tag, attribs, text;
  stTag = str.substr(str.search(/</), str.search(/>/)+1);
  tag = stTag.substr(stTag.search(/</)+1, stTag.search(stTag.match(/<([^\n ]+)>/) ? />/ : / /)-1);
  attribs = stTag.substr(stTag.search(/ /)+1, stTag.search(/>/));
  text = str.substr(stTag.length, str.search(/<\//));
  return [(attribs===`<${tag}`?null:attribs), tag, (text===stTag ? null : pen.empty(text) ? null : text)];
}
pen.handoff = (el, pr = !1) => (pr ? pen(el) : el);
pen.$ = (el, pr) => (pen.type(el) === 'string' ? pen.handoff(document.querySelector(el), pr) : el);
pen.$$ = el => document.querySelectorAll(el);
pen.create = (el, pr) => pen.handoff(document.createElement(el), pr);
pen.all = (arr, action, ...data) => {
  for (let i = 0, len = arr.length; i < len; i++) {arr[i][action](...data)}
}
pen.display = (selec, data) => {
  let el = pen.$(selec, true);
  el.text = el.text.replace(/-([^\n]*?)-/g, (whole, word) => data[word]);
}
pen.start = (it) => {
  if (pen.type(it.el) === 'string') {
    if (it.el.startsWith('<')) {
      let [attrs, tag, text] = pen.parseEl(it.el);
      it.el = pen.create(tag);
      if (attrs != null) {let omage = pen.parse(attrs); if (omage != null) {it.attr(omage)}};
      if (text != null && (text.length !== 0)) {it.html(text, {parse:true})};
    } else {
      it.el = pen.$(it.el);
    }
  }
  if (it.el == null) {return};
  it.el = it.tag === 'template' ? it.el.content : it.el;
  it.cusOps = {};
  switch (true) {
    case it.el instanceof Document:
      pen.prototype.ready = function () {
        it.on('DOMContentLoaded', ...arguments);
        return it;
      };
    break;
    case it.el instanceof Window:
      it.doc = it.el.document;
    break;
    case it.tag === 'template':
      pen.prototype.clone = function () {
        document.importNode(...arguments);
        return it;
      };
    break;
    case it.tag === 'canvas':
      it.ctx = it.context = it.el.getContext('2d');
    break;
  }
  return this;
}
pen.fn = pen.prototype = {
  constructor: pen,
  toString() {return this.el.outerHTML},
  get tag () {
    return (this.el.tagName||'UNPARSED-OR-IOS-ELEMENT').toLowerCase();
  },

  get text () {
    return this.html();
  },
  set text (x) {
    return this.html(x);
  },

  get children () {
    let children = pen.slice(this.el.children);
    for (let i = 0, len = children.length; i < len; i++) {
      children[i] = pen(children[i]);
    }
    return children;
  },
  set children (els) {
    this.append(...els);
    return this;
  },

  get parent () {
    return this.el.parentNode || null;
  },
  set parent (el) {
    return this.appendTo(el);
  },

  get classes () {
    return pen.slice(this.el.classList);
  },
  set classes (cls) {
    return this.toggle(cls);
  },

  get attrs () {
    let ars = {}, attrs = pen.slice(this.el.attributes);
    for (let i = 0, len = attrs.length; i < len; i++) {
      ars[attrs[i].name] = attrs[i].value;
    }
    return ars;
  },
  set attrs (obj) {
    return this.attr(obj);
  },

  get selector () {
    let attrs = this.attrs, str = '', attrN,
    id = this.attr('id') != null ? `#${this.attr('id')}` : '',
    cls = this.attr('class') != null ? `.${this.classes.join('.')}` : '';
    for (attrN in attrs) {
      if (!/class|id|style/i.test(attrN)) {
        str += `[${attrN}="${attrs[attrN]}"]`;
      }
    }
    return `${this.tag}${id}${cls}${(pen.empty(str) ? '' : str)}`;
  },

  get size () {
    return this.el.getBoundingClientRect();
  },

  get hidden () {
    return this.css('display') === 'none';
  },

  get ops () {
    return {parseIt:(this.cusOps.parseIt || false), app:(this.cusOps.app || false), parse:(this.cusOps.parse || false)};
  },
  set ops (ops) {
    this.cusOps = {
      parseIt:(ops!=null?(ops.parseIt||false):false),
      app:(ops!=null?(ops.app||false):false),
      parse:(ops!=null?(ops.parse||false):false)
    }
    return this.cusOps;
  },

  html (str, ops) {
    if (str != null) {
      let {parse,app} = (this.ops = ops!=null?ops:{}),
      res = parse ? 'innerHTML' : 'innerText',
      res2 = /textarea/i.test(this.tag) ? 'innerText' : res;
      switch (this.tag) {
        case 'input':
          this.el.value = app ? this.el.value+str : str;
        break;
        case 'option':
          this.el.value = app ? this.el.value+str : str;
          this.el.innerText = app ? this.el.value+str : str;
        break;
        default:
          this.el[res2] = app ? this.el[res2]+str : str;
      }
      return this;
    } else {
      switch (this.tag) {
        case 'input': case 'option': case 'textarea':
          return this.el.value;
        break;
        default:
          return this.el.innerText;
      }
    }
  },
  attr (attr, value) {
    if (attr != null) {
      // if typeof attr is an object then call a function to handle objects and call the curried function
      // else if value isn't null then set the attribute and return 'this' else return the attribute given
      switch (true) {
        case pen.type(attr) === 'object':
          return pen.fracture(this, 'setAttribute', attr);

        case value != null:
          this.el.setAttribute(attr, value);
          return this;

        default:
          return this.el.getAttribute(attr);
      }
    } else {
      return this.attrs;
    }
  },
  css (rule, rules) {
    if (rule != null) {
      switch (true) {
        case pen.type(rule) === 'object':
          return pen.fracture(this, 'style', rule);

        case rules != null:
          this.el.style[pen.cc(rule)] = rules;
          return this;

        default:
          return this.el.style[rule];
      }
    } else {
      return this.el.style;
    }
  },
  on (evtp, cb, cp, name) {
    cp = cp || false;
    this.el.events = this.el.events || {};
    this.el.addEventListener(evtp, cb, cp);
    this.el.events[evtp] = {capture:cp};
    this.el.events[evtp][name!=null?name:(cb.name||'func')] = cb;
    return this;
  },
  off (evtp, cb, name) {
    this.el.removeEventListener(evtp, (name!=null?this.el.events[evtp][name]:cb));
    delete this.el.events[evtp][name!=null?name:(cb.name||'func')];
    return this;
  },
  append (...elements) {
    if (pen.empty(elements)) {
      throw new Error(`No elements passed`);
    }
    for (let i = 0,len = elements.length, element; i < len; i++) {
      element = pen.$(elements[i]);
      this.el.appendChild((element instanceof pen ? element.el : element));
    }
    return this;
  },
  appendTo (el) {
    pen(el).append(this);
    return this;
  },
  remove () {
    if (this.parent != null) {
      this.parent.removeChild(this.el);
    } else {
      throw new Error(`Couldn't remove ${this}`);
    }
    return this;
  },
  $ (qur) {
    return pen.handoff(this.el.querySelector(qur), this.ops.parseIt);
  },
  $$ (qur) {
    return this.el.querySelectorAll(qur);
  },
  create (el, ret) {
    el = pen(el);
    this.append(el);
    return ret === 'parent' ? this : ret === 'child' ? el : this
  },
  toggle (...classes) {
    for (let i = 0, len = classes.length; i < len; i++) {
      this.el.classList.toggle(classes[i]);
    };
    return this;
  },
  hasClass (cls) {
    for (let i = 0, len = this.classes.length; i < len; i++) {if (this.classes[i] === cls) {return true}}
    return false;
  }
}
window.pen = pen; window.pDoc = pen(document); window.pWin = pen(window);
if (document.body) {
  window.body = document.body; window.head = document.head;
  window.pHead = pen(head); window.pBody = pen(body);
} else {
  document.addEventListener('DOMContentLoaded',()=>{
    window.body = document.body; window.head = document.head;
    window.pHead = pen(head); window.pBody = pen(body);
  }, {once:true});
}
}).call(this);
<commit_msg>Html update<commit_after>(function () {
let pen;

pen = function (el, ops = {}) {
  if (!(this instanceof pen)) {return new pen(...arguments)}
  if (el instanceof pen) {return el}
  this.el = el;
  pen.start(this);
}
pen.cc = (x) => x.replace(/[_\- ](\w)/g, (whole, letter) => letter.toUpperCase());
pen.slice = (x) => ([]).slice.call(x);
pen.pipeline = (arg, ...fns) => {
  for (let i = 0, len = fns.length; i < len; i++) {
    arg = fns[i](arg);
  }
  return arg;
}
pen.type = ((function () {
  let cls2Typ = {},
  names = ['Boolean','Number','String','Function','Array','Date','RegExp','Undefined','Null','Error','Symbol','Promise','NamedNodeMap','Map','NodeList','DOMTokenList','DOMStringMap','CSSStyleDeclaration'];
  for (let i = 0, len = names.length; i < len; i++) cls2Typ[`[object ${names[i]}]`] = names[i].toLowerCase();
  return (obj) => cls2Typ[({}).toString.call(obj)] || 'object';
})());
pen.empty = (arg) => {
  switch (pen.type(arg)) {
    case 'array':
    case 'string':
      return arg.length === 0;
    case 'object':
      if (Object.keys != null) {
        return Object.keys(arg).length === 0 && arg.constructor === Object;
      } else {
        for (let prop in arg) {if (arg.hasOwnProperty(prop)) {return false}}
        return JSON.stringify(arg) === JSON.stringify({});
      }
      break;
    case 'regexp':
      return arg.source.length === 0;
  }
}
pen.random = (x) => x[Math.floor(Math.random() * x.length)];
pen.check = (reg, flg) => (pen.type(reg) === 'string' ? new RegExp(reg, (flg||'gi')) : reg);
pen.parse = (str) => {
  let data = {},
  res = str.match(/([^\n ]*?)=(['"]([^\n'"]*?)['"]|(true|false))/gi);
  if (res == null || (pen.empty(res))) {return null}
  for (let i = 0, len = res.length, lock; i < len; i++) {
    lock = res[i];
    if (lock.includes('=')) {
      data[(lock.substr(0, lock.search(/=/)))] = (lock.substr(lock.search(/=/)+1)).replace(/["']/g, '');
    }
  }
  return (!pen.empty(data) ? data : null);
}
pen.fracture = (it, propz, props, nm) => {
  let pz = pen.type(it.el[propz]), res, prop;
  for (prop in props) {
    res = nm != null ? `${nm}-${prop}` : prop;
    if (pen.type(props[prop]) === 'object') {
      pen.fracture(it, propz, props[prop], res);
    } else {
      pz === 'function' ? it.el[propz](res, props[prop]) : it.el[propz][res] = props[prop];
    }
  }
  return it;
}
pen.parseEl = (str) => {
  let stTag, tag, attribs, text;
  stTag = str.substr(str.search(/</), str.search(/>/)+1);
  tag = stTag.substr(stTag.search(/</)+1, stTag.search(stTag.match(/<([^\n ]+)>/) ? />/ : / /)-1);
  attribs = stTag.substr(stTag.search(/ /)+1, stTag.search(/>/));
  text = str.substr(stTag.length, str.search(/<\//));
  return [(attribs===`<${tag}`?null:attribs), tag, (text===stTag ? null : pen.empty(text) ? null : text)];
}
pen.handoff = (el, pr = !1) => (pr ? pen(el) : el);
pen.$ = (el, pr) => (pen.type(el) === 'string' ? pen.handoff(document.querySelector(el), pr) : el);
pen.$$ = el => document.querySelectorAll(el);
pen.create = (el, pr) => pen.handoff(document.createElement(el), pr);
pen.all = (arr, action, ...data) => {
  for (let i = 0, len = arr.length; i < len; i++) {arr[i][action](...data)}
}
pen.display = (selec, data) => {
  let el = pen.$(selec, true);
  el.text = el.text.replace(/-([^\n]*?)-/g, (whole, word) => data[word]);
}
pen.start = (it) => {
  if (pen.type(it.el) === 'string') {
    if (it.el.startsWith('<')) {
      let [attrs, tag, text] = pen.parseEl(it.el);
      it.el = pen.create(tag);
      if (attrs != null) {let omage = pen.parse(attrs); if (omage != null) {it.attr(omage)}};
      if (text != null && (text.length !== 0)) {it.html(text, {parse:true})};
    } else {
      it.el = pen.$(it.el);
    }
  }
  if (it.el == null) {return};
  it.el = it.tag === 'template' ? it.el.content : it.el;
  it.cusOps = {};
  switch (true) {
    case it.el instanceof Document:
      pen.prototype.ready = function () {
        it.on('DOMContentLoaded', ...arguments);
        return it;
      };
    break;
    case it.el instanceof Window:
      it.doc = it.el.document;
    break;
    case it.tag === 'template':
      pen.prototype.clone = function () {
        document.importNode(...arguments);
        return it;
      };
    break;
    case it.tag === 'canvas':
      it.ctx = it.context = it.el.getContext('2d');
    break;
  }
  return this;
}
pen.fn = pen.prototype = {
  constructor: pen,
  toString() {return this.el.outerHTML},
  get tag () {
    return (this.el.tagName||'UNPARSED-OR-IOS-ELEMENT').toLowerCase();
  },

  get text () {
    return this.html();
  },
  set text (x) {
    return this.html(x);
  },

  get children () {
    let children = pen.slice(this.el.children),
    i = 0, len = children.length;
    for (; i < len; i++) {
      children[i] = pen(children[i]);
    }
    return children;
  },
  set children (els) {
    this.append(...els);
    return this;
  },

  get parent () {
    return this.el.parentNode || null;
  },
  set parent (el) {
    return this.appendTo(el);
  },

  get classes () {
    return pen.slice(this.el.classList);
  },
  set classes (cls) {
    return this.toggle(cls);
  },

  get attrs () {
    let ars = {}, attrs = pen.slice(this.el.attributes);
    for (let i = 0, len = attrs.length; i < len; i++) {
      ars[attrs[i].name] = attrs[i].value;
    }
    return ars;
  },
  set attrs (obj) {
    return this.attr(obj);
  },

  get selector () {
    let attrs = this.attrs, str = '', attrN,
    id = this.attr('id') != null ? `#${this.attr('id')}` : '',
    cls = this.attr('class') != null ? `.${this.classes.join('.')}` : '';
    for (attrN in attrs) {
      if (!/class|id|style/i.test(attrN)) {
        str += `[${attrN}="${attrs[attrN]}"]`;
      }
    }
    return `${this.tag}${id}${cls}${(pen.empty(str) ? '' : str)}`;
  },

  get size () {
    return this.el.getBoundingClientRect();
  },

  get hidden () {
    return this.css('display') === 'none';
  },

  get ops () {
    return {parseIt:(this.cusOps.parseIt || false), app:(this.cusOps.app || false), parse:(this.cusOps.parse || false)};
  },
  set ops (ops) {
    this.cusOps = {
      parseIt:(ops!=null?(ops.parseIt||false):false),
      app:(ops!=null?(ops.app||false):false),
      parse:(ops!=null?(ops.parse||false):false)
    }
    return this.cusOps;
  },

  html (str, ops) {
    let {parse, app} = ops == null ? (!pen.empty(this.cusOps) ? this.cusOps : this.ops) : ops;
    let res = this.tag === 'input' || this.tag === 'option' ? 'value' : (parse ? 'innerHTML' : 'innerText');
    if (!pen.pipeline(arguments, pen.slice, pen.empty)) {
      res2 = app ? this.el[res]+str : str;
      this.el[res] = res2;
      if (this.tag === 'option') {this.el.innerHTML = this.el[res]}
      return this;
    } else {
      return this.el[res];
    }
  },
  attr (attr, value) {
    if (attr != null) {
      // if typeof attr is an object then call a function to handle objects and call the curried function
      // else if value isn't null then set the attribute and return 'this' else return the attribute given
      switch (true) {
        case pen.type(attr) === 'object':
          return pen.fracture(this, 'setAttribute', attr);

        case value != null:
          this.el.setAttribute(attr, value);
          return this;

        default:
          return this.el.getAttribute(attr);
      }
    } else {
      return this.attrs;
    }
  },
  css (rule, rules) {
    if (rule != null) {
      switch (true) {
        case pen.type(rule) === 'object':
          return pen.fracture(this, 'style', rule);

        case rules != null:
          this.el.style[pen.cc(rule)] = rules;
          return this;

        default:
          return this.el.style[rule];
      }
    } else {
      return this.el.style;
    }
  },
  on (evtp, cb, cp, name) {
    cp = cp || false;
    this.el.events = this.el.events || {};
    this.el.addEventListener(evtp, cb, cp);
    this.el.events[evtp] = {capture:cp};
    this.el.events[evtp][name!=null?name:(cb.name||'func')] = cb;
    return this;
  },
  off (evtp, cb, name) {
    this.el.removeEventListener(evtp, (name!=null?this.el.events[evtp][name]:cb));
    delete this.el.events[evtp][name!=null?name:(cb.name||'func')];
    return this;
  },
  append (...elements) {
    if (pen.empty(elements)) {
      throw new Error(`No elements passed`);
    }
    for (let i = 0,len = elements.length, element; i < len; i++) {
      element = pen.$(elements[i]);
      this.el.appendChild((element instanceof pen ? element.el : element));
    }
    return this;
  },
  appendTo (el) {
    pen(el).append(this);
    return this;
  },
  remove () {
    if (this.parent != null) {
      this.parent.removeChild(this.el);
    } else {
      throw new Error(`Couldn't remove ${this}`);
    }
    return this;
  },
  $ (qur) {
    return pen.handoff(this.el.querySelector(qur), this.ops.parseIt);
  },
  $$ (qur) {
    return this.el.querySelectorAll(qur);
  },
  create (el, ret) {
    el = pen(el);
    this.append(el);
    return ret === 'parent' ? this : ret === 'child' ? el : this
  },
  toggle (...classes) {
    for (let i = 0, len = classes.length; i < len; i++) {
      this.el.classList.toggle(classes[i]);
    };
    return this;
  },
  hasClass (cls) {
    for (let i = 0, len = this.classes.length; i < len; i++) {if (this.classes[i] === cls) {return true}}
    return false;
  }
}
window.pen = pen; window.pDoc = pen(document); window.pWin = pen(window);
if (document.body) {
  window.body = document.body; window.head = document.head;
  window.pHead = pen(head); window.pBody = pen(body);
} else {
  document.addEventListener('DOMContentLoaded',()=>{
    window.body = document.body; window.head = document.head;
    window.pHead = pen(head); window.pBody = pen(body);
  }, {once:true});
}
}).call(this);
<|endoftext|>


<commit_before>#!/usr/local/bin/node

// Convers the input argument to an integer
function toInt(num) {
  return parseInt(num, 10);
}

// Parse the input arguments
function readNumbers(input) {
  var numArgs = input.length;
  return input.slice(1, numArgs).map(toInt);
}

// Read and parse command line arguments
var numbers = readNumbers(process.argv);

// Compute the sum of the numbers
var sum = numbers.reduce(function(total, num) {
  return total + num;
}, 0);

console.log(sum);
<commit_msg>rename variable<commit_after>#!/usr/local/bin/node

// Convers the input argument to an integer
function toInt(num) {
  return parseInt(num, 10);
}

// Parse the input arguments
function readNumbers(input) {
  var numArgs = input.length;
  return input.slice(1, numArgs).map(toInt);
}

// Read and parse command line arguments
var integers = readNumbers(process.argv);

// Compute the sum of the numbers
var sum = integers.reduce(function(total, num) {
  return total + num;
}, 0);

console.log(sum);
<|endoftext|>