<commit_before>"use strict";

Object.fromList = function(xs)
{
    var result = {};

    for(var i = 0; i < xs.length; i++)
    {
        result[xs[i][0]] = xs[i][1];
    }

    return result;
};

var dbg_names = Object.fromList([
    [1, ""],
    [LOG_CPU, "CPU"],
    [LOG_DISK, "DISK"],
    [LOG_FPU, "FPU"],
    [LOG_MEM, "MEM"],
    [LOG_DMA, "DMA"],
    [LOG_IO, "IO"],
    [LOG_PS2, "PS2"],
    [LOG_PIC, "PIC"],
    [LOG_VGA, "VGA"],
    [LOG_PIT, "PIT"],
    [LOG_MOUSE, "MOUS"],
    [LOG_PCI, "PCI"],
    [LOG_BIOS, "BIOS"],
    [LOG_CD, "CD"],
    [LOG_SERIAL, "SERI"],
    [LOG_RTC, "RTC"],
    [LOG_HPET, "HPET"],
    [LOG_ACPI, "ACPI"],
    [LOG_APIC, "APIC"],
]);

var log_last_message = "",
    log_message_repetitions = 0;

/**
 * @param {number=} level
 */
function dbg_log(stuff, level)
{
    if(!DEBUG) return;

    level = level || 1;

    if(level & LOG_LEVEL)
    {
        var level_name = dbg_names[level] || "",
            message = "[" + String.pads(level_name, 4) + "] " + stuff;

        if(message === log_last_message)
        {
            log_message_repetitions++;

            if(log_message_repetitions < 2048)
            {
                return;
            }
        }

        if(log_message_repetitions)
        {
            if(log_message_repetitions === 1)
            {
                console.log(log_last_message);
            }
            else
            {
                console.log("Previous message repeated " + log_message_repetitions + " times");;
            }

            log_message_repetitions = 0;
        }

        console.log(message);

        log_last_message = message;
    }
}

/**
 * @param {number=} level
 */
function dbg_trace(level)
{
    if(!DEBUG) return;

    dbg_log(Error().stack, level);
}

/**
 * console.assert is fucking slow
 * @param {string=} msg
 * @param {number=} level
 */
function dbg_assert(cond, msg, level)
{
    if(!DEBUG) return;

    if(!cond)
    {
        //dump_regs();
        console.log(Error().stack);
        console.trace();

        if(msg)
        {
            throw "Assert failed: " + msg;
        }
        else
        {
            throw "Assert failed";
        }
    }
};

Object.extend = function(target, src)
{
    var keys = Object.keys(src);

    for(var i = 0; i < keys.length; i++)
    {
        target[keys[i]] = src[keys[i]];
    }
}

// pad string with spaces on the right
String.pads = function(str, len)
{
    str = str ? str + "" : "";

    while(str.length < len)
    {
        str = str + " ";
    }

    return str;
}

// pad string with zeros on the left
String.pad0 = function(str, len)
{
    str = str ? str + "" : "";

    while(str.length < len)
    {
        str = "0" + str;
    }

    return str;
}

Array.range = function(n)
{
    var a = [];

    for(var i = 0; i < n; i++)
    {
        a[i] = i;
    }

    return a;
}

/**
 * number to hex
 * @param {number=} len
 */
function h(n, len)
{
    //dbg_assert(typeof n === "number");

    if(!n) return String.pad0("", len || 1);

    if(len)
    {
        return String.pad0(n.toString(16).toUpperCase(), len);
    }
    else
    {
        return n.toString(16).toUpperCase();
    }
}

Number.bits = function(n)
{
    var result = [];

    for(var bit = 31; bit > -1; bit--)
    {
        if(n & 1 << bit)
        {
            result.push(bit);
        }
    }

    return result.join(', ');
}

String.chr_repeat = function(chr, count)
{
    var result = "";

    while(count--)
    {
        result += chr;
    }

    return result;
}

Math.bcd_pack = function(n)
{
    var i = 0,
        result = 0,
        digit;

    while(n)
    {
        digit = n % 10;

        result |= digit << (4 * i);
        i++;
        n = (n - digit) / 10;
    }

    return result;
}

/**
 * Synchronous access to ArrayBuffer
 * @constructor
 */
function SyncBuffer(buffer)
{
    this.byteLength = buffer.byteLength;

    // warning: fn may be called synchronously or asynchronously
    this.get = function(start, len, fn)
    {
        dbg_assert(start + len <= buffer.byteLength);

        fn(new Uint8Array(buffer, start, len));
    };

    this.set = function(start, slice, fn)
    {
        dbg_assert(start + slice.length <= buffer.byteLength);

        new Uint8Array(buffer, start, slice.byteLength).set(slice);
        fn();
    };

    this.get_buffer = function(fn)
    {
        fn(buffer);
    };
}

/**
 * Simple circular queue for logs
 *
 * @param {number} size
 * @constructor
 */
function CircularQueue(size)
{
    var data,
        index;

    this.add = function(item)
    {
        data[index] = item;

        index = (index + 1) % size;
    };

    this.toArray = function()
    {
        return [].slice.call(data, index).concat([].slice.call(data, 0, index));
    };

    this.clear = function()
    {
        data = [];

        index = 0;
    };

    this.set = function(new_data)
    {
        data = new_data;
        index = 0;
    };

    this.clear();
}

// switch number to big endian
Math.to_be32 = function(dword)
{
    return dword >>> 24 |
        dword >> 8 & 0xff00 |
        dword << 8 & 0xff0000 |
        dword << 24;
}

Math.to_be16 = function(word)
{
    return word >>> 8 & 0xff | word << 8 & 0xff00;
}

Math.int_log2 = function(x)
{
    dbg_assert(x > 0);

    // well optimized in modern browsers, http://jsperf.com/integer-log2/2
    return (Math.log(x) / Math.LN2) | 0;
}

// round away from zero, opposite of truncation
Math.roundInfinity = function(x)
{
    return x > 0 ? Math.ceil(x) : Math.floor(x);
};

/**
 * @constructor
 *
 * Queue wrapper around Uint8Array
 * Used by devices such as the PS2 controller
 */
function ByteQueue(size)
{
    var data = new Uint8Array(size),
        start,
        end;

    dbg_assert((size & size - 1) === 0);

    this.length = 0;

    this.push = function(item)
    {
        if(this.length === size)
        {
            // intentional overwrite
        }
        else
        {
            this.length++;
        }

        data[end] = item;
        end = end + 1 & size - 1;
    };

    this.shift = function()
    {
        if(!this.length)
        {
            return -1;
        }
        else
        {
            var item = data[start];

            start = start + 1 & size - 1;
            this.length--;

            return item;
        }
    };

    this.peek = function()
    {
        if(!this.length)
        {
            return -1;
        }
        else
        {
            return data[start];
        }
    };

    this.clear = function()
    {
        start = 0;
        end = 0;
        this.length = 0;
    };

    this.clear();
}

<commit_msg>make chr_repeat more defensive for invalid parameters<commit_after>"use strict";

Object.fromList = function(xs)
{
    var result = {};

    for(var i = 0; i < xs.length; i++)
    {
        result[xs[i][0]] = xs[i][1];
    }

    return result;
};

var dbg_names = Object.fromList([
    [1, ""],
    [LOG_CPU, "CPU"],
    [LOG_DISK, "DISK"],
    [LOG_FPU, "FPU"],
    [LOG_MEM, "MEM"],
    [LOG_DMA, "DMA"],
    [LOG_IO, "IO"],
    [LOG_PS2, "PS2"],
    [LOG_PIC, "PIC"],
    [LOG_VGA, "VGA"],
    [LOG_PIT, "PIT"],
    [LOG_MOUSE, "MOUS"],
    [LOG_PCI, "PCI"],
    [LOG_BIOS, "BIOS"],
    [LOG_CD, "CD"],
    [LOG_SERIAL, "SERI"],
    [LOG_RTC, "RTC"],
    [LOG_HPET, "HPET"],
    [LOG_ACPI, "ACPI"],
    [LOG_APIC, "APIC"],
]);

var log_last_message = "",
    log_message_repetitions = 0;

/**
 * @param {number=} level
 */
function dbg_log(stuff, level)
{
    if(!DEBUG) return;

    level = level || 1;

    if(level & LOG_LEVEL)
    {
        var level_name = dbg_names[level] || "",
            message = "[" + String.pads(level_name, 4) + "] " + stuff;

        if(message === log_last_message)
        {
            log_message_repetitions++;

            if(log_message_repetitions < 2048)
            {
                return;
            }
        }

        if(log_message_repetitions)
        {
            if(log_message_repetitions === 1)
            {
                console.log(log_last_message);
            }
            else
            {
                console.log("Previous message repeated " + log_message_repetitions + " times");;
            }

            log_message_repetitions = 0;
        }

        console.log(message);

        log_last_message = message;
    }
}

/**
 * @param {number=} level
 */
function dbg_trace(level)
{
    if(!DEBUG) return;

    dbg_log(Error().stack, level);
}

/**
 * console.assert is fucking slow
 * @param {string=} msg
 * @param {number=} level
 */
function dbg_assert(cond, msg, level)
{
    if(!DEBUG) return;

    if(!cond)
    {
        //dump_regs();
        console.log(Error().stack);
        console.trace();

        if(msg)
        {
            throw "Assert failed: " + msg;
        }
        else
        {
            throw "Assert failed";
        }
    }
};

Object.extend = function(target, src)
{
    var keys = Object.keys(src);

    for(var i = 0; i < keys.length; i++)
    {
        target[keys[i]] = src[keys[i]];
    }
}

// pad string with spaces on the right
String.pads = function(str, len)
{
    str = str ? str + "" : "";

    while(str.length < len)
    {
        str = str + " ";
    }

    return str;
}

// pad string with zeros on the left
String.pad0 = function(str, len)
{
    str = str ? str + "" : "";

    while(str.length < len)
    {
        str = "0" + str;
    }

    return str;
}

Array.range = function(n)
{
    var a = [];

    for(var i = 0; i < n; i++)
    {
        a[i] = i;
    }

    return a;
}

/**
 * number to hex
 * @param {number=} len
 */
function h(n, len)
{
    //dbg_assert(typeof n === "number");

    if(!n) return String.pad0("", len || 1);

    if(len)
    {
        return String.pad0(n.toString(16).toUpperCase(), len);
    }
    else
    {
        return n.toString(16).toUpperCase();
    }
}

Number.bits = function(n)
{
    var result = [];

    for(var bit = 31; bit > -1; bit--)
    {
        if(n & 1 << bit)
        {
            result.push(bit);
        }
    }

    return result.join(', ');
}

String.chr_repeat = function(chr, count)
{
    var result = "";

    while(count-- > 0)
    {
        result += chr;
    }

    return result;
}

Math.bcd_pack = function(n)
{
    var i = 0,
        result = 0,
        digit;

    while(n)
    {
        digit = n % 10;

        result |= digit << (4 * i);
        i++;
        n = (n - digit) / 10;
    }

    return result;
}

/**
 * Synchronous access to ArrayBuffer
 * @constructor
 */
function SyncBuffer(buffer)
{
    this.byteLength = buffer.byteLength;

    // warning: fn may be called synchronously or asynchronously
    this.get = function(start, len, fn)
    {
        dbg_assert(start + len <= buffer.byteLength);

        fn(new Uint8Array(buffer, start, len));
    };

    this.set = function(start, slice, fn)
    {
        dbg_assert(start + slice.length <= buffer.byteLength);

        new Uint8Array(buffer, start, slice.byteLength).set(slice);
        fn();
    };

    this.get_buffer = function(fn)
    {
        fn(buffer);
    };
}

/**
 * Simple circular queue for logs
 *
 * @param {number} size
 * @constructor
 */
function CircularQueue(size)
{
    var data,
        index;

    this.add = function(item)
    {
        data[index] = item;

        index = (index + 1) % size;
    };

    this.toArray = function()
    {
        return [].slice.call(data, index).concat([].slice.call(data, 0, index));
    };

    this.clear = function()
    {
        data = [];

        index = 0;
    };

    this.set = function(new_data)
    {
        data = new_data;
        index = 0;
    };

    this.clear();
}

// switch number to big endian
Math.to_be32 = function(dword)
{
    return dword >>> 24 |
        dword >> 8 & 0xff00 |
        dword << 8 & 0xff0000 |
        dword << 24;
}

Math.to_be16 = function(word)
{
    return word >>> 8 & 0xff | word << 8 & 0xff00;
}

Math.int_log2 = function(x)
{
    dbg_assert(x > 0);

    // well optimized in modern browsers, http://jsperf.com/integer-log2/2
    return (Math.log(x) / Math.LN2) | 0;
}

// round away from zero, opposite of truncation
Math.roundInfinity = function(x)
{
    return x > 0 ? Math.ceil(x) : Math.floor(x);
};

/**
 * @constructor
 *
 * Queue wrapper around Uint8Array
 * Used by devices such as the PS2 controller
 */
function ByteQueue(size)
{
    var data = new Uint8Array(size),
        start,
        end;

    dbg_assert((size & size - 1) === 0);

    this.length = 0;

    this.push = function(item)
    {
        if(this.length === size)
        {
            // intentional overwrite
        }
        else
        {
            this.length++;
        }

        data[end] = item;
        end = end + 1 & size - 1;
    };

    this.shift = function()
    {
        if(!this.length)
        {
            return -1;
        }
        else
        {
            var item = data[start];

            start = start + 1 & size - 1;
            this.length--;

            return item;
        }
    };

    this.peek = function()
    {
        if(!this.length)
        {
            return -1;
        }
        else
        {
            return data[start];
        }
    };

    this.clear = function()
    {
        start = 0;
        end = 0;
        this.length = 0;
    };

    this.clear();
}

<|endoftext|>


<commit_before>import simpleGit from 'simple-git/promise';
import yamlParser from 'js-yaml';
import path from 'path';
import _ from 'lodash';
import fs from 'fs';
import validate from './validator';

const workingDir = 'tmp';

function parseContent(md) {

  let start = -1;
  const parts = [];

  const lines = md.split('\n');

  // Split the multiple parts - This way enables the creator to use 4/5 level headers inside the content.
  lines.forEach((line, index) => {
    if (line.match(/#{1,3}\s/) || index === lines.length - 1) {
      if (start !== -1) {
        parts.push(lines.slice(start, index).join('\n'));
        start = index
      }
      else {
        start = index
      }
    }
  });

  const sections = {};

  // Identify and remove the header
  const summaryMatch = parts.shift().match(/^#\s(?<tutorialTitle>.*)\n+(?<tutorialDescription>[^]*)/);

  sections['summary'] = {
    title: summaryMatch.groups.tutorialTitle,
    description: summaryMatch.groups.tutorialDescription,
  };

  // Identify each part of the content
  parts.forEach(section => {
    const levelRegex = /^(##\s(?<levelId>L\d+)\s(?<levelTitle>.*)\n*>\s+(?<levelSummary>.*)\n+(?<levelContent>[^]*))/;
    const stepRegex = /^(###\s(?<stepId>(?<levelId>L\d+)S\d+)\s(?<stepTitle>.*)\n+(?<stepContent>[^]*))/;

    const levelMatch = section.match(levelRegex);
    const stepMatch = section.match(stepRegex);

    if (levelMatch) {
      const level = {
        [levelMatch.groups.levelId]: {
          id: levelMatch.groups.levelId,
          title: levelMatch.groups.levelTitle,
          summary: levelMatch.groups.levelSummary,
          content: levelMatch.groups.levelContent,
        }
      };

      _.merge(sections, level);

    }
    else if (stepMatch) {

      const step = {
        [stepMatch.groups.levelId]: {
          steps: {
            [stepMatch.groups.stepId]: {
              id: stepMatch.groups.stepId,
              // title: stepMatch.groups.stepTitle, //Not using at this momemnt
              content: stepMatch.groups.stepContent
            }
          }
        }
      };

      _.merge(sections, step);

    }

  });

  return sections;

}

function rmDir(dir, rmSelf) {

  let files;
  rmSelf = (rmSelf === undefined) ? true : rmSelf;
  // dir = dir + path.sep;

  try { files = fs.readdirSync(dir); } catch (e) { console.log(`Sorry, directory '${dir}' doesn't exist.`); return; }

  if (files.length > 0) {
    files.forEach(function (x, i) {
      if (fs.statSync(path.join(dir, x)).isDirectory()) {
        rmDir(path.join(dir, x));
      } else {
        fs.unlinkSync(path.join(dir, x));
      }
    });
  }

  if (rmSelf) {
    // check if user want to delete the directory ir just the files in this directory
    fs.rmdirSync(dir);
  }

}

async function cleanupFiles(workingDir) {

  try {

    const gitModule = simpleGit(__dirname);

    await gitModule.subModule(['deinit', '-f', workingDir]);
    await gitModule.rm(workingDir);
    await gitModule.reset(['HEAD']);
    rmDir(path.join(__dirname, '.git', 'modules', workingDir));
    rmDir(workingDir);

    return true;

  } catch (error) {

    return false;

  }

}

/**
 *
 * @param {string} repo Git url to the repo. It should finish with .git
 * @param {string} codeBranch The branch containing the tutorial code
 * @param {string} setupBranch The branch containing the configuration files
 * @param {string} isLocal define if the repo is local or remote
 */
async function build({ repo, codeBranch, setupBranch, isLocal }) {

  let git;
  let isSubModule = false;

  if (isLocal) {
    git = simpleGit(repo);
  }
  else {
    const gitTest = simpleGit(__dirname);
    const isRepo = await gitTest.checkIsRepo();

    if (isRepo) {
      // Add the new one as a submodule
      const startCleanup = cleanupFiles(workingDir);

      if (startCleanup) {
        console.log('Clenned old files from git module...');
      }

      await gitTest.submoduleAdd(repo, workingDir);

      git = simpleGit(path.join(__dirname, workingDir));

      isSubModule = true;

    }
    else {
      await gitTest.clone(repo);
      git = simpleGit(__dirname);
    }
  }

  await git.fetch();

  // checkout the branch to load configuration and content branch
  await git.checkout(setupBranch);

  // Load files
  const _mdContent = fs.readFileSync(path.join(__dirname, workingDir, 'TUTORIAL.md'), 'utf8');
  let _config = fs.readFileSync(path.join(__dirname, workingDir, 'coderoad.yaml'), 'utf8');

  // Add one more line to the content as per Shawn's request
  const mdContent = parseContent(_mdContent);

  // Parse config to JSON
  const config = yamlParser.load(_config);

  // Add the summary to the config file
  config['summary'] = mdContent.summary;

  // merge content and config
  config.levels.forEach(level => {
    const { steps, ...content } = mdContent[level.id];

    level.steps.forEach(step => _.merge(step, steps[step.id]));

    _.merge(level, content);
  });

  // Checkout the code branches
  await git.checkout(codeBranch);

  // Load all logs
  const logs = await git.log();

  // Filter relevant logs
  const parts = new Set();

  for (const commit of logs.all) {

    const matches = commit.message.match(/^(?<stepId>(?<levelId>L\d+)S\d+)(?<stepType>[QA])?/);

    if (matches && !parts.has(matches[0])) {
      // Uses a set to make sure only the latest commit is proccessed
      parts.add(matches[0]);

      // Add the content and git hash to the config
      if (matches.groups.stepId) {
        // If it's a step: add the content and the setup/solution hashes depending on the type
        const theStep = config.levels
          .find(level => level.id === matches.groups.levelId)
          .steps.find(step => step.id === matches.groups.stepId);

        if (matches.groups.stepType === 'Q') {
          theStep.setup.commits.push(commit.hash.substr(0, 7));
        }
        else if (matches.groups.stepType === 'A' && theStep.solution.commits) {
          theStep.solution.commits.push(commit.hash.substr(0, 7));
        }
      }
      else {
        // If it's level: add the commit hash (if the level has the commit key) and the content to the config
        const theLevel = config.levels.find(level => level.id === matches.groups.levelId);

        if (_.has(theLevel, 'config.commits')) {
          theLevel.setup.commits.push(commit.hash.substr(0, 7));
        }
      }
    }
  };

  // cleanup the submodules
  if (!isLocal) {
    const endCleanup = await cleanupFiles(workingDir);

    if (!endCleanup) {
      console.log('Error when deleting the git submodules');
    }
  }

  const isValid = validate(config);

  if (!isValid) return validate.errors;

  return config;

}

module.exports = build;
<commit_msg>Make the level summary optional<commit_after>import simpleGit from 'simple-git/promise';
import yamlParser from 'js-yaml';
import path from 'path';
import _ from 'lodash';
import fs from 'fs';
import validate from './validator';

const workingDir = 'tmp';

function parseContent(md) {

  let start = -1;
  const parts = [];

  const lines = md.split('\n');

  // Split the multiple parts - This way enables the creator to use 4/5 level headers inside the content.
  lines.forEach((line, index) => {
    if (line.match(/#{1,3}\s/) || index === lines.length - 1) {
      if (start !== -1) {
        parts.push(lines.slice(start, index).join('\n'));
        start = index
      }
      else {
        start = index
      }
    }
  });

  const sections = {};

  // Identify and remove the header
  const summaryMatch = parts.shift().match(/^#\s(?<tutorialTitle>.*)\n+(?<tutorialDescription>[^]*)/);

  sections['summary'] = {
    title: summaryMatch.groups.tutorialTitle,
    description: summaryMatch.groups.tutorialDescription,
  };

  // Identify each part of the content
  parts.forEach(section => {
    const levelRegex = /^(##\s(?<levelId>L\d+)\s(?<levelTitle>.*)\n*(>\s*(?<levelSummary>.*))?\n+(?<levelContent>[^]*))/;
    const stepRegex = /^(###\s(?<stepId>(?<levelId>L\d+)S\d+)\s(?<stepTitle>.*)\n+(?<stepContent>[^]*))/;

    const levelMatch = section.match(levelRegex);
    const stepMatch = section.match(stepRegex);

    if (levelMatch) {
      const level = {
        [levelMatch.groups.levelId]: {
          id: levelMatch.groups.levelId,
          title: levelMatch.groups.levelTitle,
          summary: levelMatch.groups.levelSummary,
          content: levelMatch.groups.levelContent,
        }
      };

      _.merge(sections, level);

    }
    else if (stepMatch) {

      const step = {
        [stepMatch.groups.levelId]: {
          steps: {
            [stepMatch.groups.stepId]: {
              id: stepMatch.groups.stepId,
              // title: stepMatch.groups.stepTitle, //Not using at this momemnt
              content: stepMatch.groups.stepContent
            }
          }
        }
      };

      _.merge(sections, step);

    }

  });

  return sections;

}

function rmDir(dir, rmSelf) {

  let files;
  rmSelf = (rmSelf === undefined) ? true : rmSelf;
  // dir = dir + path.sep;

  try { files = fs.readdirSync(dir); } catch (e) { console.log(`Sorry, directory '${dir}' doesn't exist.`); return; }

  if (files.length > 0) {
    files.forEach(function (x, i) {
      if (fs.statSync(path.join(dir, x)).isDirectory()) {
        rmDir(path.join(dir, x));
      } else {
        fs.unlinkSync(path.join(dir, x));
      }
    });
  }

  if (rmSelf) {
    // check if user want to delete the directory ir just the files in this directory
    fs.rmdirSync(dir);
  }

}

async function cleanupFiles(workingDir) {

  try {

    const gitModule = simpleGit(__dirname);

    await gitModule.subModule(['deinit', '-f', workingDir]);
    await gitModule.rm(workingDir);
    await gitModule.reset(['HEAD']);
    rmDir(path.join(__dirname, '.git', 'modules', workingDir));
    rmDir(workingDir);

    return true;

  } catch (error) {

    return false;

  }

}

/**
 *
 * @param {string} repo Git url to the repo. It should finish with .git
 * @param {string} codeBranch The branch containing the tutorial code
 * @param {string} setupBranch The branch containing the configuration files
 * @param {string} isLocal define if the repo is local or remote
 */
async function build({ repo, codeBranch, setupBranch, isLocal }) {

  let git;
  let isSubModule = false;

  if (isLocal) {
    git = simpleGit(repo);
  }
  else {
    const gitTest = simpleGit(__dirname);
    const isRepo = await gitTest.checkIsRepo();

    if (isRepo) {
      const startCleanup = cleanupFiles(workingDir);

      if (startCleanup) {
        console.log('Clenned old files from git module...');
      }

      await gitTest.submoduleAdd(repo, workingDir);

      git = simpleGit(path.join(__dirname, workingDir));

      isSubModule = true;

    }
    else {
      await gitTest.clone(repo);
      git = simpleGit(__dirname);
    }
  }

  await git.fetch();

  // checkout the branch to load configuration and content branch
  await git.checkout(setupBranch);

  // Load files
  const _mdContent = fs.readFileSync(path.join(__dirname, workingDir, 'TUTORIAL.md'), 'utf8');
  let _config = fs.readFileSync(path.join(__dirname, workingDir, 'coderoad.yaml'), 'utf8');

  // Add one more line to the content as per Shawn's request
  const mdContent = parseContent(_mdContent);

  // Parse config to JSON
  const config = yamlParser.load(_config);

  // Add the summary to the config file
  config['summary'] = mdContent.summary;

  // merge content and config
  config.levels.forEach(level => {
    const { steps, ...content } = mdContent[level.id];

    level.steps.forEach(step => _.merge(step, steps[step.id]));

    _.merge(level, content);
  });

  // Checkout the code branches
  await git.checkout(codeBranch);

  // Load all logs
  const logs = await git.log();

  // Filter relevant logs
  const parts = new Set();

  for (const commit of logs.all) {

    const matches = commit.message.match(/^(?<stepId>(?<levelId>L\d+)S\d+)(?<stepType>[QA])?/);

    if (matches && !parts.has(matches[0])) {
      // Uses a set to make sure only the latest commit is proccessed
      parts.add(matches[0]);

      // Add the content and git hash to the config
      if (matches.groups.stepId) {
        // If it's a step: add the content and the setup/solution hashes depending on the type
        const theStep = config.levels
          .find(level => level.id === matches.groups.levelId)
          .steps.find(step => step.id === matches.groups.stepId);

        if (matches.groups.stepType === 'Q') {
          theStep.setup.commits.push(commit.hash.substr(0, 7));
        }
        else if (matches.groups.stepType === 'A' && theStep.solution.commits) {
          theStep.solution.commits.push(commit.hash.substr(0, 7));
        }
      }
      else {
        // If it's level: add the commit hash (if the level has the commit key) and the content to the config
        const theLevel = config.levels.find(level => level.id === matches.groups.levelId);

        if (_.has(theLevel, 'config.commits')) {
          theLevel.setup.commits.push(commit.hash.substr(0, 7));
        }
      }
    }
  };

  // cleanup the submodules
  if (!isLocal) {
    const endCleanup = await cleanupFiles(workingDir);

    if (!endCleanup) {
      console.log('Error when deleting the git submodules');
    }
  }

  const isValid = validate(config);

  if (!isValid) return validate.errors;

  return config;

}

module.exports = build;
<|endoftext|>


<commit_before>/*
 *
 * node shtml2html -h 使用帮助
 *
 * 依赖 node module: commander
 * npm install commander
 */

// 模块依赖
var program = require('commander'),
    fs  = require('fs'),
    path = require('path');

var ROOT = __dirname + '/';

program
    .version('0.0.1')
    .usage('node shtml2html <"dir1" [,"dir2" [,"dir3",[...]]]>')
    .option('-r', '--root [path]', '服务器根目录')
    .parse(process.argv);

var dirs = process.argv[2].split(',');

var warn = function(msg) {
    console.log(['\x1B[33m' , msg, '\x1B[39m'].join(''));
}
var log = function(msg) {
    console.log(['\x1B[32m' , msg, '\x1B[39m'].join(''));
}
var err = function(msg) {
    console.log(['\x1B[31m' , msg, '\x1B[39m'].join(''));
}

var rmdir = (function() {
    function iterator(url, dirs) {
        var stat = fs.statSync(url);
        if (stat.isDirectory()) {
            dirs.unshift(url);
            inner(url, dirs);
        } else if (stat.isFile()) {
            fs.unlinkSync(url);
        }
    }
    function inner(path, dirs) {
        var arr = fs.readdirSync(path),
            i, el;
        for (i = 0; el = arr[i++];) {
            iterator(path + "/" + el, dirs);
        }
    }
    return function(dir) {
        var dirs = [];
        try {
            iterator(dir, dirs);
            for (var i = 0, el; el = dirs[i++];) {
                fs.rmdirSync(el);
            }
        } catch (e) {
        }
        log('[rmdir] ' + dir);
    };
})();

// 递归建立目录
var mkdir = function(dest) {
    var destDirs = dest.split('/'),
        destDirsLen = destDirs.length,
        destDir = '';

    log('[mkdir] ' + dest);
    destDirs.forEach(function(dir, i) {
        if (i < destDirsLen - 1) {
            destDir += dir + '/';
            if (!fs.existsSync(destDir)) {
                fs.mkdirSync(destDir);
                log('  ' + destDir + '');
            }
        }
    });
};

// filters 为用“|”分隔的扩展名列表，不带“.”，扩展名为 filters 列表中者通过复制
// 若 filters 列表以“!”开头，则非列表中者通过复制
var copyFile = function(src, dest, filters, encode) {
    var canCopy = true, encode = encode || 'binary', fileExt;

    if (fs.statSync(src).isDirectory()) {
        err('[copyFile] ' + src + ' is a Directory, use copyDir() instead');
        return;
    }

    fileExt = path.extname(src);
    if (filters) {
        canCopy = (typeof filters === 'string' ? new RegExp(filters, 'i') : filters).test(fileExt);
        if(filters.indexOf('!|') !== -1) canCopy = !canCopy;
    }

    mkdir(dest);
    if (canCopy) {
        fs.writeFileSync(dest, fs.readFileSync(src, encode), encode);
        log('[copyFile] from [' + src + '] to [' + dest + ']');
    }
};

var copyDir = function(src, dest, filters, encode) {
    var files, encode = encode || 'binary';
    if (src.charAt(src.length - 1) !== '/') src += '/';
    if (dest.charAt(dest.length - 1) !== '/') dest += '/';

    if (!fs.statSync(src).isDirectory()) {
        err('[copyDir] ' + src + ' is not a Directory');
        return;
    }
    mkdir(dest);
    files = fs.readdirSync(src);
    files.forEach(function(file) {
        if (fs.statSync(src + file).isDirectory()) {
            copyDir(src + file + '/', dest + file + '/', filters, encode);
        } else {
            copyFile(src + file, dest + file, filters, encode);
        }
    });
    log('[copyDir] from [' + src + '] to [' + dest + ']');
};

var merge = function(src) {
    var file = fs.readFileSync(src, 'utf-8'),
        basePath = path.dirname(src) + '/',
        baseName = path.basename(src, '.shtml'),
        incs,
        dest = basePath + baseName + '.html';

    incs = file.match(/<!--#include\s.*"-->/ig) || [];
    incs.forEach(function(inc, i) {
        var incSrc = inc.match(/"(\w.*)"-->/i)[1], replacement;
        if (incSrc.charAt(0) !== '/') incSrc = basePath + incSrc;

        if (fs.existsSync(dest)) replacement = fs.readFileSync(incSrc, 'utf-8');
        else replacement = merge(incSrc);

        file = file.replace(inc, replacement);
    });

    fs.writeFileSync(dest, file, 'utf-8');
    return file;
};

var handle = function(dir) {
    var files = fs.readdirSync(dir);
    files.forEach(function(src, i) {
        src = ROOT + dir + src;
        if (fs.statSync(src).isFile()) {
            merge(src);
        }
    });
};

for(var i= 0, j = dirs.length; i < j; i++) {
    if (dirs[i].charAt(dirs[i].length - 1) !== '/') dirs[i] += '/';
    handle(dirs[i]);
}

<commit_msg>destination added<commit_after>/*
 *
 * node shtml2html -h 使用帮助
 *
 * 依赖 node module: commander
 * npm install commander
 */

// 模块依赖
var program = require('commander'),
    fs  = require('fs'),
    path = require('path');

var ROOT = __dirname + '/';

program
    .version('0.0.1')
    .usage('node shtml2html <"source dir"> [<"destination dir">]')
    .option('-r', '--root [path]', '服务器根目录')
    .parse(process.argv);

var srcDirs = process.argv[2].split(',');
var destDirs = process.argv[3].split(',');

var warn = function(msg) {
    console.log(['\x1B[33m' , msg, '\x1B[39m'].join(''));
};
var log = function(msg) {
    console.log(['\x1B[32m' , msg, '\x1B[39m'].join(''));
};
var err = function(msg) {
    console.log(['\x1B[31m' , msg, '\x1B[39m'].join(''));
};

var fixPath = function(src) {
    if (src.charAt(src.length - 1) !== '/') src += '/';
    return src;
};

var rmdir = (function() {
    function iterator(url, dirs) {
        var stat = fs.statSync(url);
        if (stat.isDirectory()) {
            dirs.unshift(url);
            inner(url, dirs);
        } else if (stat.isFile()) {
            fs.unlinkSync(url);
        }
    }
    function inner(path, dirs) {
        var arr = fs.readdirSync(path),
            i, el;
        for (i = 0; el = arr[i++];) {
            iterator(path + "/" + el, dirs);
        }
    }
    return function(dir) {
        var dirs = [];
        try {
            iterator(dir, dirs);
            for (var i = 0, el; el = dirs[i++];) {
                fs.rmdirSync(el);
            }
        } catch (e) {
        }
        log('[rmdir] ' + dir);
    };
})();

// 递归建立目录
var mkdir = function(dest) {
    var destDirs = dest.split('/'),
        destDirsLen = destDirs.length,
        destDir = '';

    if (!fs.existsSync(dest)) {
        destDirs.forEach(function(dir, i) {
            if (i < destDirsLen - 1) {
                destDir += dir + '/';
                if (!fs.existsSync(destDir)) {
                    fs.mkdirSync(destDir);
                    log('[mkdir] ' + path.resolve(destDir) + '');
                }
            }
        });
    }
};

// filters 为用“|”分隔的扩展名列表，不带“.”，扩展名为 filters 列表中者通过复制
// 若 filters 列表以“!”开头，则非列表中者通过复制
var copyFile = function(src, dest, filters, encode) {
    var canCopy = true, encode = encode || 'binary', fileExt;

    if (fs.statSync(src).isDirectory()) {
        err('[copyFile] ' + src + ' is a Directory, use copyDir() instead');
        return;
    }

    fileExt = path.extname(src);
    if (filters) {
        canCopy = (typeof filters === 'string' ? new RegExp(filters, 'i') : filters).test(fileExt);
        if(filters.indexOf('!|') !== -1) canCopy = !canCopy;
    }

    mkdir(dest);
    if (canCopy) {
        fs.writeFileSync(dest, fs.readFileSync(src, encode), encode);
        log('[copyFile] from [' + src + '] to [' + dest + ']');
    }
};

var copyDir = function(src, dest, filters, encode) {
    var files, encode = encode || 'binary';
    src = fixPath(src);
    dest = fixPath(dest);

    if (!fs.statSync(src).isDirectory()) {
        err('[copyDir] ' + src + ' is not a Directory');
        return;
    }
    mkdir(dest);
    files = fs.readdirSync(src);
    files.forEach(function(file) {
        if (fs.statSync(src + file).isDirectory()) {
            copyDir(src + file + '/', dest + file + '/', filters, encode);
        } else {
            copyFile(src + file, dest + file, filters, encode);
        }
    });
    log('[copyDir] from [' + src + '] to [' + dest + ']');
};

var merge = function(src, dest) {
    var file = fs.readFileSync(src, 'utf-8'),
        baseSrc = path.dirname(src) + '/',
        baseDest = path.dirname(dest) + '/',
        incs;
    dest = dest.replace(/.shtml$/i, '.html');
    incs = file.match(/<!--#include\s.*"-->/ig) || [];

    incs.forEach(function(inc, i) {
        var incFile = inc.match(/"(\w.*)"-->/i)[1],
            replacement;

        if (incFile.charAt(0) === '/') {
            warn('[merge] web root path needed...');
            return;
        }
        else {
            incSrc = baseSrc + incFile;
            incDest = baseDest + incFile;

            if (fs.existsSync(dest)) replacement = fs.readFileSync(incSrc, 'utf-8');
            else replacement = merge(incSrc, incDest);

            file = file.replace(inc, replacement);
        }
    });
    mkdir(dest);
    fs.writeFileSync(dest, file, 'utf-8');
    log('[merge] ' + dest)
    return file;
};

var handle = function(from, to) {
    var files = fs.readdirSync(from);
    files.forEach(function(src, i) {
        fileSrc = path.resolve(ROOT + from + src);
        fileDest = path.resolve(ROOT + to + src);
        if (fs.statSync(fileSrc).isFile()) {
            merge(fileSrc, fileDest);
        }
    });
};

for(var i= 0, j = srcDirs.length; i < j; i++) {
    srcDirs[i] = fixPath(srcDirs[i]);
    destDirs[i] = fixPath(destDirs[i]);
    handle(srcDirs[i], destDirs[i]);
}

<|endoftext|>


<commit_before>const {app, shell, dialog, ipcMain, BrowserWindow, Menu} = require('electron')
const fs = require('fs')
const setting = require('./setting')
const updater = require('./updater')

let windows = []
let openfile = null
let isReady = false

function newWindow(path) {
    let window = new BrowserWindow({
        icon: process.platform === 'linux' ? `${__dirname}/../logo.png` : null,
        title: app.getName(),
        useContentSize: true,
        width: setting.get('window.width'),
        height: setting.get('window.height'),
        minWidth: setting.get('window.minwidth'),
        minHeight: setting.get('window.minheight'),
        autoHideMenuBar: !setting.get('view.show_menubar'),
        backgroundColor: '#111111',
        show: false,
        webPreferences: {
            zoomFactor: setting.get('app.zoom_factor')
        }
    })

    windows.push(window)
    buildMenu()

    window.webContents.setAudioMuted(!setting.get('sound.enable'))
    window.webContents.on('did-finish-load', () => {
        if (path) window.webContents.send('load-file', path)
    }).on('new-window', evt => {
        evt.preventDefault()
    })

    window.on('closed', () => {
        window = null
    })

    window.loadURL(`file://${__dirname}/../index.html`)

    if (setting.get('debug.dev_tools')) {
        window.openDevTools()
    }

    return window
}

function buildMenu(disableAll = false) {
    let template = require('./menu').clone()

    // Process menu items

    let processMenu = items => {
        items.forEach(item => {
            if ('click' in item) {
                item.click = () => {
                    let window = BrowserWindow.getFocusedWindow()
                    if (!window) return

                    window.webContents.send(`menu-click-${item.id}`)
                }
            }

            if ('clickMain' in item) {
                let key = item.clickMain

                item.click = () => ({
                    newWindow,
                    checkForUpdates: () => checkForUpdates(true)
                })[key]()

                delete item.clickMain
            }

            if ('checked' in item) {
                item.type = 'checkbox'
                item.checked = !!setting.get(item.checked)
            }

            if (disableAll && !item.enabled && !('submenu' in item || 'role' in item)) {
                item.enabled = false
            }

            if ('submenu' in item) {
                processMenu(item.submenu)
            }
        })
    }

    processMenu(template)

    // Build

    Menu.setApplicationMenu(Menu.buildFromTemplate(template))

    // Create dock menu

    if (process.platform === 'darwin') {
        app.dock.setMenu(Menu.buildFromTemplate([{
            label: 'New Window',
            click: () => newWindow()
        }]))
    }
}

function checkForUpdates(showFailDialogs) {
    updater.check(`yishn/${app.getName()}`, (err, info) => {
        if (err) return showFailDialogs && dialog.showMessageBox({
            type: 'warning',
            buttons: ['OK'],
            title: app.getName(),
            message: 'An error occurred when checking for updates.'
        })

        if (info.hasUpdates) {
            dialog.showMessageBox({
                type: 'info',
                buttons: ['Download Update', 'Not Now'],
                title: app.getName(),
                message: `${app.getName()} v${info.latestVersion} is available now.`,
                noLink: true,
                cancelId: 1
            }, response => response === 0 ? shell.openExternal(info.url) : null)
        } else if (showFailDialogs) {
            dialog.showMessageBox({
                type: 'info',
                buttons: ['OK'],
                title: 'No update available',
                message: `Sabaki v${app.getVersion()} is the latest version.`
            }, () => {})
        }
    })
}

ipcMain.on('new-window', (evt, ...args) => newWindow(...args))
ipcMain.on('build-menu', (evt, ...args) => buildMenu(...args))
ipcMain.on('check-for-updates', (evt, ...args) => checkForUpdates(...args))

app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit()
    } else {
        buildMenu(true)
    }
})

app.on('ready', () => {
    isReady = true

    if (!openfile && process.argv.length >= 2) {
        if (process.argv[0].slice(-'electron.exe'.length) !== 'electron.exe') {
            openfile = process.argv[1]
        } else if (process.argv.length >= 3) {
            openfile = process.argv[2]
        }
    }

    newWindow(openfile)

    if (setting.get('app.startup_check_updates')) {
        setTimeout(() => checkForUpdates(), setting.get('app.startup_check_updates_delay'))
    }
})

app.on('activate', (evt, hasVisibleWindows) => {
    if (!hasVisibleWindows) newWindow()
})

app.on('open-file', (evt, path) => {
    evt.preventDefault()

    if (!isReady) {
        openfile = path
    } else {
        newWindow(path)
    }
})

process.on('uncaughtException', err => {
    dialog.showErrorBox(`${app.getName()} v${app.getVersion()}`, [
        'Something weird happened. ',
        `${app.getName()} will shut itself down. `,
        'If possible, please report this on ',
        `${app.getName()}’s repository on GitHub.\n\n`,
        err.stack
    ].join(''))

    process.exit(1)
})
<commit_msg>Fix window icon in Linux<commit_after>const {app, shell, dialog, ipcMain, BrowserWindow, Menu} = require('electron')
const fs = require('fs')
const {join} = require('path')
const setting = require('./setting')
const updater = require('./updater')

let windows = []
let openfile = null
let isReady = false

function newWindow(path) {
    let window = new BrowserWindow({
        icon: process.platform === 'linux' ? join(__dirname, '..', 'logo.png') : null,
        title: app.getName(),
        useContentSize: true,
        width: setting.get('window.width'),
        height: setting.get('window.height'),
        minWidth: setting.get('window.minwidth'),
        minHeight: setting.get('window.minheight'),
        autoHideMenuBar: !setting.get('view.show_menubar'),
        backgroundColor: '#111111',
        show: false,
        webPreferences: {
            zoomFactor: setting.get('app.zoom_factor')
        }
    })

    windows.push(window)
    buildMenu()

    window.webContents.setAudioMuted(!setting.get('sound.enable'))
    window.webContents.on('did-finish-load', () => {
        if (path) window.webContents.send('load-file', path)
    }).on('new-window', evt => {
        evt.preventDefault()
    })

    window.on('closed', () => {
        window = null
    })

    window.loadURL(`file://${__dirname}/../index.html`)

    if (setting.get('debug.dev_tools')) {
        window.openDevTools()
    }

    return window
}

function buildMenu(disableAll = false) {
    let template = require('./menu').clone()

    // Process menu items

    let processMenu = items => {
        items.forEach(item => {
            if ('click' in item) {
                item.click = () => {
                    let window = BrowserWindow.getFocusedWindow()
                    if (!window) return

                    window.webContents.send(`menu-click-${item.id}`)
                }
            }

            if ('clickMain' in item) {
                let key = item.clickMain

                item.click = () => ({
                    newWindow,
                    checkForUpdates: () => checkForUpdates(true)
                })[key]()

                delete item.clickMain
            }

            if ('checked' in item) {
                item.type = 'checkbox'
                item.checked = !!setting.get(item.checked)
            }

            if (disableAll && !item.enabled && !('submenu' in item || 'role' in item)) {
                item.enabled = false
            }

            if ('submenu' in item) {
                processMenu(item.submenu)
            }
        })
    }

    processMenu(template)

    // Build

    Menu.setApplicationMenu(Menu.buildFromTemplate(template))

    // Create dock menu

    if (process.platform === 'darwin') {
        app.dock.setMenu(Menu.buildFromTemplate([{
            label: 'New Window',
            click: () => newWindow()
        }]))
    }
}

function checkForUpdates(showFailDialogs) {
    updater.check(`yishn/${app.getName()}`, (err, info) => {
        if (err) return showFailDialogs && dialog.showMessageBox({
            type: 'warning',
            buttons: ['OK'],
            title: app.getName(),
            message: 'An error occurred when checking for updates.'
        })

        if (info.hasUpdates) {
            dialog.showMessageBox({
                type: 'info',
                buttons: ['Download Update', 'Not Now'],
                title: app.getName(),
                message: `${app.getName()} v${info.latestVersion} is available now.`,
                noLink: true,
                cancelId: 1
            }, response => response === 0 ? shell.openExternal(info.url) : null)
        } else if (showFailDialogs) {
            dialog.showMessageBox({
                type: 'info',
                buttons: ['OK'],
                title: 'No update available',
                message: `Sabaki v${app.getVersion()} is the latest version.`
            }, () => {})
        }
    })
}

ipcMain.on('new-window', (evt, ...args) => newWindow(...args))
ipcMain.on('build-menu', (evt, ...args) => buildMenu(...args))
ipcMain.on('check-for-updates', (evt, ...args) => checkForUpdates(...args))

app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit()
    } else {
        buildMenu(true)
    }
})

app.on('ready', () => {
    isReady = true

    if (!openfile && process.argv.length >= 2) {
        if (process.argv[0].slice(-'electron.exe'.length) !== 'electron.exe') {
            openfile = process.argv[1]
        } else if (process.argv.length >= 3) {
            openfile = process.argv[2]
        }
    }

    newWindow(openfile)

    if (setting.get('app.startup_check_updates')) {
        setTimeout(() => checkForUpdates(), setting.get('app.startup_check_updates_delay'))
    }
})

app.on('activate', (evt, hasVisibleWindows) => {
    if (!hasVisibleWindows) newWindow()
})

app.on('open-file', (evt, path) => {
    evt.preventDefault()

    if (!isReady) {
        openfile = path
    } else {
        newWindow(path)
    }
})

process.on('uncaughtException', err => {
    dialog.showErrorBox(`${app.getName()} v${app.getVersion()}`, [
        'Something weird happened. ',
        `${app.getName()} will shut itself down. `,
        'If possible, please report this on ',
        `${app.getName()}’s repository on GitHub.\n\n`,
        err.stack
    ].join(''))

    process.exit(1)
})
<|endoftext|>


<commit_before>import 'babel-polyfill';
import 'whatwg-fetch';

import React from 'react';
import ReactDOM from 'react-dom';
import FastClick from 'fastclick';
import { ThemeProvider } from 'styled-components';
import 'semantic-ui-css/semantic.css';
import { Provider } from 'react-redux';
import configureStore from '../redux/configureStore';

import router from './router';
import history from './history';

const defaultTheme = {
  primary: 'palevioletred',
  secondary: 'white'
};

// Loaded with utils/routes-loader.js
let routes = require('./routes.json').default;

const container = document.getElementById('container');
const store = configureStore();

function renderComponent(component) {
  ReactDOM.render(
    <Provider store={store}>
      <ThemeProvider theme={defaultTheme}>
        {component}
      </ThemeProvider>
    </Provider>,
    container
  );
}

// Find and render a web page matching the current URL path, if such page is not found then render an error page (see
// routes.json, core/router.js)
function render(location) {
  router
    .resolve(routes, location)
    .then(renderComponent)
    .catch(error =>
      router.resolve(routes, { ...location, error }).then(renderComponent)
    );
}

// Handle client-side navigation by using HTML5 History API
// For more information visit https://github.com/ReactJSTraining/history/tree/master/docs#readme
history.listen(render);
render(history.location);

// Eliminates the 300ms delay between a physical tap
// and the firing of a click event on mobile browsers
// https://github.com/ftlabs/fastclick
FastClick.attach(document.body);

// Enable Hot Module Replacement (HMR)
if (module.hot) {
  module.hot.accept('./routes.json', () => {
    routes = require('./routes.json').default; // eslint-disable-line global-require
    render(history.location);
  });
}
<commit_msg>fix issue with react-hot-loader not working with react-router<commit_after>import 'babel-polyfill';
import 'whatwg-fetch';

import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import FastClick from 'fastclick';
import { ThemeProvider } from 'styled-components';
import 'semantic-ui-css/semantic.css';
import { Provider } from 'react-redux';
import configureStore from '../redux/configureStore';

import router from './router';
import history from './history';

const defaultTheme = {
  primary: 'palevioletred',
  secondary: 'white'
};

// Loaded with utils/routes-loader.js
let routes = require('./routes.json').default;

const container = document.getElementById('container');
const store = configureStore();

function renderComponent(component) {
  ReactDOM.render(
    <AppContainer>
      <Provider store={store}>
        <ThemeProvider theme={defaultTheme}>
          {component}
        </ThemeProvider>
      </Provider>
    </AppContainer>,
    container
  );
}

// Find and render a web page matching the current URL path, if such page is not found then render an error page (see
// routes.json, core/router.js)
function render(location) {
  router
    .resolve(routes, location)
    .then(renderComponent)
    .catch(error =>
      router.resolve(routes, { ...location, error }).then(renderComponent)
    );
}

// Handle client-side navigation by using HTML5 History API
// For more information visit https://github.com/ReactJSTraining/history/tree/master/docs#readme
history.listen(render);
render(history.location);

// Eliminates the 300ms delay between a physical tap
// and the firing of a click event on mobile browsers
// https://github.com/ftlabs/fastclick
FastClick.attach(document.body);

// Enable Hot Module Replacement (HMR)
if (module.hot) {
  module.hot.accept('./routes.json', () => {
    routes = require('./routes.json').default; // eslint-disable-line global-require
    render(history.location);
  });
}
<|endoftext|>


<commit_before>import _cloneDeep from 'lodash/fp/cloneDeep'
import _random from 'lodash/fp/random'

import React from 'react'
import ReactDOM from 'react-dom'

import pressed from 'pressed'
pressed.start()

import config from './config'
import pieces from './pieces'
import { detectCollision as detectMatrixCollision, getFullRows } from './matrixUtil'

import store from './store'
import * as score from './stores/score'
import * as lines from './stores/lines'
import * as level from './stores/level'
import * as fallRate from './stores/fallRate'
import * as nextPiece from './stores/nextPiece'
import * as currentPiece from './stores/currentPiece'
import * as board from './stores/board'
import * as gameState from './stores/gameState'

const getCurrentPiece = () => currentPiece.getCurrentPiece(store.getState())
const getBoard = () => board.getBoard(store.getState())
const getNextPiece = () => nextPiece.getNextPiece(store.getState())

import App from './containers/App'

const DOWN_KEYS = ['down', 's']
const LEFT_KEYS = ['left', 'a']
const RIGHT_KEYS = ['right', 'd']
const ROTATE_LEFT_KEYS = ['/', 'z']
const ROTATE_RIGHT_KEYS = ['shift']
const START_KEYS = ['enter']

let lateralMovementRate = null // Rate of pieces moving by user control in steps per second
let downMovementRate = null // Rate of pieces moving down by user control in steps per second
let timeSincePieceLastFell = 0 // time since the piece last moved down automatically
let lastFrameTime = 0 // previous frame's current time
let lastRightMove = 0
let lastLeftMove = 0
let lastDownMove = 0
let lastRotate = 0

// Main executable code:
reset()
window.requestAnimationFrame(onFrame)
ReactDOM.render(<App />, document.getElementById('app'))
//

function onFrame (currentTime) {
  update(currentTime)

  window.requestAnimationFrame(onFrame)
}

// Automatically pause when window is out of focus
window.onblur = (e) => {
  const currentGameState = gameState.getGameState(store.getState())
  if (currentGameState === gameState.GAME_STATE_RUNNING) {
    pauseGame()

    // Unpause when it comes back to focus (but not if the user manually paused)
    window.onfocus = (e) => {
      unpauseGame()
      window.onfocus = null
    }
  }
}

function reset () {
  timeSincePieceLastFell = 0
  lastFrameTime = 0
  lateralMovementRate = config.lateralMovementRate
  downMovementRate = config.downMovementRate

  store.dispatch(board.resetBoard())
  store.dispatch(score.resetScore())
  const {currentPiece: newCurrentPiece, nextPiece: randomNextPiece} = spawnNextAndCurrentPieces()
  store.dispatch(currentPiece.setCurrentPiece(newCurrentPiece))
  store.getState()
  store.dispatch(nextPiece.setNextPiece(randomNextPiece))

  store.dispatch(gameState.setGameState(gameState.GAME_STATE_RUNNING))
}

function pauseGame () {
  store.dispatch(gameState.setGameState(gameState.GAME_STATE_PAUSED))
}
function unpauseGame () {
  store.dispatch(gameState.setGameState(gameState.GAME_STATE_RUNNING))
}

function update (currentTime) {
  let deltaTime = currentTime - lastFrameTime
  lastFrameTime = currentTime
  let currentGameState = gameState.getGameState(store.getState())

  if (pressed.some(...START_KEYS)) {
    if (currentGameState === gameState.GAME_STATE_GAME_OVER) {
      reset()
    } else {
      currentGameState === gameState.GAME_STATE_PAUSED ? unpauseGame() : pauseGame()
    }
    pressed.remove(...START_KEYS)
  }

  currentGameState = gameState.getGameState(store.getState())
  if (currentGameState !== gameState.GAME_STATE_RUNNING) {
    return
  }

  if (!getCurrentPiece()) {
    const {currentPiece: newCurrentPiece, nextPiece: newNextPiece} = spawnNextAndCurrentPieces()
    store.dispatch(currentPiece.setCurrentPiece(newCurrentPiece))
    store.dispatch(nextPiece.setNextPiece(newNextPiece))
  }

  const lateralMovementThreshold = Math.ceil(1000 / lateralMovementRate)
  const downMovementThreshold = Math.ceil(1000 / downMovementRate)

  // Handle user input...
  if (pressed.some(...DOWN_KEYS)) {
    if (currentTime - lastDownMove > downMovementThreshold) {
      lastDownMove = currentTime

      if (config.instantDown) {
        while (!detectCollisionBelow(getBoard(), getCurrentPiece())) {
          timeSincePieceLastFell = 0
          makePieceFall(getCurrentPiece())
        }
        pressed.remove(...DOWN_KEYS)
      } else {
        timeSincePieceLastFell = 0
        makePieceFall(getCurrentPiece())
      }
    }
  } else {
    lastDownMove = 0
  }

  if (pressed.some(...LEFT_KEYS)) {
    if (currentTime - lastLeftMove > lateralMovementThreshold) {
      lastLeftMove = currentTime

      movePieceLeft(getCurrentPiece())
    }
  } else {
    lastLeftMove = 0
  }

  if (pressed.some(...RIGHT_KEYS)) {
    if (currentTime - lastRightMove > lateralMovementThreshold) {
      lastRightMove = currentTime
      movePieceRight(getCurrentPiece())
    }
  } else {
    lastRightMove = 0
  }

  if (pressed.some(...ROTATE_LEFT_KEYS, ...ROTATE_RIGHT_KEYS)) {
    if (currentTime - lastRotate > lateralMovementThreshold) {
      lastRotate = currentTime
      if (pressed.some(...ROTATE_LEFT_KEYS)) {
        rotatePieceLeft(getCurrentPiece())
      }
      if (pressed.some(...ROTATE_RIGHT_KEYS)) {
        rotatePieceRight(getCurrentPiece())
      }
    }
  } else {
    lastRotate = 0
  }

  timeSincePieceLastFell += deltaTime

  const currentFallRate = fallRate.getFallRate(store.getState())
  const stepThreshold = Math.ceil(1000 / currentFallRate)
  if (timeSincePieceLastFell > stepThreshold) {
    // console.log('tick')
    timeSincePieceLastFell = 0
    makePieceFall(getCurrentPiece())
  }

  if (detectCollision(getBoard(), getCurrentPiece())) {
    // console.log('Collision detected!')

    // This bit of foo allows you to shift the piece around a bit and only
    // detects collisions at the end of the step instead of at the beginning.
    const previousPositionPiece = _cloneDeep(getCurrentPiece())
    previousPositionPiece.y -= 1
    store.dispatch(board.mergePieceIntoBoard(previousPositionPiece))

    const {currentPiece: newCurrentPiece, nextPiece: newNextPiece} = spawnNextAndCurrentPieces()
    store.dispatch(currentPiece.setCurrentPiece(newCurrentPiece))
    store.dispatch(nextPiece.setNextPiece(newNextPiece))

    const currentLevel = level.getLevel(store.getState())
    store.dispatch(score.addPieceScore(currentLevel))

    const fullRowIndeces = getFullRows(getBoard())
    const numberOfClearedLines = fullRowIndeces ? fullRowIndeces.length : 0
    if (numberOfClearedLines > 0) {
      const currentLevel = level.getLevel(store.getState())
      store.dispatch(score.addClearedLineScore(numberOfClearedLines, currentLevel))
      store.dispatch(lines.incrementLines(numberOfClearedLines))
      store.dispatch(board.clearCompletedLines())
    }

    // If there is still a collision right after a new piece is spawned, the game ends.
    if (detectCollision(getBoard(), getCurrentPiece())) {
      console.error('Game over! Press ENTER to restart.')
      store.dispatch(gameState.setGameState(gameState.GAME_STATE_GAME_OVER))
    }
  }
}

function spawnNextAndCurrentPieces () {
  const [W] = config.boardSize
  let newCurrentPiece
  const nextPieceValue = getNextPiece()

  if (nextPieceValue) {
    newCurrentPiece = clonePiece(nextPieceValue)
  } else {
    newCurrentPiece = clonePiece(getRandomPiece())
  }
  newCurrentPiece.x = Math.floor((W - newCurrentPiece.matrix[0].length) / 2)

  const randomNextPiece = getRandomPiece()

  return {currentPiece: newCurrentPiece, nextPiece: randomNextPiece}
}

function clonePiece (piece) {
  let clonedPiece = _cloneDeep(piece)
  clonedPiece.x = clonedPiece.x || 0
  clonedPiece.y = clonedPiece.y || 0
  return clonedPiece
}

function getRandomPiece () {
  const l = pieces.length
  const i = _random(0, l - 1)
  return pieces[i]
}

function makePieceFall (piece) {
  store.dispatch(currentPiece.movePieceDown())
}

function movePieceLeft (piece) {
  store.dispatch(currentPiece.movePieceLeft(getBoard()))
}

function movePieceRight (piece) {
  store.dispatch(currentPiece.movePieceRight(getBoard()))
}

function rotatePieceRight (piece) {
  store.dispatch(currentPiece.rotateRight(getBoard()))
}

function rotatePieceLeft (piece) {
  store.dispatch(currentPiece.rotateLeft(getBoard()))
}

function detectCollision (board, piece) {
  if (!board) {
    throw new Error('"board" is not defined.')
  }
  if (!piece) {
    throw new Error('"piece" is not defined.')
  }
  const {x, y, matrix} = piece
  return detectMatrixCollision(board, matrix, x, y)
}

function detectCollisionBelow (board, {x, y, matrix: pieceMatrix}) {
  return detectMatrixCollision(board, pieceMatrix, x, y + 1)
}
<commit_msg>wrapped all getters in main<commit_after>import _cloneDeep from 'lodash/fp/cloneDeep'
import _random from 'lodash/fp/random'

import React from 'react'
import ReactDOM from 'react-dom'

import pressed from 'pressed'
pressed.start()

import config from './config'
import pieces from './pieces'
import { detectCollision as detectMatrixCollision, getFullRows } from './matrixUtil'

import store from './store'
import * as score from './stores/score'
import * as lines from './stores/lines'
import * as level from './stores/level'
import * as fallRate from './stores/fallRate'
import * as nextPiece from './stores/nextPiece'
import * as currentPiece from './stores/currentPiece'
import * as board from './stores/board'
import * as gameState from './stores/gameState'

const wrapGetter = getter => getter(store.getState())
const getCurrentPiece = () => wrapGetter(currentPiece.getCurrentPiece)
const getBoard = () => wrapGetter(board.getBoard)
const getNextPiece = () => wrapGetter(nextPiece.getNextPiece)
const getLevel = () => wrapGetter(level.getLevel)
const getFallRate = () => wrapGetter(fallRate.getFallRate)
const getGameState = () => wrapGetter(gameState.getGameState)

import App from './containers/App'

const DOWN_KEYS = ['down', 's']
const LEFT_KEYS = ['left', 'a']
const RIGHT_KEYS = ['right', 'd']
const ROTATE_LEFT_KEYS = ['/', 'z']
const ROTATE_RIGHT_KEYS = ['shift']
const START_KEYS = ['enter']

let lateralMovementRate = null // Rate of pieces moving by user control in steps per second
let downMovementRate = null // Rate of pieces moving down by user control in steps per second
let timeSincePieceLastFell = 0 // time since the piece last moved down automatically
let lastFrameTime = 0 // previous frame's current time
let lastRightMove = 0
let lastLeftMove = 0
let lastDownMove = 0
let lastRotate = 0

// Main executable code:
reset()
window.requestAnimationFrame(onFrame)
ReactDOM.render(<App />, document.getElementById('app'))
//

function onFrame (currentTime) {
  update(currentTime)

  window.requestAnimationFrame(onFrame)
}

// Automatically pause when window is out of focus
window.onblur = (e) => {
  const currentGameState = getGameState()
  if (currentGameState === gameState.GAME_STATE_RUNNING) {
    pauseGame()

    // Unpause when it comes back to focus (but not if the user manually paused)
    window.onfocus = (e) => {
      unpauseGame()
      window.onfocus = null
    }
  }
}

function reset () {
  timeSincePieceLastFell = 0
  lastFrameTime = 0
  lateralMovementRate = config.lateralMovementRate
  downMovementRate = config.downMovementRate

  store.dispatch(board.resetBoard())
  store.dispatch(score.resetScore())
  const {currentPiece: newCurrentPiece, nextPiece: randomNextPiece} = spawnNextAndCurrentPieces()
  store.dispatch(currentPiece.setCurrentPiece(newCurrentPiece))
  store.dispatch(nextPiece.setNextPiece(randomNextPiece))

  store.dispatch(gameState.setGameState(gameState.GAME_STATE_RUNNING))
}

function pauseGame () {
  store.dispatch(gameState.setGameState(gameState.GAME_STATE_PAUSED))
}
function unpauseGame () {
  store.dispatch(gameState.setGameState(gameState.GAME_STATE_RUNNING))
}

function update (currentTime) {
  let deltaTime = currentTime - lastFrameTime
  lastFrameTime = currentTime
  let currentGameState = getGameState()

  if (pressed.some(...START_KEYS)) {
    if (currentGameState === gameState.GAME_STATE_GAME_OVER) {
      reset()
    } else {
      currentGameState === gameState.GAME_STATE_PAUSED ? unpauseGame() : pauseGame()
    }
    pressed.remove(...START_KEYS)
  }

  currentGameState = getGameState()
  if (currentGameState !== gameState.GAME_STATE_RUNNING) {
    return
  }

  if (!getCurrentPiece()) {
    const {currentPiece: newCurrentPiece, nextPiece: newNextPiece} = spawnNextAndCurrentPieces()
    store.dispatch(currentPiece.setCurrentPiece(newCurrentPiece))
    store.dispatch(nextPiece.setNextPiece(newNextPiece))
  }

  const lateralMovementThreshold = Math.ceil(1000 / lateralMovementRate)
  const downMovementThreshold = Math.ceil(1000 / downMovementRate)

  // Handle user input...
  if (pressed.some(...DOWN_KEYS)) {
    if (currentTime - lastDownMove > downMovementThreshold) {
      lastDownMove = currentTime

      if (config.instantDown) {
        while (!detectCollisionBelow(getBoard(), getCurrentPiece())) {
          timeSincePieceLastFell = 0
          makePieceFall(getCurrentPiece())
        }
        pressed.remove(...DOWN_KEYS)
      } else {
        timeSincePieceLastFell = 0
        makePieceFall(getCurrentPiece())
      }
    }
  } else {
    lastDownMove = 0
  }

  if (pressed.some(...LEFT_KEYS)) {
    if (currentTime - lastLeftMove > lateralMovementThreshold) {
      lastLeftMove = currentTime

      movePieceLeft(getCurrentPiece())
    }
  } else {
    lastLeftMove = 0
  }

  if (pressed.some(...RIGHT_KEYS)) {
    if (currentTime - lastRightMove > lateralMovementThreshold) {
      lastRightMove = currentTime
      movePieceRight(getCurrentPiece())
    }
  } else {
    lastRightMove = 0
  }

  if (pressed.some(...ROTATE_LEFT_KEYS, ...ROTATE_RIGHT_KEYS)) {
    if (currentTime - lastRotate > lateralMovementThreshold) {
      lastRotate = currentTime
      if (pressed.some(...ROTATE_LEFT_KEYS)) {
        rotatePieceLeft(getCurrentPiece())
      }
      if (pressed.some(...ROTATE_RIGHT_KEYS)) {
        rotatePieceRight(getCurrentPiece())
      }
    }
  } else {
    lastRotate = 0
  }

  timeSincePieceLastFell += deltaTime

  const currentFallRate = getFallRate()
  const stepThreshold = Math.ceil(1000 / currentFallRate)
  if (timeSincePieceLastFell > stepThreshold) {
    // console.log('tick')
    timeSincePieceLastFell = 0
    makePieceFall(getCurrentPiece())
  }

  if (detectCollision(getBoard(), getCurrentPiece())) {
    // console.log('Collision detected!')

    // This bit of foo allows you to shift the piece around a bit and only
    // detects collisions at the end of the step instead of at the beginning.
    const previousPositionPiece = _cloneDeep(getCurrentPiece())
    previousPositionPiece.y -= 1
    store.dispatch(board.mergePieceIntoBoard(previousPositionPiece))

    const {currentPiece: newCurrentPiece, nextPiece: newNextPiece} = spawnNextAndCurrentPieces()
    store.dispatch(currentPiece.setCurrentPiece(newCurrentPiece))
    store.dispatch(nextPiece.setNextPiece(newNextPiece))

    const currentLevel = getLevel()
    store.dispatch(score.addPieceScore(currentLevel))

    const fullRowIndeces = getFullRows(getBoard())
    const numberOfClearedLines = fullRowIndeces ? fullRowIndeces.length : 0
    if (numberOfClearedLines > 0) {
      const currentLevel = getLevel()
      store.dispatch(score.addClearedLineScore(numberOfClearedLines, currentLevel))
      store.dispatch(lines.incrementLines(numberOfClearedLines))
      store.dispatch(board.clearCompletedLines())
    }

    // If there is still a collision right after a new piece is spawned, the game ends.
    if (detectCollision(getBoard(), getCurrentPiece())) {
      console.error('Game over! Press ENTER to restart.')
      store.dispatch(gameState.setGameState(gameState.GAME_STATE_GAME_OVER))
    }
  }
}

function spawnNextAndCurrentPieces () {
  const [W] = config.boardSize
  let newCurrentPiece
  const nextPieceValue = getNextPiece()

  if (nextPieceValue) {
    newCurrentPiece = clonePiece(nextPieceValue)
  } else {
    newCurrentPiece = clonePiece(getRandomPiece())
  }
  newCurrentPiece.x = Math.floor((W - newCurrentPiece.matrix[0].length) / 2)

  const randomNextPiece = getRandomPiece()

  return {currentPiece: newCurrentPiece, nextPiece: randomNextPiece}
}

function clonePiece (piece) {
  let clonedPiece = _cloneDeep(piece)
  clonedPiece.x = clonedPiece.x || 0
  clonedPiece.y = clonedPiece.y || 0
  return clonedPiece
}

function getRandomPiece () {
  const l = pieces.length
  const i = _random(0, l - 1)
  return pieces[i]
}

function makePieceFall (piece) {
  store.dispatch(currentPiece.movePieceDown())
}

function movePieceLeft (piece) {
  store.dispatch(currentPiece.movePieceLeft(getBoard()))
}

function movePieceRight (piece) {
  store.dispatch(currentPiece.movePieceRight(getBoard()))
}

function rotatePieceRight (piece) {
  store.dispatch(currentPiece.rotateRight(getBoard()))
}

function rotatePieceLeft (piece) {
  store.dispatch(currentPiece.rotateLeft(getBoard()))
}

function detectCollision (board, piece) {
  if (!board) {
    throw new Error('"board" is not defined.')
  }
  if (!piece) {
    throw new Error('"piece" is not defined.')
  }
  const {x, y, matrix} = piece
  return detectMatrixCollision(board, matrix, x, y)
}

function detectCollisionBelow (board, {x, y, matrix: pieceMatrix}) {
  return detectMatrixCollision(board, pieceMatrix, x, y + 1)
}
<|endoftext|>


<commit_before>define(function (require, exports, module) {

  const Plugin = require('extplug/Plugin');
  const request = require('extplug/util/request');
  const Style = require('extplug/util/Style');
  const _ = require('underscore');
  const $ = require('jquery');

  const ranks = [ 'subscriber',
                  'host', 'cohost', 'manager', 'bouncer', 'dj',
                  'admin', 'ambassador' ];

  const RoomStyles = Plugin.extend({
    name: 'Room Styles',
    description: 'Applies custom room-specific styles. ' +
                 'Supports both the plugCubed and Radiant Script formats.',

    init(id, ext) {
      this._super(id, ext);
      this.colors = this.colors.bind(this);
      this.css    = this.css.bind(this);
      this.images = this.images.bind(this);
      this.unload = this.unload.bind(this);
      this.reload = this.reload.bind(this);
    },

    enable() {
      this._super();
      this.all();

      this.ext.roomSettings.on('change', this.reload);
    },

    disable() {
      this._super();
      this.unload();
      this.ext.roomSettings.off('change', this.reload);
    },

    reload() {
      this.unload();
      this.all();
    },

    _normalizeRanks(ranks) {
      // plug³ and RCS have different names for Resident DJ colours and icons.
      // we simply use the plug.dj icon classname instead.
      if (ranks.rdj && !ranks.dj) ranks.dj = ranks.rdj;
      if (ranks.residentdj && !ranks.dj) ranks.dj = ranks.residentdj;
      // plug³ room styles have an `icons` sub-property on their `images`
      // properties, but RCS doesn't. so we don't particularly care if it's
      // there or not.
      if (ranks.icons) ranks.icons = this._normalizeRanks(ranks.icons);
      return ranks;
    },

    colors() {
      // plugCubed
      let colors = this.ext.roomSettings.get('colors');
      // Radiant
      let ccc = this.ext.roomSettings.get('ccc');

      let chatColors = colors && colors.chat || ccc;
      if (_.isObject(chatColors)) {
        let colorStyles = this.Style();

        chatColors = this._normalizeRanks(chatColors);
        ranks.forEach(level => {
          if (chatColors[level]) {
            let color = chatColors[level];
            if (color[0] !== '#') color = `#${color}`;
            let value = { color: `${color} !important` };
            colorStyles
              .set(`.role-${level} .un`, value)
              .set(`.role-${level} .name`, value)
              .set(`#user-rollover.role-${level} .role span`, value)
              .set(`#app .list.staff .group.${level} span`, value);
          }
        });
      }
    },

    css() {
      let css = this.ext.roomSettings.get('css');
      // plugCubed
      if (_.isObject(css)) {
        if (_.isObject(css.rule)) {
          this.Style(css.rule);
        }

        if (_.isArray(css.import)) {
          this._imports = $('<style>').text(
            css.import.map(url => `@import url(${url});`).join('\n')
          ).appendTo('head');
        }
      }
      // Radiant
      else if (_.isString(css)) {
        this._imports = $('<style>').text(`@import url(${css});`).appendTo('head');
      }
    },

    images() {
      let images = this.ext.roomSettings.get('images');
      if (_.isObject(images)) {
        let style = this.Style();
        if (images.background) {
          style.set({
            '.room-background': {
              'background-image': 'url(' + images.background + ') !important'
            }
          });
        }
        if (images.playback) {
          let playbackImg = this.$('#playback .background img');
          this._oldPlayback = playbackImg.attr('src');
          playbackImg.attr('src', images.playback);
        }
        if (images.booth) {
          style.set({
            '.extplug-booth': {
              'position': 'absolute',
              'width': '300px',
              'height': '100px',
              'left': '15px',
              'top': '135px',
              'z-index': -1
            }
          });
          this.$booth = $('<div />')
            // plug³ compatibility
            .attr('id', 'p3-dj-booth')
            .addClass('extplug-booth')
            .css({ 'background': 'url(' + images.booth + ') no-repeat center center' })
            .appendTo(this.$('#dj-booth'));
        }

        images = this._normalizeRanks(images);
        ranks.forEach(rank => {
          let url = images[rank] || images.icons && images.icons[rank];
          if (url) {
            let selector = `.icon.icon-chat-${rank}`
            // special-case cohosts, because they also have the "chat-host" icon
            // class sometimes
            if (rank === 'host' || rank === 'cohost') {
              selector += `, .role-${rank} .icon-chat-host`
            }
            style.set(selector, { background: `url(${url})` });
          }
        });
      }
    },

    all() {
      this.colors();
      this.css();
      this.images();
    },

    unload() {
      if (this.$booth) {
        this.$booth.remove();
        this.$booth = null;
      }
      if (this._oldPlayback) {
        this.$('#playback .background img').attr('src', this._oldPlayback);
        delete this._oldPlayback;
      }
      if (this._imports) {
        this._imports.remove();
        this._imports = null;
      }
      this.removeStyles();
    }

  });

  module.exports = RoomStyles;

});
<commit_msg>fix deprecations<commit_after>define(function (require, exports, module) {

  const Plugin = require('extplug/Plugin');
  const request = require('extplug/util/request');
  const Style = require('extplug/util/Style');
  const _ = require('underscore');
  const $ = require('jquery');

  const ranks = [ 'subscriber',
                  'host', 'cohost', 'manager', 'bouncer', 'dj',
                  'admin', 'ambassador' ];

  const RoomStyles = Plugin.extend({
    name: 'Room Styles',
    description: 'Applies custom room-specific styles. ' +
                 'Supports both the plugCubed and Radiant Script formats.',

    init(id, ext) {
      this._super(id, ext);
      this.colors = this.colors.bind(this);
      this.css    = this.css.bind(this);
      this.images = this.images.bind(this);
      this.unload = this.unload.bind(this);
      this.reload = this.reload.bind(this);
    },

    enable() {
      this._super();
      this.all();

      this.ext.roomSettings.on('change', this.reload);
    },

    disable() {
      this._super();
      this.unload();
      this.ext.roomSettings.off('change', this.reload);
    },

    reload() {
      this.unload();
      this.all();
    },

    _normalizeRanks(ranks) {
      // plug³ and RCS have different names for Resident DJ colours and icons.
      // we simply use the plug.dj icon classname instead.
      if (ranks.rdj && !ranks.dj) ranks.dj = ranks.rdj;
      if (ranks.residentdj && !ranks.dj) ranks.dj = ranks.residentdj;
      // plug³ room styles have an `icons` sub-property on their `images`
      // properties, but RCS doesn't. so we don't particularly care if it's
      // there or not.
      if (ranks.icons) ranks.icons = this._normalizeRanks(ranks.icons);
      return ranks;
    },

    colors() {
      // plugCubed
      let colors = this.ext.roomSettings.get('colors');
      // Radiant
      let ccc = this.ext.roomSettings.get('ccc');

      let chatColors = colors && colors.chat || ccc;
      if (_.isObject(chatColors)) {
        let colorStyles = this.createStyle();

        chatColors = this._normalizeRanks(chatColors);
        ranks.forEach(level => {
          if (chatColors[level]) {
            let color = chatColors[level];
            if (color[0] !== '#') color = `#${color}`;
            let value = { color: `${color} !important` };
            colorStyles
              .set(`.role-${level} .un`, value)
              .set(`.role-${level} .name`, value)
              .set(`#user-rollover.role-${level} .role span`, value)
              .set(`#app .list.staff .group.${level} span`, value);
          }
        });
      }
    },

    css() {
      let css = this.ext.roomSettings.get('css');
      // plugCubed
      if (_.isObject(css)) {
        if (_.isObject(css.rule)) {
          this.createStyle(css.rule);
        }

        if (_.isArray(css.import)) {
          this._imports = $('<style>').text(
            css.import.map(url => `@import url(${url});`).join('\n')
          ).appendTo('head');
        }
      }
      // Radiant
      else if (_.isString(css)) {
        this._imports = $('<style>').text(`@import url(${css});`).appendTo('head');
      }
    },

    images() {
      let images = this.ext.roomSettings.get('images');
      if (_.isObject(images)) {
        let style = this.createStyle();
        if (images.background) {
          style.set({
            '.room-background': {
              'background-image': 'url(' + images.background + ') !important'
            }
          });
        }
        if (images.playback) {
          let playbackImg = $('#playback .background img');
          this._oldPlayback = playbackImg.attr('src');
          playbackImg.attr('src', images.playback);
        }
        if (images.booth) {
          style.set({
            '.extplug-booth': {
              'position': 'absolute',
              'width': '300px',
              'height': '100px',
              'left': '15px',
              'top': '135px',
              'z-index': -1
            }
          });
          this.$booth = $('<div />')
            // plug³ compatibility
            .attr('id', 'p3-dj-booth')
            .addClass('extplug-booth')
            .css({ 'background': 'url(' + images.booth + ') no-repeat center center' })
            .appendTo($('#dj-booth'));
        }

        images = this._normalizeRanks(images);
        ranks.forEach(rank => {
          let url = images[rank] || images.icons && images.icons[rank];
          if (url) {
            let selector = `.icon.icon-chat-${rank}`
            // special-case cohosts, because they also have the "chat-host" icon
            // class sometimes
            if (rank === 'host' || rank === 'cohost') {
              selector += `, .role-${rank} .icon-chat-host`
            }
            style.set(selector, { background: `url(${url})` });
          }
        });
      }
    },

    all() {
      this.colors();
      this.css();
      this.images();
    },

    unload() {
      if (this.$booth) {
        this.$booth.remove();
        this.$booth = null;
      }
      if (this._oldPlayback) {
        $('#playback .background img').attr('src', this._oldPlayback);
        delete this._oldPlayback;
      }
      if (this._imports) {
        this._imports.remove();
        this._imports = null;
      }
      this.removeStyles();
    }

  });

  module.exports = RoomStyles;

});
<|endoftext|>


<commit_before>'use strict';

// Regular expression to validate the requested date format
const request = require('./lib/reports-request');
const parse = require('./lib/reports-parse');
const co = require('co');

// Default options, which determine the data attributes to request and return
const defaultOptions = {
	tornado: true,
    hail: true,
	windDamage: true,
	gust: false,
	blizzard: false,
	freezingRain: false,
	heavySnow: false,
	iceStorm: false,
	sleet: false,
	snow: false,
	wildfire: false
};

module.exports = function requestReports(date, userOptions) {
	return co(function* gatherReports() {
		let productOptions = Object.assign({}, defaultOptions);

		if (userOptions) {
			productOptions = Object.assign(productOptions, userOptions);
		}

        let response = yield request(date);
		return parse(response.body, productOptions);
	}).catch(function errorHandler(err) {
		console.error(err.stack);
		throw(err);
	});
};
<commit_msg>Fix formatting<commit_after>'use strict';

// Regular expression to validate the requested date format
const request = require('./lib/reports-request');
const parse = require('./lib/reports-parse');
const co = require('co');

// Default options, which determine the data attributes to request and return
const defaultOptions = {
	tornado: true,
	hail: true,
	windDamage: true,
	gust: false,
	blizzard: false,
	freezingRain: false,
	heavySnow: false,
	iceStorm: false,
	sleet: false,
	snow: false,
	wildfire: false
};

module.exports = function requestReports(date, userOptions) {
	return co(function* gatherReports() {
		let productOptions = Object.assign({}, defaultOptions);

		if (userOptions) {
			productOptions = Object.assign(productOptions, userOptions);
		}

		let response = yield request(date);
		return parse(response.body, productOptions);
	}).catch(function errorHandler(err) {
		console.error(err.stack);
		throw(err);
	});
};
<|endoftext|>


<commit_before>var world = require('./world');
var Display = require('./graphics');

var source1 = [
  "while (true) {",
  "  if (this.hasFood() === false) {",
  "    var t = Math.random();",
  "    if (t < 0.25) {",
  "      this.moveDigDown();",
  "    } else if (t < 0.5) {",
  "      this.moveDigUp();",
  "    } else if (t < 0.75) {",
  "      this.moveDigRight();",
  "    } else {",
  "      this.moveDigLeft();",
  "    }",
  "  } else {",
  "    if (this.homeLocation().x > this.location().x) {",
  "      this.moveDigRight();",
  "    }",
  "    if (this.homeLocation().x < this.location().x) {",
  "      this.moveDigLeft();",
  "    }",
  "    if (this.homeLocation().y > this.location().y) {",
  "      this.moveDigDown();",
  "    }",
  "    if (this.homeLocation().y < this.location().y) {",
  "      this.moveDigUp();",
  "    }",
  "  }",
  "}"
].join('\n');
var source2 = source1;

var display = new Display(document.getElementById("canvas"));

var theWorld = new world.World(source1, source2);

var requestAnimFrame =
       window.requestAnimationFrame ||
       window.webkitRequestAnimationFrame ||
       window.mozRequestAnimationFrame ||
       window.oRequestAnimationFrame ||
       window.msRequestAnimationFrame ||
       function(callback) {
         window.setTimeout(callback, 2000);
       };

// function callback() {
//   theWorld.step();
//   display.render(theWorld);
//
//   requestAnimFrame(callback);
// };
// callback();

var tc = document.getElementById("tickCounter");
var tlcount = document.getElementById("tlCount");
var brcount = document.getElementById("brCount");
var tickNum = 0;

setInterval(function() {
  theWorld.step();
  display.render(theWorld);

  tickNum++;
  tc.textContent = tickNum;
  tlcount.textContent = theWorld.tl.ants.length;
  brcount.textContent = theWorld.br.ants.length;

  // TODO(michael): Check for win conditions etc
}, 100);
<commit_msg>markov AI<commit_after>var world = require('./world');
var Display = require('./graphics');

var source1 = [
  "var t = Math.random();",
  "while (true) {",
  "  if (this.hasFood() === false) {",
  " if(Math.random()>.95)  t = Math.random();",
  "    if (t < 0.25) {",
  "      this.moveDigDown();",
  "    } else if (t < 0.5) {",
  "      this.moveDigUp();",
  "    } else if (t < 0.75) {",
  "      this.moveDigRight();",
  "    } else {",
  "      this.moveDigLeft();",
  "    }",
  "  } else {",
  "    if (this.homeLocation().x > this.location().x) {",
  "      this.moveDigRight();",
  "    }",
  "    if (this.homeLocation().x < this.location().x) {",
  "      this.moveDigLeft();",
  "    }",
  "    if (this.homeLocation().y > this.location().y) {",
  "      this.moveDigDown();",
  "    }",
  "    if (this.homeLocation().y < this.location().y) {",
  "      this.moveDigUp();",
  "    }",
  "  }",
  "}"
].join('\n');
var source2 = ["while (true) {",
  "  if (this.hasFood() === false) {",
  "    var t = Math.random();",
  "    if (t < 0.25) {",
  "      this.moveDigDown();",
  "    } else if (t < 0.5) {",
  "      this.moveDigUp();",
  "    } else if (t < 0.75) {",
  "      this.moveDigRight();",
  "    } else {",
  "      this.moveDigLeft();",
  "    }",
  "  } else {",
  "    if (this.homeLocation().x > this.location().x) {",
  "      this.moveDigRight();",
  "    }",
  "    if (this.homeLocation().x < this.location().x) {",
  "      this.moveDigLeft();",
  "    }",
  "    if (this.homeLocation().y > this.location().y) {",
  "      this.moveDigDown();",
  "    }",
  "    if (this.homeLocation().y < this.location().y) {",
  "      this.moveDigUp();",
  "    }",
  "  }",
  "}"
].join('\n');

var display = new Display(document.getElementById("canvas"));

var theWorld = new world.World(source1, source2);

var requestAnimFrame =
       window.requestAnimationFrame ||
       window.webkitRequestAnimationFrame ||
       window.mozRequestAnimationFrame ||
       window.oRequestAnimationFrame ||
       window.msRequestAnimationFrame ||
       function(callback) {
         window.setTimeout(callback, 2000);
       };

// function callback() {
//   theWorld.step();
//   display.render(theWorld);
//
//   requestAnimFrame(callback);
// };
// callback();

var tc = document.getElementById("tickCounter");
var tlcount = document.getElementById("tlCount");
var brcount = document.getElementById("brCount");
var tickNum = 0;

setInterval(function() {
  theWorld.step();
  display.render(theWorld);

  tickNum++;
  tc.textContent = tickNum;
  tlcount.textContent = theWorld.tl.ants.length;
  brcount.textContent = theWorld.br.ants.length;

  // TODO(michael): Check for win conditions etc
}, 100);
<|endoftext|>


<commit_before>/* multi_tab_singleton main */
/*global window */
/*global ObjectObserver */
/**
 * @param name unique name for that object to be referenced
 * @param obj future singleton object
 * @param options {
 *   heartBeat : 100 in milliseconds
 * }
 * @returns object with same fields that is singleton across tabs
 */
var MultiTabSingleton = function(name, obj, optionsParam) {
  var __toString = Object.prototype.toString;

  var defaultOptions = {
    heartBeat : 50,
    heartBeatTimeout : 10
  };

  if (optionsParam == null) {
    optionsParam = {}
  }

  var options = {};
  for ( var o in defaultOptions) {
    options[o] = optionsParam[o] || defaultOptions[o];
  }
  var clone = function(source, destination) {
    for(var property in source) {
      if(typeof source[property] === "object" && source[property] !== null && destination[property]) {
        clone(destination[property], source[property]);
      } else {
        destination[property] = source[property];
      }
    }
  };

  var substituteFunctionsInObject = function(obj, fn, iterPosParam) {
    var ret, objType, iterPos = iterPosParam || [],
      i = 0;

    if(__toString.call(obj) === '[object Array]') {
      obj.forEach(function(item) {
        objType = __toString.call(item);
        if((objType === "[object Object]") || (objType === "[object Array]")) {
          ret = substituteFunctionsInObject(item, fn, iterPos.concat(i));
        } else {
          ret = fn(iterPos.concat(i), item, obj);
        }
        if(ret) {
          obj[i] = ret;
        }
        i++;
      });
    }

    for(var key in obj) {
      if(!("" + key).match(/^\d+$/)) {
        objType = __toString.call(obj[key]);
        if((objType === "[object Object]") || (objType === "[object Array]")) {
          ret = substituteFunctionsInObject(obj[key], fn, iterPos.concat(key));
        } else {
          ret = fn(iterPos.concat(key), obj[key], obj);
        }
        if(ret) {
          obj[key] = ret;
        }
      }
    }
    return obj;
  };

  var store = {
    _s: null,
    init: function() {
      this._s = window.$.jStorage || window.jStorage;
      if(this._s == null) {
        throw new ReferenceError("Error referencing jStorage, include jStorage or use full distribution");
      }
    },
    get: function(key, defaultValue) {
      return this._s.get(key, defaultValue);
    },
    set: function(key, value) {
      this._s.set(key, value);
    },
    subscribe: function(key,callback) {
      this._s.listenKeyChange(key,callback)
    }
  };
  store.init();

  if(ObjectObserver === null) {
    throw new ReferenceError("observer-js is not exists");
  }

  var observer = {
    _o: null,
    _obj: null,
    init: function(objToObserve) {
      if (objToObserve === null) {
        throw new ReferenceError("Object should not be null");
      }
      this._obj = objToObserve;

      if(ObjectObserver === null) {
        throw new ReferenceError("observer-js is not exists");
      }
      this._o = new ObjectObserver(this._obj);
      var self = this;
      this._o.open(function(added, removed, changed, getOldValueFn) {
        self._obj.saveValues();
      });
    },
    close : function() {
      this._o.close();
    }
  }
  var api = {
    master: false,
    lastAccessedTime: new Date().getTime()
  };

  //We also need to negotiate master/slave configuration
  var negotiateMasterSlave = function() {
    //let`s check participants section in store, if there are idle participants
    //We delete them and becomes master, if there are live one we becomes slave
    var participants = store.get(name+'_participants', [])
    var liveParticipants = []
    for(var p in participants) {
      var part = participants[p];
      var currentTimestamp = new Date().getTime();
      if(part.lastAccessedTime > currentTimestamp - options.heartBeat) {
        liveParticipants.push(part)
      }
    }

    //We need to check weather there is any master in live list
    var master = null;

    for(var lp in liveParticipants) {
      var liveP = liveParticipants[lp];
      if(liveP.master && master) {
        liveP.master = false;
      } else if(liveP.master) {
        master = liveP;
      }
    }

    if(master === null) {
      //then we could become master
      api.master = true;
    }

    api.lastAccessedTime = new Date().getTime();

    liveParticipants.push(api)
    store.set(name+'_participants', liveParticipants);
    return api;
  };

  //also we need to mock all functions we will find in that object
  //so we can execute function only on master singleton object
  obj = substituteFunctionsInObject(obj, function(path, item, parent) {});
  api = negotiateMasterSlave();

  obj.loadValues = function() {
    var storedObj = store.get(name + "_value");
    //
    if(storedObj) {
      clone(storedObj, this);
    }
  };

  obj.saveValues = function() {
    store.set(name + "_value", this)
  };

  obj.subscribe = function() {
    var self = this;
    store.subscribe(name + "_value", function(key, action){
      self.loadValues();
    })
  }

  obj.substituteFunctionsInObject = substituteFunctionsInObject;
  obj.api = api;

  if (obj.api.master) {
    obj.saveValues();
  }

  obj.loadValues();
  observer.init(obj);
  //setup periodic interval to propagate changes and function calls if object observe doesn`t exists
  if (Object.observe == null) {
    var timeoutCallback = function() {
      Platform.performMicrotaskCheckpoint();
      api = negotiateMasterSlave();
      setTimeout(timeoutCallback, options.heartBeatTimeout);
    }
    timeoutCallback();
  }
  obj.subscribe();
  return observer._obj;
};
// Version.
MultiTabSingleton.VERSION = '0.0.1';
// Export to the root, which is probably `window`.
root.MultiTabSingleton = MultiTabSingleton;<commit_msg>Added additional fields<commit_after>/* multi_tab_singleton main */
/*global window */
/*global ObjectObserver */
/**
 * @param name unique name for that object to be referenced
 * @param obj future singleton object
 * @param options {
 *   heartBeat : 100 in milliseconds
 * }
 * @returns object with same fields that is singleton across tabs
 */
var MultiTabSingleton = function(name, obj, optionsParam) {
  var __toString = Object.prototype.toString;

  var defaultOptions = {
    heartBeat : 50,
    heartBeatTimeout : 10,
    forseMaster : false,
    singletonFunctionExecution : true
  };

  if (optionsParam == null) {
    optionsParam = {}
  }

  var options = {};
  for ( var o in defaultOptions) {
    options[o] = optionsParam[o] || defaultOptions[o];
  }
  var clone = function(source, destination) {
    for(var property in source) {
      if(typeof source[property] === "object" && source[property] !== null && destination[property]) {
        clone(destination[property], source[property]);
      } else {
        destination[property] = source[property];
      }
    }
  };

  var substituteFunctionsInObject = function(obj, fn, iterPosParam) {
    var ret, objType, iterPos = iterPosParam || [],
      i = 0;

    if(__toString.call(obj) === '[object Array]') {
      obj.forEach(function(item) {
        objType = __toString.call(item);
        if((objType === "[object Object]") || (objType === "[object Array]")) {
          ret = substituteFunctionsInObject(item, fn, iterPos.concat(i));
        } else {
          ret = fn(iterPos.concat(i), item, obj);
        }
        if(ret) {
          obj[i] = ret;
        }
        i++;
      });
    }

    for(var key in obj) {
      if(!("" + key).match(/^\d+$/)) {
        objType = __toString.call(obj[key]);
        if((objType === "[object Object]") || (objType === "[object Array]")) {
          ret = substituteFunctionsInObject(obj[key], fn, iterPos.concat(key));
        } else {
          ret = fn(iterPos.concat(key), obj[key], obj);
        }
        if(ret) {
          obj[key] = ret;
        }
      }
    }
    return obj;
  };

  var store = {
    _s: null,
    init: function() {
      this._s = window.$.jStorage || window.jStorage;
      if(this._s == null) {
        throw new ReferenceError("Error referencing jStorage, include jStorage or use full distribution");
      }
    },
    get: function(key, defaultValue) {
      return this._s.get(key, defaultValue);
    },
    set: function(key, value) {
      this._s.set(key, value);
    },
    subscribe: function(key,callback) {
      this._s.listenKeyChange(key,callback)
    }
  };
  store.init();

  if(ObjectObserver === null) {
    throw new ReferenceError("observer-js is not exists");
  }

  var observer = {
    _o: null,
    _obj: null,
    init: function(objToObserve) {
      if (objToObserve === null) {
        throw new ReferenceError("Object should not be null");
      }
      this._obj = objToObserve;

      if(ObjectObserver === null) {
        throw new ReferenceError("observer-js is not exists");
      }
      this._o = new ObjectObserver(this._obj);
      var self = this;
      this._o.open(function(added, removed, changed, getOldValueFn) {
        self._obj.saveValues();
      });
    },
    close : function() {
      this._o.close();
    }
  }
  var api = {
    master: false,
    lastAccessedTime: new Date().getTime()
  };

  //We also need to negotiate master/slave configuration
  var negotiateMasterSlave = function() {
    //let`s check participants section in store, if there are idle participants
    //We delete them and becomes master, if there are live one we becomes slave
    var participants = store.get(name+'_participants', [])
    var liveParticipants = []
    for(var p in participants) {
      var part = participants[p];
      var currentTimestamp = new Date().getTime();
      if(part.lastAccessedTime > currentTimestamp - options.heartBeat) {
        liveParticipants.push(part)
      }
    }

    //We need to check weather there is any master in live list
    var master = null;

    for(var lp in liveParticipants) {
      var liveP = liveParticipants[lp];
      if(liveP.master && master) {
        liveP.master = false;
      } else if(liveP.master) {
        master = liveP;
      }
    }

    if(master === null) {
      //then we could become master
      api.master = true;
    }

    api.lastAccessedTime = new Date().getTime();

    liveParticipants.push(api)
    store.set(name+'_participants', liveParticipants);
    return api;
  };

  //also we need to mock all functions we will find in that object
  //so we can execute function only on master singleton object
  obj = substituteFunctionsInObject(obj, function(path, item, parent) {});
  api = negotiateMasterSlave();

  obj.loadValues = function() {
    var storedObj = store.get(name + "_value");
    //
    if(storedObj) {
      clone(storedObj, this);
    }
  };

  obj.saveValues = function() {
    store.set(name + "_value", this)
  };

  obj.subscribe = function() {
    var self = this;
    store.subscribe(name + "_value", function(key, action){
      self.loadValues();
    })
  }

  obj.substituteFunctionsInObject = substituteFunctionsInObject;
  obj.api = api;

  if (obj.api.master) {
    obj.saveValues();
  }

  obj.loadValues();
  observer.init(obj);
  //setup periodic interval to propagate changes and function calls if object observe doesn`t exists
  if (Object.observe == null) {
    var timeoutCallback = function() {
      Platform.performMicrotaskCheckpoint();
      api = negotiateMasterSlave();
      setTimeout(timeoutCallback, options.heartBeatTimeout);
    }
    timeoutCallback();
  }
  obj.subscribe();
  return observer._obj;
};
// Version.
MultiTabSingleton.VERSION = '0.0.1';
// Export to the root, which is probably `window`.
root.MultiTabSingleton = MultiTabSingleton;<|endoftext|>