<commit_before>var partialURL = 'http://localhost:8000';
var i;
var errors = [];

casper.test.begin(
    'Visit crimenmexico pages and check for errors',
    1,
    function suite(test) {
        casper.start(partialURL + '/en/', function() {
            test.assertTitle('Mexico Crime Report - Monthly Crime Info from Mexico',
                             'homepage title is the one expected');
            test.assertExists('svg g path', 'chart exists');
            test.assertEval(function() {
                return __utils__
                    .findAll('.national-chart').length === 5;
            }, '5 charts of crime');
            test.assertExists('#hexmap svg path', 'hexmap exists')
        });

        casper.thenOpen(partialURL + '/es/', function() {
            test.assertExists('svg g path', 'chart exists');
            test.assertEval(function() {
                return __utils__
                    .findAll('.national-chart').length === 5;
            }, 'es 5 charts of crime');
            test.assertExists('#hexmap svg path', 'hexmap exists')
        });

        casper.thenOpen(partialURL + '/en/states.html', function() {
            test.assertEval(function() {
                return __utils__
                    .findAll('.line-chart').length === 32;
            }, 'state small multiples');
        });

        casper.thenOpen(partialURL + '/es/states.html', function() {
            test.assertEval(function() {
                return __utils__
                    .findAll('.line-chart').length === 32;
            }, 'state small multiples');
        });

        // casper.thenOpen(partialURL + '/en/municipios-map.html', function() {
        //     test.assertExists('img.leaflet-tile-loaded', 'municipio map tiles exist');
        // });

        casper.thenOpen(partialURL + '/en/municipios.html', function() {
            test.assertEval(function() {
                return __utils__
                    .findAll('.line-chart').length > 30;
            }, 'municipio small multiples');
        });

        casper.thenOpen(partialURL + '/es/municipios.html', function() {
            test.assertEval(function() {
                return __utils__
                    .findAll('.line-chart').length > 30;
            }, 'municipio small multiples');
        });

        casper.thenOpen(partialURL + '/en/anomalies.html', function() {
        });
        casper.thenOpen(partialURL + '/es/anomalies.html', function() {
        });

        casper.thenOpen(partialURL + '/en/infographics.html', function() {
        });
        casper.thenOpen(partialURL + '/es/infographics.html', function() {
        });

        casper.thenOpen(partialURL + '/en/datos.html', function() {
        });
        casper.thenOpen(partialURL + '/es/datos.html', function() {
        });

        casper.on('page.error', function(msg, trace) {
            this.echo('Error:    ' + msg, 'ERROR');
            this.echo('file:     ' + trace[0].file, 'WARNING');
            this.echo('line:     ' + trace[0].line, 'WARNING');
            this.echo('function: ' + trace[0]['function'], 'WARNING');
            errors.push(msg);
            test.fail('console error');
        });

        casper.on('resource.received', function(resource) {
            var status = resource.status;
            if (status >= 400) {
                test.fail('Resource ' + resource.url +
                          ' failed to load (' + status + ')', 'error');
                casper.log('Resource ' + resource.url +
                           ' failed to load (' + status + ')', 'error');

                errors.push({
                    url: resource.url,
                    status: resource.status
                });
            }
        });

        casper.run(function() {
            if (errors.length > 0) {
                this.echo(errors.length +
                          'errors found', 'WARNING');
            } else {
                this.echo(errors.length + ' Javascript errors found', 'INFO');
            }
            casper.exit();
        });
    });
<commit_msg>test that line charts actually have lines<commit_after>var partialURL = 'http://localhost:8000';
var i;
var errors = [];

casper.test.begin(
    'Visit crimenmexico pages and check for errors',
    1,
    function suite(test) {
        casper.start(partialURL + '/en/', function() {
            test.assertTitle('Mexico Crime Report - Monthly Crime Info from Mexico',
                             'homepage title is the one expected');
            test.assertExists('svg g path', 'chart exists');
            test.assertEval(function() {
                return __utils__
                    .findAll('.national-chart').length === 5;
            }, '5 charts of crime');
            test.assertEval(function() {
                return __utils__
                    .findAll('svg > path.mg-line2-color').length === 1;
            }, '1 line of INEGI homicides');
            test.assertEval(function() {
                return __utils__
                    .findAll('svg > path.mg-line1-color').length === 5;
            }, '5 line of SESNSP crimes');
            test.assertExists('#hexmap svg path', 'hexmap exists');
        });

        casper.thenOpen(partialURL + '/es/', function() {
            test.assertExists('svg g path', 'chart exists');
            test.assertEval(function() {
                return __utils__
                    .findAll('.national-chart').length === 5;
            }, 'es 5 charts of crime');
            test.assertEval(function() {
                return __utils__
                    .findAll('svg > path.mg-line2-color').length === 1;
            }, '1 line of INEGI homicides');
            test.assertEval(function() {
                return __utils__
                    .findAll('svg > path.mg-line1-color').length === 5;
            }, '5 line of SESNSP crimes');
            test.assertExists('#hexmap svg path', 'hexmap exists');
        });

        casper.thenOpen(partialURL + '/en/states.html', function() {
            test.assertEval(function() {
                return __utils__
                    .findAll('.line-chart').length === 32;
            }, 'state small multiples');
            test.assertEval(function() {
                return __utils__
                    .findAll('svg > path.mg-line2-color').length === 32;
            }, '32 lines of INEGI homicides');
            test.assertEval(function() {
                return __utils__
                    .findAll('svg > path.mg-line1-color').length === 32;
            }, '32 line of SESNSP crimes');
        });

        casper.thenOpen(partialURL + '/es/states.html', function() {
            test.assertEval(function() {
                return __utils__
                    .findAll('.line-chart').length === 32;
            }, 'state small multiples');
            test.assertEval(function() {
                return __utils__
                    .findAll('svg > path.mg-line2-color').length === 32;
            }, '32 lines of INEGI homicides');
            test.assertEval(function() {
                return __utils__
                    .findAll('svg > path.mg-line1-color').length === 32;
            }, '32 line of SESNSP crimes');
        });

        // casper.thenOpen(partialURL + '/en/municipios-map.html', function() {
        //     test.assertExists('img.leaflet-tile-loaded', 'municipio map tiles exist');
        // });

        casper.thenOpen(partialURL + '/en/municipios.html', function() {
            test.assertEval(function() {
                return __utils__
                    .findAll('.line-chart').length > 30;
            }, 'municipio small multiples');

            test.assertEval(function() {
                return __utils__
                    .findAll('svg > path.mg-line2-color').length > 32;
            }, 'more than 30 lines of Municipio INEGI homicides');
            test.assertEval(function() {
                return __utils__
                    .findAll('svg > path.mg-line1-color').length > 32;
            }, 'more than 30 line of municipio SESNSP crimes');
        });

        casper.thenOpen(partialURL + '/es/municipios.html', function() {
            test.assertEval(function() {
                return __utils__
                    .findAll('.line-chart').length > 30;
            }, 'municipio small multiples');

            test.assertEval(function() {
                return __utils__
                    .findAll('svg > path.mg-line2-color').length > 32;
            }, 'more than 30 lines of Municipio INEGI homicides');
            test.assertEval(function() {
                return __utils__
                    .findAll('svg > path.mg-line1-color').length > 32;
            }, 'more than 30 line of municipio SESNSP crimes');
        });

        casper.thenOpen(partialURL + '/en/anomalies.html', function() {
            test.assertEval(function() {
                return __utils__
                    .findAll('svg > path.mg-line1-color').length > 1;
            }, 'more than 1 lines of Anomalies');
            test.assertEval(function() {
                return __utils__
                    .findAll('svg g path').length > 32;
            }, 'more than 1 lines of Anomalies');
        });
        casper.thenOpen(partialURL + '/es/anomalies.html', function() {
            test.assertEval(function() {
                return __utils__
                    .findAll('svg > path.mg-line1-color').length > 1;
            }, 'more than 1 lines of Anomalies');
            test.assertEval(function() {
                return __utils__
                    .findAll('svg g path').length > 32;
            }, 'more than 1 lines of Anomalies');
        });

        casper.thenOpen(partialURL + '/en/infographics.html', function() {
        });
        casper.thenOpen(partialURL + '/es/infographics.html', function() {
        });

        casper.thenOpen(partialURL + '/en/datos.html', function() {
        });
        casper.thenOpen(partialURL + '/es/datos.html', function() {
        });

        casper.on('page.error', function(msg, trace) {
            this.echo('Error:    ' + msg, 'ERROR');
            this.echo('file:     ' + trace[0].file, 'WARNING');
            this.echo('line:     ' + trace[0].line, 'WARNING');
            this.echo('function: ' + trace[0]['function'], 'WARNING');
            errors.push(msg);
            test.fail('console error');
        });

        casper.on('resource.received', function(resource) {
            var status = resource.status;
            if (status >= 400) {
                test.fail('Resource ' + resource.url +
                          ' failed to load (' + status + ')', 'error');
                casper.log('Resource ' + resource.url +
                           ' failed to load (' + status + ')', 'error');

                errors.push({
                    url: resource.url,
                    status: resource.status
                });
            }
        });

        casper.run(function() {
            if (errors.length > 0) {
                this.echo(errors.length +
                          'errors found', 'WARNING');
            } else {
                this.echo(errors.length + ' Javascript errors found', 'INFO');
            }
            casper.exit();
        });
    });
<|endoftext|>


<commit_before>try {
  console.log('Dave is ready.');
} catch (e) {
  window.console = {
    log: function () {
      // alert([].slice.call(arguments).join('\n'));
    },
    warn: function () {},
    trace: function () {},
    error: function () {}
  };
}

// required because jQuery 1.4.4 lost ability to search my object property :( (i.e. a[host=foo.com])
jQuery.expr[':'].host = function(obj, index, meta) {
  return obj.host === meta[3];
};

function throttle(fn, delay) {
  var throttled = function () {
    var context = this, args = arguments;
    throttled.cancel();
    throttled.timer = setTimeout(function () {
      fn.apply(context, args);
    }, delay);
  };

  throttled.cancel = function () {
    clearTimeout(throttled.timer);
  };

  return throttled;
}

function debounceAsync(fn) {
  'use strict';
  var waiting = false;
  var last = null;

  return function debouceRunner() {
    var args = [].slice.call(arguments, 0);
    // console.time('tracker');

    var tracker = function () {
      waiting = false;
        // console.timeEnd('tracker');
      if (last) {
        // console.log('applying the last');
        fn.apply(last.context, last.args);
        // console.log('and now clear');
        last = null;
      }
    };

    // put the tracker in place of the callback
    args.push(tracker);

    if (!waiting) {
      // console.log('running this time...');
      waiting = true;
      return fn.apply(this, args);
    } else {
      // console.log('going to wait...');
      last = { args: args, context: this };
    }
  };
}

function escapeHTML(html){
  return String(html)
    .replace(/&(?!\w+;)/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/"/g, '"');
}

function dedupe(array) {
  var hash    = {},
      results = [],
      hasOwn  = Object.prototype.hasOwnProperty,
      i, item, len;

  for (i = 0, len = array.length; i < len; i += 1) {
    item = array[i];

    if (!hasOwn.call(hash, item)) {
      hash[item] = 1;
      results.push(item);
    }
  }

  return results;
}

function isDOM(obj) {
  'use strict';
  var Node = window.Node || false;
  if (Node) {
    return obj instanceof Node;
  }
  return obj.nodeType === 1;
}

function exposeSettings() {
  'use strict';

  function mockEditor (editor, methods) {
    return methods.reduce(function (mockEditor, method) {
      mockEditor[method] = editor[method].bind(editor);
      return mockEditor;
    }, {});
  }

  function mockPanels() {
    var results = {};
    var panels = jsbin.panels.panels;
    ['css', 'javascript', 'html'].forEach(function (type) {
      results[type] = {
        setCode: panels[type].setCode.bind(panels[type]),
        getCode: panels[type].getCode.bind(panels[type]),
        editor: mockEditor(panels[type].editor, [
          'setCursor',
          'getCursor',
          'addKeyMap',
          'on'
        ])
      };
    });

    return results;
  }

  if (isDOM(window.jsbin) || !window.jsbin || !window.jsbin.state) { // because...STUPIDITY!!!
    window.jsbin = {
      user: $.extend(true, {}, window.jsbin.user, jsbin.user),
      'static': jsbin['static'],
      version: jsbin.version,
      analytics: jsbin.analytics,
      state: {
        title: jsbin.state.title,
        description: jsbin.state.description
      },
      embed: jsbin.embed,
      panels: {
        // FIXME decide whether this should be locked down further
        panels: mockPanels()
      }
    }; // create the holding object

    if (jsbin.state.metadata && jsbin.user && jsbin.state.metadata.name === jsbin.user.name && jsbin.user.name) {
      window.jsbin.settings = jsbin.settings;
      return;
    }

    var key = 'o' + (Math.random() * 1).toString(32).slice(2);
    Object.defineProperty(window, key, {
      get:function () {
        window.jsbin.settings = jsbin.settings;
        console.log('jsbin.settings can now be modified on the console');
      }
    });
    if (!jsbin.embed) {
      console.log('To edit settings, type this string into the console: ' + key);
    }
  }
}

var storedSettings = store.localStorage.getItem('settings');
if (storedSettings === 'undefined' || jsbin.embed) {
  // yes, equals the *string* 'undefined', then something went wrong
  storedSettings = null;
}

// try to copy across statics
['root', 'shareRoot', 'runner', 'static', 'version'].map(function (key) {
  if (!jsbin[key]) {
    jsbin[key] = window.jsbin[key];
  }
});

if (jsbin.user && jsbin.user.name) {
  jsbin.settings = $.extend(true, {}, jsbin.user.settings, jsbin.settings);
  if (jsbin.user.settings.font) {
    jsbin.settings.font = parseInt(jsbin.user.settings.font, 10);
  }
} else {
  // In all cases localStorage takes precedence over user settings so users can
  // configure it from the console and overwrite the server delivered settings
  jsbin.settings = $.extend({}, jsbin.settings, JSON.parse(storedSettings || '{}'));
}

// if the above code isn't dodgy, this for hellz bells is:
jsbin.mobile = /WebKit.*Mobile.*|Android/.test(navigator.userAgent);
jsbin.tablet = /iPad/i.test(navigator.userAgent); // sue me.
// IE detect - sadly uglify is compressing the \v1 trick to death :(
// via @padolsey & @jdalton - https://gist.github.com/527683
jsbin.ie = (function(){
  var undef,
      v = 3,
      div = document.createElement('div'),
      all = div.getElementsByTagName('i');
  while (
    div.innerHTML = '',
    all[0]
  ) {}
  return v > 4 ? v : undef;
}());

if (!storedSettings && (location.origin + location.pathname) === jsbin.root + '/') {
  // first timer - let's welcome them shall we, Dave?
  store.localStorage.setItem('settings', '{}');
}

if (!jsbin.settings.editor) {
  // backward compat with jsbin-v2
  jsbin.settings.editor = {};
}

if (jsbin.settings.codemirror) {
  $.extend(jsbin.settings.editor, jsbin.settings.codemirror);
}

if (jsbin.settings.editor.theme) {
  $(document.documentElement).addClass('cm-s-' + jsbin.settings.editor.theme.split(' ')[0]);
}

// Add a pre-filter to all ajax requests to add a CSRF header to prevent
// malicious form submissions from other domains.
jQuery.ajaxPrefilter(function (options, original, xhr) {
  var skip = {head: 1, get: 1};
  if (!skip[options.type.toLowerCase()] &&
      !options.url.match(/^https:\/\/api.github.com/)) {
    xhr.setRequestHeader('x-csrf-token', jsbin.state.token);
  }
});

jsbin.owner = function () {
  return jsbin.user && jsbin.user.name && jsbin.state.metadata && jsbin.state.metadata.name === jsbin.user.name;
};

jsbin.getURL = function (options) {
  if (!options) { options = {}; }

  var withoutRoot = options.withoutRoot;
  var root = options.root || jsbin.root;
  var url = withoutRoot ? '' : root;
  var state = jsbin.state;

  if (state.code) {
    url += '/' + state.code;

    if (!state.latest || options.withRevision) { //} && state.revision !== 1) {
      if (options.withRevision !== false) {
        url += '/' + (state.revision || 1);
      }
    }
  }
  return url;
};

jsbin.state.updateSettings = throttle(function updateBinSettingsInner(update, method) {
  if (!method) {
    method = 'POST';
  }

  if (jsbin.state.code) {
    update.checksum = jsbin.state.checksum;
    $.ajax({
      type: method, // consistency ftw :-\
      url: jsbin.getURL({ withRevision: true }) + '/settings',
      data: update
    });
  }
}, 500);

function objectValue(path, context) {
  var props = path.split('.'),
      length = props.length,
      i = 1,
      currentProp = context || window,
      value = currentProp[path];
  try {
    if (currentProp[props[0]] !== undefined) {
      currentProp = currentProp[props[0]];
      for (; i < length; i++) {
        if (currentProp[props[i]] === undefined) {
          break;
        } else if (i === length - 1) {
          value = currentProp[props[i]];
        }
        currentProp = currentProp[props[i]];
      }
    }
  } catch (e) {
    value = undefined;
  }

  return value;
}

var $window = $(window),
    $body = $('body'),
    $document = $(document),
    debug = jsbin.settings.debug === undefined ? false : jsbin.settings.debug,
    documentTitle = 'JS Bin',
    $bin = $('#bin'),
    loadGist,
    // splitterSettings = JSON.parse(store.localStorage.getItem('splitterSettings') || '[ { "x" : null }, { "x" : null } ]'),
    unload = function () {
      store.sessionStorage.setItem('url', jsbin.getURL());
      store.localStorage.setItem('settings', JSON.stringify(jsbin.settings));

      if (jsbin.panels.saveOnExit === false) {
        return;
      }

      jsbin.panels.save();
      jsbin.panels.savecontent();

      var panel = jsbin.panels.focused;
      if (panel) {
        store.sessionStorage.setItem('panel', panel.id);
      }
    };

$window.unload(unload);

// window.addEventListener('storage', function (e) {
//   if (e.storageArea === localStorage && e.key === 'settings') {
//     console.log('updating from storage');
//     console.log(JSON.parse(store.localStorage.settings));
//     jsbin.settings = JSON.parse(store.localStorage.settings);
//   }
// });

// hack for Opera because the unload event isn't firing to capture the settings, so we put it on a timer
if ($.browser.opera) {
  setInterval(unload, 500);
}

$document.one('jsbinReady', function () {
  exposeSettings();
  $bin.removeAttr('style');
  $body.addClass('ready');
});

if (navigator.userAgent.indexOf(' Mac ') !== -1) {
  (function () {
  var el = $('#keyboardHelp')[0];
  el.innerHTML = el.innerHTML.replace(/ctrl/g, 'cmd').replace(/Ctrl/g, 'ctrl');
})();
}

if (jsbin.embed) {
  $window.on('focus', function () {
    return false;
  });
}

window.addEventListener('message', function (event) {
  var data;
  try {
    data = JSON.parse(event.data);
  } catch (e) {
    return;
  }

  if (data.type === 'cached') {
    if (data.updated > jsbin.state.metadata.last_updated || !jsbin.state.metadata.last_updated) {
      console.log('restored from cache: %sms newer', (new Date(data.updated).getTime() - new Date(jsbin.state.metadata.last_updated).getTime()) / 100);
      // update the bin
      jsbin.panels.panels.html.setCode(data.template.html);
      jsbin.panels.panels.javascript.setCode(data.template.javascript);
      jsbin.panels.panels.css.setCode(data.template.css);
      $('a.save:first').click();
    }
  }
});

(function() {
  if (!jsbin.settings.debug) {
    return;
  }

  var active = document.createElement('pre');
  document.body.appendChild(active);
  active.tabindex = -1;
  with (active.style) { // warning: `with` I know what I'm doing!
    position = 'fixed';
    padding = '2px';
    bottom = right = '20px';
    margin = 0;
    fontSize = 12;
    color = '#fff';
    background = '#aaa';
    whiteSpace = 'pre-wrap';
    maxWidth = '95%';
    zIndex = 10000000;
    pointerEvents = 'none';
  }

  var lastActive = null;
  var showActive = function () {
    var el = document.activeElement;
    var html = '';
    var attrs = el.attributes;
    var i = 0;

    if (el !== lastActive && el !== active) {
      for (; i < attrs.length; i++) {
        html += ' ' + attrs[i].name + '="' + attrs[i].value + '"';
      }

      active.textContent = '<' + el.nodeName.toLowerCase() + html + '>';
      lastActive = el;
    }

    requestAnimationFrame(showActive);
  };

  showActive();

})();
<commit_msg>chore: add secret message<commit_after>try {
  console.log('Dave is ready.');
} catch (e) {
  window.console = {
    log: function () {
      // alert([].slice.call(arguments).join('\n'));
    },
    warn: function () {},
    trace: function () {},
    error: function () {}
  };
}

// required because jQuery 1.4.4 lost ability to search my object property :( (i.e. a[host=foo.com])
jQuery.expr[':'].host = function(obj, index, meta) {
  return obj.host === meta[3];
};

function throttle(fn, delay) {
  var throttled = function () {
    var context = this, args = arguments;
    throttled.cancel();
    throttled.timer = setTimeout(function () {
      fn.apply(context, args);
    }, delay);
  };

  throttled.cancel = function () {
    clearTimeout(throttled.timer);
  };

  return throttled;
}

function debounceAsync(fn) {
  'use strict';
  var waiting = false;
  var last = null;

  return function debouceRunner() {
    var args = [].slice.call(arguments, 0);
    // console.time('tracker');

    var tracker = function () {
      waiting = false;
        // console.timeEnd('tracker');
      if (last) {
        // console.log('applying the last');
        fn.apply(last.context, last.args);
        // console.log('and now clear');
        last = null;
      }
    };

    // put the tracker in place of the callback
    args.push(tracker);

    if (!waiting) {
      // console.log('running this time...');
      waiting = true;
      return fn.apply(this, args);
    } else {
      // console.log('going to wait...');
      last = { args: args, context: this };
    }
  };
}

function escapeHTML(html){
  return String(html)
    .replace(/&(?!\w+;)/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/"/g, '"');
}

function dedupe(array) {
  var hash    = {},
      results = [],
      hasOwn  = Object.prototype.hasOwnProperty,
      i, item, len;

  for (i = 0, len = array.length; i < len; i += 1) {
    item = array[i];

    if (!hasOwn.call(hash, item)) {
      hash[item] = 1;
      results.push(item);
    }
  }

  return results;
}

function isDOM(obj) {
  'use strict';
  var Node = window.Node || false;
  if (Node) {
    return obj instanceof Node;
  }
  return obj.nodeType === 1;
}

function exposeSettings() {
  'use strict';

  function mockEditor (editor, methods) {
    return methods.reduce(function (mockEditor, method) {
      mockEditor[method] = editor[method].bind(editor);
      return mockEditor;
    }, {});
  }

  function mockPanels() {
    var results = {};
    var panels = jsbin.panels.panels;
    ['css', 'javascript', 'html'].forEach(function (type) {
      results[type] = {
        setCode: panels[type].setCode.bind(panels[type]),
        getCode: panels[type].getCode.bind(panels[type]),
        editor: mockEditor(panels[type].editor, [
          'setCursor',
          'getCursor',
          'addKeyMap',
          'on'
        ])
      };
    });

    return results;
  }

  if (isDOM(window.jsbin) || !window.jsbin || !window.jsbin.state) { // because...STUPIDITY!!!
    window.jsbin = {
      user: $.extend(true, {}, window.jsbin.user, jsbin.user),
      'static': jsbin['static'],
      version: jsbin.version,
      analytics: jsbin.analytics,
      state: {
        title: jsbin.state.title,
        description: jsbin.state.description
      },
      embed: jsbin.embed,
      panels: {
        // FIXME decide whether this should be locked down further
        panels: mockPanels()
      }
    }; // create the holding object

    if (jsbin.state.metadata && jsbin.user && jsbin.state.metadata.name === jsbin.user.name && jsbin.user.name) {
      window.jsbin.settings = jsbin.settings;
      return;
    }

    var key = 'o' + (Math.random() * 1).toString(32).slice(2);
    Object.defineProperty(window, key, {
      get:function () {
        window.jsbin.settings = jsbin.settings;
        console.log('jsbin.settings can now be modified on the console');
      }
    });
    if (!jsbin.embed) {
      console.log('To edit settings, type this string into the console: ' + key);
      console.log("Want to try out the alpha version 5 of jsbin? On https://jsbin.com, run the following code in your console:\n\ndocument.cookie = 'version=v5; domain=.jsbin.com'");
    }
  }
}

var storedSettings = store.localStorage.getItem('settings');
if (storedSettings === 'undefined' || jsbin.embed) {
  // yes, equals the *string* 'undefined', then something went wrong
  storedSettings = null;
}

// try to copy across statics
['root', 'shareRoot', 'runner', 'static', 'version'].map(function (key) {
  if (!jsbin[key]) {
    jsbin[key] = window.jsbin[key];
  }
});

if (jsbin.user && jsbin.user.name) {
  jsbin.settings = $.extend(true, {}, jsbin.user.settings, jsbin.settings);
  if (jsbin.user.settings.font) {
    jsbin.settings.font = parseInt(jsbin.user.settings.font, 10);
  }
} else {
  // In all cases localStorage takes precedence over user settings so users can
  // configure it from the console and overwrite the server delivered settings
  jsbin.settings = $.extend({}, jsbin.settings, JSON.parse(storedSettings || '{}'));
}

// if the above code isn't dodgy, this for hellz bells is:
jsbin.mobile = /WebKit.*Mobile.*|Android/.test(navigator.userAgent);
jsbin.tablet = /iPad/i.test(navigator.userAgent); // sue me.
// IE detect - sadly uglify is compressing the \v1 trick to death :(
// via @padolsey & @jdalton - https://gist.github.com/527683
jsbin.ie = (function(){
  var undef,
      v = 3,
      div = document.createElement('div'),
      all = div.getElementsByTagName('i');
  while (
    div.innerHTML = '',
    all[0]
  ) {}
  return v > 4 ? v : undef;
}());

if (!storedSettings && (location.origin + location.pathname) === jsbin.root + '/') {
  // first timer - let's welcome them shall we, Dave?
  store.localStorage.setItem('settings', '{}');
}

if (!jsbin.settings.editor) {
  // backward compat with jsbin-v2
  jsbin.settings.editor = {};
}

if (jsbin.settings.codemirror) {
  $.extend(jsbin.settings.editor, jsbin.settings.codemirror);
}

if (jsbin.settings.editor.theme) {
  $(document.documentElement).addClass('cm-s-' + jsbin.settings.editor.theme.split(' ')[0]);
}

// Add a pre-filter to all ajax requests to add a CSRF header to prevent
// malicious form submissions from other domains.
jQuery.ajaxPrefilter(function (options, original, xhr) {
  var skip = {head: 1, get: 1};
  if (!skip[options.type.toLowerCase()] &&
      !options.url.match(/^https:\/\/api.github.com/)) {
    xhr.setRequestHeader('x-csrf-token', jsbin.state.token);
  }
});

jsbin.owner = function () {
  return jsbin.user && jsbin.user.name && jsbin.state.metadata && jsbin.state.metadata.name === jsbin.user.name;
};

jsbin.getURL = function (options) {
  if (!options) { options = {}; }

  var withoutRoot = options.withoutRoot;
  var root = options.root || jsbin.root;
  var url = withoutRoot ? '' : root;
  var state = jsbin.state;

  if (state.code) {
    url += '/' + state.code;

    if (!state.latest || options.withRevision) { //} && state.revision !== 1) {
      if (options.withRevision !== false) {
        url += '/' + (state.revision || 1);
      }
    }
  }
  return url;
};

jsbin.state.updateSettings = throttle(function updateBinSettingsInner(update, method) {
  if (!method) {
    method = 'POST';
  }

  if (jsbin.state.code) {
    update.checksum = jsbin.state.checksum;
    $.ajax({
      type: method, // consistency ftw :-\
      url: jsbin.getURL({ withRevision: true }) + '/settings',
      data: update
    });
  }
}, 500);

function objectValue(path, context) {
  var props = path.split('.'),
      length = props.length,
      i = 1,
      currentProp = context || window,
      value = currentProp[path];
  try {
    if (currentProp[props[0]] !== undefined) {
      currentProp = currentProp[props[0]];
      for (; i < length; i++) {
        if (currentProp[props[i]] === undefined) {
          break;
        } else if (i === length - 1) {
          value = currentProp[props[i]];
        }
        currentProp = currentProp[props[i]];
      }
    }
  } catch (e) {
    value = undefined;
  }

  return value;
}

var $window = $(window),
    $body = $('body'),
    $document = $(document),
    debug = jsbin.settings.debug === undefined ? false : jsbin.settings.debug,
    documentTitle = 'JS Bin',
    $bin = $('#bin'),
    loadGist,
    // splitterSettings = JSON.parse(store.localStorage.getItem('splitterSettings') || '[ { "x" : null }, { "x" : null } ]'),
    unload = function () {
      store.sessionStorage.setItem('url', jsbin.getURL());
      store.localStorage.setItem('settings', JSON.stringify(jsbin.settings));

      if (jsbin.panels.saveOnExit === false) {
        return;
      }

      jsbin.panels.save();
      jsbin.panels.savecontent();

      var panel = jsbin.panels.focused;
      if (panel) {
        store.sessionStorage.setItem('panel', panel.id);
      }
    };

$window.unload(unload);

// window.addEventListener('storage', function (e) {
//   if (e.storageArea === localStorage && e.key === 'settings') {
//     console.log('updating from storage');
//     console.log(JSON.parse(store.localStorage.settings));
//     jsbin.settings = JSON.parse(store.localStorage.settings);
//   }
// });

// hack for Opera because the unload event isn't firing to capture the settings, so we put it on a timer
if ($.browser.opera) {
  setInterval(unload, 500);
}

$document.one('jsbinReady', function () {
  exposeSettings();
  $bin.removeAttr('style');
  $body.addClass('ready');
});

if (navigator.userAgent.indexOf(' Mac ') !== -1) {
  (function () {
  var el = $('#keyboardHelp')[0];
  el.innerHTML = el.innerHTML.replace(/ctrl/g, 'cmd').replace(/Ctrl/g, 'ctrl');
})();
}

if (jsbin.embed) {
  $window.on('focus', function () {
    return false;
  });
}

window.addEventListener('message', function (event) {
  var data;
  try {
    data = JSON.parse(event.data);
  } catch (e) {
    return;
  }

  if (data.type === 'cached') {
    if (data.updated > jsbin.state.metadata.last_updated || !jsbin.state.metadata.last_updated) {
      console.log('restored from cache: %sms newer', (new Date(data.updated).getTime() - new Date(jsbin.state.metadata.last_updated).getTime()) / 100);
      // update the bin
      jsbin.panels.panels.html.setCode(data.template.html);
      jsbin.panels.panels.javascript.setCode(data.template.javascript);
      jsbin.panels.panels.css.setCode(data.template.css);
      $('a.save:first').click();
    }
  }
});

(function() {
  if (!jsbin.settings.debug) {
    return;
  }

  var active = document.createElement('pre');
  document.body.appendChild(active);
  active.tabindex = -1;
  with (active.style) { // warning: `with` I know what I'm doing!
    position = 'fixed';
    padding = '2px';
    bottom = right = '20px';
    margin = 0;
    fontSize = 12;
    color = '#fff';
    background = '#aaa';
    whiteSpace = 'pre-wrap';
    maxWidth = '95%';
    zIndex = 10000000;
    pointerEvents = 'none';
  }

  var lastActive = null;
  var showActive = function () {
    var el = document.activeElement;
    var html = '';
    var attrs = el.attributes;
    var i = 0;

    if (el !== lastActive && el !== active) {
      for (; i < attrs.length; i++) {
        html += ' ' + attrs[i].name + '="' + attrs[i].value + '"';
      }

      active.textContent = '<' + el.nodeName.toLowerCase() + html + '>';
      lastActive = el;
    }

    requestAnimationFrame(showActive);
  };

  showActive();

})();
<|endoftext|>


<commit_before>
import React from 'react';
import renderer from 'react-test-renderer';

import ThemeWrapper from '../../ThemeWrapper';
import BuildCard from '../BuildCard.react';

jest.mock('react-dom');

describe('BuildCard component', () => {

  it('should render correctly', () => {
    const data = {
      id: 'abcd',
      build: {
        type: 'DEBUG',
        version: '1.0.1'
      }
    };

    const component = renderer.create(
      <ThemeWrapper>
        <BuildCard job={data} />
      </ThemeWrapper>
    );

    let tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  });

  // it('should update value by onChange event', () => {
  //   const component = renderer.create(
  //     <ThemeWrapper>
  //       <Search />
  //     </ThemeWrapper>
  //   );

  //   let tree = component.toJSON();

  //   // manually trigger the callback
  //   tree.children[0].children[0].children[0].children[1].props.onChange({
  //     target: {
  //       value: 'a'
  //     }
  //   });

  //   // re-rendering
  //   tree = component.toJSON();
  //   expect(tree).toMatchSnapshot();
  // });

  // it('should call onSubmit handler if <Enter> key pressed', () => {
  //   let onSubmit = jasmine.createSpy();

  //   const component = renderer.create(
  //     <ThemeWrapper>
  //       <Search onSubmit={onSubmit} />
  //     </ThemeWrapper>
  //   );

  //   let tree = component.toJSON();

  //   // type some text
  //   tree.children[0].children[0].children[0].children[1].props.onChange({
  //     target: {
  //       value: 'alex'
  //     }
  //   });

  //   // re-rendering
  //   tree = component.toJSON();
  //   expect(tree).toMatchSnapshot();

  //   // type space
  //   tree.children[0].children[0].children[0].children[1].props.onKeyDown({
  //     keyCode: 32
  //   });

  //   // type enter
  //   tree.children[0].children[0].children[0].children[1].props.onKeyDown({
  //     keyCode: 13
  //   });

  //   expect(onSubmit.calls.count()).toBe(1);
  //   expect(onSubmit.calls.argsFor(0)[0]).toBe('alex');
  // });
});
<commit_msg>BuildCard onClick handler test<commit_after>
import React from 'react';
import renderer from 'react-test-renderer';

import ThemeWrapper from '../../ThemeWrapper';
import BuildCard from '../BuildCard.react';

jest.mock('react-dom');

describe('BuildCard component', () => {

  it('should render correctly', () => {
    const data = {
      id: 'abcd',
      build: {
        type: 'DEBUG',
        version: '1.0.1'
      }
    };

    const component = renderer.create(
      <ThemeWrapper>
        <BuildCard job={data} />
      </ThemeWrapper>
    );

    let tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  });

  it('should call onClick handler when clicking on image', () => {
    const onClick = jasmine.createSpy();
    const data = {
      id: 'abcd',
      build: {
        type: 'DEBUG',
        version: '1.0.1'
      }
    };

    const component = renderer.create(
      <ThemeWrapper>
        <BuildCard job={data} onClick={onClick} />
      </ThemeWrapper>
    );

    let tree = component.toJSON();

    // click on image
    tree.children[0].children[0].children[0].children[0].props.onClick();

    expect(onClick.calls.count()).toBe(1);
  });
});
<|endoftext|>


<commit_before>#!/usr/bin/env node
'use strict';

var cliArgs = require('command-line-args');
var async = require('async');
var ai = require('ascii-images');

var cli = cliArgs(
  {
    name: 'all',
    type: Boolean,
    alias: 'a',
    description: 'Install everything'
  },
  {
    name: 'no-test',
    type: Boolean,
    alias:'t',
    description: 'Install everything but tests'
  },
  {
    name: 'basic',
    type: Boolean,
    alias: 'b',
    description: 'Install only package.json, fancom & jshint'
  }
);
//
// var usage = cli.getUsage({
//     header: 'My Initialization Config Handling Automator. LOL.',
//     footer: 'For more information, visit http://example.com'
// });

var options = cli.parse();

var tasks = [
  require('../scripts/precommit-hook'),
  require('../scripts/jshintrc'),
  require('../scripts/fancom'),
  require('../scripts/tests'),
  require('../scripts/assert')
];

// I know this carbonara code is awful, but it's 05:27AM
// and my last cigarette break was around 18:30
ai(__dirname+'/../michal.png', function(logo){
  console.log('\u001b[2J\u001b[0;0H');
  console.log(logo);

  if (Object.keys(options).length === 0) {

  }

  require('../scripts/package-json')(function(err, msg) {
    if (err) {
      console.log(err);
      return;
    }
    console.log(msg);

    async.each(
      tasks,
      function(task, cb) {
        task(function(err, msg) {
          if (err) {
            throw new Error(err);
          }
          console.log(msg);
          cb();
        });
      },
      function() {
        require('../scripts/scripts')(function(err, msg){
          if (err) {
            console.log(err);
            return;
          }
          console.log(msg);
          console.log('DONE...');
        });
      }
    );
  });
});
<commit_msg>:basketball: display usage when needed<commit_after>#!/usr/bin/env node
'use strict';

var cliArgs = require('command-line-args');
var async = require('async');
var ai = require('ascii-images');
var packageJsonTask = require('../scripts/package-json');

var cli = cliArgs([
  {
    name: 'all',
    type: Boolean,
    alias: 'a',
    description: 'Install everything'
  },
  {
    name: 'no-test',
    type: Boolean,
    alias:'t',
    description: 'Install everything but tests'
  },
  {
    name: 'basic',
    type: Boolean,
    alias: 'b',
    description: 'Install only package.json, fancom & jshint'
  }
]);

var usage = cli.getUsage({
    header: 'My Initialization Config Handling Automator. LOL.',
    footer: '\nFor more information, visit https://github.com/michalbe'
});

var options = cli.parse();

var tasks = [
  require('../scripts/precommit-hook'),
  require('../scripts/jshintrc'),
  require('../scripts/fancom'),
  require('../scripts/tests'),
  require('../scripts/assert')
];

// I know this carbonara code is awful, but it's 05:27AM
// and my last cigarette break was around 18:30
ai(__dirname+'/../michal.png', function(logo){
  console.log('\u001b[2J\u001b[0;0H');
  console.log(logo);

  if (Object.keys(options).length === 0) {
    console.log(usage);
    return;
  }

  packageJsonTask(function(err, msg) {
    if (err) {
      console.log(err);
      return;
    }
    console.log(msg);

    async.each(
      tasks,
      function(task, cb) {
        task(function(err, msg) {
          if (err) {
            throw new Error(err);
          }
          console.log(msg);
          cb();
        });
      },
      function() {
        require('../scripts/scripts')(function(err, msg){
          if (err) {
            console.log(err);
            return;
          }
          console.log(msg);
          console.log('DONE...');
        });
      }
    );
  });
});
<|endoftext|>


<commit_before>import Ace from 'react-ace';
import classNames from 'classnames';
import {Hooks} from 'PluginSDK';
import mixin from 'reactjs-mixin';
import {Modal, Tooltip} from 'reactjs-components';
import React from 'react';
import {StoreMixin} from 'mesosphere-shared-reactjs';

import 'brace/mode/json';
import 'brace/theme/monokai';
import 'brace/ext/language_tools';

import ServiceConfig from '../../constants/ServiceConfig';
import Config from '../../config/Config';
import Icon from '../Icon';
import MarathonStore from '../../stores/MarathonStore';
import ServiceForm from '../ServiceForm';
import Service from '../../structs/Service';
import ServiceUtil from '../../utils/ServiceUtil';
import ServiceSchema from '../../schemas/ServiceSchema';
import ToggleButton from '../ToggleButton';
import ErrorPaths from '../../constants/ErrorPaths';
import CollapsibleErrorMessage from '../CollapsibleErrorMessage';

const METHODS_TO_BIND = [
  'getTriggerSubmit',
  'handleCancel',
  'handleClearError',
  'handleJSONChange',
  'handleJSONToggle',
  'handleSubmit',
  'handleTabChange',
  'onMarathonStoreServiceCreateError',
  'onMarathonStoreServiceCreateSuccess',
  'onMarathonStoreServiceEditError',
  'onMarathonStoreServiceEditSuccess'
];

const serverResponseMappings = {
  'error.path.missing': 'Specify a path',
  'error.minLength': 'Field may not be blank',
  'error.expected.jsnumber': 'A number is expected',
  'error.expected.jsstring': 'A string is expected'
};

const responseAttributePathToFieldIdMap = Object.assign({
  '/acceptedResourceRoles': 'acceptedResourceRoles',
  '/cmd': 'cmd',
  '/constraints': 'constraints',
  '/constraints({INDEX})': 'constraints',
  '/container/docker/forcePullImage': 'dockerForcePullImage',
  '/container/docker/image': 'dockerImage',
  '/container/docker/network': 'dockerNetwork',
  '/container/docker/privileged': 'dockerPrivileged',
  '/container/docker/parameters({INDEX})/key':
    'dockerParameters/{INDEX}/key',
  '/container/docker/parameters': 'dockerParameters',
  '/container/docker/parameters({INDEX})/value':
    'dockerParameters/{INDEX}/value',
  '/container/volumes({INDEX})/containerPath':
    'containerVolumes/{INDEX}/containerPath',
  '/container/volumes({INDEX})/hostPath':
    'containerVolumes/{INDEX}/hostPath',
  '/container/volumes({INDEX})/mode':
    'containerVolumes/{INDEX}/mode',
  '/cpus': 'cpus',
  '/disk': 'disk',
  '/env': 'env',
  '/executor': 'executor',
  '/healthChecks({INDEX})/command/value':
    'healthChecks/{INDEX}/command',
  '/healthChecks({INDEX})/path':
    'healthChecks/{INDEX}/path',
  '/healthChecks({INDEX})/intervalSeconds':
    'healthChecks/{INDEX}/intervalSeconds',
  '/healthChecks({INDEX})/port':
    'healthChecks/{INDEX}/port',
  '/healthChecks({INDEX})/portIndex':
    'healthChecks/{INDEX}/portIndex',
  '/healthChecks({INDEX})/timeoutSeconds':
    'healthChecks/{INDEX}/timeoutSeconds',
  '/healthChecks({INDEX})/gracePeriodSeconds':
    'healthChecks/{INDEX}/gracePeriodSeconds',
  '/healthChecks({INDEX})/maxConsecutiveFailures':
    'healthChecks/{INDEX}/maxConsecutiveFailures',
  '/instances': 'instances',
  '/mem': 'mem',
  '/portDefinitions': 'portDefinitions',
  '/portDefinitions({INDEX})/name': 'portDefinitions/{INDEX}/name',
  '/portDefinitions({INDEX})/port': 'portDefinitions/{INDEX}/port',
  '/portDefinitions({INDEX})/protocol': 'portDefinitions/{INDEX}/protocol',
  '/value/isResident': 'Residency',
  '/value/upgradeStrategy': 'Update Strategy',
  '/container/docker/portMappings': 'dockerPortMappings',
  '/container/docker/portMappings({INDEX})/containerPort':
    'dockerPortMappings/{INDEX}/port',
  '/container/docker/portMappings({INDEX})/protocol':
    'dockerPortMappings/{INDEX}/protocol',
  '/container/docker/portMappings({INDEX})/hostPort': 'dockerPortMappings',
  '/container/docker/portMappings({INDEX})/servicePort': 'dockerPortMappings',
  '/labels': 'labels',
  '/uris': 'uris',
  '/user': 'user'
}, ErrorPaths);

var cleanJSONdefinition = function (jsonDefinition) {
  return Object.keys(jsonDefinition).filter(function (key) {
    return !ServiceConfig.BLACKLIST.includes(key);
  }).reduce(function (memo, key) {
    memo[key] = jsonDefinition[key];

    return memo;
  }, {});
};

class ServiceFormModal extends mixin(StoreMixin) {
  constructor() {
    super(...arguments);

    let model =
      ServiceUtil.createFormModelFromSchema(ServiceSchema);

    this.state = {
      defaultTab: '',
      errorMessage: null,
      jsonMode: false,
      model,
      pendingRequest: false,
      service: ServiceUtil.createServiceFromFormModel(model, ServiceSchema)
    };

    this.store_listeners = [
      {
        name: 'marathon',
        events: [
          'serviceCreateError',
          'serviceCreateSuccess',
          'serviceEditError',
          'serviceEditSuccess'
        ],
        suppressUpdate: true
      }
    ];

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });
  }

  componentWillReceiveProps(nextProps) {
    super.componentWillReceiveProps(...arguments);
    if (!this.props.open && nextProps.open) {
      this.resetState(nextProps);
    }
  }

  shouldComponentUpdate(nextProps, nextState) {
    let {state, props} = this;
    return props.open !== nextProps.open ||
      state.jsonMode !== nextState.jsonMode ||
      state.errorMessage !== nextState.errorMessage ||
      state.warningMessage !== nextState.warningMessage ||
      state.pendingRequest !== nextState.pendingRequest;
  }

  resetState(props = this.props) {
    let model = ServiceUtil.createFormModelFromSchema(ServiceSchema);
    if (props.id) {
      model.general.id = props.id;
    }
    let service = ServiceUtil.createServiceFromFormModel(
      model,
      ServiceSchema,
      this.props.isEdit
    );
    if (props.service) {
      service = props.service;
    }

    let warningMessage = null;
    let jsonMode = false;
    if (this.shouldDisableForm(service)) {
      warningMessage = {
        message: 'Your config contains attributes we currently only support ' +
        'in the JSON mode.'
      };
      jsonMode = true;
    }

    this.setState({
      defaultTab: '',
      warningMessage,
      errorMessage: null,
      force: false,
      jsonMode,
      model,
      pendingRequest: false,
      service
    });
  }

  handleClearError() {
    if (this.state.errorMessage == null) {
      return;
    }
    this.setState({
      errorMessage: null
    });
  }

  handleJSONChange(jsonDefinition) {
    let service = Object.assign({}, this.state.service);
    try {
      service = new Service(JSON.parse(jsonDefinition));
    } catch (e) {

    }
    this.setState(
      {
        service,
        errorMessage: null,
        warningMessage: null
      }
    );
  }

  handleJSONToggle() {
    let nextState = {};

    if (!this.state.jsonMode) {
      let {model} = this.triggerSubmit();
      let service = ServiceUtil.createServiceFromFormModel(
        model,
        ServiceSchema,
        this.props.isEdit,
        JSON.parse(this.state.service.toJSON())
      );
      nextState.model = model;
      nextState.service = service;
    }

    if (this.shouldDisableForm(this.state.service)) {
      nextState.warningMessage = {
        message: 'Your config contains attributes we currently only support ' +
        'in the JSON mode.'
      };
    } else {
      nextState.jsonMode = !this.state.jsonMode;
    }
    this.setState(nextState);
  }

  handleTabChange(tab) {
    this.setState({defaultTab: tab});
  }

  onMarathonStoreServiceCreateSuccess() {
    this.resetState();
    this.props.onClose();
  }

  onMarathonStoreServiceCreateError(errorMessage) {
    this.setState({
      errorMessage,
      pendingRequest: false
    });
  }

  shouldDisableForm(service) {
    let containerSettings = service.getContainerSettings();

    return containerSettings != null && containerSettings.type === 'MESOS' && (
      (containerSettings.docker && containerSettings.docker.image != null) ||
      (containerSettings.appc && containerSettings.appc.image != null)
    );
  }

  shouldForceUpdate(message = this.state.errorMessage) {
    return message && message.message && /force=true/.test(message.message);
  }

  onMarathonStoreServiceEditSuccess() {
    this.props.onClose();
    this.resetState();
  }

  onMarathonStoreServiceEditError(errorMessage) {
    if (!this.props.open) {
      return;
    }
    this.setState({
      errorMessage,
      force: this.shouldForceUpdate(errorMessage),
      pendingRequest: false
    });
  }

  handleCancel() {
    this.props.onClose();
  }

  handleSubmit() {
    let marathonAction = MarathonStore.createService;

    if (this.props.isEdit) {
      marathonAction = MarathonStore.editService;
    }

    if (this.state.jsonMode) {
      let jsonDefinition = JSON.parse(this.state.service.toJSON());
      jsonDefinition = cleanJSONdefinition(jsonDefinition);
      marathonAction(jsonDefinition, this.state.force);
      this.setState({
        errorMessage: null,
        pendingRequest: true
      });
      return;
    }

    if (this.triggerSubmit) {
      let {model, isValidated} = this.triggerSubmit();

      if (!isValidated) {
        return;
      }
      let service = ServiceUtil.createServiceFromFormModel(
        model,
        ServiceSchema,
        this.props.isEdit,
        JSON.parse(this.state.service.toJSON())
      );
      this.setState({
        errorMessage: null,
        model,
        pendingRequest: true,
        service
      });
      marathonAction(
        cleanJSONdefinition(ServiceUtil.getAppDefinitionFromService(service)),
        this.state.force
      );
    }
  }

  getTriggerSubmit(triggerSubmit) {
    this.triggerSubmit = triggerSubmit;
  }

  getErrorMessage() {
    let {errorMessage} = this.state;
    if (!errorMessage) {
      return null;
    }

    // Stringify error details
    let errorList = null;
    if (errorMessage.details != null) {
      let responseMap = Hooks.applyFilter(
        'serviceFormErrorResponseMap',
        responseAttributePathToFieldIdMap
      );
      errorList = errorMessage.details.map(function ({path, errors}) {
        let fieldId = 'general';

        // Check if attributePath contains an index like path(0)/attribute
        // Matches as defined: [0] : '(0)', [1]: '0'
        let matches = Hooks.applyFilter('serviceFormMatchErrorPath',
          path.match(/\(([0-9]+)\)/),
          path
        );

        if (matches != null) {
          let resolvePath = responseMap[
            path.replace(matches[0], '({INDEX})')
          ];

          if (resolvePath == null) {
            resolvePath = responseMap[
              path.replace(matches[0], '{INDEX}')
            ];
          }
          if (resolvePath != null) {
            fieldId = resolvePath.replace('{INDEX}', matches[1]);
          }
        } else {
          fieldId = responseMap[path] || fieldId;
        }
        errors = errors.map(function (error) {
          if (serverResponseMappings[error]) {
            return serverResponseMappings[error];
          }
          return error;
        });

        // Return path-prefixed error string
        return `${fieldId}: ${errors}`;

      });
    }

    if (this.shouldForceUpdate(errorMessage)) {
      return (
        <CollapsibleErrorMessage
          className="error-for-modal"
          message={`App is currently locked by one or more deployments.
            Press the button again to forcefully change and deploy the new configuration.`} />
      );
    }

    return (
      <CollapsibleErrorMessage
        className="error-for-modal"
        details={errorList}
        message={errorMessage.message} />
    );

  }

  getSubmitText() {
    if (this.props.isEdit) {
      return 'Deploy Changes';
    }
    return 'Deploy';
  }

  getFooter() {
    let {pendingRequest} = this.state;
    let deployButtonClassNames = classNames('button button-large flush-bottom',
      {
        'button-success': !pendingRequest,
        'disabled': pendingRequest
      }
    );

    return (

        <button
          className="button button-large flush-top flush-bottom"
          onClick={this.handleCancel}>
          Cancel
        </button>
        <button
          className={deployButtonClassNames}
          onClick={this.handleSubmit}>
          {this.getSubmitText()}
        </button>

    );
  }

  getModalContents() {
    let {defaultTab, jsonMode, service} = this.state;

    let jsonDefinition = JSON.stringify(
      cleanJSONdefinition(service.get()),
      null,
      2
    );

    if (jsonMode) {
      let toolTipContent = (

          Use the JSON editor to enter Marathon Application definitions manually.
          {' '}
          <a href={`${Config.marathonDocsURI}generated/api.html#v2_apps_post`} target="_blank">
            View API docs.

      );

      return (

          <Ace editorProps={{$blockScrolling: true}}
            mode="json"
            onChange={this.handleJSONChange}
            showGutter={true}
            showPrintMargin={false}
            theme="monokai"
            height="462px"
            value={jsonDefinition}
            width="100%"/>
          <Tooltip
            content={toolTipContent}
            interactive={true}
            wrapperClassName="tooltip-wrapper media-object-item json-editor-help"
            wrapText={true}
            maxWidth={300}
            position="left"
            scrollContainer=".gm-scroll-view">
            <Icon color="grey" id="ring-question" size="mini" family="mini" />
          </Tooltip>

      );
    }

    let model = ServiceUtil.createFormModelFromSchema(ServiceSchema, service);

    return (
      <ServiceForm
        defaultTab={defaultTab}
        getTriggerSubmit={this.getTriggerSubmit}
        model={model}
        onChange={this.handleClearError}
        onTabChange={this.handleTabChange}
        schema={ServiceSchema}/>
    );
  }

  getToggleButton() {
    let classSet = 'modal-form-title-label';

    if (this.shouldDisableForm(this.state.service)) {
      classSet = `${classSet} disabled`;
    }

    return (<ToggleButton
      className={classSet}
      checkboxClassName="modal-form-title-toggle-button toggle-button"
      checked={this.state.jsonMode}
      onChange={this.handleJSONToggle}>
      JSON mode
    </ToggleButton>);
  }

  getWarningMessage() {
    let {warningMessage} = this.state;
    if (!warningMessage) {
      return null;
    }

    return (

            {warningMessage.message}

    );
  }

  render() {
    let titleText = 'Deploy New Service';

    if (this.props.isEdit) {
      titleText = 'Edit Service';
    }

    let title = (

              {titleText}

            {this.getToggleButton()}

          {this.getErrorMessage()}
          {this.getWarningMessage()}

    );

    return (
      <Modal
        backdropClass="modal-backdrop default-cursor"
        maxHeightPercentage={.9}
        bodyClass=""
        modalWrapperClass="multiple-form-modal modal-form"
        open={this.props.open}
        showCloseButton={false}
        showHeader={true}
        footer={this.getFooter()}
        scrollContainerClass=""
        titleText={title}
        titleClass="modal-header-title flush-top flush-bottom"
        showFooter={true}>
        {this.getModalContents()}
      </Modal>
    );
  }
}

ServiceFormModal.defaultProps = {
  isEdit: false,
  onClose() {},
  open: false,
  id: null,
  service: null
};

ServiceFormModal.propTypes = {
  id: React.PropTypes.string,
  isEdit: React.PropTypes.bool,
  open: React.PropTypes.bool,
  onClose: React.PropTypes.func,
  service: React.PropTypes.instanceOf(Service)
};

module.exports = ServiceFormModal;
<commit_msg>Fix error when submitting a invalid JSON<commit_after>import Ace from 'react-ace';
import classNames from 'classnames';
import {Hooks} from 'PluginSDK';
import mixin from 'reactjs-mixin';
import {Modal, Tooltip} from 'reactjs-components';
import React from 'react';
import {StoreMixin} from 'mesosphere-shared-reactjs';

import 'brace/mode/json';
import 'brace/theme/monokai';
import 'brace/ext/language_tools';

import ServiceConfig from '../../constants/ServiceConfig';
import Config from '../../config/Config';
import Icon from '../Icon';
import MarathonStore from '../../stores/MarathonStore';
import ServiceForm from '../ServiceForm';
import Service from '../../structs/Service';
import ServiceUtil from '../../utils/ServiceUtil';
import ServiceSchema from '../../schemas/ServiceSchema';
import ToggleButton from '../ToggleButton';
import ErrorPaths from '../../constants/ErrorPaths';
import CollapsibleErrorMessage from '../CollapsibleErrorMessage';

const METHODS_TO_BIND = [
  'getTriggerSubmit',
  'handleCancel',
  'handleClearError',
  'handleJSONChange',
  'handleJSONToggle',
  'handleSubmit',
  'handleTabChange',
  'onMarathonStoreServiceCreateError',
  'onMarathonStoreServiceCreateSuccess',
  'onMarathonStoreServiceEditError',
  'onMarathonStoreServiceEditSuccess'
];

const serverResponseMappings = {
  'error.path.missing': 'Specify a path',
  'error.minLength': 'Field may not be blank',
  'error.expected.jsnumber': 'A number is expected',
  'error.expected.jsstring': 'A string is expected'
};

const responseAttributePathToFieldIdMap = Object.assign({
  '/acceptedResourceRoles': 'acceptedResourceRoles',
  '/cmd': 'cmd',
  '/constraints': 'constraints',
  '/constraints({INDEX})': 'constraints',
  '/container/docker/forcePullImage': 'dockerForcePullImage',
  '/container/docker/image': 'dockerImage',
  '/container/docker/network': 'dockerNetwork',
  '/container/docker/privileged': 'dockerPrivileged',
  '/container/docker/parameters({INDEX})/key':
    'dockerParameters/{INDEX}/key',
  '/container/docker/parameters': 'dockerParameters',
  '/container/docker/parameters({INDEX})/value':
    'dockerParameters/{INDEX}/value',
  '/container/volumes({INDEX})/containerPath':
    'containerVolumes/{INDEX}/containerPath',
  '/container/volumes({INDEX})/hostPath':
    'containerVolumes/{INDEX}/hostPath',
  '/container/volumes({INDEX})/mode':
    'containerVolumes/{INDEX}/mode',
  '/cpus': 'cpus',
  '/disk': 'disk',
  '/env': 'env',
  '/executor': 'executor',
  '/healthChecks({INDEX})/command/value':
    'healthChecks/{INDEX}/command',
  '/healthChecks({INDEX})/path':
    'healthChecks/{INDEX}/path',
  '/healthChecks({INDEX})/intervalSeconds':
    'healthChecks/{INDEX}/intervalSeconds',
  '/healthChecks({INDEX})/port':
    'healthChecks/{INDEX}/port',
  '/healthChecks({INDEX})/portIndex':
    'healthChecks/{INDEX}/portIndex',
  '/healthChecks({INDEX})/timeoutSeconds':
    'healthChecks/{INDEX}/timeoutSeconds',
  '/healthChecks({INDEX})/gracePeriodSeconds':
    'healthChecks/{INDEX}/gracePeriodSeconds',
  '/healthChecks({INDEX})/maxConsecutiveFailures':
    'healthChecks/{INDEX}/maxConsecutiveFailures',
  '/instances': 'instances',
  '/mem': 'mem',
  '/portDefinitions': 'portDefinitions',
  '/portDefinitions({INDEX})/name': 'portDefinitions/{INDEX}/name',
  '/portDefinitions({INDEX})/port': 'portDefinitions/{INDEX}/port',
  '/portDefinitions({INDEX})/protocol': 'portDefinitions/{INDEX}/protocol',
  '/value/isResident': 'Residency',
  '/value/upgradeStrategy': 'Update Strategy',
  '/container/docker/portMappings': 'dockerPortMappings',
  '/container/docker/portMappings({INDEX})/containerPort':
    'dockerPortMappings/{INDEX}/port',
  '/container/docker/portMappings({INDEX})/protocol':
    'dockerPortMappings/{INDEX}/protocol',
  '/container/docker/portMappings({INDEX})/hostPort': 'dockerPortMappings',
  '/container/docker/portMappings({INDEX})/servicePort': 'dockerPortMappings',
  '/labels': 'labels',
  '/uris': 'uris',
  '/user': 'user'
}, ErrorPaths);

var cleanJSONdefinition = function (jsonDefinition) {
  return Object.keys(jsonDefinition).filter(function (key) {
    return !ServiceConfig.BLACKLIST.includes(key);
  }).reduce(function (memo, key) {
    memo[key] = jsonDefinition[key];

    return memo;
  }, {});
};

class ServiceFormModal extends mixin(StoreMixin) {
  constructor() {
    super(...arguments);

    let model =
      ServiceUtil.createFormModelFromSchema(ServiceSchema);

    this.state = {
      defaultTab: '',
      errorMessage: null,
      jsonMode: false,
      model,
      pendingRequest: false,
      service: ServiceUtil.createServiceFromFormModel(model, ServiceSchema)
    };

    this.store_listeners = [
      {
        name: 'marathon',
        events: [
          'serviceCreateError',
          'serviceCreateSuccess',
          'serviceEditError',
          'serviceEditSuccess'
        ],
        suppressUpdate: true
      }
    ];

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });
  }

  componentWillReceiveProps(nextProps) {
    super.componentWillReceiveProps(...arguments);
    if (!this.props.open && nextProps.open) {
      this.resetState(nextProps);
    }
  }

  shouldComponentUpdate(nextProps, nextState) {
    let {state, props} = this;
    return props.open !== nextProps.open ||
      state.jsonMode !== nextState.jsonMode ||
      state.errorMessage !== nextState.errorMessage ||
      state.warningMessage !== nextState.warningMessage ||
      state.pendingRequest !== nextState.pendingRequest;
  }

  resetState(props = this.props) {
    let model = ServiceUtil.createFormModelFromSchema(ServiceSchema);
    if (props.id) {
      model.general.id = props.id;
    }
    let service = ServiceUtil.createServiceFromFormModel(
      model,
      ServiceSchema,
      this.props.isEdit
    );
    if (props.service) {
      service = props.service;
    }

    let warningMessage = null;
    let jsonMode = false;
    if (this.shouldDisableForm(service)) {
      warningMessage = {
        message: 'Your config contains attributes we currently only support ' +
        'in the JSON mode.'
      };
      jsonMode = true;
    }

    this.setState({
      defaultTab: '',
      warningMessage,
      errorMessage: null,
      force: false,
      jsonMode,
      model,
      pendingRequest: false,
      service
    });
  }

  handleClearError() {
    if (this.state.errorMessage == null) {
      return;
    }
    this.setState({
      errorMessage: null
    });
  }

  handleJSONChange(jsonDefinition) {
    let {service} = this.state;
    try {
      service = new Service(JSON.parse(jsonDefinition));
    } catch (e) {

    }
    this.setState(
      {
        service,
        errorMessage: null,
        warningMessage: null
      }
    );
  }

  handleJSONToggle() {
    let nextState = {};

    if (!this.state.jsonMode) {
      let {model} = this.triggerSubmit();
      let service = ServiceUtil.createServiceFromFormModel(
        model,
        ServiceSchema,
        this.props.isEdit,
        JSON.parse(this.state.service.toJSON())
      );
      nextState.model = model;
      nextState.service = service;
    }

    if (this.shouldDisableForm(this.state.service)) {
      nextState.warningMessage = {
        message: 'Your config contains attributes we currently only support ' +
        'in the JSON mode.'
      };
    } else {
      nextState.jsonMode = !this.state.jsonMode;
    }
    this.setState(nextState);
  }

  handleTabChange(tab) {
    this.setState({defaultTab: tab});
  }

  onMarathonStoreServiceCreateSuccess() {
    this.resetState();
    this.props.onClose();
  }

  onMarathonStoreServiceCreateError(errorMessage) {
    this.setState({
      errorMessage,
      pendingRequest: false
    });
  }

  shouldDisableForm(service) {
    let containerSettings = service.getContainerSettings();

    return containerSettings != null && containerSettings.type === 'MESOS' && (
      (containerSettings.docker && containerSettings.docker.image != null) ||
      (containerSettings.appc && containerSettings.appc.image != null)
    );
  }

  shouldForceUpdate(message = this.state.errorMessage) {
    return message && message.message && /force=true/.test(message.message);
  }

  onMarathonStoreServiceEditSuccess() {
    this.props.onClose();
    this.resetState();
  }

  onMarathonStoreServiceEditError(errorMessage) {
    if (!this.props.open) {
      return;
    }
    this.setState({
      errorMessage,
      force: this.shouldForceUpdate(errorMessage),
      pendingRequest: false
    });
  }

  handleCancel() {
    this.props.onClose();
  }

  handleSubmit() {
    let marathonAction = MarathonStore.createService;

    if (this.props.isEdit) {
      marathonAction = MarathonStore.editService;
    }

    if (this.state.jsonMode) {
      let jsonDefinition = JSON.parse(this.state.service.toJSON());
      jsonDefinition = cleanJSONdefinition(jsonDefinition);
      marathonAction(jsonDefinition, this.state.force);
      this.setState({
        errorMessage: null,
        pendingRequest: true
      });
      return;
    }

    if (this.triggerSubmit) {
      let {model, isValidated} = this.triggerSubmit();

      if (!isValidated) {
        return;
      }
      let service = ServiceUtil.createServiceFromFormModel(
        model,
        ServiceSchema,
        this.props.isEdit,
        JSON.parse(this.state.service.toJSON())
      );
      this.setState({
        errorMessage: null,
        model,
        pendingRequest: true,
        service
      });
      marathonAction(
        cleanJSONdefinition(ServiceUtil.getAppDefinitionFromService(service)),
        this.state.force
      );
    }
  }

  getTriggerSubmit(triggerSubmit) {
    this.triggerSubmit = triggerSubmit;
  }

  getErrorMessage() {
    let {errorMessage} = this.state;
    if (!errorMessage) {
      return null;
    }

    // Stringify error details
    let errorList = null;
    if (errorMessage.details != null) {
      let responseMap = Hooks.applyFilter(
        'serviceFormErrorResponseMap',
        responseAttributePathToFieldIdMap
      );
      errorList = errorMessage.details.map(function ({path, errors}) {
        let fieldId = 'general';

        // Check if attributePath contains an index like path(0)/attribute
        // Matches as defined: [0] : '(0)', [1]: '0'
        let matches = Hooks.applyFilter('serviceFormMatchErrorPath',
          path.match(/\(([0-9]+)\)/),
          path
        );

        if (matches != null) {
          let resolvePath = responseMap[
            path.replace(matches[0], '({INDEX})')
          ];

          if (resolvePath == null) {
            resolvePath = responseMap[
              path.replace(matches[0], '{INDEX}')
            ];
          }
          if (resolvePath != null) {
            fieldId = resolvePath.replace('{INDEX}', matches[1]);
          }
        } else {
          fieldId = responseMap[path] || fieldId;
        }
        errors = errors.map(function (error) {
          if (serverResponseMappings[error]) {
            return serverResponseMappings[error];
          }
          return error;
        });

        // Return path-prefixed error string
        return `${fieldId}: ${errors}`;

      });
    }

    if (this.shouldForceUpdate(errorMessage)) {
      return (
        <CollapsibleErrorMessage
          className="error-for-modal"
          message={`App is currently locked by one or more deployments.
            Press the button again to forcefully change and deploy the new configuration.`} />
      );
    }

    return (
      <CollapsibleErrorMessage
        className="error-for-modal"
        details={errorList}
        message={errorMessage.message} />
    );

  }

  getSubmitText() {
    if (this.props.isEdit) {
      return 'Deploy Changes';
    }
    return 'Deploy';
  }

  getFooter() {
    let {pendingRequest} = this.state;
    let deployButtonClassNames = classNames('button button-large flush-bottom',
      {
        'button-success': !pendingRequest,
        'disabled': pendingRequest
      }
    );

    return (

        <button
          className="button button-large flush-top flush-bottom"
          onClick={this.handleCancel}>
          Cancel
        </button>
        <button
          className={deployButtonClassNames}
          onClick={this.handleSubmit}>
          {this.getSubmitText()}
        </button>

    );
  }

  getModalContents() {
    let {defaultTab, jsonMode, service} = this.state;

    let jsonDefinition = JSON.stringify(
      cleanJSONdefinition(service.get()),
      null,
      2
    );

    if (jsonMode) {
      let toolTipContent = (

          Use the JSON editor to enter Marathon Application definitions manually.
          {' '}
          <a href={`${Config.marathonDocsURI}generated/api.html#v2_apps_post`} target="_blank">
            View API docs.

      );

      return (

          <Ace editorProps={{$blockScrolling: true}}
            mode="json"
            onChange={this.handleJSONChange}
            showGutter={true}
            showPrintMargin={false}
            theme="monokai"
            height="462px"
            value={jsonDefinition}
            width="100%"/>
          <Tooltip
            content={toolTipContent}
            interactive={true}
            wrapperClassName="tooltip-wrapper media-object-item json-editor-help"
            wrapText={true}
            maxWidth={300}
            position="left"
            scrollContainer=".gm-scroll-view">
            <Icon color="grey" id="ring-question" size="mini" family="mini" />
          </Tooltip>

      );
    }

    let model = ServiceUtil.createFormModelFromSchema(ServiceSchema, service);

    return (
      <ServiceForm
        defaultTab={defaultTab}
        getTriggerSubmit={this.getTriggerSubmit}
        model={model}
        onChange={this.handleClearError}
        onTabChange={this.handleTabChange}
        schema={ServiceSchema}/>
    );
  }

  getToggleButton() {
    let classSet = 'modal-form-title-label';

    if (this.shouldDisableForm(this.state.service)) {
      classSet = `${classSet} disabled`;
    }

    return (<ToggleButton
      className={classSet}
      checkboxClassName="modal-form-title-toggle-button toggle-button"
      checked={this.state.jsonMode}
      onChange={this.handleJSONToggle}>
      JSON mode
    </ToggleButton>);
  }

  getWarningMessage() {
    let {warningMessage} = this.state;
    if (!warningMessage) {
      return null;
    }

    return (

            {warningMessage.message}

    );
  }

  render() {
    let titleText = 'Deploy New Service';

    if (this.props.isEdit) {
      titleText = 'Edit Service';
    }

    let title = (

              {titleText}

            {this.getToggleButton()}

          {this.getErrorMessage()}
          {this.getWarningMessage()}

    );

    return (
      <Modal
        backdropClass="modal-backdrop default-cursor"
        maxHeightPercentage={.9}
        bodyClass=""
        modalWrapperClass="multiple-form-modal modal-form"
        open={this.props.open}
        showCloseButton={false}
        showHeader={true}
        footer={this.getFooter()}
        scrollContainerClass=""
        titleText={title}
        titleClass="modal-header-title flush-top flush-bottom"
        showFooter={true}>
        {this.getModalContents()}
      </Modal>
    );
  }
}

ServiceFormModal.defaultProps = {
  isEdit: false,
  onClose() {},
  open: false,
  id: null,
  service: null
};

ServiceFormModal.propTypes = {
  id: React.PropTypes.string,
  isEdit: React.PropTypes.bool,
  open: React.PropTypes.bool,
  onClose: React.PropTypes.func,
  service: React.PropTypes.instanceOf(Service)
};

module.exports = ServiceFormModal;
<|endoftext|>


<commit_before>#!/usr/bin/env node
"use strict";

const fs = require("fs");
const path = require("path");
const { promisify } = require("util");

const mkdirp = require("mkdirp");

const mkdir = promisify(mkdirp);
const writeFile = promisify(fs.writeFile);
const readFile = promisify(fs.readFile);

const buildStats = require("../plugin/build-stats");
const TEMPLATE = require("../plugin/template-types");

const argv = require("yargs")
  .strict()
  .option("extra-style-path", {
    describe: "Extra override css file path",
    string: true
  })
  .option("filename", {
    describe: "Output file name",
    string: true,
    default: "./stats.html"
  })
  .option("title", {
    describe: "Output file title",
    string: true,
    default: "RollUp Visualizer"
  })
  .option("template", {
    describe: "Template type",
    string: true,
    choices: TEMPLATE,
    default: "treemap"
  })
  .help().argv;

const listOfFiles = argv._;

const run = async (title, template, extraStylePath, filename, files) => {
  if (files.length === 0) {
    throw new Error("Empty file list");
  }

  const fileContents = await Promise.all(
    files.map(async file => {
      const textContent = readFile(file, { encoding: "utf-8" });
      const jsonContent = JSON.parse(textContent);
      return [file, jsonContent];
    })
  );

  const tree = {
    name: "root",
    children: []
  };
  const nodes = Object.create(null);
  const nodeIds = Object.create(null);
  let links = [];

  for (const [, fileContent] of fileContents) {
    if (fileContent.tree.name === "root") {
      tree.children = tree.children.concat(fileContent.tree.children);
    } else {
      tree.children.push(fileContent.tree);
    }

    Object.assign(nodes, fileContent.nodes);
    Object.assign(nodeIds, fileContent.nodeIds);//TODO this will override things

    links = links.concat(fileContent.links);
  }

  const data = { tree, links, nodeIds, nodes };

  const fileContent = await buildStats(
    title,
    data,
    template,
    extraStylePath,
    {}
  );

  await mkdir(path.dirname(filename));
  await writeFile(filename, fileContent);
};

run(
  argv.title,
  argv.template,
  argv.extraStylePath,
  argv.filename,
  listOfFiles
).catch(err => {
  console.error(err.message);
  process.exit(1);
});
<commit_msg>Add appending of file paths (still weird but at least it will not override it anymore)<commit_after>#!/usr/bin/env node
"use strict";

const fs = require("fs");
const path = require("path");
const { promisify } = require("util");

const mkdirp = require("mkdirp");

const mkdir = promisify(mkdirp);
const writeFile = promisify(fs.writeFile);
const readFile = promisify(fs.readFile);

const buildStats = require("../plugin/build-stats");
const TEMPLATE = require("../plugin/template-types");

const argv = require("yargs")
  .strict()
  .option("extra-style-path", {
    describe: "Extra override css file path",
    string: true
  })
  .option("filename", {
    describe: "Output file name",
    string: true,
    default: "./stats.html"
  })
  .option("title", {
    describe: "Output file title",
    string: true,
    default: "RollUp Visualizer"
  })
  .option("template", {
    describe: "Template type",
    string: true,
    choices: TEMPLATE,
    default: "treemap"
  })
  .help().argv;

const listOfFiles = argv._;

const run = async (title, template, extraStylePath, filename, files) => {
  if (files.length === 0) {
    throw new Error("Empty file list");
  }

  const fileContents = await Promise.all(
    files.map(async file => {
      const textContent = readFile(file, { encoding: "utf-8" });
      const jsonContent = JSON.parse(textContent);
      return [file, jsonContent];
    })
  );

  const tree = {
    name: "root",
    children: []
  };
  const nodes = Object.create(null);
  const nodeIds = Object.create(null);
  let links = [];

  for (const [file, fileContent] of fileContents) {
    if (fileContent.tree.name === "root") {
      tree.children = tree.children.concat(fileContent.tree.children);
    } else {
      tree.children.push(fileContent.tree);
    }

    Object.assign(nodes, fileContent.nodes);

    for (const [id, uid] of fileContent.nodeIds) {
      nodeIds[file + "/" + id] = uid;
    }

    links = links.concat(fileContent.links);
  }

  const data = { tree, links, nodeIds, nodes };

  const fileContent = await buildStats(
    title,
    data,
    template,
    extraStylePath,
    {}
  );

  await mkdir(path.dirname(filename));
  await writeFile(filename, fileContent);
};

run(
  argv.title,
  argv.template,
  argv.extraStylePath,
  argv.filename,
  listOfFiles
).catch(err => {
  console.error(err.message);
  process.exit(1);
});
<|endoftext|>


<commit_before>import _request from 'request-promise'
import cheerio from 'cheerio'

const jar = _request.jar()
const request = _request.defaults({
  headers: {'Content-Type': 'application/json'},
  jar,
})

const SCV='https://www.alza.sk/Services/EShopService.svc/'

export function* login(credentials) {
  return yield request.post(`${SCV}LoginUser`, {body: JSON.stringify(credentials)})
}

export function* addToCart({url, count}) {
  const id = url.match(/(d|dq=)(\d*)(\.htm)?$/)[2]
  return yield request.post(`${SCV}OrderCommodity`, {body: JSON.stringify(
    {id, count}
  )})
}

export function* getInfo(url) {
  const $ = cheerio.load(yield request(url))
  const name = $('h1[itemprop="name"]').text()
  const price = parseFloat(
    $('span.price_withoutVat').text()
      .replace(/[^\d,]/g, '')
      .replace(/,/g, '.')
  )
  const description = $('div.nameextc').text()

  return {name, price, description}
}
<commit_msg>Trim whitespace and remove \n from item name.<commit_after>import _request from 'request-promise'
import cheerio from 'cheerio'

const jar = _request.jar()
const request = _request.defaults({
  headers: {'Content-Type': 'application/json'},
  jar,
})

const SCV='https://www.alza.sk/Services/EShopService.svc/'

export function* login(credentials) {
  return yield request.post(`${SCV}LoginUser`, {body: JSON.stringify(credentials)})
}

export function* addToCart({url, count}) {
  const id = url.match(/(d|dq=)(\d*)(\.htm)?$/)[2]
  return yield request.post(`${SCV}OrderCommodity`, {body: JSON.stringify(
    {id, count}
  )})
}

export function* getInfo(url) {
  const $ = cheerio.load(yield request(url))
  const name = $('h1[itemprop="name"]').text().trim().replace(/\s+/g, ' ')
  const price = parseFloat(
    $('span.price_withoutVat').text()
      .replace(/[^\d,]/g, '')
      .replace(/,/g, '.')
  )
  const description = $('div.nameextc').text()

  return {name, price, description}
}
<|endoftext|>


<commit_before>/*
Plan:
just dump JSON stringified object into the db to start
once it's working as a glorified key/value store, start
exploding into columns/rebuilding the object
*/

var
	_           = require('lodash'),
	assert      = require('assert'),
	scamandrios = require('scamandrios'),
	util        = require('util')
	;

function Defaults()
{
	this.hosts    = ['127.0.0.1:9160'];
	this.keyspace = 'test';
	this.user     = '';
	this.pass     = '';
	this.timeout  = 3000;
}

function CassandraAdapter()
{
	this.options = new Defaults();
	this.connection = null;
}

CassandraAdapter.prototype.configure = function(options, modelfunc)
{
	var self = this;

	_.assign(this.options, options);
	this.constructor = modelfunc;
	this.family = modelfunc.prototype.plural;

	// If you hand us a connection, we presume you have connected with it & added
	// an error listener.
	if (options.connection)
		this.connection = options.connection;
	else
	{
		this.connection = new scamandrios.ConnectionPool({
			hosts:        this.options.hosts, // ['localhost:9160'],
			keyspace:     this.options.keyspace,
			user:         this.options.user,
			password:     this.options.password,
			timeout:      3000
		});

		this.connection.on('error', function(err)
		{
			console.error(err.name, err.message);
			throw(err);
		});

		this.connection.connect().then(function(keyspace)
		{
			self.keyspace = keyspace;
		}, function(err)
		{
			throw(err);
		}).done();
	}
};

CassandraAdapter.prototype.provision = function(callback)
{
	var self = this;
	var keyspace = self.options.keyspace;

	this.connection.createKeyspace(keyspace)
	.then(function()
	{
		return self.connection.useKeyspace(keyspace);
	})
		.then(function(ks)
		{
			self.keyspace = ks;
			self.keyspace.describe().then(function(columnFamilies)
			{
				if (columnFamilies[self.family])
				{
					self.columnFamily = columnFamilies[self.family];
					return callback(null, 'OK');
				}

				var throwaway = new self.constructor();
				var properties = throwaway.propertyTypes();

				var names = Object.keys(properties);
				var cols = [];
				for (var i = 0; i < names.length; i++)
				{
					var validator;
					var name = names[i];

					if (name === 'key')
						continue;

					switch (properties[name])
					{
					case 'string':
						validator = 'UTF8Type';
						break;

					case 'number':
						validator = 'DoubleType';
						break;

					case 'boolean':
						validator = 'BooleanType';
						break;

					case 'date':
						validator = 'DateType';
						break;

					// for now subobjects are just JSON.stringified
					case 'array':
					case 'hash':
					case 'reference':
						validator = 'UTF8Type';
						break;

					default:
						validator = 'UTF8Type';
						break;
					}

					cols.push({
						name: name,
						validation_class: validator
					});
				}

				var options =
				{
					comment: 'polyclay ' + self.constructor.prototype.singular,
					key_alias: 'key',
					key_validation_class: 'UTF8Type',
					comparator_type: 'UTF8Type',
					default_validation_class: 'UTF8Type',
					columns: cols
				};

				self.keyspace.createColumnFamily(self.family, options)
				.then(function(res)
				{
					self.keyspace.get(self.family)
					.then(function(colfamily)
					{
						self.columnFamily = colfamily;
						callback(null, 'OK');
					});
				}, function(err) { callback(err); }).done();
			}, function(err) { callback(err); }).done();
		})
	.fail(function(err) { callback(err); })
	.done();
};

CassandraAdapter.prototype.shutdown = function() { };

CassandraAdapter.prototype.save = function(obj, properties, callback)
{
	assert(obj.key);
	var self = this;

	this.columnFamily.insert(obj.key, serialize(obj))
	.then(
		function(resp) { callback(null, 'OK');},
		function(err) { return callback(err); }
	).done();
};

CassandraAdapter.prototype.update = CassandraAdapter.prototype.save;

CassandraAdapter.prototype.merge = function(key, properties, callback)
{
	var params = [ this.family];
	var query = 'UPDATE %s  USING CONSISTENCY ALL SET ';

	_.forOwn(properties, function(v, k)
	{
		query += '? = ?, '
		params.push(k);
		params.push(v);
	});

	query = query.slice(0, query.length - 2);
	query += ' WHERE key = ?';
	params.push(key);

	this.connection.cql(query, params).then(function(rows)
	{
		callback(null, 'OK');
	})
	.fail(function(err) { callback(err); })
	.done();
};

CassandraAdapter.prototype.saveAttachment = function(obj, attachment, callback)
{
	throw(new Error('unimplemented'));
	// this.attachments[obj.key + ':' + attachment.name] = attachment;
	// callback(null, 'OK');
};

CassandraAdapter.prototype.get = function(key, callback)
{
	var self = this,
		results = [];

	if (Array.isArray(key))
		return this.getBatch(key, callback);

	this.connection.cql('SELECT * from %s WHERE key = ?', [this.family, key])
	.then(function(rows)
	{
		rows.forEach(function(row)
		{
			var props = {};
			row.forEach(function(n, v, ts, ttl)
			{
				props[n] = v;
			});

			results.push(self.inflate(props));
		});
		callback(null, results[0]);

	})
	.fail(function(err) { callback(err); })
	.done();
};

CassandraAdapter.prototype.getBatch = function(keylist, callback)
{
	var results = [];
	var self = this;

	this.connection.cql('SELECT * from %s WHERE key IN (%s)', [this.family, keylist])
	.then(function(rows)
	{
		rows.forEach(function(row)
		{
			var props = {};
			row.forEach(function(n, v, ts, ttl)
			{
				props[n] = v;
			});
			results.push(self.inflate(props));
		});

		callback(null, results);

	})
	.fail(function(err) { callback(err); })
	.done();
};

CassandraAdapter.prototype.all = function(callback)
{
	var results = [];
	var self = this;

	this.connection.cql('SELECT * from %s', [this.family])
	.then(function(rows)
	{
		rows.forEach(function(row)
		{
			var props = {};
			row.forEach(function(n, v, ts, ttl)
			{
				props[n] = v;
			});
			results.push(self.inflate(props));
		});

		callback(null, results, true);
	})
	.fail(function(err) { callback(err); })
	.done();
};

CassandraAdapter.prototype.attachment = function(key, name, callback)
{
	// TODO
	callback(new Error('unimplemented'));
	// callback(null, this.attachments[key + ':' + name]);
};

CassandraAdapter.prototype.remove = function(obj, callback)
{
	var self = this;

	this.connection.cql('DELETE FROM %s WHERE KEY = ?', [this.family, obj.key])
	.then(function(reply)
	{
		// TODO also delete attachments
		callback();
	})
	.fail(function(err) { console.log(err); callback(err); })
	.done();
};

CassandraAdapter.prototype.destroyMany = function(objlist, callback)
{
	var keylist = _.map(objlist, function(item) { return item.key; });

	this.connection.cql('DELETE from %s WHERE key IN (%s)', [this.family, keylist.join(', ')])
	.then(function(reply)
	{
		// TODO also delete attachments
		callback();
	})
	.fail(function(err) { console.log(err); callback(err); })
	.done();
};

CassandraAdapter.prototype.removeAttachment = function(obj, name, callback)
{
	callback(new Error('unimplemented'));
	//delete this.attachments[obj.key + ':' + name];
	// callback(null, 'OK');
};

function serialize(obj)
{
	var struct = obj.serialize();
	var types = obj.propertyTypes();

	var keys = Object.keys(struct);
	for (var i = 0; i < keys.length; i++)
	{
		var k = keys[i];
		if (('array' === types[k]) || ('hash' === types[k]) || ('reference' === types[k]) || 'untyped' === types[k])
			struct[k] = JSON.stringify(struct[k]);
	}

	return struct;
}

function convert(value, type)
{
	switch (type)
	{
	case 'string':    return value;
	case 'boolean':   return value;
	case 'date':      return value;
	case 'number':    return value;
	case 'array':     return JSON.parse(value);
	case 'hash':      return JSON.parse(value);
	case 'reference': return JSON.parse(value);
	default:          return JSON.parse(value);
	}
}

CassandraAdapter.prototype.inflate = function(hash)
{
	if (!hash || !_.isObject(hash))
		return;

	var obj = new this.constructor();
	var converted = {};
	var keys = Object.keys(hash);

	for (var i = 0; i < keys.length; i++)
	{
		var k = keys[i];
		var v = hash[k];
		var type = obj.__types[k];
		converted[k] = convert(v, type);
	}

	obj.update(converted);
	return obj;
};

module.exports = CassandraAdapter;
<commit_msg>Massive refactor of the Scamandrios adapter.<commit_after>/*
Plan:
just dump JSON stringified object into the db to start
once it's working as a glorified key/value store, start
exploding into columns/rebuilding the object
*/

var
	_           = require('lodash'),
	assert      = require('assert'),
	scamandrios = require('scamandrios'),
	util        = require('util')
	;

function Defaults()
{
	this.hosts    = ['127.0.0.1:9160'];
	this.keyspace = 'test';
	this.user     = '';
	this.pass     = '';
	this.timeout  = 3000;
}

function CassandraAdapter()
{
	this.options = new Defaults();
	this.connection = null;
	this.link = null;
}

var typeToValidator =
{
	'string': 'UTF8Type',
	'number': 'DoubleType',
	'boolean': 'BooleanType',
	'date': 'DateType',

	// for now subobjects are just JSON.stringified
	'array': 'UTF8Type',
	'hash': 'UTF8Type',
	'reference': 'UTF8Type'
};
CassandraAdapter.typeToValidator = typeToValidator;

CassandraAdapter.prototype.configure = function(options, modelfunc)
{
	var self = this;

	_.assign(this.options, options);
	this.constructor = modelfunc;
	this.family = modelfunc.prototype.plural;

	// If you hand us a pool, we presume you have added an error listener.
	if (options.connection)
		this.connection = options.connection;
	else
	{
		this.connection = new scamandrios.ConnectionPool({
			hosts:        this.options.hosts, // ['localhost:9160'],
			user:         this.options.user,
			password:     this.options.password,
			timeout:      3000
		});

		this.connection.on('error', function(err)
		{
			console.error(err.name, err.message);
			throw(err);
		});
	}

	this.link = this.connection.connect();
};

CassandraAdapter.prototype.assignKeyspace = function()
{
	var self = this;
	var keyspace = self.options.keyspace;

	return this.link.then(function()
	{
		return self.connection.use(keyspace);
	})
	.fail(function(err)
	{
		if (err.name !== 'HelenusNotFoundException')
			throw err;

		return self.connection.createKeyspace(keyspace).then(function()
		{
			return self.connection.use(keyspace);
		});
	})
	.then(function(keyspace)
	{
		if (Array.isArray(keyspace))
			keyspace = _.find(keyspace, { 'state': 'fulfilled' }).value;

		self.keyspace = keyspace;
		return keyspace;
	});
};

CassandraAdapter.prototype.getColumnFamily = function getColumnFamily()
{
	var self = this;

	return this.assignKeyspace()
	.then(function(keyspace)
	{
		return keyspace.describe();
	})
	.then(function(columnFamilies)
	{
		if (columnFamilies[self.family])
		{
			self.columnFamily = columnFamilies[self.family];
			return self.columnFamily;
		}

		return null;
	});
};

CassandraAdapter.prototype.provision = function(callback)
{
	var self = this;

	this.getColumnFamily().then(function(colfamily)
	{
		if (colfamily)
			return callback(null, 'OK');

		var throwaway = new self.constructor();
		var properties = throwaway.propertyTypes();

		var cols = [];
		_.forOwn(properties, function(property, name)
		{
			if (name === 'key')
				return;

			var validator = typeToValidator[property] || 'UTF8Type';

			cols.push({
				name: name,
				validation_class: validator
			});
		});

		var options =
		{
			comment: 'polyclay ' + self.constructor.prototype.singular,
			key_alias: 'key',
			key_validation_class: 'UTF8Type',
			comparator_type: 'UTF8Type',
			default_validation_class: 'UTF8Type',
			columns: cols
		};

		self.keyspace.createColumnFamily(self.family, options)
		.then(function(res)
		{
			return self.keyspace.get(self.family);
		})
		.then(function(colfamily)
		{
			self.columnFamily = colfamily;
			callback(null, 'OK');
		}, function(err) { callback(err); }).done();
	}, function(err) { callback(err); }).done();
};

CassandraAdapter.prototype.shutdown = function() {};

CassandraAdapter.prototype.save = function(obj, properties, callback)
{
	assert(obj.key);
	var self = this;

	this.getColumnFamily()
	.then(function(colfamily)
	{
		return colfamily.insert(obj.key, serialize(obj));
	})
	.then(
		function(resp) { callback(null, 'OK');},
		function(err) { return callback(err); }
	).done();
};

CassandraAdapter.prototype.update = CassandraAdapter.prototype.save;

CassandraAdapter.prototype.merge = function(key, properties, callback)
{
	var params = [ this.family];
	var query = 'UPDATE %s  USING CONSISTENCY ALL SET ';

	_.forOwn(properties, function(v, k)
	{
		query += '? = ?, '
		params.push(k);
		params.push(v);
	});

	query = query.slice(0, query.length - 2);
	query += ' WHERE key = ?';
	params.push(key);

	this.connection.cql(query, params).then(function(rows)
	{
		callback(null, 'OK');
	})
	.fail(function(err) { callback(err); })
	.done();
};

CassandraAdapter.prototype.saveAttachment = function(obj, attachment, callback)
{
	throw(new Error('unimplemented'));
	// this.attachments[obj.key + ':' + attachment.name] = attachment;
	// callback(null, 'OK');
};

CassandraAdapter.prototype.get = function(key, callback)
{
	var self = this,
		results = [];

	if (Array.isArray(key))
		return this.getBatch(key, callback);

	this.link.then(function()
	{
		return self.connection.cql('SELECT * from %s WHERE key = ?', [self.family, key]);
	})
	.then(function(rows)
	{
		rows.forEach(function(row)
		{
			var props = {};
			row.forEach(function(n, v, ts, ttl)
			{
				props[n] = v;
			});

			results.push(self.inflate(props));
		});
		callback(null, results[0]);

	})
	.fail(function(err) { callback(err); })
	.done();
};

CassandraAdapter.prototype.getBatch = function(keylist, callback)
{
	var results = [];
	var self = this;

	this.link.then(function()
	{
		return self.connection.cql('SELECT * from %s WHERE key IN (%s)', [self.family, keylist]);
	})
	.then(function(rows)
	{
		rows.forEach(function(row)
		{
			var props = {};
			row.forEach(function(n, v, ts, ttl)
			{
				props[n] = v;
			});
			results.push(self.inflate(props));
		});

		callback(null, results);

	})
	.fail(function(err) { callback(err); })
	.done();
};

CassandraAdapter.prototype.all = function(callback)
{
	var results = [];
	var self = this;

	this.link.then(function()
	{
		return self.connection.cql('SELECT * from %s', [self.family]);
	})
	.then(function(rows)
	{
		rows.forEach(function(row)
		{
			var props = {};
			row.forEach(function(n, v, ts, ttl)
			{
				props[n] = v;
			});
			results.push(self.inflate(props));
		});

		callback(null, results, true);
	})
	.fail(function(err) { callback(err); })
	.done();
};

CassandraAdapter.prototype.attachment = function(key, name, callback)
{
	// TODO
	callback(new Error('unimplemented'));
	// callback(null, this.attachments[key + ':' + name]);
};

CassandraAdapter.prototype.remove = function(obj, callback)
{
	var self = this;

	this.link.then(function()
	{
		return self.connection.cql('DELETE FROM %s WHERE KEY = ?', [self.family, obj.key]);
	})
	.then(function(reply)
	{
		// TODO also delete attachments
		callback();
	})
	.fail(function(err) { console.log(err); callback(err); })
	.done();
};

CassandraAdapter.prototype.destroyMany = function(objlist, callback)
{
	var self = this;
	var keylist = _.map(objlist, function(item) { return item.key; });

	this.link.then(function()
	{
		return self.connection.cql('DELETE from %s WHERE key IN (%s)', [self.family, keylist.join(', ')]);
	})
	.then(function(reply)
	{
		// TODO also delete attachments
		callback();
	})
	.fail(function(err) { console.log(err); callback(err); })
	.done();
};

CassandraAdapter.prototype.removeAttachment = function(obj, name, callback)
{
	callback(new Error('unimplemented'));
	//delete this.attachments[obj.key + ':' + name];
	// callback(null, 'OK');
};

function serialize(obj)
{
	var struct = obj.serialize();
	var types = obj.propertyTypes();

	var keys = Object.keys(struct);
	for (var i = 0; i < keys.length; i++)
	{
		var k = keys[i];
		if (('array' === types[k]) || ('hash' === types[k]) || ('reference' === types[k]) || 'untyped' === types[k])
			struct[k] = JSON.stringify(struct[k]);
	}

	return struct;
}

function convert(value, type)
{
	switch (type)
	{
	case 'string':    return value;
	case 'boolean':   return value;
	case 'date':      return value;
	case 'number':    return value;
	case 'array':     return JSON.parse(value);
	case 'hash':      return JSON.parse(value);
	case 'reference': return JSON.parse(value);
	default:          return JSON.parse(value);
	}
}

CassandraAdapter.prototype.inflate = function(hash)
{
	if (!hash || !_.isObject(hash))
		return;

	var obj = new this.constructor();
	var converted = {};
	var keys = Object.keys(hash);

	for (var i = 0; i < keys.length; i++)
	{
		var k = keys[i];
		var v = hash[k];
		var type = obj.__types[k];
		converted[k] = convert(v, type);
	}

	obj.update(converted);
	return obj;
};

module.exports = CassandraAdapter;
<|endoftext|>