<commit_before>/**
 * Common JS that talks XHR back to the server and runs the code and receives
 * the results.
 */

/**
 * A polyfill for HTML Templates.
 *
 * This just adds in the content attribute, it doesn't stop scripts
 * from running nor does it stop other side-effects.
 */
(function polyfillTemplates() {
  if('content' in document.createElement('template')) {
    return false;
  }

  var templates = document.getElementsByTagName('template');
  for (var i=0; i<templates.length; i++) {
    var content = document.createDocumentFragment();
    while (templates[i].firstChild) {
     content.appendChild(templates[i].firstChild);
    }
    templates[i].content = content;
  }
})();

/**
 * Enable zooming for any images with a class of 'zoom'.
 */
(function () {
  var PIXELS       = 20; // The number of pixels in width and height in a zoom.
  var clientX      = 0;
  var clientY      = 0;
  var lastClientX  = 0;
  var lastClientY  = 0;
  var ctx          = null; // The 2D canvas context of the zoom.
  var currentImage = null; // The img node we are zooming for, otherwise null.
  var hex          = document.getElementById('zoomHex');
  var canvasCopy   = null;

  function zoomMove(e) {
    clientX = e.clientX;
    clientY = e.clientY;
  }

  function zoomMouseDown(e) {
    e.preventDefault();
    // Only do zooming on the primary mouse button.
    if (e.button != 0) {
      return
    }
    currentImage = e.target;
    clientX = e.clientX;
    clientY = e.clientY;
    lastClientX = clientX-1;
    lastClientY = clientY-1;
    document.body.style.cursor = 'crosshair';
    canvas = document.createElement('canvas');
    canvas.width = 1024;
    canvas.height = 1024;
    canvas.classList.add('zoomCanvas');
    ctx = canvas.getContext('2d');
    ctx.imageSmoothingEnabled = false;
    this.parentNode.insertBefore(canvas, this);

    // Copy the image over to a canvas so we can read RGBA values for each point.
    if (hex) {
      canvasCopy = document.createElement('canvas');
      canvasCopy.width = currentImage.width;
      canvasCopy.height = currentImage.height;
      canvasCopy.id = 'zoomCopy';
      canvasCopy.getContext('2d').drawImage(currentImage, 0, 0, currentImage.width, currentImage.height);
      this.parentNode.insertBefore(canvasCopy, this);
    }

    document.body.addEventListener('mousemove',  zoomMove,     true);
    document.body.addEventListener('mouseup',    zoomFinished);
    document.body.addEventListener('mouseleave', zoomFinished);

    // Kick off the drawing.
    setTimeout(drawZoom, 1);
  }

  function hexify(i) {
    var s = i.toString(16).toUpperCase();
    // Pad out to two hex digits if necessary.
    if (s.length < 2) {
      s = '0' + s;
    }
    return s;
  }

  function drawZoom() {
    if (currentImage) {
      // Only draw if the mouse has moved from the last time we drew.
      if (lastClientX != clientX || lastClientY != clientY) {
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        var x = clientX - currentImage.x;
        var y = clientY - currentImage.y;
        ctx.drawImage(currentImage,
            x, y,                                 // src zero
            PIXELS, PIXELS,                       // src dimensions
            0, 0,                                 // dst zero
            ctx.canvas.width, ctx.canvas.height); // dst dimensions
        lastClientX = clientX;
        lastClientY = clientY;
        // Box and label one selected pixel with its rgba values.
        if (hex) {
          ctx.lineWidth = 1;
          ctx.strokeStyle = '#000';
          var dx = ctx.canvas.width/PIXELS;
          var dy = ctx.canvas.height/PIXELS;
          ctx.strokeRect((PIXELS/2)*dx, (PIXELS/2)*dy, dx, dy);
          var p = canvasCopy.getContext('2d').getImageData(x+PIXELS/2, y+PIXELS/2, 1, 1).data;
          hex.textContent = 'rgba('
            + p[0] + ', '
            + p[1] + ', '
            + p[2] + ', '
            + p[3] + ') '
            + hexify(p[0])
            + hexify(p[1])
            + hexify(p[2])
            + hexify(p[3]);
        }
      }
      setTimeout(drawZoom, 1000/30);
    }
  }

  function zoomFinished() {
    currentImage = null;
    if (hex) {
      hex.textContent = '';
    }
    document.body.style.cursor = 'default';
    ctx.canvas.parentNode.removeChild(ctx.canvas);
    canvasCopy.parentNode.removeChild(canvasCopy);
    document.body.removeEventListener('mousemove',  zoomMove,     true);
    document.body.removeEventListener('mouseup',    zoomFinished);
    document.body.removeEventListener('mouseleave', zoomFinished);
  }

  this.addEventListener('DOMContentLoaded', function() {
    var zoomables = document.body.querySelectorAll('.zoom');
    for (var i=0; i<zoomables.length; i++) {
      zoomables[i].addEventListener('mousedown', zoomMouseDown);
    }
  });
})();

/**
 * All the functionality is wrapped up in this anonymous closure, but we need
 * to be told if we are on the workspace page or a normal try page, so the
 * workspaceName is passed into the closure, it must be set in the global
 * namespace. If workspaceName is the empty string then we know we aren't
 * running on a workspace page.
 *
 * If we are on a workspace page we also look for a 'history'
 * variable in the global namespace which contains the list of tries
 * that are included in this workspace. That variable is used to
 * populate the history list.
 */
(function(workspaceName) {
    var run = document.getElementById('run');
    var permalink = document.getElementById('permalink');
    var embed = document.getElementById('embed');
    var embedButton = document.getElementById('embedButton');
    var code = document.getElementById('code');
    var output = document.getElementById('output');
    var stdout = document.getElementById('stdout');
    var img = document.getElementById('img');
    var tryHistory = document.getElementById('tryHistory');
    var parser = new DOMParser();
    var tryTemplate = document.getElementById('tryTemplate');

    var editor = CodeMirror.fromTextArea(code, {
      theme: "default",
      lineNumbers: true,
      matchBrackets: true,
      mode: "text/x-c++src",
      indentUnit: 4,
    });

    // Match the initial textarea size.
    editor.setSize(editor.defaultCharWidth() * code.cols,
                   editor.defaultTextHeight() * code.rows);

    // Suppress changes to the first/last line (draw wrapper method)
    editor.on('beforeChange', function(cm, change) {
      if (change.from.line < 1 || change.from.line == cm.lineCount() - 1)
        change.cancel();
    });

    function beginWait() {
      document.body.classList.add('waiting');
      run.disabled = true;
    }

    function endWait() {
      document.body.classList.remove('waiting');
      run.disabled = false;
    }

    /**
     * Callback when there's an XHR error.
     * @param e The callback event.
     */
    function xhrError(e) {
      endWait();
      alert('Something bad happened: ' + e);
    }

    function clearOutput() {
      output.textContent = "";
      if (stdout) {
        stdout.textContent = "";
      }
      embed.style.display='none';
    }

    /**
     * Called when an image in the workspace history is clicked.
     */
    function historyClick() {
      beginWait();
      clearOutput();
      var req = new XMLHttpRequest();
      req.addEventListener('load', historyComplete);
      req.addEventListener('error', xhrError);
      req.overrideMimeType('application/json');
      req.open('GET', this.getAttribute('data-try'), true);
      req.send();
    }

    /**
     * Callback for when the XHR kicked off in historyClick() returns.
     */
    function historyComplete(e) {
      // The response is JSON of the form:
      // {
      //   "hash": "unique id for a try",
      //   "code": "source code for try"
      // }
      endWait();
      body = JSON.parse(e.target.response);
      code.value = body.code;
      img.src = '/i/'+body.hash+'.png';
      if (permalink) {
        permalink.href = '/c/' + body.hash;
      }
    }

    /**
     * Add the given try image to the history of a workspace.
     */
    function addToHistory(hash, imgUrl) {
      var clone = tryTemplate.content.cloneNode(true);
      clone.querySelector('img').src = imgUrl;
      clone.querySelector('.tries').setAttribute('data-try', '/json/' + hash);
      tryHistory.insertBefore(clone, tryHistory.firstChild);
      tryHistory.querySelector('.tries').addEventListener('click', historyClick, true);
    }

    /**
     * Callback for when the XHR returns after attempting to run the code.
     * @param e The callback event.
     */
    function codeComplete(e) {
      // The response is JSON of the form:
      // {
      //   "message": "you had an error...",
      //   "img": "<base64 encoded image but only on success>"
      // }
      //
      // The img is optional and only appears if there is a valid
      // image to display.
      endWait();
      console.log(e.target.response);
      body = JSON.parse(e.target.response);
      output.textContent = body.message;
      if (stdout) {
        stdout.textContent = body.stdout;
      }
      if (body.hasOwnProperty('img')) {
        img.src = 'data:image/png;base64,' + body.img;
      } else {
        img.src = '';
      }
      // Add the image to the history if we are on a workspace page.
      if (tryHistory) {
        addToHistory(body.hash, 'data:image/png;base64,' + body.img);
      } else {
        window.history.pushState(null, null, '/c/' + body.hash);
      }
      if (permalink) {
        permalink.href = '/c/' + body.hash;
      }
      if (embed) {
        var url = document.URL;
        url = url.replace('/c/', '/iframe/');
        embed.value = '<iframe src="' + url + '" width="740" height="550" style="border: solid #00a 5px; border-radius: 5px;"/>'
      }
      if (embedButton && embedButton.hasAttribute('disabled')) {
        embedButton.removeAttribute('disabled');
      }
    }

    function onSubmitCode() {
      beginWait();
      clearOutput();
      var req = new XMLHttpRequest();
      req.addEventListener('load', codeComplete);
      req.addEventListener('error', xhrError);
      req.overrideMimeType('application/json');
      req.open('POST', '/', true);
      req.setRequestHeader('content-type', 'application/json');
      req.send(JSON.stringify({'code': editor.getValue(), 'name': workspaceName}));
    }
    run.addEventListener('click', onSubmitCode);

    function onEmbedClick() {
      embed.style.display='inline';
    }

    if (embedButton) {
      embedButton.addEventListener('click', onEmbedClick);
    }

    // Add the images to the history if we are on a workspace page.
    if (tryHistory && history) {
      for (var i=0; i<history.length; i++) {
        addToHistory(history[i].hash, '/i/'+history[i].hash+'.png');
      }
    }

})(workspaceName);
<commit_msg>Fix history on workspaces, where you couldn't click on old tries.<commit_after>/**
 * Common JS that talks XHR back to the server and runs the code and receives
 * the results.
 */

/**
 * A polyfill for HTML Templates.
 *
 * This just adds in the content attribute, it doesn't stop scripts
 * from running nor does it stop other side-effects.
 */
(function polyfillTemplates() {
  if('content' in document.createElement('template')) {
    return false;
  }

  var templates = document.getElementsByTagName('template');
  for (var i=0; i<templates.length; i++) {
    var content = document.createDocumentFragment();
    while (templates[i].firstChild) {
     content.appendChild(templates[i].firstChild);
    }
    templates[i].content = content;
  }
})();

/**
 * Enable zooming for any images with a class of 'zoom'.
 */
(function () {
  var PIXELS       = 20; // The number of pixels in width and height in a zoom.
  var clientX      = 0;
  var clientY      = 0;
  var lastClientX  = 0;
  var lastClientY  = 0;
  var ctx          = null; // The 2D canvas context of the zoom.
  var currentImage = null; // The img node we are zooming for, otherwise null.
  var hex          = document.getElementById('zoomHex');
  var canvasCopy   = null;

  function zoomMove(e) {
    clientX = e.clientX;
    clientY = e.clientY;
  }

  function zoomMouseDown(e) {
    e.preventDefault();
    // Only do zooming on the primary mouse button.
    if (e.button != 0) {
      return
    }
    currentImage = e.target;
    clientX = e.clientX;
    clientY = e.clientY;
    lastClientX = clientX-1;
    lastClientY = clientY-1;
    document.body.style.cursor = 'crosshair';
    canvas = document.createElement('canvas');
    canvas.width = 1024;
    canvas.height = 1024;
    canvas.classList.add('zoomCanvas');
    ctx = canvas.getContext('2d');
    ctx.imageSmoothingEnabled = false;
    this.parentNode.insertBefore(canvas, this);

    // Copy the image over to a canvas so we can read RGBA values for each point.
    if (hex) {
      canvasCopy = document.createElement('canvas');
      canvasCopy.width = currentImage.width;
      canvasCopy.height = currentImage.height;
      canvasCopy.id = 'zoomCopy';
      canvasCopy.getContext('2d').drawImage(currentImage, 0, 0, currentImage.width, currentImage.height);
      this.parentNode.insertBefore(canvasCopy, this);
    }

    document.body.addEventListener('mousemove',  zoomMove,     true);
    document.body.addEventListener('mouseup',    zoomFinished);
    document.body.addEventListener('mouseleave', zoomFinished);

    // Kick off the drawing.
    setTimeout(drawZoom, 1);
  }

  function hexify(i) {
    var s = i.toString(16).toUpperCase();
    // Pad out to two hex digits if necessary.
    if (s.length < 2) {
      s = '0' + s;
    }
    return s;
  }

  function drawZoom() {
    if (currentImage) {
      // Only draw if the mouse has moved from the last time we drew.
      if (lastClientX != clientX || lastClientY != clientY) {
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        var x = clientX - currentImage.x;
        var y = clientY - currentImage.y;
        ctx.drawImage(currentImage,
            x, y,                                 // src zero
            PIXELS, PIXELS,                       // src dimensions
            0, 0,                                 // dst zero
            ctx.canvas.width, ctx.canvas.height); // dst dimensions
        lastClientX = clientX;
        lastClientY = clientY;
        // Box and label one selected pixel with its rgba values.
        if (hex) {
          ctx.lineWidth = 1;
          ctx.strokeStyle = '#000';
          var dx = ctx.canvas.width/PIXELS;
          var dy = ctx.canvas.height/PIXELS;
          ctx.strokeRect((PIXELS/2)*dx, (PIXELS/2)*dy, dx, dy);
          var p = canvasCopy.getContext('2d').getImageData(x+PIXELS/2, y+PIXELS/2, 1, 1).data;
          hex.textContent = 'rgba('
            + p[0] + ', '
            + p[1] + ', '
            + p[2] + ', '
            + p[3] + ') '
            + hexify(p[0])
            + hexify(p[1])
            + hexify(p[2])
            + hexify(p[3]);
        }
      }
      setTimeout(drawZoom, 1000/30);
    }
  }

  function zoomFinished() {
    currentImage = null;
    if (hex) {
      hex.textContent = '';
    }
    document.body.style.cursor = 'default';
    ctx.canvas.parentNode.removeChild(ctx.canvas);
    canvasCopy.parentNode.removeChild(canvasCopy);
    document.body.removeEventListener('mousemove',  zoomMove,     true);
    document.body.removeEventListener('mouseup',    zoomFinished);
    document.body.removeEventListener('mouseleave', zoomFinished);
  }

  this.addEventListener('DOMContentLoaded', function() {
    var zoomables = document.body.querySelectorAll('.zoom');
    for (var i=0; i<zoomables.length; i++) {
      zoomables[i].addEventListener('mousedown', zoomMouseDown);
    }
  });
})();

/**
 * All the functionality is wrapped up in this anonymous closure, but we need
 * to be told if we are on the workspace page or a normal try page, so the
 * workspaceName is passed into the closure, it must be set in the global
 * namespace. If workspaceName is the empty string then we know we aren't
 * running on a workspace page.
 *
 * If we are on a workspace page we also look for a 'history'
 * variable in the global namespace which contains the list of tries
 * that are included in this workspace. That variable is used to
 * populate the history list.
 */
(function(workspaceName) {
    var run = document.getElementById('run');
    var permalink = document.getElementById('permalink');
    var embed = document.getElementById('embed');
    var embedButton = document.getElementById('embedButton');
    var code = document.getElementById('code');
    var output = document.getElementById('output');
    var stdout = document.getElementById('stdout');
    var img = document.getElementById('img');
    var tryHistory = document.getElementById('tryHistory');
    var parser = new DOMParser();
    var tryTemplate = document.getElementById('tryTemplate');

    var editor = CodeMirror.fromTextArea(code, {
      theme: "default",
      lineNumbers: true,
      matchBrackets: true,
      mode: "text/x-c++src",
      indentUnit: 4,
    });

    // Match the initial textarea size.
    editor.setSize(editor.defaultCharWidth() * code.cols,
                   editor.defaultTextHeight() * code.rows);

    function beginWait() {
      document.body.classList.add('waiting');
      run.disabled = true;
    }

    function endWait() {
      document.body.classList.remove('waiting');
      run.disabled = false;
    }

    /**
     * Callback when there's an XHR error.
     * @param e The callback event.
     */
    function xhrError(e) {
      endWait();
      alert('Something bad happened: ' + e);
    }

    function clearOutput() {
      output.textContent = "";
      if (stdout) {
        stdout.textContent = "";
      }
      embed.style.display='none';
    }

    /**
     * Called when an image in the workspace history is clicked.
     */
    function historyClick() {
      beginWait();
      clearOutput();
      var req = new XMLHttpRequest();
      req.addEventListener('load', historyComplete);
      req.addEventListener('error', xhrError);
      req.overrideMimeType('application/json');
      req.open('GET', this.getAttribute('data-try'), true);
      req.send();
    }

    /**
     * Callback for when the XHR kicked off in historyClick() returns.
     */
    function historyComplete(e) {
      // The response is JSON of the form:
      // {
      //   "hash": "unique id for a try",
      //   "code": "source code for try"
      // }
      endWait();
      body = JSON.parse(e.target.response);
      code.value = body.code;
      editor.setValue(body.code);
      img.src = '/i/'+body.hash+'.png';
      if (permalink) {
        permalink.href = '/c/' + body.hash;
      }
    }

    /**
     * Add the given try image to the history of a workspace.
     */
    function addToHistory(hash, imgUrl) {
      var clone = tryTemplate.content.cloneNode(true);
      clone.querySelector('img').src = imgUrl;
      clone.querySelector('.tries').setAttribute('data-try', '/json/' + hash);
      tryHistory.insertBefore(clone, tryHistory.firstChild);
      tryHistory.querySelector('.tries').addEventListener('click', historyClick, true);
    }

    /**
     * Callback for when the XHR returns after attempting to run the code.
     * @param e The callback event.
     */
    function codeComplete(e) {
      // The response is JSON of the form:
      // {
      //   "message": "you had an error...",
      //   "img": "<base64 encoded image but only on success>"
      // }
      //
      // The img is optional and only appears if there is a valid
      // image to display.
      endWait();
      console.log(e.target.response);
      body = JSON.parse(e.target.response);
      output.textContent = body.message;
      if (stdout) {
        stdout.textContent = body.stdout;
      }
      if (body.hasOwnProperty('img')) {
        img.src = 'data:image/png;base64,' + body.img;
      } else {
        img.src = '';
      }
      // Add the image to the history if we are on a workspace page.
      if (tryHistory) {
        addToHistory(body.hash, 'data:image/png;base64,' + body.img);
      } else {
        window.history.pushState(null, null, '/c/' + body.hash);
      }
      if (permalink) {
        permalink.href = '/c/' + body.hash;
      }
      if (embed) {
        var url = document.URL;
        url = url.replace('/c/', '/iframe/');
        embed.value = '<iframe src="' + url + '" width="740" height="550" style="border: solid #00a 5px; border-radius: 5px;"/>'
      }
      if (embedButton && embedButton.hasAttribute('disabled')) {
        embedButton.removeAttribute('disabled');
      }
    }

    function onSubmitCode() {
      beginWait();
      clearOutput();
      var req = new XMLHttpRequest();
      req.addEventListener('load', codeComplete);
      req.addEventListener('error', xhrError);
      req.overrideMimeType('application/json');
      req.open('POST', '/', true);
      req.setRequestHeader('content-type', 'application/json');
      req.send(JSON.stringify({'code': editor.getValue(), 'name': workspaceName}));
    }
    run.addEventListener('click', onSubmitCode);

    function onEmbedClick() {
      embed.style.display='inline';
    }

    if (embedButton) {
      embedButton.addEventListener('click', onEmbedClick);
    }

    // Add the images to the history if we are on a workspace page.
    if (tryHistory && history) {
      for (var i=0; i<history.length; i++) {
        addToHistory(history[i].hash, '/i/'+history[i].hash+'.png');
      }
    }

})(workspaceName);
<|endoftext|>


<commit_before>const {google} = require('googleapis');
const util = require('util');
const fs = require('fs');
const OAuth2Client = google.auth.OAuth2;
fs.readFileAsync = util.promisify(fs.readFile);
const titlecase = require('titlecase');
const escapeStringRegexp = require('escape-string-regexp');
const pad = require('pad');

const SPREADSHEET_ID = '11gTjAkpm4D3uoxnYCN7ZfbiVnKyi7tmm9Vp9HvTkGpw';
const TOKEN_PATH = 'secrets/credentials.json';
const SECRETS_PATH = 'secrets/client_secret.json';

/** authorize():
 * Authorizes Google credentials.
 * @return {google.auth.OAuth2} oAuth2Client
 **/
async function authorize() {
  let secrets;
  let token;
  try {
    secrets = await fs.readFileAsync(SECRETS_PATH);
    token = await fs.readFileAsync(TOKEN_PATH);
    const credentials = JSON.parse(secrets);
    const {client_secret, client_id, redirect_uris} = credentials.installed;
    const oAuth2Client = new OAuth2Client(
      client_id, client_secret, redirect_uris[0]);
    oAuth2Client.setCredentials(JSON.parse(token));
    return oAuth2Client;
  } catch (err) {
    console.log('Unable to open SECRETS_PATH or TOKEN_PATH', err);
  }
}

exports.speedrun = function lookupSpeedrun(
  msg, rows, category, secondaryCategory) {
  return new Promise((resolve, reject) => {
    const sheets = google.sheets({version: 'v4'});
    authorize()
      .then((oAuth2Client) => {
        category = escapeStringRegexp(category);
        category = category.toLowerCase();
        const categorySheet = getSheet(category);
        secondaryCategory = titlecase.toLaxTitleCase(secondaryCategory);
        const request = {
          spreadsheetId: SPREADSHEET_ID,
          range: categorySheet,
          auth: oAuth2Client,
        };
        sheets.spreadsheets.values.get(request, (err, {data}) => {
          if (err) {
            if (err.code === 400) {
              console.log(err.message);
              msg.channel.send(`Invalid speedrun category "${category}".`)
                .then((res) => {
                  resolve(res);
                  return;
                });
            } else {
              console.log(err);
              msg.channel.send(
                'The bot user has not set up valid Google API credentials yet.')
              .then((res) => {
                resolve(res);
                return;
              });
            }
          } else {
            // Find where the secondaryCategory is on the sheet in question
            console.log(data);
            const categoryRange = find(secondaryCategory, data.values);
            let categoryNames = [];
            // Get the row where the categories lie.
            const categoryRow = data.values[categoryRange.row + 1];
            // Start from the place where secondaryCategory was found,
            // Add 2 to categoryRow because the category rows are always going
            // to be 2 below the secondary category,
            // push secondaryCategory into cagetoryNames,
            // increment the place where secondaryCategory was found by one,
            // and keep going until we either hit a '' or undefined.
            for
            (let i = categoryRange.columnNum - 1; i < categoryRow.length; i++) {
              console.log(i);
              if (categoryRow[i] === undefined ||
                  categoryRow[i] === '' ||
                  categoryRow[i] === null) {
                break;
              }
              console.log(categoryRow[i]);
              categoryNames.push(categoryRow[i]);
            }
            // Now that we know the category names, we need to get the values of
            // the top {rows} contestants.
            // The contestants will always be in the row right below the
            // category
            let contestants = [];
            let padLength = 0;
            for (let i = 0; i < rows; i++) {
              // categoryRange.row + 2 will give us the starting row of
              // contestants.
              let row = categoryRange.row + 2 + i;
              let contestant = [];
              // entryStartPos gives us the starting cell
              // with the contestant name.
              const entryStartPos = categoryRange.columnNum - 1;
              for (let j = 0;
                   j < categoryNames.length; j++) {
                     console.log(data.values[row][entryStartPos + j]);
                     if (data.values[row][entryStartPos + j].length
                         > padLength) {
                       // Pad the contestant name table with the longest
                       // contestant name (and add an extra 1 for spacing.)
                       padLength = data.values[row][entryStartPos + j]
                       .length + 1;
                     }
                     contestant.push(
                       data.values[row][entryStartPos + j]);
                   }
              contestants.push(contestant);
            }
            const rankTable =
              outputRankTable(categorySheet, rows, secondaryCategory,
                categoryNames, contestants, padLength);
            msg.channel.send(rankTable)
              .then( (res) => {
                resolve(res);
            });
          }
        });
      });
  });
};

/** getSheet():
 *  returns a sheet based on the shorthand input the user provides.
 * @param {String} category: the category the user inputs.
 * @return {String} sheet
 **/
function getSheet(category) {
  switch (category) {
    case 'overall':
      category = 'GL 4* Overall rankings';
      break;
    case 'no-csb':
      category = 'GL 4* No CSB rankings';
      break;
    case 'cod':
      category = 'GL CoD Speedrun rankings';
      break;
    case 'magicite':
      category = '4* Magicite';
      break;
    default:
      category = titlecase.toLaxTitleCase(category);
    }
  return category;
}
 /**
 * Finds a value within a given range.
 * @see https://stackoverflow.com/questions/10807936/how-do-i-search-google-spreadsheets/10823543#10823543
 * @param {String} value The value to find.
 * @param {String} data The range to search in using Google's A1 format.
 * @return {Object} A range pointing to the first cell containing the value,
 *     or null if not found.
 */
function find(value, data) {
  for (let i = 0; i < data.length; i++) {
    for (let j = 0; j < data[i].length; j++) {
      if (data[i][j] == value) {
        const columnName = columnToName(j + 1);
        return {row: i + 1, column: columnName, columnNum: j + 1};
      }
    }
  }
  return null;
}
/**
 * Returns the Google Spreadsheet column name equivalent of a number.
 * @param {Integer} columnNumber The column number to look for
 * @return {String} columnName
 */
function columnToName(columnNumber) {
  let columnName = '';
  let modulo;
  const alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  while (columnNumber > 0) {
    modulo = (columnNumber - 1) % 26;
    columnName = alpha.charAt(modulo) + columnName;
    columnNumber = Math.floor((columnNumber - modulo)/26);
  }
  return columnName;
}

/**
  * Formats a ranking table for output.
  * @param {String} category
  * @param {Integer} rows
  * @param {String} secondaryCategory
  * @param {Array} categoryNames
  * @param {Array} contestants
  * @param {Integer} namePadLength
  * @return {String} table
  */
function outputRankTable(category, rows, secondaryCategory,
  categoryNames, contestants, namePadLength) {
  let table = '';
  table += outputTitle(category, rows, secondaryCategory);
  table += outputCategoryHeader(categoryNames, namePadLength);
  table += outputContestants(contestants, namePadLength);
  table = util.format('```%s```', table);
  return table;
}
/**
  * Outputs the title of the table.
  * @param {String} category
  * @param {Integer} rows
  * @param {String} secondaryCategory
  * @return {String} title
  */
function outputTitle(category, rows, secondaryCategory) {
  console.log(category, rows, secondaryCategory);
  let title = '';
  if (category === 'GL 4* Overall rankings' ||
      category === 'GL 4* No CSB rankings') {
        title = `Top ${rows} ${category}\n`;
  } else {
    title = `Top ${rows} ${category} (${secondaryCategory})\n`;
  }
  return title;
}
/**
  * Outputs category headers for table.
  * @param {Array} categoryNames
  * @param {Integer} namePadLength
  * @return {String} categoryHeader
  */
function outputCategoryHeader(categoryNames, namePadLength) {
  let categoryHeader = '';
  categoryHeader += 'Rank | ';
  categoryHeader += pad(categoryNames[0], namePadLength);
  categoryHeader += ' | ';
  for (let i = 1; i < categoryNames.length; i++) {
    categoryHeader += pad(categoryNames[i], 9);
    categoryHeader += ' | ';
  }
  const repeatLength = categoryHeader.length - 1;
  categoryHeader += '\n';
  // create a series of dashes to indicate header
  categoryHeader += '-'.repeat(repeatLength);
  categoryHeader += '\n';
  return categoryHeader;
}
/**
  * Outputs contestants.
  * @param {Array} contestants
  * @param {Integer} namePadLength
  * @return {String} formattedContestants
  */
function outputContestants(contestants, namePadLength) {
  let formattedContestants = '';
  for (let i = 0; i < contestants.length; i++) {
    console.log(contestants[i]);
    // Rank output
    formattedContestants += pad(4, i + 1);
    formattedContestants += ' | ';
    // Name output
    formattedContestants += pad(contestants[i][0], namePadLength);
    formattedContestants += ' | ';
    // Any other attribute output
    for (let j = 1; j < contestants[i].length; j++) {
      formattedContestants += pad(contestants[i][j], 9);
      formattedContestants += ' | ';
    }
    formattedContestants += '\n';
  }
  return formattedContestants;
}
<commit_msg>Console logging cleanup.<commit_after>const {google} = require('googleapis');
const util = require('util');
const fs = require('fs');
const OAuth2Client = google.auth.OAuth2;
fs.readFileAsync = util.promisify(fs.readFile);
const titlecase = require('titlecase');
const escapeStringRegexp = require('escape-string-regexp');
const pad = require('pad');

const SPREADSHEET_ID = '11gTjAkpm4D3uoxnYCN7ZfbiVnKyi7tmm9Vp9HvTkGpw';
const TOKEN_PATH = 'secrets/credentials.json';
const SECRETS_PATH = 'secrets/client_secret.json';

/** authorize():
 * Authorizes Google credentials.
 * @return {google.auth.OAuth2} oAuth2Client
 **/
async function authorize() {
  let secrets;
  let token;
  try {
    secrets = await fs.readFileAsync(SECRETS_PATH);
    token = await fs.readFileAsync(TOKEN_PATH);
    const credentials = JSON.parse(secrets);
    const {client_secret, client_id, redirect_uris} = credentials.installed;
    const oAuth2Client = new OAuth2Client(
      client_id, client_secret, redirect_uris[0]);
    oAuth2Client.setCredentials(JSON.parse(token));
    return oAuth2Client;
  } catch (err) {
    console.log('Unable to open SECRETS_PATH or TOKEN_PATH', err);
  }
}

exports.speedrun = function lookupSpeedrun(
  msg, rows, category, secondaryCategory) {
  console.log(util.format('.top caller: %s#%s for top %s %s (%s)',
    msg.author.username, msg.author.discriminator,
    rows, category, secondaryCategory));
  return new Promise((resolve, reject) => {
    const sheets = google.sheets({version: 'v4'});
    authorize()
      .then((oAuth2Client) => {
        category = escapeStringRegexp(category);
        category = category.toLowerCase();
        const categorySheet = getSheet(category);
        secondaryCategory = titlecase.toLaxTitleCase(secondaryCategory);
        const request = {
          spreadsheetId: SPREADSHEET_ID,
          range: categorySheet,
          auth: oAuth2Client,
        };
        sheets.spreadsheets.values.get(request, (err, {data}) => {
          if (err) {
            if (err.code === 400) {
              console.log(err.message);
              msg.channel.send(`Invalid speedrun category "${category}".`)
                .then((res) => {
                  resolve(res);
                  return;
                });
            } else {
              console.log(err);
              msg.channel.send(
                'The bot user has not set up valid Google API credentials yet.')
              .then((res) => {
                resolve(res);
                return;
              });
            }
          } else {
            // Find where the secondaryCategory is on the sheet in question
            const categoryRange = find(secondaryCategory, data.values);
            let categoryNames = [];
            // Get the row where the categories lie.
            const categoryRow = data.values[categoryRange.row + 1];
            // Start from the place where secondaryCategory was found,
            // Add 2 to categoryRow because the category rows are always going
            // to be 2 below the secondary category,
            // push secondaryCategory into cagetoryNames,
            // increment the place where secondaryCategory was found by one,
            // and keep going until we either hit a '' or undefined.
            for
            (let i = categoryRange.columnNum - 1; i < categoryRow.length; i++) {
              if (categoryRow[i] === undefined ||
                  categoryRow[i] === '' ||
                  categoryRow[i] === null) {
                break;
              }
              categoryNames.push(categoryRow[i]);
            }
            // Now that we know the category names, we need to get the values of
            // the top {rows} contestants.
            // The contestants will always be in the row right below the
            // category
            let contestants = [];
            let padLength = 0;
            for (let i = 0; i < rows; i++) {
              // categoryRange.row + 2 will give us the starting row of
              // contestants.
              let row = categoryRange.row + 2 + i;
              let contestant = [];
              // entryStartPos gives us the starting cell
              // with the contestant name.
              const entryStartPos = categoryRange.columnNum - 1;
              for (let j = 0;
                   j < categoryNames.length; j++) {
                     if (data.values[row][entryStartPos + j].length
                         > padLength) {
                       // Pad the contestant name table with the longest
                       // contestant name (and add an extra 1 for spacing.)
                       padLength = data.values[row][entryStartPos + j]
                       .length + 1;
                     }
                     contestant.push(
                       data.values[row][entryStartPos + j]);
                   }
              contestants.push(contestant);
            }
            const rankTable =
              outputRankTable(categorySheet, rows, secondaryCategory,
                categoryNames, contestants, padLength);
            msg.channel.send(rankTable)
              .then( (res) => {
                resolve(res);
            });
          }
        });
      });
  });
};

/** getSheet():
 *  returns a sheet based on the shorthand input the user provides.
 * @param {String} category: the category the user inputs.
 * @return {String} sheet
 **/
function getSheet(category) {
  switch (category) {
    case 'overall':
      category = 'GL 4* Overall rankings';
      break;
    case 'no-csb':
      category = 'GL 4* No CSB rankings';
      break;
    case 'cod':
      category = 'GL CoD Speedrun rankings';
      break;
    case 'magicite':
      category = '4* Magicite';
      break;
    default:
      category = titlecase.toLaxTitleCase(category);
    }
  return category;
}
 /**
 * Finds a value within a given range.
 * @see https://stackoverflow.com/questions/10807936/how-do-i-search-google-spreadsheets/10823543#10823543
 * @param {String} value The value to find.
 * @param {String} data The range to search in using Google's A1 format.
 * @return {Object} A range pointing to the first cell containing the value,
 *     or null if not found.
 */
function find(value, data) {
  for (let i = 0; i < data.length; i++) {
    for (let j = 0; j < data[i].length; j++) {
      if (data[i][j] == value) {
        const columnName = columnToName(j + 1);
        return {row: i + 1, column: columnName, columnNum: j + 1};
      }
    }
  }
  return null;
}
/**
 * Returns the Google Spreadsheet column name equivalent of a number.
 * @param {Integer} columnNumber The column number to look for
 * @return {String} columnName
 */
function columnToName(columnNumber) {
  let columnName = '';
  let modulo;
  const alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  while (columnNumber > 0) {
    modulo = (columnNumber - 1) % 26;
    columnName = alpha.charAt(modulo) + columnName;
    columnNumber = Math.floor((columnNumber - modulo)/26);
  }
  return columnName;
}

/**
  * Formats a ranking table for output.
  * @param {String} category
  * @param {Integer} rows
  * @param {String} secondaryCategory
  * @param {Array} categoryNames
  * @param {Array} contestants
  * @param {Integer} namePadLength
  * @return {String} table
  */
function outputRankTable(category, rows, secondaryCategory,
  categoryNames, contestants, namePadLength) {
  let table = '';
  table += outputTitle(category, rows, secondaryCategory);
  table += outputCategoryHeader(categoryNames, namePadLength);
  table += outputContestants(contestants, namePadLength);
  table = util.format('```%s```', table);
  return table;
}
/**
  * Outputs the title of the table.
  * @param {String} category
  * @param {Integer} rows
  * @param {String} secondaryCategory
  * @return {String} title
  */
function outputTitle(category, rows, secondaryCategory) {
  let title = '';
  if (category === 'GL 4* Overall rankings' ||
      category === 'GL 4* No CSB rankings') {
        title = `Top ${rows} ${category}\n`;
  } else {
    title = `Top ${rows} ${category} (${secondaryCategory})\n`;
  }
  return title;
}
/**
  * Outputs category headers for table.
  * @param {Array} categoryNames
  * @param {Integer} namePadLength
  * @return {String} categoryHeader
  */
function outputCategoryHeader(categoryNames, namePadLength) {
  let categoryHeader = '';
  categoryHeader += 'Rank | ';
  categoryHeader += pad(categoryNames[0], namePadLength);
  categoryHeader += ' | ';
  for (let i = 1; i < categoryNames.length; i++) {
    categoryHeader += pad(categoryNames[i], 9);
    categoryHeader += ' | ';
  }
  const repeatLength = categoryHeader.length - 1;
  categoryHeader += '\n';
  // create a series of dashes to indicate header
  categoryHeader += '-'.repeat(repeatLength);
  categoryHeader += '\n';
  return categoryHeader;
}
/**
  * Outputs contestants.
  * @param {Array} contestants
  * @param {Integer} namePadLength
  * @return {String} formattedContestants
  */
function outputContestants(contestants, namePadLength) {
  let formattedContestants = '';
  for (let i = 0; i < contestants.length; i++) {
    // Rank output
    formattedContestants += pad(4, i + 1);
    formattedContestants += ' | ';
    // Name output
    formattedContestants += pad(contestants[i][0], namePadLength);
    formattedContestants += ' | ';
    // Any other attribute output
    for (let j = 1; j < contestants[i].length; j++) {
      formattedContestants += pad(contestants[i][j], 9);
      formattedContestants += ' | ';
    }
    formattedContestants += '\n';
  }
  return formattedContestants;
}
<|endoftext|>


<commit_before>// Page specific js code

/*global
    msos: false,
    jQuery: false
*/

msos.provide("demo.siteos.modules");
msos.require("msos.page");

// Load MSOS modules (to the extreme!)
msos.require("bootstrap.affix");
msos.require("bootstrap.alert");
msos.require("bootstrap.blockquote");
msos.require("bootstrap.button");
msos.require("bootstrap.carousel");
msos.require("bootstrap.collapse");
msos.require("bootstrap.dropdown");
msos.require("bootstrap.dropdownV3");
msos.require("bootstrap.form");
msos.require("bootstrap.inputgroup");
msos.require("bootstrap.jumbotron");
msos.require("bootstrap.labels");
msos.require("bootstrap.listgroup");
msos.require("bootstrap.media");
msos.require("bootstrap.modal");
msos.require("bootstrap.navbar");
msos.require("bootstrap.navigation");
msos.require("bootstrap.pager");
msos.require("bootstrap.pagination");
msos.require("bootstrap.panel");
msos.require("bootstrap.popover");
msos.require("bootstrap.print");
msos.require("bootstrap.progress");
msos.require("bootstrap.responsive");
msos.require("bootstrap.scrollspy");
msos.require("bootstrap.tab");
msos.require("bootstrap.table");
msos.require("bootstrap.thumbnail");
msos.require("bootstrap.tooltip");
msos.require("bootstrap.transition");
msos.require("bootstrap.typeahead");
msos.require("bootstrap.visible");
msos.require("jquery.effects.animo");
msos.require("jquery.effects.blind");
msos.require("jquery.effects.bounce");
msos.require("jquery.effects.clip");
msos.require("jquery.effects.drop");
msos.require("jquery.effects.explode");
msos.require("jquery.effects.fade");
msos.require("jquery.effects.fold");
msos.require("jquery.effects.highlight");
msos.require("jquery.effects.pulsate");
msos.require("jquery.effects.scale");
msos.require("jquery.effects.shake");
msos.require("jquery.effects.slide");
msos.require("jquery.effects.textillate");
msos.require("jquery.effects.transfer");
msos.require("jquery.tools.actual");
msos.require("jquery.tools.attributes");
msos.require("jquery.tools.autocomplete");
msos.require("jquery.tools.cookie");
msos.require("jquery.tools.draggabilly");
msos.require("jquery.tools.equalize");
msos.require("jquery.tools.flogin");
msos.require("jquery.tools.flowtype");
msos.require("jquery.tools.formobject");
msos.require("jquery.tools.imagesloaded");
msos.require("jquery.tools.lettering");
msos.require("jquery.tools.mousehold");
msos.require("jquery.tools.mousepress");
msos.require("jquery.tools.mousewheel");
msos.require("jquery.tools.packery");
msos.require("jquery.tools.panzoom");
msos.require("jquery.tools.payment");
msos.require("jquery.tools.picture");
msos.require("jquery.tools.range");
msos.require("jquery.tools.slidepanel");
msos.require("jquery.tools.timeago");
msos.require("jquery.tools.validate");
msos.require("jquery.tools.validation");
msos.require("jquery.tools.xml2json");
msos.require("jquery.touch.hammer");
msos.require("jquery.touch.scrollbar");
msos.require("jquery.touch.sly");
msos.require("jquery.touch.toe");
msos.require("jquery.ui.accordion");
msos.require("jquery.ui.addplaceholder");
msos.require("jquery.ui.autocomplete");
msos.require("jquery.ui.button");
msos.require("jquery.ui.datepicker");
msos.require("jquery.ui.datetimepicker");
msos.require("jquery.ui.dialog");
msos.require("jquery.ui.droppable");
msos.require("jquery.ui.menu");
msos.require("jquery.ui.placeholder");
msos.require("jquery.ui.progressbar");
msos.require("jquery.ui.resizable");
msos.require("jquery.ui.selectable");
msos.require("jquery.ui.slider");
msos.require("jquery.ui.sortable");
msos.require("jquery.ui.spinner");
msos.require("jquery.ui.tabs");
msos.require("jquery.ui.timepicker");
msos.require("jquery.ui.tooltip");
msos.require("jquery.ui.touch");
msos.require("msos.ajax");
msos.require("msos.auto");
msos.require("msos.base64");
msos.require("msos.browser");
msos.require("msos.calendar");
msos.require("msos.calendar.config");
msos.require("msos.colortool");
msos.require("msos.colortool.calc");
msos.require("msos.common");
msos.require("msos.countrystate");
msos.require("msos.d8");
msos.require("msos.date");
msos.require("msos.date.locale");
msos.require("msos.debug");
msos.require("msos.debugform");
msos.require("msos.detection");
msos.require("msos.detect.insert");
msos.require("msos.dhtml");
msos.require("msos.diacritic");
msos.require("msos.dot");
msos.require("msos.fitimgs");
msos.require("msos.fitmedia");
msos.require("msos.form2js");
msos.require("msos.geo");
msos.require("msos.geolocation");
msos.require("msos.google.ad");
// msos.require("msos.google.cart");
msos.require("msos.google.maps");
msos.require("msos.html5.color");
msos.require("msos.html5.date");
msos.require("msos.html5.es5.sham");
msos.require("msos.html5.es5.shim");
msos.require("msos.html5.geolocation");
msos.require("msos.html5.matchmedia");
msos.require("msos.html5.number");
msos.require("msos.html5.range");
msos.require("msos.html5.sql");
msos.require("msos.i18n");
msos.require("msos.iframe");
msos.require("msos.ify");
msos.require("msos.infinitedrag");
msos.require("msos.input.select");
msos.require("msos.input.text");
msos.require("msos.intl");
msos.require("msos.jgmap");
msos.require("msos.js2form");
msos.require("msos.jscroll");
msos.require("msos.json.check");
msos.require("msos.keyboard");
msos.require("msos.latlon");
msos.require("msos.maptile");
msos.require("msos.mbp.ios");
msos.require("msos.md5");
msos.require("msos.mobile");
msos.require("msos.numberctrl");
msos.require("msos.onerror");
msos.require("msos.overflowscroll");
msos.require("msos.picture");
msos.require("msos.popdiv");
msos.require("msos.popwin");
msos.require("msos.popwindesktop");
msos.require("msos.position");
msos.require("msos.prism");
msos.require("msos.pyromane");
msos.require("msos.sdmenu");
msos.require("msos.selection");
msos.require("msos.site.detect");
msos.require("msos.size");
msos.require("msos.social.facebook");
msos.require("msos.social.googleplus");
msos.require("msos.social.twitter");
msos.require("msos.stickup");
msos.require("msos.style");
msos.require("msos.syntaxhighlighter");
msos.require("msos.tab");
msos.require("msos.table");
msos.require("msos.tools.kbdpos");
msos.require("msos.touch.tap");
msos.require("msos.translate");
msos.require("msos.visualevent");
msos.require("msos.xml.prettify");
msos.require("msos.zoomify");

msos.onload_func_done.push(
    function () {
        "use strict";

        msos.console.info('Content: modules.html loaded!');

        var module_loaded = function() {
            var html_array = [],
                output_array = [],
                output1 = '',
                output2 = '',
                mod_id = '',
                module_name = '',
                module_obj,
                chunks = function (array, size) {
                    var results = [];
                    while (array.length) {
                        results.push(array.splice(0, size));
                    }
                    return results;
                };

            jQuery('#results1').empty();
            jQuery('#results2').empty();

            for (mod_id in msos.registered_modules) {

                module_name = mod_id.replace(/_/g, '.');

                module_obj = msos.gen_namespace(module_name);

                if (msos.registered_modules[mod_id]) {
                    html_array.push('
' + module_name + " " + (module_obj.version ? module_obj.version.toString() : '') + "
");
                } else {
                    html_array.push('
' + module_name + "
");
                }
            }

            output_array = chunks(html_array, parseInt(html_array.length / 2, 10));

            output1 = output_array[0].join("\n");
            output2 = output_array[1].join("\n");

            jQuery('#results1').append(output1);
            jQuery('#results2').append(output2);

            jQuery('#status').html('Loading done!');

            // Special case for modules.js & modules.html. We need to reload "base page" and then new content
            msos.page.reset = true;
        };

        setTimeout(module_loaded, 1000);
    }
);<commit_msg>Remove msos.prism require<commit_after>// Page specific js code

/*global
    msos: false,
    jQuery: false
*/

msos.provide("demo.siteos.modules");
msos.require("msos.page");

// Load MSOS modules (to the extreme!)
msos.require("bootstrap.affix");
msos.require("bootstrap.alert");
msos.require("bootstrap.blockquote");
msos.require("bootstrap.button");
msos.require("bootstrap.carousel");
msos.require("bootstrap.collapse");
msos.require("bootstrap.dropdown");
msos.require("bootstrap.dropdownV3");
msos.require("bootstrap.form");
msos.require("bootstrap.inputgroup");
msos.require("bootstrap.jumbotron");
msos.require("bootstrap.labels");
msos.require("bootstrap.listgroup");
msos.require("bootstrap.media");
msos.require("bootstrap.modal");
msos.require("bootstrap.navbar");
msos.require("bootstrap.navigation");
msos.require("bootstrap.pager");
msos.require("bootstrap.pagination");
msos.require("bootstrap.panel");
msos.require("bootstrap.popover");
msos.require("bootstrap.print");
msos.require("bootstrap.progress");
msos.require("bootstrap.responsive");
msos.require("bootstrap.scrollspy");
msos.require("bootstrap.tab");
msos.require("bootstrap.table");
msos.require("bootstrap.thumbnail");
msos.require("bootstrap.tooltip");
msos.require("bootstrap.transition");
msos.require("bootstrap.typeahead");
msos.require("bootstrap.visible");
msos.require("jquery.effects.animo");
msos.require("jquery.effects.blind");
msos.require("jquery.effects.bounce");
msos.require("jquery.effects.clip");
msos.require("jquery.effects.drop");
msos.require("jquery.effects.explode");
msos.require("jquery.effects.fade");
msos.require("jquery.effects.fold");
msos.require("jquery.effects.highlight");
msos.require("jquery.effects.pulsate");
msos.require("jquery.effects.scale");
msos.require("jquery.effects.shake");
msos.require("jquery.effects.slide");
msos.require("jquery.effects.textillate");
msos.require("jquery.effects.transfer");
msos.require("jquery.tools.actual");
msos.require("jquery.tools.attributes");
msos.require("jquery.tools.autocomplete");
msos.require("jquery.tools.cookie");
msos.require("jquery.tools.draggabilly");
msos.require("jquery.tools.equalize");
msos.require("jquery.tools.flogin");
msos.require("jquery.tools.flowtype");
msos.require("jquery.tools.formobject");
msos.require("jquery.tools.imagesloaded");
msos.require("jquery.tools.lettering");
msos.require("jquery.tools.mousehold");
msos.require("jquery.tools.mousepress");
msos.require("jquery.tools.mousewheel");
msos.require("jquery.tools.packery");
msos.require("jquery.tools.panzoom");
msos.require("jquery.tools.payment");
msos.require("jquery.tools.picture");
msos.require("jquery.tools.range");
msos.require("jquery.tools.slidepanel");
msos.require("jquery.tools.timeago");
msos.require("jquery.tools.validate");
msos.require("jquery.tools.validation");
msos.require("jquery.tools.xml2json");
msos.require("jquery.touch.hammer");
msos.require("jquery.touch.scrollbar");
msos.require("jquery.touch.sly");
msos.require("jquery.touch.toe");
msos.require("jquery.ui.accordion");
msos.require("jquery.ui.addplaceholder");
msos.require("jquery.ui.autocomplete");
msos.require("jquery.ui.button");
msos.require("jquery.ui.datepicker");
msos.require("jquery.ui.datetimepicker");
msos.require("jquery.ui.dialog");
msos.require("jquery.ui.droppable");
msos.require("jquery.ui.menu");
msos.require("jquery.ui.placeholder");
msos.require("jquery.ui.progressbar");
msos.require("jquery.ui.resizable");
msos.require("jquery.ui.selectable");
msos.require("jquery.ui.slider");
msos.require("jquery.ui.sortable");
msos.require("jquery.ui.spinner");
msos.require("jquery.ui.tabs");
msos.require("jquery.ui.timepicker");
msos.require("jquery.ui.tooltip");
msos.require("jquery.ui.touch");
msos.require("msos.ajax");
msos.require("msos.auto");
msos.require("msos.base64");
msos.require("msos.browser");
msos.require("msos.calendar");
msos.require("msos.calendar.config");
msos.require("msos.colortool");
msos.require("msos.colortool.calc");
msos.require("msos.common");
msos.require("msos.countrystate");
msos.require("msos.d8");
msos.require("msos.date");
msos.require("msos.date.locale");
msos.require("msos.debug");
msos.require("msos.debugform");
msos.require("msos.detection");
msos.require("msos.detect.insert");
msos.require("msos.dhtml");
msos.require("msos.diacritic");
msos.require("msos.dot");
msos.require("msos.fitimgs");
msos.require("msos.fitmedia");
msos.require("msos.form2js");
msos.require("msos.geo");
msos.require("msos.geolocation");
msos.require("msos.google.ad");
msos.require("msos.google.maps");
msos.require("msos.html5.color");
msos.require("msos.html5.date");
msos.require("msos.html5.es5.sham");
msos.require("msos.html5.es5.shim");
msos.require("msos.html5.geolocation");
msos.require("msos.html5.matchmedia");
msos.require("msos.html5.number");
msos.require("msos.html5.range");
msos.require("msos.html5.sql");
msos.require("msos.i18n");
msos.require("msos.iframe");
msos.require("msos.ify");
msos.require("msos.infinitedrag");
msos.require("msos.input.select");
msos.require("msos.input.text");
msos.require("msos.intl");
msos.require("msos.jgmap");
msos.require("msos.js2form");
msos.require("msos.jscroll");
msos.require("msos.json.check");
msos.require("msos.keyboard");
msos.require("msos.latlon");
msos.require("msos.maptile");
msos.require("msos.mbp.ios");
msos.require("msos.md5");
msos.require("msos.mobile");
msos.require("msos.numberctrl");
msos.require("msos.onerror");
msos.require("msos.overflowscroll");
msos.require("msos.picture");
msos.require("msos.popdiv");
msos.require("msos.popwin");
msos.require("msos.popwindesktop");
msos.require("msos.position");
msos.require("msos.pyromane");
msos.require("msos.sdmenu");
msos.require("msos.selection");
msos.require("msos.site.detect");
msos.require("msos.size");
msos.require("msos.social.facebook");
msos.require("msos.social.googleplus");
msos.require("msos.social.twitter");
msos.require("msos.stickup");
msos.require("msos.style");
msos.require("msos.syntaxhighlighter");
msos.require("msos.tab");
msos.require("msos.table");
msos.require("msos.tools.kbdpos");
msos.require("msos.touch.tap");
msos.require("msos.translate");
msos.require("msos.visualevent");
msos.require("msos.xml.prettify");
msos.require("msos.zoomify");

msos.onload_func_done.push(
    function () {
        "use strict";

        msos.console.info('Content: modules.html loaded!');

        var module_loaded = function() {
            var html_array = [],
                output_array = [],
                output1 = '',
                output2 = '',
                mod_id = '',
                module_name = '',
                module_obj,
                chunks = function (array, size) {
                    var results = [];
                    while (array.length) {
                        results.push(array.splice(0, size));
                    }
                    return results;
                };

            jQuery('#results1').empty();
            jQuery('#results2').empty();

            for (mod_id in msos.registered_modules) {

                module_name = mod_id.replace(/_/g, '.');

                module_obj = msos.gen_namespace(module_name);

                if (msos.registered_modules[mod_id]) {
                    html_array.push('
' + module_name + " " + (module_obj.version ? module_obj.version.toString() : '') + "
");
                } else {
                    html_array.push('
' + module_name + "
");
                }
            }

            output_array = chunks(html_array, parseInt(html_array.length / 2, 10));

            output1 = output_array[0].join("\n");
            output2 = output_array[1].join("\n");

            jQuery('#results1').append(output1);
            jQuery('#results2').append(output2);

            jQuery('#status').html('Loading done!');

            // Special case for modules.js & modules.html. We need to reload "base page" and then new content
            msos.page.reset = true;
        };

        setTimeout(module_loaded, 1000);
    }
);<|endoftext|>


<commit_before>const async = require('async')
const fs = require('fs')
const BIDS = require('./obj')
const utils = require('../../utils')
const Issue = utils.issues.Issue
const TSV = require('../tsv')
const json = require('../json')
const NIFTI = require('../nii')
const bval = require('../bval')
const bvec = require('../bvec')
const Events = require('../events')
const session = require('../session')
const checkAnyDataPresent = require('../checkAnyDataPresent')
const headerFields = require('../headerFields')
const subSesMismatchTest = require('./subSesMismatchTest')

/**
 * Full Test
 *
 * Takes on an array of files and starts
 * the validation process for a BIDS
 * package.
 */
const fullTest = (fileList, callback) => {
  let self = BIDS

  let jsonContentsDict = {},
    bContentsDict = {},
    events = [],
    stimuli = {
      events: [],
      directory: [],
    },
    niftis = [],
    jsonFiles = [],
    ephys = [],
    headers = [],
    participants = null,
    phenotypeParticipants = [],
    hasSubjectDir = false

  let tsvs = []
  let hasDatasetDescription = false

  let summary = {
    sessions: [],
    subjects: [],
    tasks: [],
    modalities: [],
    totalFiles: Object.keys(fileList).length,
    size: 0,
  }

  //collect file directory statistics
  utils.files.collectDirectoryStatistics(fileList, summary)

  // remove ignored files from list:
  Object.keys(fileList).forEach(function(key) {
    if (fileList[key].ignore) {
      delete fileList[key]
    }
  })

  BIDS.issues = BIDS.issues.concat(subSesMismatchTest(fileList))

  // check for illegal character in task name and acq name
  utils.files.illegalCharacterTest(fileList, BIDS.issues)

  // validate individual files
  async.eachOfLimit(
    fileList,
    200,
    function(file, key, cb) {
      const path = file.relativePath
      const pathParts = path.split('_')
      const suffix = pathParts[pathParts.length - 1]

      // ignore associated data
      if (utils.type.file.isStimuliData(file.relativePath)) {
        stimuli.directory.push(file)
        process.nextTick(cb)
      }

      // validate path naming
      else if (
        !utils.type.isBIDS(
          file.relativePath,
          BIDS.options.bep006,
          BIDS.options.bep010,
        )
      ) {
        self.issues.push(
          new Issue({
            file: file,
            evidence: file.name,
            code: 1,
          }),
        )
        process.nextTick(cb)
      }
      // check modality by data file extension ...
      // and capture data files for later sanity checks (when available)
      else if (utils.type.file.isModality(file.name)) {
        // capture nifties for later validation
        if (file.name.endsWith('.nii') || file.name.endsWith('.nii.gz')) {
          niftis.push(file)
        }
        // collect modality summary
        const modality = suffix.slice(0, suffix.indexOf('.'))
        if (summary.modalities.indexOf(modality) === -1) {
          summary.modalities.push(modality)
        }

        process.nextTick(cb)
      }

      // capture ieeg files for summary
      else if (
        [
          'edf',
          'vhdr',
          'vmrk',
          'dat',
          'cnt',
          'bdf',
          'set',
          'nwb',
          'tdat',
          'tidx',
          'tmet',
        ].includes(file.name.split('.').pop())
      ) {
        ephys.push(file)

        process.nextTick(cb)
      }

      // validate tsv
      else if (file.name && file.name.endsWith('.tsv')) {
        utils.files
          .readFile(file)
          .then(contents => {
            // Push TSV to list for custom column verification after all data dictionaries have been read
            tsvs.push({
              file: file,
              contents: contents,
            })
            if (file.name.endsWith('_events.tsv')) {
              events.push({
                file: file,
                path: file.relativePath,
                contents: contents,
              })
            }
            TSV.TSV(file, contents, fileList, function(
              issues,
              participantList,
              stimFiles,
            ) {
              if (participantList) {
                if (file.name.endsWith('participants.tsv')) {
                  participants = {
                    list: participantList,
                    file: file,
                  }
                } else if (file.relativePath.includes('phenotype/')) {
                  phenotypeParticipants.push({
                    list: participantList,
                    file: file,
                  })
                }
              }
              if (stimFiles.length) {
                // add unique new events to the stimuli.events array
                stimuli.events = [...new Set([...stimuli.events, ...stimFiles])]
              }
              self.issues = self.issues.concat(issues)
              process.nextTick(cb)
            })
          })
          .catch(issue => {
            self.issues.push(issue)
            process.nextTick(cb)
          })
      }

      // validate bvec
      else if (file.name && file.name.endsWith('.bvec')) {
        utils.files
          .readFile(file)
          .then(contents => {
            bContentsDict[file.relativePath] = contents
            bvec(file, contents, function(issues) {
              self.issues = self.issues.concat(issues)
              process.nextTick(cb)
            })
          })
          .catch(issue => {
            self.issues.push(issue)
            process.nextTick(cb)
          })
      }

      // validate bval
      else if (file.name && file.name.endsWith('.bval')) {
        utils.files
          .readFile(file)
          .then(contents => {
            bContentsDict[file.relativePath] = contents
            bval(file, contents, function(issues) {
              self.issues = self.issues.concat(issues)
              process.nextTick(cb)
            })
          })
          .catch(issue => {
            self.issues.push(issue)
            process.nextTick(cb)
          })
      }

      // load json data for validation later
      else if (file.name && file.name.endsWith('.json')) {
        // Verify that the json file has an accompanying data file
        // Need to limit checks to files in sub-*/**/ - Not all data dictionaries are sidecars
        const pathArgs = file.relativePath.split('/')
        const isSidecar =
          pathArgs[1].includes('sub-') && pathArgs.length > 3 ? true : false
        if (isSidecar) {
          // Check for suitable datafile accompanying this sidecar
          const dataFile = utils.bids_files.checkSidecarForDatafiles(
            file,
            fileList,
          )
          if (!dataFile) {
            self.issues.push(
              new Issue({
                code: 90,
                file: file,
              }),
            )
          }
        }
        utils.files
          .readFile(file)
          .then(contents => {
            utils.json.parse(file, contents, function(issues, jsObj) {
              self.issues = self.issues.concat(issues)

              // abort further tests if schema test does not pass
              if (issues.some(issue => issue.severity === 'error')) {
                process.nextTick(cb)
                return
              }

              jsonContentsDict[file.relativePath] = jsObj
              jsonFiles.push(file)
              process.nextTick(cb)
            })
          })
          .catch(issue => {
            self.issues.push(issue)
            process.nextTick(cb)
          })
      } else {
        process.nextTick(cb)
      }

      // check for subject directory presence
      if (path.startsWith('/sub-')) {
        hasSubjectDir = true
      }

      // check for dataset_description.json presence
      if (path === '/dataset_description.json') {
        hasDatasetDescription = true
      }

      // collect sessions & subjects
      if (
        !utils.type.file.isStimuliData(file.relativePath) &&
        utils.type.isBIDS(
          file.relativePath,
          BIDS.options.bep006,
          BIDS.options.bep010,
        )
      ) {
        const pathValues = utils.type.getPathValues(file.relativePath)
        const isEmptyRoom = pathValues.sub && pathValues.sub == 'emptyroom'

        if (
          pathValues.sub &&
          summary.subjects.indexOf(pathValues.sub) === -1 &&
          !isEmptyRoom
        ) {
          summary.subjects.push(pathValues.sub)
        }
        if (
          pathValues.ses &&
          summary.sessions.indexOf(pathValues.ses) === -1 &&
          !isEmptyRoom
        ) {
          summary.sessions.push(pathValues.ses)
        }
      }
    },
    function() {
      // check json data
      async.eachOfLimit(
        jsonFiles,
        200,
        function(file, key, cb) {
          json(file, jsonContentsDict, function(issues, jsObj) {
            self.issues = self.issues.concat(issues)
            // collect task summary
            if (file.name.indexOf('task') > -1) {
              const task = jsObj ? jsObj.TaskName : null
              if (task && summary.tasks.indexOf(task) === -1) {
                summary.tasks.push(task)
              }
            }
            process.nextTick(cb)
          })
        },
        function() {
          // check if same file with .nii and .nii.gz extensions is present
          const niftiCounts = niftis
            .map(function(val) {
              return { count: 1, val: val.name.split('.')[0] }
            })
            .reduce(function(a, b) {
              a[b.val] = (a[b.val] || 0) + b.count
              return a
            }, {})

          const duplicates = Object.keys(niftiCounts).filter(function(a) {
            return niftiCounts[a] > 1
          })
          if (duplicates.length !== 0) {
            for (let key in duplicates) {
              const duplicateFiles = niftis.filter(function(a) {
                return a.name.split('.')[0] === duplicates[key]
              })
              for (let file in duplicateFiles) {
                self.issues.push(
                  new Issue({
                    code: 74,
                    file: duplicateFiles[file],
                  }),
                )
              }
            }
          }
          // Check for _fieldmap nifti exists without corresponding _magnitude
          // TODO: refactor into external function call
          const niftiNames = niftis.map(nifti => nifti.name)
          const fieldmaps = niftiNames.filter(
            nifti => nifti.indexOf('_fieldmap') > -1,
          )
          const magnitudes = niftiNames.filter(
            nifti => nifti.indexOf('_magnitude') > -1,
          )
          fieldmaps.map(nifti => {
            const associatedMagnitudeFile = nifti.replace(
              'fieldmap',
              'magnitude',
            )
            if (magnitudes.indexOf(associatedMagnitudeFile) === -1) {
              self.issues.push(
                new Issue({
                  code: 91,
                  file: niftis.find(niftiFile => niftiFile.name == nifti),
                }),
              )
            }
          })
          // End fieldmap check

          // check to see if each phasediff is associated with magnitude1
          const phaseDiffNiftis = niftiNames.filter(
            nifti => nifti.indexOf('phasediff') > -1,
          )
          const magnitude1Niftis = niftiNames.filter(
            nifti => nifti.indexOf('magnitude1') > -1,
          )
          phaseDiffNiftis.map(nifti => {
            const associatedMagnitudeFile = nifti.replace(
              'phasediff',
              'magnitude1',
            )
            if (magnitude1Niftis.indexOf(associatedMagnitudeFile) === -1) {
              self.issues.push(
                new Issue({
                  code: 92,
                  file: niftis.find(niftiFile => niftiFile.name == nifti),
                }),
              )
            }
          })

          async.eachOfLimit(
            niftis,
            200,
            function(file, key, cb) {
              if (self.options.ignoreNiftiHeaders) {
                NIFTI(
                  null,
                  file,
                  jsonContentsDict,
                  bContentsDict,
                  fileList,
                  events,
                  function(issues) {
                    self.issues = self.issues.concat(issues)
                    process.nextTick(cb)
                  },
                )
              } else {
                utils.files.readNiftiHeader(file, function(header) {
                  // check if header could be read
                  if (header && header.hasOwnProperty('error')) {
                    self.issues.push(header.error)
                    process.nextTick(cb)
                  } else {
                    headers.push([file, header])
                    NIFTI(
                      header,
                      file,
                      jsonContentsDict,
                      bContentsDict,
                      fileList,
                      events,
                      function(issues) {
                        self.issues = self.issues.concat(issues)
                        process.nextTick(cb)
                      },
                    )
                  }
                })
              }
            },
            function() {
              if (!hasSubjectDir) {
                self.issues.push(new Issue({ code: 45 }))
              }
              if (!hasDatasetDescription) {
                self.issues.push(new Issue({ code: 57 }))
              }
              // check if participants file match found subjects
              if (participants) {
                const participantsFromFile = participants.list.sort()
                const participantsFromFolders = summary.subjects.sort()
                if (
                  !utils.array.equals(
                    participantsFromFolders,
                    participantsFromFile,
                    true,
                  )
                ) {
                  self.issues.push(
                    new Issue({
                      code: 49,
                      evidence:
                        'participants.tsv: ' +
                        participantsFromFile.join(', ') +
                        ' folder structure: ' +
                        participantsFromFolders.join(', '),
                      file: participants.file,
                    }),
                  )
                }
              }

              // check if dataset contains T1w
              if (summary.modalities.indexOf('T1w') < 0) {
                self.issues.push(
                  new Issue({
                    code: 53,
                  }),
                )
              }

              //check for equal number of participants from ./phenotype/*.tsv and participants in dataset
              TSV.checkphenotype(phenotypeParticipants, summary, self.issues)

              // validate nii header fields
              self.issues = self.issues.concat(headerFields(headers))

              // Events validation
              Events.validateEvents(
                events,
                stimuli,
                headers,
                jsonContentsDict,
                self.issues,
              )

              // validate custom fields in all TSVs and add any issues to the list
              self.issues = self.issues.concat(
                TSV.validateTsvColumns(tsvs, jsonContentsDict),
              )

              // validation session files
              self.issues = self.issues.concat(session(fileList))

              self.issues = self.issues.concat(
                checkAnyDataPresent(fileList, summary.subjects),
              )
              summary.modalities = utils.modalities.group(summary.modalities)
              const issues = utils.issues.format(
                self.issues,
                summary,
                self.options,
              )
              callback(issues, summary)
            },
          )
        },
      )
    },
  )
}

module.exports = fullTest
<commit_msg>Closes #544<commit_after>const async = require('async')
const fs = require('fs')
const BIDS = require('./obj')
const utils = require('../../utils')
const Issue = utils.issues.Issue
const TSV = require('../tsv')
const json = require('../json')
const NIFTI = require('../nii')
const bval = require('../bval')
const bvec = require('../bvec')
const Events = require('../events')
const session = require('../session')
const checkAnyDataPresent = require('../checkAnyDataPresent')
const headerFields = require('../headerFields')
const subSesMismatchTest = require('./subSesMismatchTest')

/**
 * Full Test
 *
 * Takes on an array of files and starts
 * the validation process for a BIDS
 * package.
 */
const fullTest = (fileList, callback) => {
  let self = BIDS

  let jsonContentsDict = {},
    bContentsDict = {},
    events = [],
    stimuli = {
      events: [],
      directory: [],
    },
    niftis = [],
    jsonFiles = [],
    ephys = [],
    headers = [],
    participants = null,
    phenotypeParticipants = [],
    hasSubjectDir = false

  let tsvs = []
  let hasDatasetDescription = false

  let summary = {
    sessions: [],
    subjects: [],
    tasks: [],
    modalities: [],
    totalFiles: Object.keys(fileList).length,
    size: 0,
  }

  //collect file directory statistics
  utils.files.collectDirectoryStatistics(fileList, summary)

  // remove ignored files from list:
  Object.keys(fileList).forEach(function(key) {
    if (fileList[key].ignore) {
      delete fileList[key]
    }
  })

  BIDS.issues = BIDS.issues.concat(subSesMismatchTest(fileList))

  // check for illegal character in task name and acq name
  utils.files.illegalCharacterTest(fileList, BIDS.issues)

  // validate individual files
  async.eachOfLimit(
    fileList,
    200,
    function(file, key, cb) {
      const path = file.relativePath
      const pathParts = path.split('_')
      const suffix = pathParts[pathParts.length - 1]

      // ignore associated data
      if (utils.type.file.isStimuliData(file.relativePath)) {
        stimuli.directory.push(file)
        process.nextTick(cb)
      }

      // validate path naming
      else if (
        !utils.type.isBIDS(
          file.relativePath,
          BIDS.options.bep006,
          BIDS.options.bep010,
        )
      ) {
        self.issues.push(
          new Issue({
            file: file,
            evidence: file.name,
            code: 1,
          }),
        )
        process.nextTick(cb)
      }
      // check modality by data file extension ...
      // and capture data files for later sanity checks (when available)
      else if (utils.type.file.isModality(file.name)) {
        // capture nifties for later validation
        if (file.name.endsWith('.nii') || file.name.endsWith('.nii.gz')) {
          niftis.push(file)
        }
        // collect modality summary
        const modality = suffix.slice(0, suffix.indexOf('.'))
        if (summary.modalities.indexOf(modality) === -1) {
          summary.modalities.push(modality)
        }

        process.nextTick(cb)
      }

      // capture ieeg files for summary
      else if (
        [
          'edf',
          'vhdr',
          'vmrk',
          'dat',
          'cnt',
          'bdf',
          'set',
          'nwb',
          'tdat',
          'tidx',
          'tmet',
        ].includes(file.name.split('.').pop())
      ) {
        ephys.push(file)

        process.nextTick(cb)
      }

      // validate tsv
      else if (file.name && file.name.endsWith('.tsv')) {
        utils.files
          .readFile(file)
          .then(contents => {
            // Push TSV to list for custom column verification after all data dictionaries have been read
            tsvs.push({
              file: file,
              contents: contents,
            })
            if (file.name.endsWith('_events.tsv')) {
              events.push({
                file: file,
                path: file.relativePath,
                contents: contents,
              })
            }
            TSV.TSV(file, contents, fileList, function(
              issues,
              participantList,
              stimFiles,
            ) {
              if (participantList) {
                if (file.name.endsWith('participants.tsv')) {
                  participants = {
                    list: participantList,
                    file: file,
                  }
                } else if (file.relativePath.includes('phenotype/')) {
                  phenotypeParticipants.push({
                    list: participantList,
                    file: file,
                  })
                }
              }
              if (stimFiles && stimFiles.length) {
                // add unique new events to the stimuli.events array
                stimuli.events = [...new Set([...stimuli.events, ...stimFiles])]
              }
              self.issues = self.issues.concat(issues)
              process.nextTick(cb)
            })
          })
          .catch(issue => {
            self.issues.push(issue)
            process.nextTick(cb)
          })
      }

      // validate bvec
      else if (file.name && file.name.endsWith('.bvec')) {
        utils.files
          .readFile(file)
          .then(contents => {
            bContentsDict[file.relativePath] = contents
            bvec(file, contents, function(issues) {
              self.issues = self.issues.concat(issues)
              process.nextTick(cb)
            })
          })
          .catch(issue => {
            self.issues.push(issue)
            process.nextTick(cb)
          })
      }

      // validate bval
      else if (file.name && file.name.endsWith('.bval')) {
        utils.files
          .readFile(file)
          .then(contents => {
            bContentsDict[file.relativePath] = contents
            bval(file, contents, function(issues) {
              self.issues = self.issues.concat(issues)
              process.nextTick(cb)
            })
          })
          .catch(issue => {
            self.issues.push(issue)
            process.nextTick(cb)
          })
      }

      // load json data for validation later
      else if (file.name && file.name.endsWith('.json')) {
        // Verify that the json file has an accompanying data file
        // Need to limit checks to files in sub-*/**/ - Not all data dictionaries are sidecars
        const pathArgs = file.relativePath.split('/')
        const isSidecar =
          pathArgs[1].includes('sub-') && pathArgs.length > 3 ? true : false
        if (isSidecar) {
          // Check for suitable datafile accompanying this sidecar
          const dataFile = utils.bids_files.checkSidecarForDatafiles(
            file,
            fileList,
          )
          if (!dataFile) {
            self.issues.push(
              new Issue({
                code: 90,
                file: file,
              }),
            )
          }
        }
        utils.files
          .readFile(file)
          .then(contents => {
            utils.json.parse(file, contents, function(issues, jsObj) {
              self.issues = self.issues.concat(issues)

              // abort further tests if schema test does not pass
              if (issues.some(issue => issue.severity === 'error')) {
                process.nextTick(cb)
                return
              }

              jsonContentsDict[file.relativePath] = jsObj
              jsonFiles.push(file)
              process.nextTick(cb)
            })
          })
          .catch(issue => {
            self.issues.push(issue)
            process.nextTick(cb)
          })
      } else {
        process.nextTick(cb)
      }

      // check for subject directory presence
      if (path.startsWith('/sub-')) {
        hasSubjectDir = true
      }

      // check for dataset_description.json presence
      if (path === '/dataset_description.json') {
        hasDatasetDescription = true
      }

      // collect sessions & subjects
      if (
        !utils.type.file.isStimuliData(file.relativePath) &&
        utils.type.isBIDS(
          file.relativePath,
          BIDS.options.bep006,
          BIDS.options.bep010,
        )
      ) {
        const pathValues = utils.type.getPathValues(file.relativePath)
        const isEmptyRoom = pathValues.sub && pathValues.sub == 'emptyroom'

        if (
          pathValues.sub &&
          summary.subjects.indexOf(pathValues.sub) === -1 &&
          !isEmptyRoom
        ) {
          summary.subjects.push(pathValues.sub)
        }
        if (
          pathValues.ses &&
          summary.sessions.indexOf(pathValues.ses) === -1 &&
          !isEmptyRoom
        ) {
          summary.sessions.push(pathValues.ses)
        }
      }
    },
    function() {
      // check json data
      async.eachOfLimit(
        jsonFiles,
        200,
        function(file, key, cb) {
          json(file, jsonContentsDict, function(issues, jsObj) {
            self.issues = self.issues.concat(issues)
            // collect task summary
            if (file.name.indexOf('task') > -1) {
              const task = jsObj ? jsObj.TaskName : null
              if (task && summary.tasks.indexOf(task) === -1) {
                summary.tasks.push(task)
              }
            }
            process.nextTick(cb)
          })
        },
        function() {
          // check if same file with .nii and .nii.gz extensions is present
          const niftiCounts = niftis
            .map(function(val) {
              return { count: 1, val: val.name.split('.')[0] }
            })
            .reduce(function(a, b) {
              a[b.val] = (a[b.val] || 0) + b.count
              return a
            }, {})

          const duplicates = Object.keys(niftiCounts).filter(function(a) {
            return niftiCounts[a] > 1
          })
          if (duplicates.length !== 0) {
            for (let key in duplicates) {
              const duplicateFiles = niftis.filter(function(a) {
                return a.name.split('.')[0] === duplicates[key]
              })
              for (let file in duplicateFiles) {
                self.issues.push(
                  new Issue({
                    code: 74,
                    file: duplicateFiles[file],
                  }),
                )
              }
            }
          }
          // Check for _fieldmap nifti exists without corresponding _magnitude
          // TODO: refactor into external function call
          const niftiNames = niftis.map(nifti => nifti.name)
          const fieldmaps = niftiNames.filter(
            nifti => nifti.indexOf('_fieldmap') > -1,
          )
          const magnitudes = niftiNames.filter(
            nifti => nifti.indexOf('_magnitude') > -1,
          )
          fieldmaps.map(nifti => {
            const associatedMagnitudeFile = nifti.replace(
              'fieldmap',
              'magnitude',
            )
            if (magnitudes.indexOf(associatedMagnitudeFile) === -1) {
              self.issues.push(
                new Issue({
                  code: 91,
                  file: niftis.find(niftiFile => niftiFile.name == nifti),
                }),
              )
            }
          })
          // End fieldmap check

          // check to see if each phasediff is associated with magnitude1
          const phaseDiffNiftis = niftiNames.filter(
            nifti => nifti.indexOf('phasediff') > -1,
          )
          const magnitude1Niftis = niftiNames.filter(
            nifti => nifti.indexOf('magnitude1') > -1,
          )
          phaseDiffNiftis.map(nifti => {
            const associatedMagnitudeFile = nifti.replace(
              'phasediff',
              'magnitude1',
            )
            if (magnitude1Niftis.indexOf(associatedMagnitudeFile) === -1) {
              self.issues.push(
                new Issue({
                  code: 92,
                  file: niftis.find(niftiFile => niftiFile.name == nifti),
                }),
              )
            }
          })

          async.eachOfLimit(
            niftis,
            200,
            function(file, key, cb) {
              if (self.options.ignoreNiftiHeaders) {
                NIFTI(
                  null,
                  file,
                  jsonContentsDict,
                  bContentsDict,
                  fileList,
                  events,
                  function(issues) {
                    self.issues = self.issues.concat(issues)
                    process.nextTick(cb)
                  },
                )
              } else {
                utils.files.readNiftiHeader(file, function(header) {
                  // check if header could be read
                  if (header && header.hasOwnProperty('error')) {
                    self.issues.push(header.error)
                    process.nextTick(cb)
                  } else {
                    headers.push([file, header])
                    NIFTI(
                      header,
                      file,
                      jsonContentsDict,
                      bContentsDict,
                      fileList,
                      events,
                      function(issues) {
                        self.issues = self.issues.concat(issues)
                        process.nextTick(cb)
                      },
                    )
                  }
                })
              }
            },
            function() {
              if (!hasSubjectDir) {
                self.issues.push(new Issue({ code: 45 }))
              }
              if (!hasDatasetDescription) {
                self.issues.push(new Issue({ code: 57 }))
              }
              // check if participants file match found subjects
              if (participants) {
                const participantsFromFile = participants.list.sort()
                const participantsFromFolders = summary.subjects.sort()
                if (
                  !utils.array.equals(
                    participantsFromFolders,
                    participantsFromFile,
                    true,
                  )
                ) {
                  self.issues.push(
                    new Issue({
                      code: 49,
                      evidence:
                        'participants.tsv: ' +
                        participantsFromFile.join(', ') +
                        ' folder structure: ' +
                        participantsFromFolders.join(', '),
                      file: participants.file,
                    }),
                  )
                }
              }

              // check if dataset contains T1w
              if (summary.modalities.indexOf('T1w') < 0) {
                self.issues.push(
                  new Issue({
                    code: 53,
                  }),
                )
              }

              //check for equal number of participants from ./phenotype/*.tsv and participants in dataset
              TSV.checkphenotype(phenotypeParticipants, summary, self.issues)

              // validate nii header fields
              self.issues = self.issues.concat(headerFields(headers))

              // Events validation
              Events.validateEvents(
                events,
                stimuli,
                headers,
                jsonContentsDict,
                self.issues,
              )

              // validate custom fields in all TSVs and add any issues to the list
              self.issues = self.issues.concat(
                TSV.validateTsvColumns(tsvs, jsonContentsDict),
              )

              // validation session files
              self.issues = self.issues.concat(session(fileList))

              self.issues = self.issues.concat(
                checkAnyDataPresent(fileList, summary.subjects),
              )
              summary.modalities = utils.modalities.group(summary.modalities)
              const issues = utils.issues.format(
                self.issues,
                summary,
                self.options,
              )
              callback(issues, summary)
            },
          )
        },
      )
    },
  )
}

module.exports = fullTest
<|endoftext|>


<commit_before>var fs = require("fs"),
	cp = require("child_process"),
	path = require('path'),
	utils = require('./utils.js'),
	Promise = require('bluebird'),
	fetcherCommon = require('./fetcher.common'),
	genericImporter = require("./genericImporter.js"),
	CSVWriter = require("./CSVWriter.js")

exports.loadDir = function(dir){

	if (dir.indexOf(path.sep) != dir.length){
		dir += path.sep;
	}

	console.log("loading dir:" + dir);
	var files = fs.readdirSync(dir).filter(function(file){
		return file.indexOf(".xlsx") > -1 || file.indexOf(".xls") > -1;
	});

	fetcherCommon.changeBaseFolder(dir);

	return new Promise.each(files, function(file){

		console.log("Converting: "+  file);

		var xlFilename = path.join(dir,file);
		var fund = utils.getFundFromFile(file);
		var csvFilename = utils.filename('./tmp',fund, '.csv');

		if (fs.existsSync(csvFilename)){
			console.log("converted file exists:" + csvFilename );
			return;
		}

		return genericImporter.parseXls(xlFilename)
			.then(function(result){
				return CSVWriter.writeParsedResult(fund.body, fund.number, fund.year, fund.quarter, result);
			});
	})
	.then(function(){
		console.log("all done");
	})

}

<commit_msg>catch errors<commit_after>var fs = require("fs"),
	cp = require("child_process"),
	path = require('path'),
	utils = require('./utils.js'),
	Promise = require('bluebird'),
	fetcherCommon = require('./fetcher.common'),
	genericImporter = require("./genericImporter.js"),
	CSVWriter = require("./CSVWriter.js")

exports.loadDir = function(dir){

	if (dir.indexOf(path.sep) != dir.length){
		dir += path.sep;
	}

	console.log("loading dir:" + dir);
	var files = fs.readdirSync(dir).filter(function(file){
		return file.indexOf(".xlsx") > -1 || file.indexOf(".xls") > -1;
	});

	fetcherCommon.changeBaseFolder(dir);

	return new Promise.each(files, function(file){

		console.log("Converting: "+  file);

		var xlFilename = path.join(dir,file);
		var fund = utils.getFundFromFile(file);
		var csvFilename = utils.filename('./tmp',fund, '.csv');

		if (fs.existsSync(csvFilename)){
			console.log("converted file exists:" + csvFilename );
			return;
		}

		return genericImporter.parseXls(xlFilename)
			.then(function(result){
				return CSVWriter.writeParsedResult(fund.body, fund.number, fund.year, fund.quarter, result);
			})
			.catch(function(e){
				console.log("error converting file: " + file);
				console.log(e.stack);
				if (fs.existsSync(csvFilename)){
					fs.unlinkSync(csvFilename);
				}
			});
	})
	.then(function(){
		console.log("all done");
	})

}

<|endoftext|>


<commit_before>'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.

const path = require('path')

module.exports = {
  dev: {

    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {},

    // Various Dev Server settings
    host: 'localhost', // can be overwritten by process.env.HOST
    port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
    autoOpenBrowser: false,
    errorOverlay: true,
    notifyOnErrors: true,
    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-

    /**
     * Source Maps
     */

    // https://webpack.js.org/configuration/devtool/#development
    devtool: 'cheap-module-eval-source-map',

    // If you have problems debugging vue-files in devtools,
    // set this to false - it *may* help
    // https://vue-loader.vuejs.org/en/options.html#cachebusting
    cacheBusting: true,

    cssSourceMap: true
  },

  build: {
    // Template for index.html
    index: path.resolve(__dirname, '../dist/index.html'),

    // Paths
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: './',
    assetsPublicPath: './',

    /**
     * Source Maps
     */

    productionSourceMap: true,
    // https://webpack.js.org/configuration/devtool/#production
    devtool: '#source-map',

    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],

    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  }
}
<commit_msg>disable source map and enable gzip compression<commit_after>'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.

const path = require('path')

module.exports = {
  dev: {

    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {},

    // Various Dev Server settings
    host: 'localhost', // can be overwritten by process.env.HOST
    port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
    autoOpenBrowser: false,
    errorOverlay: true,
    notifyOnErrors: true,
    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-

    /**
     * Source Maps
     */

    // https://webpack.js.org/configuration/devtool/#development
    devtool: 'cheap-module-eval-source-map',

    // If you have problems debugging vue-files in devtools,
    // set this to false - it *may* help
    // https://vue-loader.vuejs.org/en/options.html#cachebusting
    cacheBusting: true,

    cssSourceMap: false
  },

  build: {
    // Template for index.html
    index: path.resolve(__dirname, '../dist/index.html'),

    // Paths
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: './',
    assetsPublicPath: './',

    /**
     * Source Maps
     */

    productionSourceMap: false,
    // https://webpack.js.org/configuration/devtool/#production
    devtool: '#source-map',

    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: true,
    productionGzipExtensions: ['js', 'css'],

    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  }
}
<|endoftext|>