<commit_before>module.exports = {
    "name": "scenario3",
    "title": "A Dedication to the Island Gods",
    "description": "You will need to go on Sea Expeditions now. Also, due to \
        the Desert in Region 4, you will need to build up enough Tribes to \
        sail to Region 4 and build the Monolith before upkeep occurs.",
    "goal": "In Region 4, build a Huge Monolith of Impressiveness before the end of Era 5. \
    (NOTE! Wonders not yet implemented!)",
    'end_of_era.post': function(ctx) {
        console.log("Check for winning conditions")
        var engine = this;
        // End of era 5
        if (engine.era == 6)
        {
            engine.gameOver(false, "Do you have the monolith?");
            ctx.done && ctx.done();
        }
    },
    "map": {
        "areas": {
            "1": {
                "id": 1,
                "desert": true,
                "neighbours": [ 3, 'sea', 'frontier'],
            },
            "2": {
                "id": 2,
                "tribes": 1,
                "desert": true,
                "forest": true,
                "neighbours": [ 7, 'sea'],
            },
            "3": {
                "id": 3,
                "mountain": true,
                "forest": true,
                "neighbours": [ 1, 5, 'sea', 'frontier'],
            },
            "4": {
                "id": 4,
                "desert": true,
                "neighbours": [ 'sea', 'frontier']
            },
            "5": {
                "id": 5,
                "forest": true,
                "neighbours": [ 3, 'sea', 'frontier'],
            },
            "7": {
                "id": 7,
                "tribes": 4,
                "city": 1,
                "forest": true,
                "mountain": true,
                "farm": true,
                "neighbours": [ 2, 'sea' ]
            },
        },
        "width": 11,
        "height": 11,
        "grid":[
        [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ],
        [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ],
        [-1,-1, 7,-1,-1,-1,-1,-1,-1,-1,-1 ],
        [-1,-1, 2, 7,-1,-1,-1,-1,-1,-1,-1 ],
        [-1, 2, 2, 7, 7,-1,-1,-1,-1,-1,-1 ],
        [-1,-1,-1, 2,-1,-1,-1, 4,-1,-1,-1 ],
        [-1,-1,-1,-1,-1,-1, 0, 4, 4, 0, 0 ],
        [-1,-1, 5, 5, 3, 1, 0, 0, 4, 0, 0 ],
        [-1,-1, 0, 5, 3, 3, 1, 0, 0, 0, 0 ],
        [-1, 0, 0, 0, 5, 3, 1, 1, 0, 0, 0 ],
        [-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
        [-1,-1,-1,-1,-1, 0, 0, 0, 0, 0, 0 ],
        [-1,-1,-1,-1,-1,-1, 0, 0, 0, 0, 0 ],
        [-1,-1,-1,-1,-1,-1, 0, 0, 0, 0, 0 ],
        [-1,-1,-1,-1,-1,-1, 0, 0, 0, 0, 0 ],
        ]
    }
}<commit_msg>Scenario 3 goal updated<commit_after>module.exports = {
    "name": "scenario3",
    "title": "A Dedication to the Island Gods",
    "description": "You will need to go on Sea Expeditions now. Also, due to \
        the Desert in Region 4, you will need to build up enough Tribes to \
        sail to Region 4 and build the Monolith before upkeep occurs.",
    "goal": "In Region 4, build a Huge Monolith of Impressiveness before the end of Era 5.",
    'end_of_era.post': function(ctx) {
        console.log("Check for winning conditions")
        var engine = this;
        // End of era 5
        if (engine.era == 6)
        {
            engine.gameOver(false, "Do you have the monolith?");
            ctx.done && ctx.done();
        }
    },
    "map": {
        "areas": {
            "1": {
                "id": 1,
                "desert": true,
                "neighbours": [ 3, 'sea', 'frontier'],
            },
            "2": {
                "id": 2,
                "tribes": 1,
                "desert": true,
                "forest": true,
                "neighbours": [ 7, 'sea'],
            },
            "3": {
                "id": 3,
                "mountain": true,
                "forest": true,
                "neighbours": [ 1, 5, 'sea', 'frontier'],
            },
            "4": {
                "id": 4,
                "desert": true,
                "neighbours": [ 'sea', 'frontier']
            },
            "5": {
                "id": 5,
                "forest": true,
                "neighbours": [ 3, 'sea', 'frontier'],
            },
            "7": {
                "id": 7,
                "tribes": 4,
                "city": 1,
                "forest": true,
                "mountain": true,
                "farm": true,
                "neighbours": [ 2, 'sea' ]
            },
        },
        "width": 11,
        "height": 11,
        "grid":[
        [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ],
        [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ],
        [-1,-1, 7,-1,-1,-1,-1,-1,-1,-1,-1 ],
        [-1,-1, 2, 7,-1,-1,-1,-1,-1,-1,-1 ],
        [-1, 2, 2, 7, 7,-1,-1,-1,-1,-1,-1 ],
        [-1,-1,-1, 2,-1,-1,-1, 4,-1,-1,-1 ],
        [-1,-1,-1,-1,-1,-1, 0, 4, 4, 0, 0 ],
        [-1,-1, 5, 5, 3, 1, 0, 0, 4, 0, 0 ],
        [-1,-1, 0, 5, 3, 3, 1, 0, 0, 0, 0 ],
        [-1, 0, 0, 0, 5, 3, 1, 1, 0, 0, 0 ],
        [-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
        [-1,-1,-1,-1,-1, 0, 0, 0, 0, 0, 0 ],
        [-1,-1,-1,-1,-1,-1, 0, 0, 0, 0, 0 ],
        [-1,-1,-1,-1,-1,-1, 0, 0, 0, 0, 0 ],
        [-1,-1,-1,-1,-1,-1, 0, 0, 0, 0, 0 ],
        ]
    }
}<|endoftext|>


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

const jsBuilder = require('./JSBuilder');
const mdBuilder = require('./MDBuilder');
const Documentation = require('./Documentation');
const Message = require('../Message');

const EXCLUDE_CLASSES = new Set([
  'Connection',
  'EmulationManager',
  'FrameManager',
  'Helper',
  'Launcher',
  'Multimap',
  'NavigatorWatcher',
  'NetworkManager',
  'ProxyStream',
  'Session',
  'TaskQueue',
  'WaitTask',
]);

const EXCLUDE_METHODS = new Set([
  'Body.constructor',
  'Browser.constructor',
  'ConsoleMessage.constructor',
  'Dialog.constructor',
  'ElementHandle.constructor',
  'Frame.constructor',
  'Headers.constructor',
  'Headers.fromPayload',
  'Keyboard.constructor',
  'Mouse.constructor',
  'Touchscreen.constructor',
  'Tracing.constructor',
  'Page.constructor',
  'Page.create',
  'Request.constructor',
  'Response.constructor',
]);

/**
 * @param {!Page} page
 * @param {!Array<!Source>} mdSources
 * @param {!Array<!Source>} jsSources
 * @return {!Promise<!Array<!Message>>}
 */
module.exports = async function lint(page, mdSources, jsSources) {
  const mdResult = await mdBuilder(page, mdSources);
  const jsResult = await jsBuilder(jsSources);
  const jsDocumentation = filterJSDocumentation(jsResult.documentation);
  const mdDocumentation = mdResult.documentation;

  const jsErrors = jsResult.errors;
  jsErrors.push(...checkDuplicates(jsDocumentation));

  const mdErrors = mdResult.errors;
  mdErrors.push(...compareDocumentations(mdDocumentation, jsDocumentation));
  mdErrors.push(...checkDuplicates(mdDocumentation));
  mdErrors.push(...checkSorting(mdDocumentation));

  // Push all errors with proper prefixes
  const errors = jsErrors.map(error => '[JavaScript] ' + error);
  errors.push(...mdErrors.map(error => '[MarkDown] ' + error));
  return errors.map(error => Message.error(error));
};

/**
 * @param {!Documentation} doc
 * @return {!Array<string>}
 */
function checkSorting(doc) {
  const errors = [];
  for (const cls of doc.classesArray) {
    const members = cls.membersArray;

    // Events should go first.
    let eventIndex = 0;
    for (; eventIndex < members.length && members[eventIndex].type === 'event'; ++eventIndex);
    for (; eventIndex < members.length && members[eventIndex].type !== 'event'; ++eventIndex);
    if (eventIndex < members.length)
      errors.push(`Events should go first. Event '${members[eventIndex].name}' in class ${cls.name} breaks order`);

    // Constructor should be right after events and before all other members.
    const constructorIndex = members.findIndex(member => member.type === 'method' && member.name === 'constructor');
    if (constructorIndex > 0 && members[constructorIndex - 1].type !== 'event')
      errors.push(`Constructor of ${cls.name} should go before other methods`);

    // Events should be sorted alphabetically.
    for (let i = 0; i < members.length - 1; ++i) {
      const member1 = cls.membersArray[i];
      const member2 = cls.membersArray[i + 1];
      if (member1.type !== 'event' || member2.type !== 'event')
        continue;
      if (member1.name > member2.name)
        errors.push(`Event '${member1.name}' in class ${cls.name} breaks alphabetic ordering of events`);
    }

    // All other members should be sorted alphabetically.
    for (let i = 0; i < members.length - 1; ++i) {
      const member1 = cls.membersArray[i];
      const member2 = cls.membersArray[i + 1];
      if (member1.type === 'event' || member2.type === 'event')
        continue;
      if (member1.type === 'method' && member1.name === 'constructor')
        continue;
      if (member1.name > member2.name) {
        let memberName1 = `${cls.name}.${member1.name}`;
        if (member1.type === 'method')
          memberName1 += '()';
        let memberName2 = `${cls.name}.${member2.name}`;
        if (member2.type === 'method')
          memberName2 += '()';
        errors.push(`Bad alphabetic ordering of ${cls.name} members: ${memberName1} should go after ${memberName2}`);
      }
    }
  }
  return errors;
}

/**
 * @param {!Documentation} jsDocumentation
 * @return {!Documentation}
 */
function filterJSDocumentation(jsDocumentation) {
  // Filter classes and methods.
  const classes = [];
  for (const cls of jsDocumentation.classesArray) {
    if (EXCLUDE_CLASSES.has(cls.name))
      continue;
    const members = cls.membersArray.filter(member => {
      if (member.name.startsWith('_'))
        return false;
      return !EXCLUDE_METHODS.has(`${cls.name}.${member.name}`);
    });
    classes.push(new Documentation.Class(cls.name, members));
  }
  return new Documentation(classes);
}

/**
 * @param {!Documentation} doc
 * @return {!Array<string>}
 */
function checkDuplicates(doc) {
  const errors = [];
  const classes = new Set();
  // Report duplicates.
  for (const cls of doc.classesArray) {
    if (classes.has(cls.name))
      errors.push(`Duplicate declaration of class ${cls.name}`);
    classes.add(cls.name);
    const members = new Set();
    for (const member of cls.membersArray) {
      if (members.has(member.type + ' ' + member.name))
        errors.push(`Duplicate declaration of ${member.type} ${cls.name}.${member.name}()`);
      members.add(member.type + ' ' + member.name);
      const args = new Set();
      for (const arg of member.argsArray) {
        if (args.has(arg.name))
          errors.push(`Duplicate declaration of argument ${cls.name}.${member.name} "${arg.name}"`);
        args.add(arg.name);
      }
    }
  }
  return errors;
}

/**
 * @param {!Documentation} actual
 * @param {!Documentation} expected
 * @return {!Array<string>}
 */
function compareDocumentations(actual, expected) {
  const errors = [];

  const actualClasses = Array.from(actual.classes.keys()).sort();
  const expectedClasses = Array.from(expected.classes.keys()).sort();
  const classesDiff = diff(actualClasses, expectedClasses);
  for (const className of classesDiff.extra)
    errors.push(`Non-existing class found: ${className}`);
  for (const className of classesDiff.missing)
    errors.push(`Class not found: ${className}`);

  for (const className of classesDiff.equal) {
    const actualClass = actual.classes.get(className);
    const expectedClass = expected.classes.get(className);
    const actualMethods = Array.from(actualClass.methods.keys()).sort();
    const expectedMethods = Array.from(expectedClass.methods.keys()).sort();
    const methodDiff = diff(actualMethods, expectedMethods);
    for (const methodName of methodDiff.extra)
      errors.push(`Non-existing method found: ${className}.${methodName}()`);
    for (const methodName of methodDiff.missing)
      errors.push(`Method not found: ${className}.${methodName}()`);

    for (const methodName of methodDiff.equal) {
      const actualMethod = actualClass.methods.get(methodName);
      const expectedMethod = expectedClass.methods.get(methodName);
      if (actualMethod.hasReturn !== expectedMethod.hasReturn) {
        if (actualMethod.hasReturn)
          errors.push(`Method ${className}.${methodName} has unneeded description of return type`);
        else if (!expectedMethod.async)
          errors.push(`Method ${className}.${methodName} is missing return type description`);
        else
          errors.push(`Async method ${className}.${methodName} should describe return type Promise`);
      }
      const actualArgs = Array.from(actualMethod.args.keys());
      const expectedArgs = Array.from(expectedMethod.args.keys());
      const argDiff = diff(actualArgs, expectedArgs);
      if (argDiff.extra.length || argDiff.missing.length) {
        const text = [`Method ${className}.${methodName}() fails to describe its parameters:`];
        for (const arg of argDiff.missing)
          text.push(`- Argument not found: ${arg}`);
        for (const arg of argDiff.extra)
          text.push(`- Non-existing argument found: ${arg}`);
        errors.push(text.join('\n'));
      }
    }
    const actualProperties = Array.from(actualClass.properties.keys()).sort();
    const expectedProperties = Array.from(expectedClass.properties.keys()).sort();
    const propertyDiff = diff(actualProperties, expectedProperties);
    for (const propertyName of propertyDiff.extra)
      errors.push(`Non-existing property found: ${className}.${propertyName}`);
    for (const propertyName of propertyDiff.missing)
      errors.push(`Property not found: ${className}.${propertyName}`);

    const actualEvents = Array.from(actualClass.events.keys()).sort();
    const expectedEvents = Array.from(expectedClass.events.keys()).sort();
    const eventsDiff = diff(actualEvents, expectedEvents);
    for (const eventName of eventsDiff.extra)
      errors.push(`Non-existing event found in class ${className}: '${eventName}'`);
    for (const eventName of eventsDiff.missing)
      errors.push(`Event not found in class ${className}: '${eventName}'`);
  }
  return errors;
}

/**
 * @param {!Array<string>} actual
 * @param {!Array<string>} expected
 * @return {{extra: !Array<string>, missing: !Array<string>, equal: !Array<string>}}
 */
function diff(actual, expected) {
  const N = actual.length;
  const M = expected.length;
  if (N === 0 && M === 0)
    return { extra: [], missing: [], equal: []};
  if (N === 0)
    return {extra: [], missing: expected.slice(), equal: []};
  if (M === 0)
    return {extra: actual.slice(), missing: [], equal: []};
  const d = new Array(N);
  const bt = new Array(N);
  for (let i = 0; i < N; ++i) {
    d[i] = new Array(M);
    bt[i] = new Array(M);
    for (let j = 0; j < M; ++j) {
      const top = val(i - 1, j);
      const left = val(i, j - 1);
      if (top > left) {
        d[i][j] = top;
        bt[i][j] = 'extra';
      } else {
        d[i][j] = left;
        bt[i][j] = 'missing';
      }
      const diag = val(i - 1, j - 1);
      if (actual[i] === expected[j] && d[i][j] < diag + 1) {
        d[i][j] = diag + 1;
        bt[i][j] = 'eq';
      }
    }
  }
  // Backtrack results.
  let i = N - 1;
  let j = M - 1;
  const missing = [];
  const extra = [];
  const equal = [];
  while (i >= 0 && j >= 0) {
    switch (bt[i][j]) {
      case 'extra':
        extra.push(actual[i]);
        i -= 1;
        break;
      case 'missing':
        missing.push(expected[j]);
        j -= 1;
        break;
      case 'eq':
        equal.push(actual[i]);
        i -= 1;
        j -= 1;
        break;
    }
  }
  while (i >= 0)
    extra.push(actual[i--]);
  while (j >= 0)
    missing.push(expected[j--]);
  extra.reverse();
  missing.reverse();
  equal.reverse();
  return {extra, missing, equal};

  function val(i, j) {
    return i < 0 || j < 0 ? 0 : d[i][j];
  }
}

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

const jsBuilder = require('./JSBuilder');
const mdBuilder = require('./MDBuilder');
const Documentation = require('./Documentation');
const Message = require('../Message');

const EXCLUDE_CLASSES = new Set([
  'Connection',
  'EmulationManager',
  'FrameManager',
  'Helper',
  'Launcher',
  'Multimap',
  'NavigatorWatcher',
  'NetworkManager',
  'ProxyStream',
  'Session',
  'TaskQueue',
  'WaitTask',
]);

const EXCLUDE_METHODS = new Set([
  'Headers.fromPayload',
  'Page.create',
]);

/**
 * @param {!Page} page
 * @param {!Array<!Source>} mdSources
 * @param {!Array<!Source>} jsSources
 * @return {!Promise<!Array<!Message>>}
 */
module.exports = async function lint(page, mdSources, jsSources) {
  const mdResult = await mdBuilder(page, mdSources);
  const jsResult = await jsBuilder(jsSources);
  const jsDocumentation = filterJSDocumentation(jsResult.documentation);
  const mdDocumentation = mdResult.documentation;

  const jsErrors = jsResult.errors;
  jsErrors.push(...checkDuplicates(jsDocumentation));

  const mdErrors = mdResult.errors;
  mdErrors.push(...compareDocumentations(mdDocumentation, jsDocumentation));
  mdErrors.push(...checkDuplicates(mdDocumentation));
  mdErrors.push(...checkSorting(mdDocumentation));

  // Push all errors with proper prefixes
  const errors = jsErrors.map(error => '[JavaScript] ' + error);
  errors.push(...mdErrors.map(error => '[MarkDown] ' + error));
  return errors.map(error => Message.error(error));
};

/**
 * @param {!Documentation} doc
 * @return {!Array<string>}
 */
function checkSorting(doc) {
  const errors = [];
  for (const cls of doc.classesArray) {
    const members = cls.membersArray;

    // Events should go first.
    let eventIndex = 0;
    for (; eventIndex < members.length && members[eventIndex].type === 'event'; ++eventIndex);
    for (; eventIndex < members.length && members[eventIndex].type !== 'event'; ++eventIndex);
    if (eventIndex < members.length)
      errors.push(`Events should go first. Event '${members[eventIndex].name}' in class ${cls.name} breaks order`);

    // Constructor should be right after events and before all other members.
    const constructorIndex = members.findIndex(member => member.type === 'method' && member.name === 'constructor');
    if (constructorIndex > 0 && members[constructorIndex - 1].type !== 'event')
      errors.push(`Constructor of ${cls.name} should go before other methods`);

    // Events should be sorted alphabetically.
    for (let i = 0; i < members.length - 1; ++i) {
      const member1 = cls.membersArray[i];
      const member2 = cls.membersArray[i + 1];
      if (member1.type !== 'event' || member2.type !== 'event')
        continue;
      if (member1.name > member2.name)
        errors.push(`Event '${member1.name}' in class ${cls.name} breaks alphabetic ordering of events`);
    }

    // All other members should be sorted alphabetically.
    for (let i = 0; i < members.length - 1; ++i) {
      const member1 = cls.membersArray[i];
      const member2 = cls.membersArray[i + 1];
      if (member1.type === 'event' || member2.type === 'event')
        continue;
      if (member1.type === 'method' && member1.name === 'constructor')
        continue;
      if (member1.name > member2.name) {
        let memberName1 = `${cls.name}.${member1.name}`;
        if (member1.type === 'method')
          memberName1 += '()';
        let memberName2 = `${cls.name}.${member2.name}`;
        if (member2.type === 'method')
          memberName2 += '()';
        errors.push(`Bad alphabetic ordering of ${cls.name} members: ${memberName1} should go after ${memberName2}`);
      }
    }
  }
  return errors;
}

/**
 * @param {!Documentation} jsDocumentation
 * @return {!Documentation}
 */
function filterJSDocumentation(jsDocumentation) {
  // Filter classes and methods.
  const classes = [];
  for (const cls of jsDocumentation.classesArray) {
    if (EXCLUDE_CLASSES.has(cls.name))
      continue;
    const members = cls.membersArray.filter(member => {
      if (member.name.startsWith('_'))
        return false;
      // Exclude all constructors by default.
      if (member.name === 'constructor' && member.type === 'method')
        return false;
      return !EXCLUDE_METHODS.has(`${cls.name}.${member.name}`);
    });
    classes.push(new Documentation.Class(cls.name, members));
  }
  return new Documentation(classes);
}

/**
 * @param {!Documentation} doc
 * @return {!Array<string>}
 */
function checkDuplicates(doc) {
  const errors = [];
  const classes = new Set();
  // Report duplicates.
  for (const cls of doc.classesArray) {
    if (classes.has(cls.name))
      errors.push(`Duplicate declaration of class ${cls.name}`);
    classes.add(cls.name);
    const members = new Set();
    for (const member of cls.membersArray) {
      if (members.has(member.type + ' ' + member.name))
        errors.push(`Duplicate declaration of ${member.type} ${cls.name}.${member.name}()`);
      members.add(member.type + ' ' + member.name);
      const args = new Set();
      for (const arg of member.argsArray) {
        if (args.has(arg.name))
          errors.push(`Duplicate declaration of argument ${cls.name}.${member.name} "${arg.name}"`);
        args.add(arg.name);
      }
    }
  }
  return errors;
}

/**
 * @param {!Documentation} actual
 * @param {!Documentation} expected
 * @return {!Array<string>}
 */
function compareDocumentations(actual, expected) {
  const errors = [];

  const actualClasses = Array.from(actual.classes.keys()).sort();
  const expectedClasses = Array.from(expected.classes.keys()).sort();
  const classesDiff = diff(actualClasses, expectedClasses);
  for (const className of classesDiff.extra)
    errors.push(`Non-existing class found: ${className}`);
  for (const className of classesDiff.missing)
    errors.push(`Class not found: ${className}`);

  for (const className of classesDiff.equal) {
    const actualClass = actual.classes.get(className);
    const expectedClass = expected.classes.get(className);
    const actualMethods = Array.from(actualClass.methods.keys()).sort();
    const expectedMethods = Array.from(expectedClass.methods.keys()).sort();
    const methodDiff = diff(actualMethods, expectedMethods);
    for (const methodName of methodDiff.extra)
      errors.push(`Non-existing method found: ${className}.${methodName}()`);
    for (const methodName of methodDiff.missing)
      errors.push(`Method not found: ${className}.${methodName}()`);

    for (const methodName of methodDiff.equal) {
      const actualMethod = actualClass.methods.get(methodName);
      const expectedMethod = expectedClass.methods.get(methodName);
      if (actualMethod.hasReturn !== expectedMethod.hasReturn) {
        if (actualMethod.hasReturn)
          errors.push(`Method ${className}.${methodName} has unneeded description of return type`);
        else if (!expectedMethod.async)
          errors.push(`Method ${className}.${methodName} is missing return type description`);
        else
          errors.push(`Async method ${className}.${methodName} should describe return type Promise`);
      }
      const actualArgs = Array.from(actualMethod.args.keys());
      const expectedArgs = Array.from(expectedMethod.args.keys());
      const argDiff = diff(actualArgs, expectedArgs);
      if (argDiff.extra.length || argDiff.missing.length) {
        const text = [`Method ${className}.${methodName}() fails to describe its parameters:`];
        for (const arg of argDiff.missing)
          text.push(`- Argument not found: ${arg}`);
        for (const arg of argDiff.extra)
          text.push(`- Non-existing argument found: ${arg}`);
        errors.push(text.join('\n'));
      }
    }
    const actualProperties = Array.from(actualClass.properties.keys()).sort();
    const expectedProperties = Array.from(expectedClass.properties.keys()).sort();
    const propertyDiff = diff(actualProperties, expectedProperties);
    for (const propertyName of propertyDiff.extra)
      errors.push(`Non-existing property found: ${className}.${propertyName}`);
    for (const propertyName of propertyDiff.missing)
      errors.push(`Property not found: ${className}.${propertyName}`);

    const actualEvents = Array.from(actualClass.events.keys()).sort();
    const expectedEvents = Array.from(expectedClass.events.keys()).sort();
    const eventsDiff = diff(actualEvents, expectedEvents);
    for (const eventName of eventsDiff.extra)
      errors.push(`Non-existing event found in class ${className}: '${eventName}'`);
    for (const eventName of eventsDiff.missing)
      errors.push(`Event not found in class ${className}: '${eventName}'`);
  }
  return errors;
}

/**
 * @param {!Array<string>} actual
 * @param {!Array<string>} expected
 * @return {{extra: !Array<string>, missing: !Array<string>, equal: !Array<string>}}
 */
function diff(actual, expected) {
  const N = actual.length;
  const M = expected.length;
  if (N === 0 && M === 0)
    return { extra: [], missing: [], equal: []};
  if (N === 0)
    return {extra: [], missing: expected.slice(), equal: []};
  if (M === 0)
    return {extra: actual.slice(), missing: [], equal: []};
  const d = new Array(N);
  const bt = new Array(N);
  for (let i = 0; i < N; ++i) {
    d[i] = new Array(M);
    bt[i] = new Array(M);
    for (let j = 0; j < M; ++j) {
      const top = val(i - 1, j);
      const left = val(i, j - 1);
      if (top > left) {
        d[i][j] = top;
        bt[i][j] = 'extra';
      } else {
        d[i][j] = left;
        bt[i][j] = 'missing';
      }
      const diag = val(i - 1, j - 1);
      if (actual[i] === expected[j] && d[i][j] < diag + 1) {
        d[i][j] = diag + 1;
        bt[i][j] = 'eq';
      }
    }
  }
  // Backtrack results.
  let i = N - 1;
  let j = M - 1;
  const missing = [];
  const extra = [];
  const equal = [];
  while (i >= 0 && j >= 0) {
    switch (bt[i][j]) {
      case 'extra':
        extra.push(actual[i]);
        i -= 1;
        break;
      case 'missing':
        missing.push(expected[j]);
        j -= 1;
        break;
      case 'eq':
        equal.push(actual[i]);
        i -= 1;
        j -= 1;
        break;
    }
  }
  while (i >= 0)
    extra.push(actual[i--]);
  while (j >= 0)
    missing.push(expected[j--]);
  extra.reverse();
  missing.reverse();
  equal.reverse();
  return {extra, missing, equal};

  function val(i, j) {
    return i < 0 || j < 0 ? 0 : d[i][j];
  }
}

<|endoftext|>


<commit_before>// ==UserScript==
// @name         E, R, T, and P keys
// @version      1.0
// @description  Adds E, R, T, and P keys to the vanilla client
// @author       ZfsrGhS953
// @match        *.agar.io/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

/************************************************

Made by @ZfsrGhS953 on GitHub
Go check out his project! Its really nice :)
https://github.com/ZfsrGhS953/Petridish-Ogar

************************************************/

setTimeout(function() {
    window.__WebSocket = window.WebSocket;
    window.fakeWebSocket = function() {
        return {
            readyState: 0
        };
    };
    window._WebSocket = window.WebSocket = function(ip) {
        return new window.fakeWebSocket(ip);
    };
    window.key = {
        e: false,
        r: false,
        t: false,
        p: false
    };
    window.addEventListener("load", function() {
        // код инжектинга
        if (!window.OldSocket)
        OldSocket = window.__WebSocket;
        window._WebSocket = window.WebSocket = window.fakeWebSocket = function(ip) {
            var ws = new OldSocket(ip);
            ws.binaryType = "arraybuffer";
            var fakeWS = {};
            for (var i in ws) {
                fakeWS[i] = ws[i];
            }
            fakeWS.send = function() {
                if (arguments[0][0] == 16) {
                    if (window.key.e){
                        arguments[0] = new Int8Array(1);
                        arguments[0][0] = 22;
                    }
                    if (window.key.r){
                        arguments[0] = new Int8Array(1);
                        arguments[0][0] = 23;
                    }
                    if (window.key.t){
                        arguments[0] = new Int8Array(1);
                        arguments[0][0] = 24;
                    }
                    if (window.key.p) {
                        arguments[0] = new Int8Array(1);
                        arguments[0][0] = 25;
                    }
                window.key.e = window.key.r = window.key.t = window.key.p = false;
                }
            return ws.send.apply(ws, arguments);
            };
            ws.onmessage = function() {
                fakeWS.onmessage && fakeWS.onmessage.apply(ws, arguments);
            };
            ws.onopen = function() {
                fakeWS.readyState = 1;
                fakeWS.onopen.apply(ws, arguments);
            };
            ws.onclose = function(){
                fakeWS.onclose.apply(ws, arguments);
            };
        return fakeWS;
        };
    });
    document.addEventListener('keydown', function(e) {
        var key = e.keyCode || e.which;
        switch (key) {
            case 69:
                window.key.e = true;
                break;
            case 82:
                window.key.r = true;
                break;
            case 84:
                window.key.t = true;
                break;
            case 80:
                window.key.p = true;
                break;
        }
    });
}, 200);
<commit_msg>Clean up a few things<commit_after>// ==UserScript==
// @name         E, R, T, and P keys
// @version      1.1
// @description  Adds E, R, T, and P keys to the vanilla client
// @author       ZfsrGhS953
// @match        *.agar.io/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

/************************************************
Made by @ZfsrGhS953 on GitHub
Go check out his project! Its really nice :)
https://github.com/ZfsrGhS953/Petridish-Ogar
************************************************/

window.__WebSocket = window.WebSocket;
window._WebSocket = window.WebSocket = function(ip) {
    return new window.fakeWebSocket(ip);
};
window.key = {
    e: false,
    r: false,
    t: false,
    p: false
};
window.addEventListener("load", function() {
    OldSocket = window.__WebSocket;
    window.WebSocket = window.fakeWebSocket = function(ip) {
        var fakeWS = {};
        var ws = new OldSocket(ip);
        ws.binaryType = "arraybuffer";
        for (var i in ws) fakeWS[i] = ws[i];
        fakeWS.send = function() {
            if (window.key.e){
                arguments[0] = new Int8Array(1);
                arguments[0][0] = 22;
            } else if (window.key.r){
                arguments[0] = new Int8Array(1);
                arguments[0][0] = 23;
            } else if (window.key.t){
                arguments[0] = new Int8Array(1);
                arguments[0][0] = 24;
            } else if (window.key.p) {
                arguments[0] = new Int8Array(1);
                arguments[0][0] = 25;
            }
            window.key = {};
            return ws.send.apply(ws, arguments);
        };
        ws.onmessage = function() {
            fakeWS.onmessage && fakeWS.onmessage.apply(ws, arguments);
        };
        ws.onopen = function() {
            fakeWS.readyState = 1;
            fakeWS.onopen.apply(ws, arguments);
        };
        return fakeWS;
    };
});
document.addEventListener('keydown', function(e) {
    if (e.keyCode == 69) window.key.e = true;
    if (e.keyCode == 82) window.key.r = true;
    if (e.keyCode == 84) window.key.t = true;
    if (e.keyCode == 80) window.key.p = true;
});
<|endoftext|>


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

var sys = require("util");
var fs = require("fs");

var UglifyJS = require("../tools/node");

var filename = process.argv[2];
var code = fs.readFileSync(filename, "utf8");

var ast = time_it("parse", function() {
    return UglifyJS.parse(code);
});
var stream = UglifyJS.OutputStream({ beautify: false });
time_it("scope", function(){
    ast.figure_out_scope();
});
// time_it("mangle", function(){
//     ast.mangle_names();
// });

//ast.scope_warnings();

time_it("compress", function(){
    var compressor = new UglifyJS.Compressor({
    });
    ast = ast.squeeze(compressor);
});

time_it("generate", function(){
    ast.print(stream);
});
sys.puts(stream.get());

//ast.scope_warnings();

function time_it(name, cont) {
    var t1 = new Date().getTime();
    try { return cont(); }
    finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); }
};
<commit_msg>minor<commit_after>#! /usr/bin/env node

var sys = require("util");
var fs = require("fs");

var UglifyJS = require("../tools/node");

var filename = process.argv[2];
var code = fs.readFileSync(filename, "utf8");

var ast = time_it("parse", function() {
    return UglifyJS.parse(code);
});

time_it("scope", function(){
    // calling figure_out_scope is a prerequisite for mangle_names,
    // scope_warnings and compress
    //
    // perhaps figure_out_scope should be called automatically by the
    // parser, but there might be instances where the functionality is
    // not needed.
    ast.figure_out_scope();
});

ast.scope_warnings();

time_it("mangle", function(){
    ast.mangle_names();
});

time_it("compress", function(){
    var compressor = new UglifyJS.Compressor({
        // sequences     : true,
        // properties    : true,
        // dead_code     : true,
        // keep_comps    : true,
        // drop_debugger : true,
        // unsafe        : true,
        // warnings      : true
    });
    ast = ast.squeeze(compressor);
});

var stream = UglifyJS.OutputStream({ beautify: false });
time_it("generate", function(){
    ast.print(stream);
});
sys.puts(stream.get());

function time_it(name, cont) {
    var t1 = new Date().getTime();
    try { return cont(); }
    finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); }
};
<|endoftext|>


<commit_before>import { PropTypes } from 'react';
import { BEM } from 'rebem';

const block = 'select';

export default function Option({ value, text }) {
    return BEM(
        {
            block,
            elem: 'option',
            tag: 'option',
            value
        },
        text
    );
}

Option.propTypes = {
    text: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.number
    ]),
    value: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.number
    ])
};
<commit_msg>:lipstick: minor select/option refactoring<commit_after>import { PropTypes } from 'react';
import { BEM } from 'rebem';

export default function Option({ value, text }) {
    return BEM(
        {
            block: 'select',
            elem: 'option',
            tag: 'option',
            value
        },
        text
    );
}

Option.propTypes = {
    text: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.number
    ]),
    value: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.number
    ])
};
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react'
import styles from 'part:@sanity/components/selects/style-style'
import {uniqueId, includes} from 'lodash'
import FaAngleDown from 'part:@sanity/base/angle-down-icon'
import DefaultFormField from 'part:@sanity/components/formfields/default'
import enhanceWithClickOutside from 'react-click-outside'
import CircleThinIcon from 'part:@sanity/base/circle-thin-icon'
import CircleCheckIcon from 'part:@sanity/base/circle-check-icon'

class StyleSelect extends React.Component {
  static propTypes = {
    label: PropTypes.string.isRequired,
    description: PropTypes.string,
    placeholder: PropTypes.string,
    onChange: PropTypes.func,
    onOpen: PropTypes.func,
    onFocus: PropTypes.func,
    onBlur: PropTypes.func,
    onClose: PropTypes.func,
    value: PropTypes.array,
    error: PropTypes.bool,
    renderItem: PropTypes.func,
    className: PropTypes.string,
    transparent: PropTypes.bool,
    items: PropTypes.arrayOf(
      PropTypes.shape({
        title: PropTypes.string,
        active: PropTypes.bool
      })
    ),
  }

  static defaultProps = {
    placeholder: 'Type to search…',
    loading: false,
    onChange() {},
    onBlur() {},
    onOpen() {},
    onClose() {}
  }

  constructor(props, context) {
    super(props, context)

    this.state = {
      hasFocus: false,
      inputValue: this.props.value && this.props.value.title,
      inputSelected: false,
      arrowNavigationPosition: 0
    }
  }

  handleClickOutside = () => {
    this.handleCloseList()
  }

  // componentWillReceiveProps(nextProps) {
  //   if (nextProps.items != this.props.items) {
  //     this.setState({
  //       searchResult: this.props.items,
  //       showList: true
  //     })
  //   }
  //   if (nextProps.value != this.props.value) {
  //     this.setState({
  //       inputValue: nextProps.value.title,
  //       inputSelected: true,
  //       value: nextProps.value.title,
  //       showList: false
  //     })
  //   }
  // }

  handleFocus = event => {
    this.setState({
      hasFocus: true,
      inputSelected: true
    })

    this.props.onFocus(event)
  }

  handleBlur = event => {
    this.props.onBlur(event)
  }

  handleSelect = item => {
    this.props.onChange(item)
    this.handleCloseList()
  }

  handleOpenList = () => {
    this.setState({
      showList: true,
    })
    this.props.onOpen()
  }

  handleCloseList = () => {
    this.setState({
      showList: false
    })
    this.props.onClose()
  }

  handleInnerClick = () => {
    if (this.state.showList) {
      this.handleCloseList()
    } else {
      this.handleOpenList()
    }
  }

  handleKeyDown = event => {
    const {items} = this.props
    const {arrowNavigationPosition} = this.state
    if (items) {
      if (event.key == 'ArrowUp' && arrowNavigationPosition > 0) {
        this.setState({
          arrowNavigationPosition: arrowNavigationPosition - 1,
          inputValue: items[arrowNavigationPosition - 1].title,
          showList: true
        })
        return false
      }

      if (event.key == 'ArrowDown' && arrowNavigationPosition < items.length - 1) {
        this.setState({
          arrowNavigationPosition: arrowNavigationPosition + 1,
          inputValue: items[arrowNavigationPosition + 1].title,
          showList: true
        })
        return false
      }
    }
    return true
  }

  handleKeyUp = event => {
    const {items} = this.props
    const {arrowNavigationPosition} = this.state
    if (event.key == 'Enter' && arrowNavigationPosition) {
      this.handleSelect(items[arrowNavigationPosition])
      this.setState({
        hasFocus: false
      })
      return false
    }
    return true
  }

  componentWillMount() {
    this._inputId = uniqueId('CustomSelect')
  }

  render() {
    const {label, error, value, description, items, className, transparent, placeholder} = this.props
    const {hasFocus, showList} = this.state

    return (
      <DefaultFormField
        className={`
          ${styles.root}
          ${hasFocus && styles.focused}
          ${error && styles.error}
          ${transparent && styles.transparent}
          ${className}`}
        description={description}
        labelHtmlFor={this._inputId}
        label={label}
      >

              {value && value.length > 1 && 'Multiple'}
              {value && value.length == 1 && value[0].title}
              {!value && placeholder}

              <FaAngleDown color="inherit" />

        <div className={`${showList ? styles.listContainer : styles.listContainerHidden}`}>
          {
            items.map(item => {
              const isSemiSelected = value && value.length > 1 && includes(value, item)
              const isSelected = value && value.length === 1 && value[0] == item
              const classNames = `
                ${styles.item}
                ${isSelected && styles.itemSelected}
                ${isSemiSelected && styles.itemSemiSelected}
              `
              return (

                    {
                      isSelected && <CircleCheckIcon />
                    }
                    {
                      isSemiSelected && <CircleThinIcon />
                    }

                    {this.props.renderItem(item)}

              )
            })
          }

      </DefaultFormField>
    )
  }
}

export default enhanceWithClickOutside(StyleSelect)
<commit_msg>emit the item when clicked<commit_after>import React, {PropTypes} from 'react'
import styles from 'part:@sanity/components/selects/style-style'
import {uniqueId, includes} from 'lodash'
import FaAngleDown from 'part:@sanity/base/angle-down-icon'
import DefaultFormField from 'part:@sanity/components/formfields/default'
import enhanceWithClickOutside from 'react-click-outside'
import CircleThinIcon from 'part:@sanity/base/circle-thin-icon'
import CircleCheckIcon from 'part:@sanity/base/circle-check-icon'

class StyleSelect extends React.Component {
  static propTypes = {
    label: PropTypes.string.isRequired,
    description: PropTypes.string,
    placeholder: PropTypes.string,
    onChange: PropTypes.func,
    onOpen: PropTypes.func,
    onFocus: PropTypes.func,
    onBlur: PropTypes.func,
    onClose: PropTypes.func,
    value: PropTypes.array,
    error: PropTypes.bool,
    renderItem: PropTypes.func,
    className: PropTypes.string,
    transparent: PropTypes.bool,
    items: PropTypes.arrayOf(
      PropTypes.shape({
        title: PropTypes.string,
        active: PropTypes.bool
      })
    ),
  }

  static defaultProps = {
    placeholder: 'Type to search…',
    loading: false,
    onChange() {},
    onBlur() {},
    onOpen() {},
    onClose() {}
  }

  constructor(props, context) {
    super(props, context)

    this.state = {
      hasFocus: false,
      inputValue: this.props.value && this.props.value.title,
      inputSelected: false,
      arrowNavigationPosition: 0
    }
  }

  handleClickOutside = () => {
    this.handleCloseList()
  }

  // componentWillReceiveProps(nextProps) {
  //   if (nextProps.items != this.props.items) {
  //     this.setState({
  //       searchResult: this.props.items,
  //       showList: true
  //     })
  //   }
  //   if (nextProps.value != this.props.value) {
  //     this.setState({
  //       inputValue: nextProps.value.title,
  //       inputSelected: true,
  //       value: nextProps.value.title,
  //       showList: false
  //     })
  //   }
  // }

  handleFocus = event => {
    this.setState({
      hasFocus: true,
      inputSelected: true
    })

    this.props.onFocus(event)
  }

  handleBlur = event => {
    this.props.onBlur(event)
  }

  handleSelect = item => {
    this.props.onChange(item)
    this.handleCloseList()
  }

  handleOpenList = () => {
    this.setState({
      showList: true,
    })
    this.props.onOpen()
  }

  handleCloseList = () => {
    this.setState({
      showList: false
    })
    this.props.onClose()
  }

  handleInnerClick = () => {
    if (this.state.showList) {
      this.handleCloseList()
    } else {
      this.handleOpenList()
    }
  }

  handleKeyDown = event => {
    const {items} = this.props
    const {arrowNavigationPosition} = this.state
    if (items) {
      if (event.key == 'ArrowUp' && arrowNavigationPosition > 0) {
        this.setState({
          arrowNavigationPosition: arrowNavigationPosition - 1,
          inputValue: items[arrowNavigationPosition - 1].title,
          showList: true
        })
        return false
      }

      if (event.key == 'ArrowDown' && arrowNavigationPosition < items.length - 1) {
        this.setState({
          arrowNavigationPosition: arrowNavigationPosition + 1,
          inputValue: items[arrowNavigationPosition + 1].title,
          showList: true
        })
        return false
      }
    }
    return true
  }

  handleKeyUp = event => {
    const {items} = this.props
    const {arrowNavigationPosition} = this.state
    if (event.key == 'Enter' && arrowNavigationPosition) {
      this.handleSelect(items[arrowNavigationPosition])
      this.setState({
        hasFocus: false
      })
      return false
    }
    return true
  }

  componentWillMount() {
    this._inputId = uniqueId('CustomSelect')
  }

  render() {
    const {label, error, value, description, items, className, transparent, placeholder} = this.props
    const {hasFocus, showList} = this.state

    return (
      <DefaultFormField
        className={`
          ${styles.root}
          ${hasFocus && styles.focused}
          ${error && styles.error}
          ${transparent && styles.transparent}
          ${className}`}
        description={description}
        labelHtmlFor={this._inputId}
        label={label}
      >

              {value && value.length > 1 && 'Multiple'}
              {value && value.length == 1 && value[0].title}
              {!value && placeholder}

              <FaAngleDown color="inherit" />

        <div className={`${showList ? styles.listContainer : styles.listContainerHidden}`}>
          {
            items.map(item => {
              const isSemiSelected = value && value.length > 1 && includes(value, item)
              const isSelected = value && value.length === 1 && value[0] == item
              const classNames = `
                ${styles.item}
                ${isSelected && styles.itemSelected}
                ${isSemiSelected && styles.itemSemiSelected}
              `
              return (
                 this.handleSelect(item)}>

                    {
                      isSelected && <CircleCheckIcon />
                    }
                    {
                      isSemiSelected && <CircleThinIcon />
                    }

                    {this.props.renderItem(item)}

              )
            })
          }

      </DefaultFormField>
    )
  }
}

export default enhanceWithClickOutside(StyleSelect)
<|endoftext|>


<commit_before>

// separate cache of data from app shell cache
var dataCacheName = 'weatherData-v2';

/* When the service worker is fired, it should open the caches object
* and populate it with the assets necessary to load the App Shell. */

var cacheName = 'weatherPWA-step-6-4';
var filesToCache = [
  '/',
  '/index.html',
  '/scripts/app.js',
  '/styles/inline.css',
  '/images/clear.png',
  '/images/cloudy-scattered-showers.png',
  '/images/cloudy.png',
  '/images/fog.png',
  '/images/ic_add_white_24px.svg',
  '/images/ic_refresh_white_24px.svg',
  '/images/partly-cloudy.png',
  '/images/rain.png',
  '/images/scattered-showers.png',
  '/images/sleet.png',
  '/images/snow.png',
  '/images/thunderstorm.png',
  '/images/wind.png'
];

self.addEventListener('install', function(e) {
  console.log('[ServiceWorker] Install');
  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
      console.log('[ServiceWorker] Caching app shell');
      return cache.addAll(filesToCache);
    })
  );
});

self.addEventListener('activate', function(e) {
  console.log('[ServiceWorker] Activate');
  e.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (key !== cacheName && key !== dataCacheName) {
          console.log('[ServiceWorker] Removing old cache', key);
          return caches.delete(key);
        }
      }));
    })
  );
  return self.clients.claim();
});

self.addEventListener('fetch', function(e) {
  console.log('[Service Worker] Fetch', e.request.url);
  var dataUrl = 'https://query.yahooapis.com/v1/public/yql';
  if (e.request.url.indexOf(dataUrl) > -1) {
    /*
     * When the request URL contains dataUrl, the app is asking for fresh
     * weather data. In this case, the service worker always goes to the
     * network and then caches the response. This is called the "Cache then
     * network" strategy:
     * https://jakearchibald.com/2014/offline-cookbook/#cache-then-network
     */
    e.respondWith(
      caches.open(dataCacheName).then(function(cache) {
        return fetch(e.request).then(function(response){
          cache.put(e.request.url, response.clone());
          return response;
        });
      })
    );
  } else {
    /*
     * The app is asking for app shell files. In this scenario the app uses the
     * "Cache, falling back to the network" offline strategy:
     * https://jakearchibald.com/2014/offline-cookbook/#cache-falling-back-to-network
     */
    e.respondWith(
      caches.match(e.request).then(function(response) {
        return response || fetch(e.request);
      })
    );
  }
});
<commit_msg>testing sw<commit_after>

// separate cache of data from app shell cache
var dataCacheName = 'weatherData-v2';

/* When the service worker is fired, it should open the caches object
* and populate it with the assets necessary to load the App Shell. */

var cacheName = 'weatherPWA-step-6-4';
var filesToCache = [
  'index.html',
  'scripts/app.js',
  'styles/inline.css',
  'images/clear.png',
  'images/cloudy-scattered-showers.png',
  'images/cloudy.png',
  'images/fog.png',
  'images/ic_add_white_24px.svg',
  'images/ic_refresh_white_24px.svg',
  'images/partly-cloudy.png',
  'images/rain.png',
  'images/scattered-showers.png',
  'images/sleet.png',
  'images/snow.png',
  'images/thunderstorm.png',
  'images/wind.png'
];

self.addEventListener('install', function(e) {
  console.log('[ServiceWorker] Install');
  e.waitUntil(
    caches.open(cacheName).then(function(cache) {
      console.log('[ServiceWorker] Caching app shell');
      return cache.addAll(filesToCache);
    })
  );
});

self.addEventListener('activate', function(e) {
  console.log('[ServiceWorker] Activate');
  e.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (key !== cacheName && key !== dataCacheName) {
          console.log('[ServiceWorker] Removing old cache', key);
          return caches.delete(key);
        }
      }));
    })
  );
  return self.clients.claim();
});

self.addEventListener('fetch', function(e) {
  console.log('[Service Worker] Fetch', e.request.url);
  var dataUrl = 'https://query.yahooapis.com/v1/public/yql';
  if (e.request.url.indexOf(dataUrl) > -1) {
    /*
     * When the request URL contains dataUrl, the app is asking for fresh
     * weather data. In this case, the service worker always goes to the
     * network and then caches the response. This is called the "Cache then
     * network" strategy:
     * https://jakearchibald.com/2014/offline-cookbook/#cache-then-network
     */
    e.respondWith(
      caches.open(dataCacheName).then(function(cache) {
        return fetch(e.request).then(function(response){
          cache.put(e.request.url, response.clone());
          return response;
        });
      })
    );
  } else {
    /*
     * The app is asking for app shell files. In this scenario the app uses the
     * "Cache, falling back to the network" offline strategy:
     * https://jakearchibald.com/2014/offline-cookbook/#cache-falling-back-to-network
     */
    e.respondWith(
      caches.match(e.request).then(function(response) {
        return response || fetch(e.request);
      })
    );
  }
});
<|endoftext|>


<commit_before>import sio from 'socket.io';

export default {
  /**
   * Initialize the object which socket.io
   * @private
   */
  init(httpServer, config) {
    this.io = new sio(httpServer, config);;
  },

  /**
   * Register the function to apply when a client of the given `clientType`
   * is connecting to the server
   * @param {Array} clientTypes - The different type of client, should be namespaced
   * @param {Function} callback
   * @private
   */
  onConnection(clientTypes, callback) {
    clientTypes.forEach((clientType) => {
      this.io.of(clientType).on('connection', (socket) => {
        callback(clientType, socket);
      });
    });
  },

  /**
   * Listen a WebSocket message.
   * @param {Client} client - The client that must listen to the message.
   * @param {String} channel - The channel of the message
   * @param {...*} callback - The callback to execute when a message is received.
   */
  receive(client, channel, callback) {
    client.socket.on(channel, callback);
  },

  /**
   * Sends a WebSocket message to the client.
   * @param {Client} client - The client to send the message to.
   * @param {String} channel - The channel of the message
   * @param {...*} args - Arguments of the message (as many as needed, of any type).
   */
  send(client, channel, ...args) {
    client.socket.emit(channel, ...args);
  },

  /**
   * Sends a message to all client of given `clientType` or `clientType`s. If
   * not specified, the message is sent to all clients.
   *
   * @param {String|Array} clientType - The `clientType`(s) that must receive
   *  the message.
   * @param {module:soundworks/server.Client} excludeClient - Optionnal
   *  client to ignore when broadcasting the message, typically the client
   *  at the origin of the message.
   * @param {String} channel - Channel of the message
   * @param {...*} args - Arguments of the message (as many as needed, of any type).
   */
  broadcast(clientType, excludeClient, channel, ...args) {
    let namespaces;

    if (typeof clientType === 'string')
      namespaces = [`/${clientType}`];
    else if (Array.isArray(clientType))
      namespaces = clientType.map(type => `/${type}`);
    else
      namespaces = Object.keys(this.io.nsps);

    if (excludeClient) {
      const index = namespaces.indexOf('/' + excludeClient.type);

      if (index !== -1) {
        namespaces.splice(index, 1);
        excludeClient.socket.broadcast.emit(channel, ...args);
      }
    }

    namespaces.forEach((nsp) => {
      this.io.of(nsp).emit(channel, ...args);
    });
  },
};
<commit_msg>hot fix: sockets initialization too late<commit_after>import sio from 'socket.io';

export default {
  /**
   * Initialize the object which socket.io
   * @private
   */
  init(httpServer, config) {
    this.io = new sio(httpServer, config);;
  },

  /**
   * Register the function to apply when a client of the given `clientType`
   * is connecting to the server
   * @param {Array} clientTypes - The different type of client, should be namespaced
   * @param {Function} callback
   * @private
   */
  onConnection(clientTypes, callback) {
    clientTypes.forEach((clientType) => {
      this.io.of(clientType).on('connection', (socket) => {
        callback(clientType, socket);
      });
    });
  },

  /**
   * Listen a WebSocket message.
   * @param {Client} client - The client that must listen to the message.
   * @param {String} channel - The channel of the message
   * @param {...*} callback - The callback to execute when a message is received.
   */
  receive(client, channel, callback) {
    client.socket.on(channel, callback);
  },

  /**
   * Sends a WebSocket message to the client.
   * @param {Client} client - The client to send the message to.
   * @param {String} channel - The channel of the message
   * @param {...*} args - Arguments of the message (as many as needed, of any type).
   */
  send(client, channel, ...args) {
    client.socket.emit(channel, ...args);
  },

  /**
   * Sends a message to all client of given `clientType` or `clientType`s. If
   * not specified, the message is sent to all clients.
   *
   * @param {String|Array} clientType - The `clientType`(s) that must receive
   *  the message.
   * @param {module:soundworks/server.Client} excludeClient - Optionnal
   *  client to ignore when broadcasting the message, typically the client
   *  at the origin of the message.
   * @param {String} channel - Channel of the message
   * @param {...*} args - Arguments of the message (as many as needed, of any type).
   */
  broadcast(clientType, excludeClient, channel, ...args) {
    if(!this.io)
      return;

    let namespaces;

    if (typeof clientType === 'string')
      namespaces = [`/${clientType}`];
    else if (Array.isArray(clientType))
      namespaces = clientType.map(type => `/${type}`);
    else
      namespaces = Object.keys(this.io.nsps);

    if (excludeClient) {
      const index = namespaces.indexOf('/' + excludeClient.type);

      if (index !== -1) {
        namespaces.splice(index, 1);
        excludeClient.socket.broadcast.emit(channel, ...args);
      }
    }

    namespaces.forEach((nsp) => {
      this.io.of(nsp).emit(channel, ...args);
    });
  },
};
<|endoftext|>


<commit_before>'use strict';

var log = require('./logging.js').getLogger('fsController.js');
var express = require('express');
var extend = require('extend');
var path = require('path');
var fsCtrlModule = require('./fsService.js');

var DEFAULT_CFG = {
	rootPath : '/tmp',
	filePathRegexp : /^\/fs\/\w+/,
	allowedOperations: [ 'ls', 'get', 'rm', 'mkdir', 'put', 'putgetpath', 'replace' ]
};

/**
 Class controls file manipulation operations.

 Class binds only allowed operations to fsService operations.

 @module server
 @submodule controllers
 @class FsController
*/
var create = function(options) {
	var app = express();

	var prop = extend(true, {}, DEFAULT_CFG, options);

	if (options && options.allowedOperations) {
		// ovewrride default deep copy
		prop.allowedOperations = options.allowedOperations;
	}

	app.prop = prop;
	app.fsCtrl = new fsCtrlModule.FsCtrl(prop);

// Register only allowed operations
// ls
	if (prop.allowedOperations.indexOf('ls') > -1) {
		log.verbose('Registering allowed opperation ls');
		app.get('/ls*', function(req, resp) {
			var path = req.url.substring(3);

			app.fsCtrl.ls(path, resp, function(err) {
				if (err) {
					log.error('Failed to ls', err);
					resp.status(err.code || 500).send( err);
				}

			});
		});
	}

// get
	if (prop.allowedOperations.indexOf('get') > -1) {
		log.verbose('Registering allowed opperation get');
		app.get('/get/*', function(req, resp) {
			var path = req.url.substring(5);

			app.fsCtrl.get(path, resp, function(err) {
				if (err) {
					log.error('Failed to get', err);
					resp.status(err.code || 500).send( err);
				}

			});
		});
	}

// rm
	if (prop.allowedOperations.indexOf('rm') > -1) {
		log.verbose('Registering allowed opperation rm');
		app.get('/rm/*', function(req, resp) {
			var path = req.url.substring(4);

			app.fsCtrl.rm(path, resp, function(err) {
				if (err) {
					log.error('Failed to rm', err);
					resp.status(err.code || 500).send(err);
				}

			});
		});
	}

// mkdir
	if (prop.allowedOperations.indexOf('mkdir') > -1) {
		log.verbose('Registering allowed opperation mkdir');
		app.get('/mkdir/*', function(req, resp) {
			var path = req.url.substring(7);

			app.fsCtrl.mkdir(path, resp, function(err) {
				if (err) {
					log.error('Failed to mkdir', err);
					resp.status(err.code || 500).send(err);
				}

			});
		});
	}

// put
	if (prop.allowedOperations.indexOf('put') > -1) {
		log.verbose('Registering allowed opperation put');
		app.put('/put/*', function(req, resp) {
			var path = req.url.substring(5);

			app.fsCtrl.put(path, resp, function(err) {
				if (err) {
					log.error('Failed to put', err);
					resp.status(err.code || 500).send(err);
				}

			});
		});
	}

// putgetpath
	if (prop.allowedOperations.indexOf('putgetpath') > -1) {
		log.verbose('Registering allowed opperation putgetpath');
		app.put('/putgetpath/*', function(req, resp) {
			var path = req.url.substring('/putgetpath/'.length);

			app.fsCtrl.putGetPath(path, req, req.get('Content-Type'), function(err, path) {
				if (err) {
					log.error('Failed to putGetPath', err);
					resp.status(500).send(err);
				}
				log.info('Saved file %s', path);
				resp.send(path);
			});
		});
	}

// replace
	if (prop.allowedOperations.indexOf('replace') > -1) {
		app.put('/replace/*', function(req, resp) {
			var path = req.url.substring(9);

			app.fsCtrl.replace(path, req, resp, function(err) {
				if (err) {
					log.error('Failed to replace', err);
					resp.status(err.code || 500).send(err);
				}

			});
		});
	}

	return app;
};

module.exports = {create: create};
<commit_msg>FsController serves files witch query fragment<commit_after>'use strict';

var log = require('./logging.js').getLogger('fsController.js');
var express = require('express');
var extend = require('extend');
var path = require('path');
var fsCtrlModule = require('./fsService.js');
var url = require('url');

var DEFAULT_CFG = {
	rootPath : '/tmp',
	filePathRegexp : /^\/fs\/\w+/,
	allowedOperations: [ 'ls', 'get', 'rm', 'mkdir', 'put', 'putgetpath', 'replace' ]
};

/**
 Class controls file manipulation operations.

 Class binds only allowed operations to fsService operations.

 @module server
 @submodule controllers
 @class FsController
*/
var create = function(options) {
	var app = express();

	var prop = extend(true, {}, DEFAULT_CFG, options);

	if (options && options.allowedOperations) {
		// ovewrride default deep copy
		prop.allowedOperations = options.allowedOperations;
	}

	app.prop = prop;
	app.fsCtrl = new fsCtrlModule.FsCtrl(prop);

// Register only allowed operations
// ls
	if (prop.allowedOperations.indexOf('ls') > -1) {
		log.verbose('Registering allowed opperation ls');
		app.get('/ls*', function(req, resp) {
			var path = req.url.substring(3);

			app.fsCtrl.ls(path, resp, function(err) {
				if (err) {
					log.error('Failed to ls', err);
					resp.status(err.code || 500).send( err);
				}

			});
		});
	}

// get
	if (prop.allowedOperations.indexOf('get') > -1) {
		log.verbose('Registering allowed opperation get');
		app.get('/get/*', function(req, resp) {
			var path = url.parse(req.url).pathname.substring(5);

			app.fsCtrl.get(path, resp, function(err) {
				if (err) {
					log.error('Failed to get', err);
					resp.status(err.code || 500).send( err);
				}

			});
		});
	}

// rm
	if (prop.allowedOperations.indexOf('rm') > -1) {
		log.verbose('Registering allowed opperation rm');
		app.get('/rm/*', function(req, resp) {
			var path = req.url.substring(4);

			app.fsCtrl.rm(path, resp, function(err) {
				if (err) {
					log.error('Failed to rm', err);
					resp.status(err.code || 500).send(err);
				}

			});
		});
	}

// mkdir
	if (prop.allowedOperations.indexOf('mkdir') > -1) {
		log.verbose('Registering allowed opperation mkdir');
		app.get('/mkdir/*', function(req, resp) {
			var path = req.url.substring(7);

			app.fsCtrl.mkdir(path, resp, function(err) {
				if (err) {
					log.error('Failed to mkdir', err);
					resp.status(err.code || 500).send(err);
				}

			});
		});
	}

// put
	if (prop.allowedOperations.indexOf('put') > -1) {
		log.verbose('Registering allowed opperation put');
		app.put('/put/*', function(req, resp) {
			var path = req.url.substring(5);

			app.fsCtrl.put(path, resp, function(err) {
				if (err) {
					log.error('Failed to put', err);
					resp.status(err.code || 500).send(err);
				}

			});
		});
	}

// putgetpath
	if (prop.allowedOperations.indexOf('putgetpath') > -1) {
		log.verbose('Registering allowed opperation putgetpath');
		app.put('/putgetpath/*', function(req, resp) {
			var path = req.url.substring('/putgetpath/'.length);

			app.fsCtrl.putGetPath(path, req, req.get('Content-Type'), function(err, path) {
				if (err) {
					log.error('Failed to putGetPath', err);
					resp.status(500).send(err);
				}
				log.info('Saved file %s', path);
				resp.send(path);
			});
		});
	}

// replace
	if (prop.allowedOperations.indexOf('replace') > -1) {
		app.put('/replace/*', function(req, resp) {
			var path = req.url.substring(9);

			app.fsCtrl.replace(path, req, resp, function(err) {
				if (err) {
					log.error('Failed to replace', err);
					resp.status(err.code || 500).send(err);
				}

			});
		});
	}

	return app;
};

module.exports = {create: create};
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom/server';
import Helmet from 'react-helmet';
import { Provider } from 'react-redux';

export default class Html extends Component {
  render() {
    const { assets, store, component, config } = this.props;
    const preloadedState = store.getState();
    const content = component ? this.renderComponent(component, store) : '';

    // read Helmet props after component is rendered
    const head = Helmet.rewind();
    const attrs = head.htmlAttributes.toComponent();

    return (

          {head.title.toComponent()}
          {head.meta.toComponent()}
          {head.link.toComponent()}
          {head.script.toComponent()}
          <link rel="stylesheet" href={ assets.main.css } />
          {/*
            Can't add script to Helmet in here (as we are already rendering ).
            And having script added to Helmet in App causes script to be loaded
            twice (once from server side rendered HTML, once on client side).

            See more:
            https://github.com/nfl/react-helmet/issues/149
            https://github.com/canonical-ols/javan-rhino/issues/176
          */}


<commit_before>var mongoose = require('mongoose');
var boardSchema = require('./../schemata/board');

var Promise = require('bluebird');

boardSchema.statics.findOneAndPopulate = function(query) {
  var Board = this;
  return new Promise(function (resolve, reject) {
    Board.findOne(query).populate('columns').exec(function(err, board) {
      if (err) throw err
      else if (board) {
        mongoose.model('Card')
        .populate(board.columns, { path: 'cards' }, function(err) {
          if (err)  throw err;
          else return resolve(board);
        });
      } else return reject(new Error("board not found"));
    });
  })
};

boardSchema.statics.createWithDefaultColumns = function (attributes, Column) {
  var Board = this;
  return new Promise(function (resolve, reject) {
    var Column = mongoose.model('Column');
    Promise.all([
      Board.create(attributes),
      Column.create({ name: "Icebox",  role: 1 }),
      Column.create({ name: "Backlog"          }),
      Column.create({ name: "Doing"            }),
      Column.create({ name: "Done",    role: 2 })
    ]).spread(function (board, icebox, backlog, doing, done) {
      board.columns.push(icebox);
      board.columns.push(backlog);
      board.columns.push(doing);
      board.columns.push(done);
      board.save(function(err, board) {
        if (err) reject(err);
        else resolve(board)
      })
    }).catch(reject)
  });
};
module.exports = mongoose.model('Board', boardSchema);
<commit_msg>begin implementing import mongoose method<commit_after>var _ = require('lodash');
var mongoose = require('mongoose');
var boardSchema = require('./../schemata/board');

var Promise = require('bluebird');

boardSchema.statics.findOneAndPopulate = function(query) {
  var Board = this;
  return new Promise(function (resolve, reject) {
    Board.findOne(query).populate('columns').exec(function(err, board) {
      if (err) throw err
      else if (board) {
        mongoose.model('Card')
        .populate(board.columns, { path: 'cards' }, function(err) {
          if (err)  throw err;
          else return resolve(board);
        });
      } else return reject(new Error("board not found"));
    });
  })
};

boardSchema.statics.createWithDefaultColumns = function (attributes) {
  var Board = this;
  return new Promise(function (resolve, reject) {
    var Column = mongoose.model('Column');
    Promise.all([
      Board.create(attributes),
      Column.create({ name: "Icebox",  role: 1 }),
      Column.create({ name: "Backlog"          }),
      Column.create({ name: "Doing"            }),
      Column.create({ name: "Done",    role: 2 })
    ]).spread(function (board, icebox, backlog, doing, done) {
      board.columns.push(icebox);
      board.columns.push(backlog);
      board.columns.push(doing);
      board.columns.push(done);
      board.save(function(err, board) {
        if (err) reject(err);
        else resolve(board)
      })
    }).catch(reject)
  });
};

boardSchema.statics.createViaImport = function (data, attributes) {
  var Board = this;
  return new Promise(function (resolve, reject) {
    var Column = mongoose.model('Column');
    var promises = [
      Board.create(attributes),
    ];

    _.each(data.columns, function(column) {
      var promise = Column.create({
        name: column.name,
        role: column.role
      });
      promises.push(promise);
    });

    Promise.all(promises).spread(function () {
      var args = _.toArray(arguments);
      var board = args[0];
      var columns = _.rest(args);
      _.each(columns, function (column) {
        board.columns.push(column);
      });
      board.save(function(err, board) {
        if (err) reject(err);
        else resolve(board)
      })
    }).catch(reject)
  });
};

module.exports = mongoose.model('Board', boardSchema);
<|endoftext|>


<commit_before>define([
  'angular',
  'lodash',
  '../core_module',
  'app/core/config',
],
function (angular, _, coreModule, config) {
  'use strict';

  coreModule.default.service('backendSrv', function($http, alertSrv, $timeout) {
    var self = this;

    this.get = function(url, params) {
      return this.request({ method: 'GET', url: url, params: params });
    };

    this.delete = function(url) {
      return this.request({ method: 'DELETE', url: url });
    };

    this.post = function(url, data) {
      return this.request({ method: 'POST', url: url, data: data });
    };

    this.patch = function(url, data) {
      return this.request({ method: 'PATCH', url: url, data: data });
    };

    this.put = function(url, data) {
      return this.request({ method: 'PUT', url: url, data: data });
    };

    this._handleError = function(err) {
      return function() {
        if (err.isHandled) {
          return;
        }

        var data = err.data || { message: 'Unexpected error' };
        if (_.isString(data)) {
          data = { message: data };
        }

        if (err.status === 422) {
          alertSrv.set("Validation failed", data.message, "warning", 4000);
          throw data;
        }

        data.severity = 'error';

        if (err.status < 500) {
          data.severity = "warning";
        }

        if (data.message) {
          alertSrv.set("Problem!", data.message, data.severity, 10000);
        }

        throw data;
      };
    };

    this.request = function(options) {
      options.retry = options.retry || 0;
      var requestIsLocal = options.url.indexOf('/') === 0;
      var firstAttempt = options.retry === 0;

      if (requestIsLocal && !options.hasSubUrl) {
        options.url = config.appSubUrl + options.url;
        options.hasSubUrl = true;
      }

      return $http(options).then(function(results) {
        if (options.method !== 'GET') {
          if (results && results.data.message) {
            alertSrv.set(results.data.message, '', 'success', 3000);
          }
        }
        return results.data;
      }, function(err) {
        // handle unauthorized
        if (err.status === 401 && firstAttempt) {
          return self.loginPing().then(function() {
            options.retry = 1;
            return self.request(options);
          });
        }

        $timeout(self._handleError(err), 50);
        throw err;
      });
    };

    this.datasourceRequest = function(options) {
      options.retry = options.retry || 0;
      var requestIsLocal = options.url.indexOf('/') === 0;
      var firstAttempt = options.retry === 0;

      return $http(options).then(null, function(err) {
        // handle unauthorized for backend requests
        if (requestIsLocal && firstAttempt  && err.status === 401) {
          return self.loginPing().then(function() {
            options.retry = 1;
            return self.datasourceRequest(options);
          });
        }

        //populate error obj on Internal Error
        if (_.isString(err.data) && err.status === 500 && !err.data) {
          err.data = {
            error: err.statusText
          };
        }

        // for Prometheus
        if (!err.data.message && _.isString(err.data.error)) {
          err.data.message = err.data.error;
        }

        throw err;
      });
    };

    this.loginPing = function() {
      return this.request({url: '/api/login/ping', method: 'GET', retry: 1 });
    };

    this.search = function(query) {
      return this.get('/api/search', query);
    };

    this.getDashboard = function(type, slug) {
      return this.get('/api/dashboards/' + type + '/' + slug);
    };

    this.saveDashboard = function(dash, options) {
      options = (options || {});
      return this.post('/api/dashboards/db/', {dashboard: dash, overwrite: options.overwrite === true});
    };

  });
});
<commit_msg>fix(backendsrv): remove invalid check<commit_after>define([
  'angular',
  'lodash',
  '../core_module',
  'app/core/config',
],
function (angular, _, coreModule, config) {
  'use strict';

  coreModule.default.service('backendSrv', function($http, alertSrv, $timeout) {
    var self = this;

    this.get = function(url, params) {
      return this.request({ method: 'GET', url: url, params: params });
    };

    this.delete = function(url) {
      return this.request({ method: 'DELETE', url: url });
    };

    this.post = function(url, data) {
      return this.request({ method: 'POST', url: url, data: data });
    };

    this.patch = function(url, data) {
      return this.request({ method: 'PATCH', url: url, data: data });
    };

    this.put = function(url, data) {
      return this.request({ method: 'PUT', url: url, data: data });
    };

    this._handleError = function(err) {
      return function() {
        if (err.isHandled) {
          return;
        }

        var data = err.data || { message: 'Unexpected error' };
        if (_.isString(data)) {
          data = { message: data };
        }

        if (err.status === 422) {
          alertSrv.set("Validation failed", data.message, "warning", 4000);
          throw data;
        }

        data.severity = 'error';

        if (err.status < 500) {
          data.severity = "warning";
        }

        if (data.message) {
          alertSrv.set("Problem!", data.message, data.severity, 10000);
        }

        throw data;
      };
    };

    this.request = function(options) {
      options.retry = options.retry || 0;
      var requestIsLocal = options.url.indexOf('/') === 0;
      var firstAttempt = options.retry === 0;

      if (requestIsLocal && !options.hasSubUrl) {
        options.url = config.appSubUrl + options.url;
        options.hasSubUrl = true;
      }

      return $http(options).then(function(results) {
        if (options.method !== 'GET') {
          if (results && results.data.message) {
            alertSrv.set(results.data.message, '', 'success', 3000);
          }
        }
        return results.data;
      }, function(err) {
        // handle unauthorized
        if (err.status === 401 && firstAttempt) {
          return self.loginPing().then(function() {
            options.retry = 1;
            return self.request(options);
          });
        }

        $timeout(self._handleError(err), 50);
        throw err;
      });
    };

    this.datasourceRequest = function(options) {
      options.retry = options.retry || 0;
      var requestIsLocal = options.url.indexOf('/') === 0;
      var firstAttempt = options.retry === 0;

      return $http(options).then(null, function(err) {
        // handle unauthorized for backend requests
        if (requestIsLocal && firstAttempt  && err.status === 401) {
          return self.loginPing().then(function() {
            options.retry = 1;
            return self.datasourceRequest(options);
          });
        }

        //populate error obj on Internal Error
        if (_.isString(err.data) && err.status === 500) {
          err.data = {
            error: err.statusText
          };
        }

        // for Prometheus
        if (!err.data.message && _.isString(err.data.error)) {
          err.data.message = err.data.error;
        }

        throw err;
      });
    };

    this.loginPing = function() {
      return this.request({url: '/api/login/ping', method: 'GET', retry: 1 });
    };

    this.search = function(query) {
      return this.get('/api/search', query);
    };

    this.getDashboard = function(type, slug) {
      return this.get('/api/dashboards/' + type + '/' + slug);
    };

    this.saveDashboard = function(dash, options) {
      options = (options || {});
      return this.post('/api/dashboards/db/', {dashboard: dash, overwrite: options.overwrite === true});
    };

  });
});
<|endoftext|>