<commit_before>class Background {
  constructor(args){
    this.config = args.config;
    this.notification = args.notification;
    this.storage = args.storage;
  }

  fetch(){
    this.gitlab = GitLab.createFromConfig(this.config, this.storage);
    this.config.activeProjectIds.forEach((project_id) => {
      this.gitlab.getProjectEvents(project_id).then((project_events) => {
        // latest check
        if(project_events.length < 1){
          return;
        }

        let event_count = 0;
        project_events.forEach((project_event) => {
          if(event_count >= this.config.maxNotificationCount){
            return;
          }

          this.notifyProjectEvent(project_id, project_event);
          event_count++;
        });
      });
    });
  }

  notifyProjectEvent(project_id, project_event){
    const target_type = project_event.target_type || "Commit";

    let comment_target_type;
    if(target_type == "Note" && project_event.action_name == "commented on"){
      comment_target_type = project_event.note.noteable_type;
    }

    if(!this.isNotifyTargetEvent(project_id, target_type) && !this.isNotifyTargetEvent(project_id, comment_target_type)){
      // continue loop
      return;
    }

    const project = this.config.getProject(project_id);

    switch (target_type){
      case "Commit": {
        if(!project_event.data){
          return;
        }

        const commit_message = this.getCommitMessage(project_event);
        const display_id = this.getCommitTargetId(project_event);
        const target_url = this.getCommitTargetUrl(project_id, project_event);

        if(target_url && commit_message){
          const branch_name = project_event.data.ref.replace(/^refs\/heads\//, "");

          this.notification.notify({
            project:       project,
            project_event: project_event,
            internal: {
              target_id:  display_id,
              target_url: target_url
            },
            message:      `[${branch_name}] @${project_event.data.user_name} ${display_id} ${commit_message} (${project_event.data.total_commits_count} commits)`,
            current_time: project_event.created_at || new Date(),
            author_id:    project_event.author_id
          });
        }
        break;
      }
      case "Issue":
      case "MergeRequest":
      case "Milestone": {
      // Issue, MergeRequest, Milestone
        return this.gitlab.getEventInternalId({
          project_name: project.name,
          target_type:  target_type,
          target_id:    project_event.target_id,
          project_id:   project_event.project_id,
        }).then((internal) => {
          this.notification.notify({
            project:       project,
            project_event: project_event,
            internal:      internal,
            message:       `[${target_type}] #${internal.target_id} ${project_event.target_title} ${project_event.action_name}`,
            current_time:  project_event.created_at || new Date(),
            author_id:     project_event.author_id
          });
        });
      }

      case "Note": {
      // Issue, MergeRequest (Comment)
        return this.gitlab.getEventInternalId({
          project_name: project.name,
          target_type:  project_event.note.noteable_type,
          target_id:    project_event.note.noteable_id,
          project_id:   project_event.project_id,
        }).then((internal) => {
          internal.target_url = `${internal.target_url}#note_${project_event.note.id}`;

          this.notification.notify({
            project:       project,
            project_event: project_event,
            internal:      internal,
            message:       `[${project_event.note.noteable_type}] #${internal.target_id} ${project_event.note.body} ${project_event.action_name}`,
            current_time:  project_event.created_at || new Date(),
            author_id:     project_event.author_id
          });
        });
      }
    }
  }

  getCommitMessage(project_event){
    if(!project_event.data || !project_event.data.commits || project_event.data.commits.length == 0){
      return null;
    }

    // use latest commit message
    return project_event.data.commits.reverse().map((commit) => {
      const first_line = commit.message.split(/(\r\n|\r|\n)/)[0];
      return first_line;
    }).find((first_line) => {
      return first_line.length > 0;
    });
  }

  isNotifyTargetEvent(project_id, target_type){
    const project = this.config.getProject(project_id);
    return project.events[target_type] ? true : false;
  }

  getCommitTargetId(project_event){
    const after = project_event.data.after;
    const before = project_event.data.before;

    if(this.isValidCommitId(before) && this.isValidCommitId(after)){
      return before.substr(0, 6) + "..." + after.substr(0, 6);
    } else if(this.isValidCommitId(before)){
      return before.substr(0, 6);
    } else if(this.isValidCommitId(after)){
      return after.substr(0, 6);
    }
  }

  getCommitTargetUrl(project_id, project_event){
    const project = this.config.getProject(project_id);
    const after = project_event.data.after;
    const before = project_event.data.before;

    if(this.isValidCommitId(before) && this.isValidCommitId(after)){
      return `${this.config.gitlabPath}${project.name}/compare/${before}...${after}`;
    } else if(this.isValidCommitId(before)){
      return `${this.config.gitlabPath}${project.name}/commit/${before}`;
    } else if(this.isValidCommitId(after)){
      return `${this.config.gitlabPath}${project.name}/commit/${after}`;
    }
  }

  isValidCommitId(commit_id){
    // invalid commit id is "0000000000000000000000...."
    return !commit_id.match(/^0+$/);
  }
}

try {
  module.exports = Background;
} catch (e){
}
<commit_msg>Tweak indent<commit_after>class Background {
  constructor(args){
    this.config = args.config;
    this.notification = args.notification;
    this.storage = args.storage;
  }

  fetch(){
    this.gitlab = GitLab.createFromConfig(this.config, this.storage);
    this.config.activeProjectIds.forEach((project_id) => {
      this.gitlab.getProjectEvents(project_id).then((project_events) => {
        // latest check
        if(project_events.length < 1){
          return;
        }

        let event_count = 0;
        project_events.forEach((project_event) => {
          if(event_count >= this.config.maxNotificationCount){
            return;
          }

          this.notifyProjectEvent(project_id, project_event);
          event_count++;
        });
      });
    });
  }

  notifyProjectEvent(project_id, project_event){
    const target_type = project_event.target_type || "Commit";

    let comment_target_type;
    if(target_type == "Note" && project_event.action_name == "commented on"){
      comment_target_type = project_event.note.noteable_type;
    }

    if(!this.isNotifyTargetEvent(project_id, target_type) && !this.isNotifyTargetEvent(project_id, comment_target_type)){
      // continue loop
      return;
    }

    const project = this.config.getProject(project_id);

    switch (target_type){
      case "Commit": {
        if(!project_event.data){
          return;
        }

        const commit_message = this.getCommitMessage(project_event);
        const display_id = this.getCommitTargetId(project_event);
        const target_url = this.getCommitTargetUrl(project_id, project_event);

        if(target_url && commit_message){
          const branch_name = project_event.data.ref.replace(/^refs\/heads\//, "");

          this.notification.notify({
            project:       project,
            project_event: project_event,
            internal: {
              target_id:  display_id,
              target_url: target_url
            },
            message:      `[${branch_name}] @${project_event.data.user_name} ${display_id} ${commit_message} (${project_event.data.total_commits_count} commits)`,
            current_time: project_event.created_at || new Date(),
            author_id:    project_event.author_id
          });
        }
        break;
      }
      case "Issue":
      case "MergeRequest":
      case "Milestone": {
        // Issue, MergeRequest, Milestone
        return this.gitlab.getEventInternalId({
          project_name: project.name,
          target_type:  target_type,
          target_id:    project_event.target_id,
          project_id:   project_event.project_id,
        }).then((internal) => {
          this.notification.notify({
            project:       project,
            project_event: project_event,
            internal:      internal,
            message:       `[${target_type}] #${internal.target_id} ${project_event.target_title} ${project_event.action_name}`,
            current_time:  project_event.created_at || new Date(),
            author_id:     project_event.author_id
          });
        });
      }

      case "Note": {
        // Issue, MergeRequest (Comment)
        return this.gitlab.getEventInternalId({
          project_name: project.name,
          target_type:  project_event.note.noteable_type,
          target_id:    project_event.note.noteable_id,
          project_id:   project_event.project_id,
        }).then((internal) => {
          internal.target_url = `${internal.target_url}#note_${project_event.note.id}`;

          this.notification.notify({
            project:       project,
            project_event: project_event,
            internal:      internal,
            message:       `[${project_event.note.noteable_type}] #${internal.target_id} ${project_event.note.body} ${project_event.action_name}`,
            current_time:  project_event.created_at || new Date(),
            author_id:     project_event.author_id
          });
        });
      }
    }
  }

  getCommitMessage(project_event){
    if(!project_event.data || !project_event.data.commits || project_event.data.commits.length == 0){
      return null;
    }

    // use latest commit message
    return project_event.data.commits.reverse().map((commit) => {
      const first_line = commit.message.split(/(\r\n|\r|\n)/)[0];
      return first_line;
    }).find((first_line) => {
      return first_line.length > 0;
    });
  }

  isNotifyTargetEvent(project_id, target_type){
    const project = this.config.getProject(project_id);
    return project.events[target_type] ? true : false;
  }

  getCommitTargetId(project_event){
    const after = project_event.data.after;
    const before = project_event.data.before;

    if(this.isValidCommitId(before) && this.isValidCommitId(after)){
      return before.substr(0, 6) + "..." + after.substr(0, 6);
    } else if(this.isValidCommitId(before)){
      return before.substr(0, 6);
    } else if(this.isValidCommitId(after)){
      return after.substr(0, 6);
    }
  }

  getCommitTargetUrl(project_id, project_event){
    const project = this.config.getProject(project_id);
    const after = project_event.data.after;
    const before = project_event.data.before;

    if(this.isValidCommitId(before) && this.isValidCommitId(after)){
      return `${this.config.gitlabPath}${project.name}/compare/${before}...${after}`;
    } else if(this.isValidCommitId(before)){
      return `${this.config.gitlabPath}${project.name}/commit/${before}`;
    } else if(this.isValidCommitId(after)){
      return `${this.config.gitlabPath}${project.name}/commit/${after}`;
    }
  }

  isValidCommitId(commit_id){
    // invalid commit id is "0000000000000000000000...."
    return !commit_id.match(/^0+$/);
  }
}

try {
  module.exports = Background;
} catch (e){
}
<|endoftext|>


<commit_before>'use strict'

import { app, protocol, BrowserWindow } from 'electron'
import {
  createProtocol,
  installVueDevtools
} from 'vue-cli-plugin-electron-builder/lib'
const isDevelopment = process.env.NODE_ENV !== 'production'

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win

// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([{scheme: 'app', privileges: { secure: true, standard: true } }])

function createWindow () {
  // Create the browser window.
  win = new BrowserWindow({ width: 800, height: 600, webPreferences: {
    nodeIntegration: true
  }})

  if (process.env.WEBPACK_DEV_SERVER_URL) {
    // Load the url of the dev server if in development mode
    win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
    if (!process.env.IS_TEST) win.webContents.openDevTools()
  } else {
    createProtocol('app')
    // Load the index.html when not in development
    win.loadURL('app://./index.html')
  }

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

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (win === null) {
    createWindow()
  }
})

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
  if (isDevelopment && !process.env.IS_TEST) {
    // Install Vue Devtools
    // Devtools extensions are broken in Electron 6.0.0 and greater
    // See https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/378 for more info
    // Electron will not launch with Devtools extensions installed on Windows 10 with dark mode
    // If you are not using Windows 10 dark mode, you may uncomment these lines
    // In addition, if the linked issue is closed, you can upgrade electron and uncomment these lines
    // try {
    //   await installVueDevtools()
    // } catch (e) {
    //   console.error('Vue Devtools failed to install:', e.toString())
    // }

  }
  createWindow()
})

// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
  if (process.platform === 'win32') {
    process.on('message', data => {
      if (data === 'graceful-exit') {
        app.quit()
      }
    })
  } else {
    process.on('SIGTERM', () => {
      app.quit()
    })
  }
}
<commit_msg>Temporary disable eslint warning<commit_after>'use strict'

import { app, protocol, BrowserWindow } from 'electron'
import {
  createProtocol,
  // eslint-disable-next-line no-unused-vars
  installVueDevtools
} from 'vue-cli-plugin-electron-builder/lib'
const isDevelopment = process.env.NODE_ENV !== 'production'

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win

// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([{scheme: 'app', privileges: { secure: true, standard: true } }])

function createWindow () {
  // Create the browser window.
  win = new BrowserWindow({ width: 800, height: 600, webPreferences: {
    nodeIntegration: true
  }})

  if (process.env.WEBPACK_DEV_SERVER_URL) {
    // Load the url of the dev server if in development mode
    win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
    if (!process.env.IS_TEST) win.webContents.openDevTools()
  } else {
    createProtocol('app')
    // Load the index.html when not in development
    win.loadURL('app://./index.html')
  }

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

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (win === null) {
    createWindow()
  }
})

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
  if (isDevelopment && !process.env.IS_TEST) {
    // Install Vue Devtools
    // Devtools extensions are broken in Electron 6.0.0 and greater
    // See https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/378 for more info
    // Electron will not launch with Devtools extensions installed on Windows 10 with dark mode
    // If you are not using Windows 10 dark mode, you may uncomment these lines
    // In addition, if the linked issue is closed, you can upgrade electron and uncomment these lines
    // try {
    //   await installVueDevtools()
    // } catch (e) {
    //   console.error('Vue Devtools failed to install:', e.toString())
    // }

  }
  createWindow()
})

// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
  if (process.platform === 'win32') {
    process.on('message', data => {
      if (data === 'graceful-exit') {
        app.quit()
      }
    })
  } else {
    process.on('SIGTERM', () => {
      app.quit()
    })
  }
}
<|endoftext|>


<commit_before>'use strict';
/*global describe:true, it:true*/

var should = require('should');
var mongoose = require('mongoose');
var request = require('supertest');

require('./unit-utils');
var app = require('./app').app;

describe('Sanity checks', function() {
  describe('The test system', function() {
    it('should run an isolated test', function(done) {
      'hello'.should.equal('hello');
      return done();
    });

    it('should perform tests in the test environment', function(done) {
      process.env.NODE_ENV.should.equal('test');
      return done();
    });

    it('should have a mongoose connection', function(done) {
      mongoose.connection.readyState.should.equal(1);
      return done();
    });
  });

  describe('The web server', function() {
    it('should respond to pings', function(done) {
      request(app)
        .get('/ping')
        .expect(200)
        .end(function(err, res) {
          should.not.exist(err);
          res.text.should.equal('pong');
          return done();
        });
    });
  });
});
<commit_msg>Clean up basic tests<commit_after>/*global describe:true, it:true*/
const assert = require('assert');
const request = require('supertest');

const app = require('./app').app;

describe('Sanity checks', () => {
  describe('The test system', () => {
    it('should run a simple test', done => {
      assert('hello' === 'hello', 'The `hello` strings should match');
      return done();
    });

    it('should perform tests in the test environment', done => {
      assert(process.env.NODE_ENV === 'test', 'The NODE_ENV should be `test`');
      return done();
    });
  });

  describe('The web server', () => {
    it('should respond to pings', done => {
      request(app)
        .get('/ping')
        .expect(200)
        .end(function(err, res) {
          assert(err === null, err);
          assert(res.text === 'pong', 'The app did not return with a `pong`');
          return done();
        });
    });
  });
});
<|endoftext|>


<commit_before>import { createRectangle, createEqualLateralPolygon } from 'pura/geometry/tuple';
import { addList, mapList, scaleSet } from 'pura/vector/tuple';
import { ctx, fillRectangle, fillPolygon, strokePolygon, fillText } from 'pura/canvas/tuple';
import { contains } from 'pura/array';
import { hexToVector2d } from 'pura/hex';
import { viewHeight, viewWidth } from 'dom';
import { perspective2d } from 'camera';
import { battleData, optionHexes, turnOrder } from 'battle/grid';
import { makeEvilPool } from 'overworld/pools';
import state from 'state';

const gridColor = `black`;
const selectColor = `#fcfc68`;
const optionColor = `#78005c`;

const baseHex = createEqualLateralPolygon([0, 0], 0, 6, 20);

const calculateHexGeometry = data => {
  const { hex } = data;
  const position = scaleSet(hexToVector2d(hex), 20);
  const viewPosition = perspective2d(position);
  const points = mapList(addList(baseHex.points, position), perspective2d);
  data.position = viewPosition;
  data.points = points;
  return data;
};

const drawFill =
  (options) =>
    ({ hex, points }) => {
      if(state.target === hex) Date.now() % 600 > 400 && fillPolygon(selectColor, [0, 0], points, 0);
      if(contains(options, hex)) {
        // Date.now() % 600 > 400 && fillPolygon(optionColor, [0, 0], points, 0);
        strokePolygon({ style: gridColor, thickness: 1 }, [0, 0], points, 0);
      }
    };

const drawOutline =
  ({ points }) =>
    strokePolygon({ style: gridColor, thickness: 1 }, [0, 0], points, 0);

// TODO: Draw different things based on status effects (hots/dots).
const entityTextStyle = { horizontalAlign: true };
const drawEntity =
  ([data, health, position,]) =>
    health > 0 && fillText(entityTextStyle, perspective2d(position), data.emoji);

const groundGradient = ctx.createLinearGradient(-100, -100, 200, 200);
groundGradient.addColorStop(0, `#480078`);
groundGradient.addColorStop(1, `#78005c`);

const skyGradient = ctx.createLinearGradient(0, 0, 100, 100);
skyGradient.addColorStop(0, `#F07241`);
skyGradient.addColorStop(1, `#601848`);

const basePool = makeEvilPool(400, [0, 0]);

export default ({
  geometry: createRectangle([-viewWidth / 2, -viewHeight / 2], 0, viewWidth, viewHeight),
  render() {
    // Background
    fillRectangle(
      skyGradient,
      [0, 0],
      viewWidth * 2,
      viewHeight * 2,
      0,
    );
    basePool.render();
    battleData.forEach(calculateHexGeometry);
    battleData.forEach(drawFill(optionHexes));
    // battleData.forEach(drawOutline);
    turnOrder.forEach(drawEntity);
  }
});
<commit_msg>Optimize battle rendering<commit_after>import { createRectangle, getRectanglePoints, createEqualLateralPolygon } from 'pura/geometry/tuple';
import { addList, mapListSet, scaleSet } from 'pura/vector/tuple';
import { ctx, fillRectangle, fillPolygon, strokePolygon, strokeOval, fillArc, strokeArc, fillText } from 'pura/canvas/tuple';
import { contains } from 'pura/array';
import { hexToVector2d } from 'pura/hex';
import { viewHeight, viewWidth } from 'dom';
import { perspective2d } from 'camera';
import { battleData, optionHexes, turnOrder } from 'battle/grid';
import { makeEvilPool } from 'overworld/pools';
import state from 'state';

// const selectColor = `#fcfc68`;
const optionColor = `#8f9e6f`;

const baseSquarePoints = getRectanglePoints(15, 15);

const calculatePoints = (hex, scale = 1) =>
  mapListSet(
    addList(
      scale === 1
        ? baseSquarePoints
        : getRectanglePoints(15 * scale, 15 * scale),
      scaleSet(hexToVector2d(hex), 20)
    ),
    perspective2d
  );

const drawFill =
  (hex) => {
    if(!hex) return;
    if(state.target === hex) {
      strokeOval({ style: optionColor }, [0, 0], calculatePoints(hex, Math.min(Date.now() % 2000 / 1000), 1));
    } else {
      strokeOval({ style: optionColor }, [0, 0], calculatePoints(hex));
    }
    // if(state.target === hex) {
    //   Date.now() % 600 > 400 && fillPolygon(selectColor, [0, 0], points, 0);
    // } else {
    //   strokePolygon({ style: gridColor, thickness: 1 }, [0, 0], points, 0);
    // }
  };

// const drawOutline =
//   ({ points }) =>
//     strokePolygon({ style: gridColor, thickness: 1 }, [0, 0], points, 0);

// TODO: Draw different things based on status effects (hots/dots).
const entityTextStyle = { horizontalAlign: true };
const drawEntity =
  ([data, health, position,]) =>
    health > 0 && fillText(entityTextStyle, perspective2d(position), data.emoji);

const groundGradient = ctx.createLinearGradient(-100, -100, 200, 200);
groundGradient.addColorStop(0, `#480078`);
groundGradient.addColorStop(1, `#78005c`);

const skyGradient = ctx.createLinearGradient(0, 0, 100, 100);
skyGradient.addColorStop(0, `#F07241`);
skyGradient.addColorStop(1, `#601848`);

const basePool = makeEvilPool(400, [0, 0]);

export default ({
  geometry: createRectangle([-viewWidth / 2, -viewHeight / 2], 0, viewWidth, viewHeight),
  render() {
    // Background
    fillRectangle(
      skyGradient,
      [0, 0],
      viewWidth * 2,
      viewHeight * 2,
      0,
    );
    basePool.render();
    [...optionHexes, state.target].forEach(drawFill);
    turnOrder.forEach(drawEntity);
  }
});
<|endoftext|>


<commit_before>/* utilities for Brave's HAPI servers

   soon to be an open source module...

*/

var underscore = require('underscore')
var wreck = require('wreck')

var exports = {}

exports.debug = function (info, request) {
  var sdebug = new (require('./sdebug'))(info.id)

  sdebug.initialize({ request: { id: request.id } })
  return sdebug
}

var AsyncRoute = function () {
  if (!(this instanceof AsyncRoute)) { return new AsyncRoute() }

  this.internal = {}
  this.internal.method = 'GET'
  this.internal.path = '/'
}

AsyncRoute.prototype.post = function () {
  this.internal.method = 'POST'
  return this
}

AsyncRoute.prototype.get = function () {
  this.internal.method = 'GET'
  return this
}

AsyncRoute.prototype.put = function () {
  this.internal.method = 'PUT'
  return this
}

AsyncRoute.prototype.delete = function () {
  this.internal.method = 'DELETE'
  return this
}

AsyncRoute.prototype.path = function (path) {
  this.internal.path = path
  return this
}

AsyncRoute.prototype.config = function (config) {
  if (typeof config === 'function') { config = { handler: config } }
  if (typeof config.handler === 'undefined') { throw new Error('undefined handler for ' + JSON.stringify(this.internal)) }

  return runtime => {
    var payload = { handler: { async: config.handler(runtime) } }

    underscore.keys(config).forEach(key => {
      if ((key !== 'handler') && (typeof config[key] !== 'undefined')) payload[key] = config[key]
    })

    return {
      method: this.internal.method,
      path: this.internal.path,
      config: payload
    }
  }
}

exports.routes = { async: AsyncRoute }

var ErrorInspect = function (err) {
  var i

  if (!i) { return undefined }

  i = underscore.pick(err, 'message', 'isBoom', 'isServer')
  if (err.data) { underscore.defaults(i, err.data) }

  return i
}

exports.error = { inspect: ErrorInspect }

/**
 * Async wrapper for wreck.post to return the response payload.
 */
var WreckPost = async function (server, opts) {
  return new Promise((resolve, reject) => {
    wreck.post(
      server,
      opts,
      (err, response, body) => {
        if (err) {
          return reject(err)
        }
        resolve(body)
      })
  })
}

exports.wreck = { post: WreckPost }

module.exports = exports
<commit_msg>[errors] report boom/joi errors properly<commit_after>/* utilities for Brave's HAPI servers

   soon to be an open source module...

*/

var underscore = require('underscore')
var wreck = require('wreck')

var exports = {}

exports.debug = function (info, request) {
  var sdebug = new (require('./sdebug'))(info.id)

  sdebug.initialize({ request: { id: request.id } })
  return sdebug
}

var AsyncRoute = function () {
  if (!(this instanceof AsyncRoute)) { return new AsyncRoute() }

  this.internal = {}
  this.internal.method = 'GET'
  this.internal.path = '/'
}

AsyncRoute.prototype.post = function () {
  this.internal.method = 'POST'
  return this
}

AsyncRoute.prototype.get = function () {
  this.internal.method = 'GET'
  return this
}

AsyncRoute.prototype.put = function () {
  this.internal.method = 'PUT'
  return this
}

AsyncRoute.prototype.delete = function () {
  this.internal.method = 'DELETE'
  return this
}

AsyncRoute.prototype.path = function (path) {
  this.internal.path = path
  return this
}

AsyncRoute.prototype.config = function (config) {
  if (typeof config === 'function') { config = { handler: config } }
  if (typeof config.handler === 'undefined') { throw new Error('undefined handler for ' + JSON.stringify(this.internal)) }

  return runtime => {
    var payload = { handler: { async: config.handler(runtime) } }

    underscore.keys(config).forEach(key => {
      if ((key !== 'handler') && (typeof config[key] !== 'undefined')) payload[key] = config[key]
    })

    return {
      method: this.internal.method,
      path: this.internal.path,
      config: payload
    }
  }
}

exports.routes = { async: AsyncRoute }

var ErrorInspect = function (err) {
  var i

  if (!err) { return undefined }

  i = underscore.pick(err, 'message', 'isBoom', 'isServer')
  if ((err.output) && (err.output.payload)) { underscore.defaults(i, { payload: err.output.payload }) }

  return i
}

exports.error = { inspect: ErrorInspect }

/**
 * Async wrapper for wreck.post to return the response payload.
 */
var WreckPost = async function (server, opts) {
  return new Promise((resolve, reject) => {
    wreck.post(
      server,
      opts,
      (err, response, body) => {
        if (err) {
          return reject(err)
        }
        resolve(body)
      })
  })
}

exports.wreck = { post: WreckPost }

module.exports = exports
<|endoftext|>


<commit_before>/*!
 * OS.js - JavaScript Cloud/Web Desktop Platform
 *
 * Copyright (c) 2011-2016, Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */
(function(_fs, _path, _utils, _manifest) {
  'use strict';

  var ROOT = _path.dirname(_path.dirname(_path.join(__dirname)));

  var _ugly;
  var Cleancss;

  try {
    _ugly = require('uglify-js');
  } catch ( e ) {}
  try {
    Cleancss = require('clean-css');
  } catch ( e ) {}

  /////////////////////////////////////////////////////////////////////////////
  // HELPERS
  /////////////////////////////////////////////////////////////////////////////

  /**
   * Filter a file reference by string
   */
  function _filter(i, opts) {
    if ( i.match(/^dev:/) && opts.target !== 'dist-dev' ) {
      return false;
    }
    if ( i.match(/^prod:/) && opts.target !== 'dist' ) {
      return false;
    }
    return true;
  }

  /**
   * Wrapper for reading JS
   */
  function _readJS(opts, list) {
    return list.filter(function(i) {
      return _filter(i, opts);
    }).map(function(i) {
      var path = _path.join(ROOT, i.replace(/^(dev|prod):/, ''));
      var data = opts.compress ? _ugly.minify(path, {comments: false}).code : _fs.readFileSync(path).toString();

      if ( opts.target !== 'nw' ) {
        data = data.replace(/\/\*\![\s\S]*?\*\//, '')
          .replace(/console\.(log|debug|info|group|groupStart|groupEnd|count)\((.*)\);/g, '')
          .replace(/(?:\/\*(?:[\s\S]*?)\*\/)|(?:^\s*\/\/(?:.*)$)/gm, '')
          .replace(/^\s*[\r\n]/gm, '');
      }

      return data;
    }).join('\n');
  }

  /**
   * Wrapper for reading CSS
   */
  function _readCSS(opts, list) {
    return list.filter(function(i) {
      return _filter(i, opts);
    }).map(function(i) {
      var path = _path.join(ROOT, i.replace(/^(dev|prod):/, ''))
      var data = _fs.readFileSync(path).toString();
      if ( opts.compress ) {
        data = new Cleancss().minify(data).styles;
      }

      if ( opts.target !== 'nw' ) {
        data = data.replace(/\/\*\![\s\S]*?\*\//, '')
          .replace('@charset "UTF-8";', '')
          .replace(/(?:\/\*(?:[\s\S]*?)\*\/)|(?:^\s*\/\/(?:.*)$)/gm, '')
          .replace(/^\s*[\r\n]/gm, '');
      }
      return data;
    }).join('\n');
  }

  /**
   * Create a 'index.html' file
   */
  function _createIndex(opts, dist, fn) {
    var tpldir = _path.join(ROOT, 'src', 'templates', 'dist', opts.build.dist.template);
    var outdir = _path.join(ROOT, dist || 'dist-dev');
    var scripts = [];
    var styles = [];

    fn(function(i) {
      if ( opts.verbose ) {
        _utils.log('-', i);
      }
      styles.push('    <link type="text/css" rel="stylesheet" href="' + i + '" />');
    }, function(i) {
      if ( opts.verbose ) {
        _utils.log('-', i);
      }
      scripts.push('    <script type="text/javascript" charset="utf-8" src="' + i + '"></script>');
    });

    var tpl = _fs.readFileSync(_path.join(tpldir, 'index.html')).toString();
    tpl = _utils.replaceAll(tpl, '%STYLES%', styles.join('\n'));
    tpl = _utils.replaceAll(tpl, '%SCRIPTS%', scripts.join('\n'));
    tpl = _utils.replaceAll(tpl, '%HANDLER%', opts.handler);

    _fs.writeFileSync(_path.join(outdir, 'index.html'), tpl);
  }

  /////////////////////////////////////////////////////////////////////////////
  // HELPERS
  /////////////////////////////////////////////////////////////////////////////

  var TARGETS = {
    'dist': function(opts, done) {
      var jsh = _utils.readTemplate('dist/header.js');
      var cssh = _utils.readTemplate('dist/header.css');

      function _nw(cb) {
        if ( opts.nw ) {
          var dest = _path.join(ROOT, 'dist');

          _fs.mkdirSync(_path.join(dest, 'vfs'));
          _fs.mkdirSync(_path.join(dest, 'vfs', 'home'));
          _fs.mkdirSync(_path.join(dest, 'vfs', 'home', 'demo'));

          _fs.copySync(
            _path.join(ROOT, 'README.md'),
            _path.join(dest, 'vfs', 'home', 'demo', 'README.md')
          );
          _fs.copySync(
            _path.join(ROOT, 'src', 'templates', 'nw', 'package.json'),
            _path.join(dest, 'package.json')
          );
          _fs.copySync(
            _path.join(ROOT, 'src', 'server', 'packages.json'),
            _path.join(dest, 'packages.json')
          );

          // Install dependencies
          _fs.copySync(
            _path.join(ROOT, 'src', 'server', 'node', 'node_modules'),
            _path.join(dest, 'node_modules')
          );

          var cmd = 'cd "' + dest + '" && npm install';
          require('child_process').exec(cmd, function(err, stdout, stderr) {
            console.log(stderr, stdout);
            cb();
          });
        } else {
          cb();
        }
      }

      var end = opts.compress ? '.min' : '';
      _fs.writeFileSync(_path.join(ROOT, 'dist', 'osjs' + end +  '.js'), jsh + _readJS(opts, opts.build.javascript));
      _fs.writeFileSync(_path.join(ROOT, 'dist', 'locales' + end +  '.js'), jsh + _readJS(opts, opts.build.locales));
      _fs.writeFileSync(_path.join(ROOT, 'dist', 'osjs' + end +  '.css'), cssh + _readCSS(opts, opts.build.stylesheets));

      var appendString = '';
      if ( opts.client.Connection.AppendVersion ) {
        appendString = '?ver=' + opts.client.Connection.AppendVersion;
      }

      _createIndex(opts, 'dist', function(addStyle, addScript) {
        if ( opts.compress ) {
          addStyle('osjs.min.css' + appendString);
          addScript('osjs.min.js' + appendString);
          addScript('locales.min.js' + appendString);
        } else {
          addStyle('osjs.css' + appendString);
          addScript('osjs.js' + appendString);
          addScript('locales.js' + appendString);
        }

        if ( opts.standalone ) {
          addScript('_dialogs.js');
        }
      });

      if ( opts.standalone ) {
        var src = _path.join(ROOT, 'src', 'client', 'dialogs.html');
        _utils.createStandaloneScheme(src, '/dialogs.html', _path.join(ROOT, 'dist', '_dialogs.js'));
      }

      _nw(done);
    },

    'dist-dev': function(opts, done) {
      _createIndex(opts, 'dist-dev', function(addStyle, addScript) {
        opts.build.javascript.forEach(function(i) {
          if ( !i.match(/handlers\/(\w+)\/handler\.js$/) ) { // handler scripts are automatically preloaded by config!
            addScript(i.replace(/src\/client\/(.*)/, 'client/$1'));
          }
        });

        opts.build.locales.forEach(function(i) {
          addScript(i.replace(/src\/client\/(.*)/, 'client/$1'));
        });

        opts.build.stylesheets.forEach(function(i) {
          if ( _filter(i, {target: 'dist-dev'}) ) {
            addStyle(i.replace(/^(dev|prod):/, '').replace(/src\/client\/(.*)/, 'client/$1'));
          }
        });
      });

      done();
    }
  };

  /////////////////////////////////////////////////////////////////////////////
  // API
  /////////////////////////////////////////////////////////////////////////////

  /**
   * grunt build:core
   *
   * Builds all core files and generates dist directories
   */
  function buildFiles(opts, done) {
    if ( !TARGETS[opts.target] ) {
      return done('Invalid target', false);
    }

    var tpldir = _path.join(ROOT, 'src', 'templates', 'dist', opts.build.dist.template);
    var outdir = _path.join(ROOT, opts.target);

    Object.keys(opts.build.statics).forEach(function(f) {
      var src = _path.join(ROOT, f);
      var dst = _path.join(ROOT, opts.build.statics[f]);
      if ( opts.verbose ) {
        _utils.log('-', opts.build.statics[f]);
      }
      _fs.copySync(src, dst);
    });

    var ignore = ['index.html'];
    _fs.readdirSync(tpldir).forEach(function(iter) {
      if ( ignore.indexOf(iter) < 0 ) {
        if ( opts.verbose ) {
          _utils.log('-', _path.join(tpldir, iter));
        }
        _fs.copySync(_path.join(tpldir, iter), _path.join(outdir, iter));
      }
    });

    TARGETS[opts.target](opts, done)
  }

  /////////////////////////////////////////////////////////////////////////////
  // EXPORTS
  /////////////////////////////////////////////////////////////////////////////

  module.exports.buildFiles = buildFiles;

})(require('node-fs-extra'), require('path'), require('./utils.js'), require('./manifest.js'));
<commit_msg>build: Static build files can now be skipped if already exists<commit_after>/*!
 * OS.js - JavaScript Cloud/Web Desktop Platform
 *
 * Copyright (c) 2011-2016, Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */
(function(_fs, _path, _utils, _manifest) {
  'use strict';

  var ROOT = _path.dirname(_path.dirname(_path.join(__dirname)));

  var _ugly;
  var Cleancss;

  try {
    _ugly = require('uglify-js');
  } catch ( e ) {}
  try {
    Cleancss = require('clean-css');
  } catch ( e ) {}

  /////////////////////////////////////////////////////////////////////////////
  // HELPERS
  /////////////////////////////////////////////////////////////////////////////

  /**
   * Filter a file reference by string
   */
  function _filter(i, opts) {
    if ( i.match(/^dev:/) && opts.target !== 'dist-dev' ) {
      return false;
    }
    if ( i.match(/^prod:/) && opts.target !== 'dist' ) {
      return false;
    }
    return true;
  }

  /**
   * Wrapper for reading JS
   */
  function _readJS(opts, list) {
    return list.filter(function(i) {
      return _filter(i, opts);
    }).map(function(i) {
      var path = _path.join(ROOT, i.replace(/^(dev|prod):/, ''));
      var data = opts.compress ? _ugly.minify(path, {comments: false}).code : _fs.readFileSync(path).toString();

      if ( opts.target !== 'nw' ) {
        data = data.replace(/\/\*\![\s\S]*?\*\//, '')
          .replace(/console\.(log|debug|info|group|groupStart|groupEnd|count)\((.*)\);/g, '')
          .replace(/(?:\/\*(?:[\s\S]*?)\*\/)|(?:^\s*\/\/(?:.*)$)/gm, '')
          .replace(/^\s*[\r\n]/gm, '');
      }

      return data;
    }).join('\n');
  }

  /**
   * Wrapper for reading CSS
   */
  function _readCSS(opts, list) {
    return list.filter(function(i) {
      return _filter(i, opts);
    }).map(function(i) {
      var path = _path.join(ROOT, i.replace(/^(dev|prod):/, ''))
      var data = _fs.readFileSync(path).toString();
      if ( opts.compress ) {
        data = new Cleancss().minify(data).styles;
      }

      if ( opts.target !== 'nw' ) {
        data = data.replace(/\/\*\![\s\S]*?\*\//, '')
          .replace('@charset "UTF-8";', '')
          .replace(/(?:\/\*(?:[\s\S]*?)\*\/)|(?:^\s*\/\/(?:.*)$)/gm, '')
          .replace(/^\s*[\r\n]/gm, '');
      }
      return data;
    }).join('\n');
  }

  /**
   * Create a 'index.html' file
   */
  function _createIndex(opts, dist, fn) {
    var tpldir = _path.join(ROOT, 'src', 'templates', 'dist', opts.build.dist.template);
    var outdir = _path.join(ROOT, dist || 'dist-dev');
    var scripts = [];
    var styles = [];

    fn(function(i) {
      if ( opts.verbose ) {
        _utils.log('-', i);
      }
      styles.push('    <link type="text/css" rel="stylesheet" href="' + i + '" />');
    }, function(i) {
      if ( opts.verbose ) {
        _utils.log('-', i);
      }
      scripts.push('    <script type="text/javascript" charset="utf-8" src="' + i + '"></script>');
    });

    var tpl = _fs.readFileSync(_path.join(tpldir, 'index.html')).toString();
    tpl = _utils.replaceAll(tpl, '%STYLES%', styles.join('\n'));
    tpl = _utils.replaceAll(tpl, '%SCRIPTS%', scripts.join('\n'));
    tpl = _utils.replaceAll(tpl, '%HANDLER%', opts.handler);

    _fs.writeFileSync(_path.join(outdir, 'index.html'), tpl);
  }

  /////////////////////////////////////////////////////////////////////////////
  // HELPERS
  /////////////////////////////////////////////////////////////////////////////

  var TARGETS = {
    'dist': function(opts, done) {
      var jsh = _utils.readTemplate('dist/header.js');
      var cssh = _utils.readTemplate('dist/header.css');

      function _nw(cb) {
        if ( opts.nw ) {
          var dest = _path.join(ROOT, 'dist');

          _fs.mkdirSync(_path.join(dest, 'vfs'));
          _fs.mkdirSync(_path.join(dest, 'vfs', 'home'));
          _fs.mkdirSync(_path.join(dest, 'vfs', 'home', 'demo'));

          _fs.copySync(
            _path.join(ROOT, 'README.md'),
            _path.join(dest, 'vfs', 'home', 'demo', 'README.md')
          );
          _fs.copySync(
            _path.join(ROOT, 'src', 'templates', 'nw', 'package.json'),
            _path.join(dest, 'package.json')
          );
          _fs.copySync(
            _path.join(ROOT, 'src', 'server', 'packages.json'),
            _path.join(dest, 'packages.json')
          );

          // Install dependencies
          _fs.copySync(
            _path.join(ROOT, 'src', 'server', 'node', 'node_modules'),
            _path.join(dest, 'node_modules')
          );

          var cmd = 'cd "' + dest + '" && npm install';
          require('child_process').exec(cmd, function(err, stdout, stderr) {
            console.log(stderr, stdout);
            cb();
          });
        } else {
          cb();
        }
      }

      var end = opts.compress ? '.min' : '';
      _fs.writeFileSync(_path.join(ROOT, 'dist', 'osjs' + end +  '.js'), jsh + _readJS(opts, opts.build.javascript));
      _fs.writeFileSync(_path.join(ROOT, 'dist', 'locales' + end +  '.js'), jsh + _readJS(opts, opts.build.locales));
      _fs.writeFileSync(_path.join(ROOT, 'dist', 'osjs' + end +  '.css'), cssh + _readCSS(opts, opts.build.stylesheets));

      var appendString = '';
      if ( opts.client.Connection.AppendVersion ) {
        appendString = '?ver=' + opts.client.Connection.AppendVersion;
      }

      _createIndex(opts, 'dist', function(addStyle, addScript) {
        if ( opts.compress ) {
          addStyle('osjs.min.css' + appendString);
          addScript('osjs.min.js' + appendString);
          addScript('locales.min.js' + appendString);
        } else {
          addStyle('osjs.css' + appendString);
          addScript('osjs.js' + appendString);
          addScript('locales.js' + appendString);
        }

        if ( opts.standalone ) {
          addScript('_dialogs.js');
        }
      });

      if ( opts.standalone ) {
        var src = _path.join(ROOT, 'src', 'client', 'dialogs.html');
        _utils.createStandaloneScheme(src, '/dialogs.html', _path.join(ROOT, 'dist', '_dialogs.js'));
      }

      _nw(done);
    },

    'dist-dev': function(opts, done) {
      _createIndex(opts, 'dist-dev', function(addStyle, addScript) {
        opts.build.javascript.forEach(function(i) {
          if ( !i.match(/handlers\/(\w+)\/handler\.js$/) ) { // handler scripts are automatically preloaded by config!
            addScript(i.replace(/src\/client\/(.*)/, 'client/$1'));
          }
        });

        opts.build.locales.forEach(function(i) {
          addScript(i.replace(/src\/client\/(.*)/, 'client/$1'));
        });

        opts.build.stylesheets.forEach(function(i) {
          if ( _filter(i, {target: 'dist-dev'}) ) {
            addStyle(i.replace(/^(dev|prod):/, '').replace(/src\/client\/(.*)/, 'client/$1'));
          }
        });
      });

      done();
    }
  };

  /////////////////////////////////////////////////////////////////////////////
  // API
  /////////////////////////////////////////////////////////////////////////////

  /**
   * grunt build:core
   *
   * Builds all core files and generates dist directories
   */
  function buildFiles(opts, done) {
    if ( !TARGETS[opts.target] ) {
      return done('Invalid target', false);
    }

    var tpldir = _path.join(ROOT, 'src', 'templates', 'dist', opts.build.dist.template);
    var outdir = _path.join(ROOT, opts.target);

    Object.keys(opts.build.statics).forEach(function(f) {
      var dst = _path.join(ROOT, opts.build.statics[f]);
      var path = f;

      if ( f.substr(0, 1) === '?' ) {
        path = path.substr(1);
        if ( _fs.existsSync(dst) ) {
          return;
        } else {
          _fs.mkdirSync(_path.dirname(dst));
        }
      }

      var src = _path.join(ROOT, path);
      if ( opts.verbose ) {
        _utils.log('-', opts.build.statics[f]);
      }
      _fs.copySync(src, dst);
    });

    var ignore = ['index.html'];
    _fs.readdirSync(tpldir).forEach(function(iter) {
      if ( ignore.indexOf(iter) < 0 ) {
        if ( opts.verbose ) {
          _utils.log('-', _path.join(tpldir, iter));
        }
        _fs.copySync(_path.join(tpldir, iter), _path.join(outdir, iter));
      }
    });

    TARGETS[opts.target](opts, done)
  }

  /////////////////////////////////////////////////////////////////////////////
  // EXPORTS
  /////////////////////////////////////////////////////////////////////////////

  module.exports.buildFiles = buildFiles;

})(require('node-fs-extra'), require('path'), require('./utils.js'), require('./manifest.js'));
<|endoftext|>


<commit_before>/* eslint no-console: 0, class-methods-use-this: 0, no-restricted-syntax: 0 */
const readline = require('readline');
const path = require('path');
const fs = require('fs');
const chromeLauncher = require('chrome-launcher');
const unmirror = require('chrome-unmirror');
const chokidar = require('chokidar');
const globby = require('globby');
const precinct = require('precinct');
const createHttpServer = require('./http-server');
const NYC = require('nyc');
const Mediator = require('./mediator');
const connect = require('./connect');
const utils = require('../terminal-utils');
const { ensureFilePath, getExt, getPathWithExt } = require('./file-utils');

class Runner {
  constructor(argv = { chrome: { chromeFlags: [] }, client: {} }) {
    this.argv = argv;
    this.nyc = new NYC(argv.nyc);
    this.mediator = new Mediator();
    this.chromeLauncher = chromeLauncher;
    this.ended = false;
    this.loadError = false;
    this.requests = new Map();
    this.started = false;
    this.isRunning = false;
    this.depMap = new Map();
    this.srcTestMap = new Map();
    this.onlyTestFiles = [];
    this.onlyTestFilesBrowser = [];
    this.all = true;
    this.bind();
    this.debugging = false;
  }
  logInfo(mode, testFiles) {
    if (this.debugging) {
      return this;
    }
    if (this.argv.outputReporterOnly) {
      return this;
    }
    console.log(`${mode}`);
    console.log('  test');
    testFiles.forEach((f) => {
      console.log(`    \u001b[90m${f}\u001b[0m`);
    });
    console.log('\nSave\u001b[90m a test file or source file to run only affected tests\u001b[0m');
    console.log('\u001b[90mPress\u001b[0m a \u001b[90mto run all tests\u001b[0m');
    return this;
  }
  log(msg) {
    if (this.argv.outputReporterOnly) {
      return;
    }
    console.log(msg);
  }
  logLine(msg) {
    if (this.argv.outputReporterOnly) {
      return;
    }
    utils.writeLine(msg);
  }
  logClearLine() {
    if (this.argv.outputReporterOnly) {
      return;
    }
    utils.clearLine();
  }
  bind() {
    if (this.argv.chrome.devtools && this.argv.coverage) {
      this.log('Can not use coverage with devtools. Coverage set to false');
      this.argv.coverage = false;
    }
    if (!this.argv.url) {
      this.fail('`options.url` must be specified to run tests');
    }
    this.mediator.on('width', () => {
      if (!this.client) {
        return;
      }
      const columns = parseInt(process.env.COLUMNS || process.stdout.columns) * 0.75 | 0; // eslint-disable-line
      const expression = `Mocha.reporters.Base.window.width = ${columns};`;
      this.client.Runtime.evaluate({ expression });
    });
    this.mediator.on('started', (tests) => {
      this.started = true;
      if (this.argv.coverage) {
        this.nyc.reset();
      }
      this.logClearLine();
      this.log('Runner started\n');

      if (tests === 0) {
        this.fail('mocha.run() was called with no tests');
      }
    });

    this.mediator.on('ended', async (stats) => {
      this.log('Runner ended\n');
      this.started = false;
      this.ended = true;
      this.isRunning = false;
      await this.exit(stats.failures);
    });
  }
  on(event, callback) {
    this.mediator.on(event, callback);
  }
  fail(msg) {
    console.error(msg);
    this.exit(1);
  }
  pipeOut(Runtime) {
    Runtime.exceptionThrown((exception) => {
      console.error('[chrome-exception]', exception);
    });

    Runtime.consoleAPICalled(({ type, args }) => {
      if (type === 'info') {
        process.stdout.write(args.shift().value);
        return;
      }
      if (type === 'warning') {
        type = 'warn';
      }
      if (!(type in console)) {
        type = 'log';
      }
      const data = args.map(arg => (arg.type === 'string' ? arg.value : unmirror(arg)));
      console[type](...data);
    });
  }
  pipeNetwork(Network) {
    Network.requestWillBeSent((info) => {
      this.requests.set(info.requestId, info.request);
      if (!this.started && info.request.url.match(/^(file|http(s?)):\/\//)) {
        this.logLine(info.request.url);
      }
    });
    Network.loadingFailed((info) => {
      const { errorText } = info;
      const { url, method } = this.requests.get(info.requestId);
      const msg = JSON.stringify({ url, method, errorText });
      console.error('Resource Failed to Load:', msg);
      this.mediator.emit('resourceFailed', msg);
      this.loadError = true;
    });
  }
  launch(options) {
    return this.chromeLauncher.launch(options);
  }
  async setup() {
    if (this.argv.chrome.launch) {
      this.chrome = await this.launch(this.argv.chrome);
      const { port } = this.chrome;
      this.argv.client.port = port;
    }
    this.client = await connect(this.argv, this.testFilesBrowser, this.debugging);
    if (!this.client) {
      this.fail('CDP Client could not connect');
      return;
    }
    const { DOMStorage, Runtime, Network } = this.client;
    this.mediator.bind(DOMStorage);
    this.pipeOut(Runtime);
    this.pipeNetwork(Network);
    Network.clearBrowserCache();
  }
  async navigate() {
    if (this.loadError) {
      this.fail(`Failed to load the url: ${this.argv.url}`);
      return;
    }
    this.log(`Navigating to ${this.argv.url}`);
    this.isRunning = true;
    await this.client.Page.navigate({ url: this.argv.url });
  }
  getDependencies(f) {
    const cached = this.depMap.get(f);
    if (cached) {
      return cached;
    }
    const rf = ensureFilePath(f);
    const deps = precinct(fs.readFileSync(rf, 'utf8'), { amd: { skipLazyLoaded: true } });
    this.depMap.set(f, deps);
    return deps;
  }
  matchDependencyName(srcName, deps) {
    for (const dep of deps) {
      const name = path.basename(dep);
      if (name === srcName) {
        return true;
      }
    }
    return false;
  }
  matchDependency(srcFile) {
    const cache = this.srcTestMap.get(srcFile);
    if (cache) {
      return cache;
    }
    const srcName = path.basename(srcFile).split('.').shift();
    for (const f of this.testFiles) {
      this.logLine(`Scanning ${f}`);
      const deps = this.getDependencies(f);
      const found = this.matchDependencyName(srcName, deps);
      if (found) {
        this.srcTestMap.set(srcFile, [f]);
        return [f];
      }
    }
    this.logClearLine();
    this.log(`Couldn't find a test file for ${srcFile}`);
    return [];
  }
  async reloadAndRunTests(relativeFiles) {
    this.isRunning = true;
    const injectAwFiles = `window.awFiles = ${JSON.stringify(relativeFiles)};`;
    await this.client.Page.reload({ ignoreCache: true, scriptToEvaluateOnLoad: injectAwFiles });
  }
  async onWatch(virtualAbs, virtualRel) {
    if (this.isRunning) {
      return;
    }
    const ext = getExt(virtualAbs);
    const abs = getPathWithExt(virtualAbs, 'js');
    const rel = getPathWithExt(virtualRel, 'js');
    let testFiles = [rel];
    if (this.depMap.get(abs)) {
      this.depMap.delete(abs);
    }
    this.all = false;
    const isTestFile = this.testFiles.indexOf(abs) !== -1;
    if (!isTestFile) {
      testFiles = this.matchDependency(abs);
      const nycOpts = Object.assign({}, this.argv.nyc, { exclude: ['**', `!${rel}`] });
      this.nyc = new NYC(nycOpts);
    } else {
      this.nyc = new NYC(this.argv.nyc);
    }
    this.onlyTestFiles = testFiles.map(f => getPathWithExt(f, ext));
    this.onlyTestFilesBrowser = this.relativeBaseUrlFiles(testFiles);
    await this.reloadAndRunTests(this.onlyTestFilesBrowser);
  }
  watch() {
    chokidar.watch(this.argv.watchGlob).on('change', f => this.onWatch(path.resolve(f), f));
  }
  setupKeyPress() {
    if (!this.argv.watch) {
      return this;
    }
    if (typeof process.stdin.setRawMode !== 'function') {
      return this;
    }
    readline.emitKeypressEvents(process.stdin);
    process.stdin.setRawMode(true);
    process.stdin.setEncoding('utf8');
    process.stdin.on('keypress', (str) => {
      if (str === '\u0003') {
        this.exit(0, true);
      }
      if (this.isRunning) {
        return;
      }
      switch (str) {
        case 'a':
          this.all = true;
          this.nyc = new NYC(this.argv.nyc);
          this.reloadAndRunTests(this.testFilesBrowser);
          break;
        default: break;
      }
    });
    return this;
  }
  autoDetectDebug() {
    const exv = process.execArgv.join();
    const debug = exv.includes('inspect') || exv.includes('debug');
    if (debug || this.argv.chrome.devtools) {
      this.argv.mocha.timeout = 0;
      this.debugging = true;
    }
  }
  relativeRootFile(file) {
    return path.relative(path.relative(this.argv.url, process.cwd()), file);
  }
  relativeRootFiles(files) {
    return files.map(file => this.relativeRootFile(file));
  }
  relativeBaseUrlFile(file) {
    return path.relative(path.dirname(this.argv.url), path.resolve(file));
  }
  relativeBaseUrlFiles(files) {
    return files.map(file => this.relativeBaseUrlFile(file));
  }
  setTestFiles() {
    this.testFiles = globby.sync(this.argv.glob).map(f => path.resolve(f)).map((f) => {
      const parts = f.split('.');
      const ext = parts.pop();
      if (ext === 'ts') {
        return `${parts.join('.')}.js`;
      }
      return f;
    });
    if (!this.testFiles.length) {
      this.log('No files found for:', this.argv.glob);
      process.exit(1);
    }
    this.testFilesBrowser = this.relativeBaseUrlFiles(this.testFiles);
    return this;
  }
  setUrl(url) {
    if (!/^(file|http(s?)):\/\//.test(url)) {
      if (!fs.existsSync(url)) {
        url = `file://${path.resolve(path.join(process.cwd(), url))}`;
      }
      if (!fs.existsSync(url)) {
        console.error('You must specify an existing url.');
        process.exit(1);
      }
      url = `file://${fs.realpathSync(url)}`;
    }
    this.argv.url = url;
    return this;
  }
  maybeCreateHttpServer() {
    if (/^(http(s?)):\/\//.test(this.argv.url)) {
      createHttpServer(this.argv);
    }
    return this;
  }
  async run() {
    await this.setup();
    await this.navigate();
    if (this.argv.watch) {
      this.watch();
    }
  }
  async extractCoverage() {
    const { result: { value } } = await this.client.Runtime.evaluate({ expression: 'window.__coverage__', returnByValue: true });
    return value;
  }
  async exit(code, force) {
    if (!force && this.argv.coverage) {
      const coverage = await this.extractCoverage();
      fs.writeFileSync(
        path.resolve(this.nyc.tempDirectory(), `${Date.now()}.json`),
        JSON.stringify(coverage, null, 2),
        'utf8'
      );
      this.nyc.report();
    }
    if (!force && this.argv.watch) {
      const mode = this.all ? 'All' : 'Only';
      const testFiles = this.all ? [`${this.argv.glob}`] : this.onlyTestFiles;
      this.logInfo(mode, testFiles);
      return;
    }
    await this.client.close();
    if (this.argv.chrome.launch) {
      await this.chrome.kill();
    }
    this.mediator.emit('exit', code);
  }
}

module.exports = Runner;
<commit_msg>CDP - normalize paths (#122)<commit_after>/* eslint no-console: 0, class-methods-use-this: 0, no-restricted-syntax: 0 */
const readline = require('readline');
const path = require('path');
const fs = require('fs');
const chromeLauncher = require('chrome-launcher');
const unmirror = require('chrome-unmirror');
const chokidar = require('chokidar');
const globby = require('globby');
const precinct = require('precinct');
const createHttpServer = require('./http-server');
const NYC = require('nyc');
const Mediator = require('./mediator');
const connect = require('./connect');
const utils = require('../terminal-utils');
const { ensureFilePath, getExt, getPathWithExt } = require('./file-utils');

class Runner {
  constructor(argv = { chrome: { chromeFlags: [] }, client: {} }) {
    this.argv = argv;
    this.nyc = new NYC(argv.nyc);
    this.mediator = new Mediator();
    this.chromeLauncher = chromeLauncher;
    this.ended = false;
    this.loadError = false;
    this.requests = new Map();
    this.started = false;
    this.isRunning = false;
    this.depMap = new Map();
    this.srcTestMap = new Map();
    this.onlyTestFiles = [];
    this.onlyTestFilesBrowser = [];
    this.all = true;
    this.bind();
    this.debugging = false;
  }
  logInfo(mode, testFiles) {
    if (this.debugging) {
      return this;
    }
    if (this.argv.outputReporterOnly) {
      return this;
    }
    console.log(`${mode}`);
    console.log('  test');
    testFiles.forEach((f) => {
      console.log(`    \u001b[90m${f}\u001b[0m`);
    });
    console.log('\nSave\u001b[90m a test file or source file to run only affected tests\u001b[0m');
    console.log('\u001b[90mPress\u001b[0m a \u001b[90mto run all tests\u001b[0m');
    return this;
  }
  log(msg) {
    if (this.argv.outputReporterOnly) {
      return;
    }
    console.log(msg);
  }
  logLine(msg) {
    if (this.argv.outputReporterOnly) {
      return;
    }
    utils.writeLine(msg);
  }
  logClearLine() {
    if (this.argv.outputReporterOnly) {
      return;
    }
    utils.clearLine();
  }
  bind() {
    if (this.argv.chrome.devtools && this.argv.coverage) {
      this.log('Can not use coverage with devtools. Coverage set to false');
      this.argv.coverage = false;
    }
    if (!this.argv.url) {
      this.fail('`options.url` must be specified to run tests');
    }
    this.mediator.on('width', () => {
      if (!this.client) {
        return;
      }
      const columns = parseInt(process.env.COLUMNS || process.stdout.columns) * 0.75 | 0; // eslint-disable-line
      const expression = `Mocha.reporters.Base.window.width = ${columns};`;
      this.client.Runtime.evaluate({ expression });
    });
    this.mediator.on('started', (tests) => {
      this.started = true;
      if (this.argv.coverage) {
        this.nyc.reset();
      }
      this.logClearLine();
      this.log('Runner started\n');

      if (tests === 0) {
        this.fail('mocha.run() was called with no tests');
      }
    });

    this.mediator.on('ended', async (stats) => {
      this.log('Runner ended\n');
      this.started = false;
      this.ended = true;
      this.isRunning = false;
      await this.exit(stats.failures);
    });
  }
  on(event, callback) {
    this.mediator.on(event, callback);
  }
  fail(msg) {
    console.error(msg);
    this.exit(1);
  }
  pipeOut(Runtime) {
    Runtime.exceptionThrown((exception) => {
      console.error('[chrome-exception]', exception);
    });

    Runtime.consoleAPICalled(({ type, args }) => {
      if (type === 'info') {
        process.stdout.write(args.shift().value);
        return;
      }
      if (type === 'warning') {
        type = 'warn';
      }
      if (!(type in console)) {
        type = 'log';
      }
      const data = args.map(arg => (arg.type === 'string' ? arg.value : unmirror(arg)));
      console[type](...data);
    });
  }
  pipeNetwork(Network) {
    Network.requestWillBeSent((info) => {
      this.requests.set(info.requestId, info.request);
      if (!this.started && info.request.url.match(/^(file|http(s?)):\/\//)) {
        this.logLine(info.request.url);
      }
    });
    Network.loadingFailed((info) => {
      const { errorText } = info;
      const { url, method } = this.requests.get(info.requestId);
      const msg = JSON.stringify({ url, method, errorText });
      console.error('Resource Failed to Load:', msg);
      this.mediator.emit('resourceFailed', msg);
      this.loadError = true;
    });
  }
  launch(options) {
    return this.chromeLauncher.launch(options);
  }
  async setup() {
    if (this.argv.chrome.launch) {
      this.chrome = await this.launch(this.argv.chrome);
      const { port } = this.chrome;
      this.argv.client.port = port;
    }
    this.client = await connect(this.argv, this.testFilesBrowser, this.debugging);
    if (!this.client) {
      this.fail('CDP Client could not connect');
      return;
    }
    const { DOMStorage, Runtime, Network } = this.client;
    this.mediator.bind(DOMStorage);
    this.pipeOut(Runtime);
    this.pipeNetwork(Network);
    Network.clearBrowserCache();
  }
  async navigate() {
    if (this.loadError) {
      this.fail(`Failed to load the url: ${this.argv.url}`);
      return;
    }
    this.log(`Navigating to ${this.argv.url}`);
    this.isRunning = true;
    await this.client.Page.navigate({ url: this.argv.url });
  }
  getDependencies(f) {
    const cached = this.depMap.get(f);
    if (cached) {
      return cached;
    }
    const rf = ensureFilePath(f);
    const deps = precinct(fs.readFileSync(rf, 'utf8'), { amd: { skipLazyLoaded: true } });
    this.depMap.set(f, deps);
    return deps;
  }
  matchDependencyName(srcName, deps) {
    for (const dep of deps) {
      const name = path.basename(dep);
      if (name === srcName) {
        return true;
      }
    }
    return false;
  }
  matchDependency(srcFile) {
    const cache = this.srcTestMap.get(srcFile);
    if (cache) {
      return cache;
    }
    const srcName = path.basename(srcFile).split('.').shift();
    for (const f of this.testFiles) {
      this.logLine(`Scanning ${f}`);
      const deps = this.getDependencies(f);
      const found = this.matchDependencyName(srcName, deps);
      if (found) {
        this.srcTestMap.set(srcFile, [f]);
        return [f];
      }
    }
    this.logClearLine();
    this.log(`Couldn't find a test file for ${srcFile}`);
    return [];
  }
  async reloadAndRunTests(relativeFiles) {
    this.isRunning = true;
    const injectAwFiles = `window.awFiles = ${JSON.stringify(relativeFiles)};`;
    await this.client.Page.reload({ ignoreCache: true, scriptToEvaluateOnLoad: injectAwFiles });
  }
  async onWatch(virtualAbs, virtualRel) {
    if (this.isRunning) {
      return;
    }
    const ext = getExt(virtualAbs);
    const abs = getPathWithExt(virtualAbs, 'js');
    const rel = getPathWithExt(virtualRel, 'js');
    let testFiles = [rel];
    if (this.depMap.get(abs)) {
      this.depMap.delete(abs);
    }
    this.all = false;
    const isTestFile = this.testFiles.indexOf(abs) !== -1;
    if (!isTestFile) {
      testFiles = this.matchDependency(abs);
      const nycOpts = Object.assign({}, this.argv.nyc, { exclude: ['**', `!${rel}`] });
      this.nyc = new NYC(nycOpts);
    } else {
      this.nyc = new NYC(this.argv.nyc);
    }
    this.onlyTestFiles = testFiles.map(f => getPathWithExt(f, ext));
    this.onlyTestFilesBrowser = this.relativeBaseUrlFiles(testFiles);
    await this.reloadAndRunTests(this.onlyTestFilesBrowser);
  }
  watch() {
    chokidar.watch(this.argv.watchGlob).on('change', f => this.onWatch(path.resolve(f), f));
  }
  setupKeyPress() {
    if (!this.argv.watch) {
      return this;
    }
    if (typeof process.stdin.setRawMode !== 'function') {
      return this;
    }
    readline.emitKeypressEvents(process.stdin);
    process.stdin.setRawMode(true);
    process.stdin.setEncoding('utf8');
    process.stdin.on('keypress', (str) => {
      if (str === '\u0003') {
        this.exit(0, true);
      }
      if (this.isRunning) {
        return;
      }
      switch (str) {
        case 'a':
          this.all = true;
          this.nyc = new NYC(this.argv.nyc);
          this.reloadAndRunTests(this.testFilesBrowser);
          break;
        default: break;
      }
    });
    return this;
  }
  autoDetectDebug() {
    const exv = process.execArgv.join();
    const debug = exv.includes('inspect') || exv.includes('debug');
    if (debug || this.argv.chrome.devtools) {
      this.argv.mocha.timeout = 0;
      this.debugging = true;
    }
  }
  relativeRootFile(file) {
    return path.relative(path.relative(this.argv.url, process.cwd()), file);
  }
  relativeRootFiles(files) {
    return files.map(file => this.relativeRootFile(file));
  }
  relativeBaseUrlFile(file) {
    return path.relative(path.dirname(this.argv.url), path.resolve(file));
  }
  relativeBaseUrlFiles(files) {
    return files.map(file => this.relativeBaseUrlFile(file));
  }
  setTestFiles() {
    this.testFiles = globby.sync(this.argv.glob).map(f => path.resolve(f)).map((f) => {
      const parts = f.split('.');
      const ext = parts.pop();
      if (ext === 'ts') {
        return `${parts.join('.')}.js`;
      }
      return f;
    });
    if (!this.testFiles.length) {
      this.log('No files found for:', this.argv.glob);
      process.exit(1);
    }
    this.testFilesBrowser = this.relativeBaseUrlFiles(this.testFiles)
      .map(f => f.split('\\').join('/'));
    return this;
  }
  setUrl(url) {
    if (!/^(file|http(s?)):\/\//.test(url)) {
      if (!fs.existsSync(url)) {
        url = `file://${path.resolve(path.join(process.cwd(), url))}`;
      }
      if (!fs.existsSync(url)) {
        console.error('You must specify an existing url.');
        process.exit(1);
      }
      url = `file://${fs.realpathSync(url)}`;
    }
    this.argv.url = url;
    return this;
  }
  maybeCreateHttpServer() {
    if (/^(http(s?)):\/\//.test(this.argv.url)) {
      createHttpServer(this.argv);
    }
    return this;
  }
  async run() {
    await this.setup();
    await this.navigate();
    if (this.argv.watch) {
      this.watch();
    }
  }
  async extractCoverage() {
    const { result: { value } } = await this.client.Runtime.evaluate({ expression: 'window.__coverage__', returnByValue: true });
    return value;
  }
  async exit(code, force) {
    if (!force && this.argv.coverage) {
      const coverage = await this.extractCoverage();
      fs.writeFileSync(
        path.resolve(this.nyc.tempDirectory(), `${Date.now()}.json`),
        JSON.stringify(coverage, null, 2),
        'utf8'
      );
      this.nyc.report();
    }
    if (!force && this.argv.watch) {
      const mode = this.all ? 'All' : 'Only';
      const testFiles = this.all ? [`${this.argv.glob}`] : this.onlyTestFiles;
      this.logInfo(mode, testFiles);
      return;
    }
    await this.client.close();
    if (this.argv.chrome.launch) {
      await this.chrome.kill();
    }
    this.mediator.emit('exit', code);
  }
}

module.exports = Runner;
<|endoftext|>


<commit_before>/**
 *  class Chart.Line < Chart.Base
 *
 *  A class for drawing line graphs.
**/

Chart.Line = Class.create(Chart.Base, {
  /**
   *  new Chart.Line(canvas, options)
  **/
  initialize: function($super, canvas, options) {
    $super(canvas);
    this.setOptions(options);
    this.R = Raphael(canvas, this.options.width, this.options.height);
  },

  /**
   *  Chart.Line#draw() -> undefined
  **/
  draw: function() {
    this.clear();

    if (this._datasets.length === 0) {
      throw new Krang.Error("No dataset!");
    }

    var opt = this.options, R = this.R, g = opt.gutter, max;
    if (opt.grid.maxY === 'auto') {
      max = Math.max.apply(Math, this._datasets.invoke('maxValue'));
    } else {
      max = opt.grid.maxY;
    }

    var dataLength = this._datasets.first().dataLength();
    if (dataLength <= 1) {
      // Not enough for a line chart.
      throw new Krang.Error("Not enough data points!")
    }

    // Horizontal space between each node.
    var xScale = (opt.width - (g.left + g.right)) /
     (this._datasets.first().dataLength() - 1);

    // Vertical scale
    var yRange = opt.height - (g.top + g.bottom);
    var yScale = yRange / max;

    var columns = opt.grid.vertical.enabled   ? opt.grid.vertical.lines   : 0;
    var rows    = opt.grid.horizontal.enabled ? opt.grid.horizontal.lines : 0;

    // Do a separate background fill before creating the grid.
    R.rect(
      g.left,
      g.top,
      opt.width  - (g.left + g.right),
      opt.height - (g.top + g.bottom)
    ).attr({ fill: opt.grid.backgroundColor });

    // Create the background grid.
    R.drawGrid(
      g.left,                           /* X                 */
      g.top,                            /* Y                 */
      opt.width  - (g.left + g.right),  /* width             */
      opt.height - (g.top + g.bottom),  /* height            */
      columns,                          /* number of columns */
      rows,                             /* number of rows    */
      opt.grid.color                    /* color             */
    );

    R.rect(
      g.left,
      g.top,
      opt.width  - (g.left + g.right),
      opt.height - (g.top + g.bottom)
    ).attr({ stroke: opt.border.color });

    var $color = opt.line.color;
    if ($color instanceof Krang.Colorset) {
      $color.setLength(this._datasets.length);
    }

    var blanket = R.set();

    function plotDataset(dataset, index) {
      var data = dataset.toArray();

      // Create the path objects for the line and the fill.

      // Fill color, if left unspecified, will be the same as the line
      // color (with lower opacity).
      var fillColor = opt.fill.color || opt.line.color;
      if (opt.fill.color && opt.fill.color instanceof Krang.Colorset) {
        fillColor.setLength(this._datasets.length);
      }

      var lineColor = $color.toString();

      // Mark the color on the dataset so we can read it later (e.g., for
      // building a graph legend).
      dataset.color = lineColor;

      var line = R.path({
        'stroke':          lineColor,
        'stroke-width':    opt.line.width,
        'stroke-linejoin': 'round'
      });

      var fillColor = opt.fill.color || lineColor;
      if (opt.fill.color && opt.fill.color instanceof Krang.Colorset) {
        fillColor.setLength(this._datasets.length);
      }

      var fill = R.path({
        fill:    fillColor.toString(),
        opacity: opt.fill.opacity,
        stroke:  'none'
      });

      fill.moveTo(g.left, opt.height - g.bottom);

      var label, value, x = 0, y, dot, rect;

      // Plot the values.
      for (var i = 0, l = data.length; i < l; i++) {
        label = data[i].label; value = data[i].value;

        y = Math.round(opt.height - g.bottom - (yScale * value));
        x = Math.round(g.left + (xScale * i));

        // Draw the line segment.
        if (i == 0) {
          line.moveTo(x, y, 10);
          fill.lineTo(x, y, 10);
        } else {
          line.cplineTo(x, y, opt.line.curve);
          fill.cplineTo(x, y, opt.line.curve);
        }

        // Draw the dot.
        dot = R.circle(x, y, opt.dot.radius).attr({
          fill:   opt.dot.color || lineColor,
          stroke: opt.dot.stroke
        });

        // Create an invisible, roughly-square box that will serve as the
        // hover zone for this point.
        rect = R.rect(
          g.left + (xScale * i) - (xScale / 16), /* x      */
          y - (xScale / 16),                     /* y      */
          xScale / 8,                               /* width  */
          xScale / 8                                /* height */
        ).attr({
          stroke:  '#000',
          fill:    '#fff',
          opacity: 0
        });

        Krang.Data.store(rect, {
          dot:  dot,
          data: data[i]
        });

        this._createObservers(rect, dot);

        blanket.push(rect);

        // Draw X labels.
        if (!index) {
          var textStartY = g.top + yRange + Math.round(g.bottom / 2);
          label = opt.grid.labelX(label);
          var text = R.text(x, textStartY, label).attr({
            font: '#{0} "#{1}"'.interpolate([opt.text.fontSize, opt.text.fontFamily]),
            stroke: 'none',
            fill: opt.text.color
          });
        }
      }

      // Since the fill path is drawing a shape, not just a line, we need to
      // close the path.
      fill.lineTo(x, opt.height - g.bottom).andClose();

    }

    this._datasets.each(plotDataset, this);

    // Draw Y labels.
    var yStart = g.bottom, yEnd = opt.height - g.top;

    var value, yLabel, yPlot, text;
    for (var j = 0, yLabel; j <= rows; j++) {
      value  = (j / rows) * max;
      yLabel = opt.grid.labelY(value);
      yPlot  = yStart + ((j * yRange) / rows);

      text = R.text(Math.round(g.left / 2), (opt.height - yPlot), yLabel).attr({
        font: '#{0} "#{1}"'.interpolate([opt.text.fontSize, opt.text.fontFamily]),
        stroke: 'none',
        fill: opt.text.color
      });

      // Fake right-alignment.
      var textWidth = text.getBBox().width;
      var textX = g.left - 15 - textWidth;

      text.attr({ x: textX, 'text-anchor': 'start' });
    }

    blanket.toFront();
  },

  _createObservers: function(rect, dot) {
    var position = {
      x: dot.attrs.cx,
      y: dot.attrs.cy
    };

    var canvas = this.canvas;

    var over = function() {
      var dot = Krang.Data.retrieve(rect, 'dot'),
         data = Krang.Data.retrieve(rect, 'data');

      Event.fire(canvas, 'krang:mouseover',
       { dot: dot, data: data, position: position });
    };

    var out = function() {
      var dot = Krang.Data.retrieve(rect, 'dot'),
         data = Krang.Data.retrieve(rect, 'data');

      Event.fire(canvas, 'krang:mouseout',
        { dot: dot, data: data, position: position });
    };

    var click = function() {
      var dot = Krang.Data.retrieve(rect, 'dot'),
         data = Krang.Data.retrieve(rect, 'data');

      Event.fire(canvas, 'krang:click',
        { dot: dot, data: data, position: position });
    };

    rect.mouseover(over);
    rect.mouseout(out);
    rect.click(out);
  }
});

Object.extend(Chart.Line, {
  DEFAULT_OPTIONS: {
    width:  800,
    height: 250,
    line: {
      color: new Krang.Colorset({
        vary: 'h',
        saturation: 0.6,
        lightness: 0.4
      }),
      width: 4,
      curve: 10
    },

    fill: {
      opacity: 0.3
    },

    gutter: {
      top:    20,
      bottom: 20,
      left:   30,
      right:  30
    },

    /* The chart's grid. */
    grid: {
      color: '#ddd',            /* Color of the gridlines. */
      backgroundColor: '#fff',  /* Color of the grid's background. */

      horizontal: {
        enabled: true,    /* Whether to draw horizontal gridlines. */
        lines: 10
      },

      vertical: {
        enabled: false,   /* Whether to draw vertical gridlines. */
        lines: 0
      },

      /* Callbacks that format the labels for display. */
      labelX: Prototype.K,
      labelY: function(value) {
        return value.toFixed(1);
      },

      /*
       * If set to 'auto', will determine a good max value based on the
       * scale of the chart. Otherwise one can specify a max value to use.
       */
      maxY: 'auto'
    },

    dot: {
      stroke: '#fff',
      radius: 4
    },

    /* Border around the chart itself. */
    border: {
      color: '#999'
    },

    text: {
      fontFamily: 'Lucida Grande',
      fontSize:   '12px',
      color:      "#000"
    }
  }
});<commit_msg>Various fixes to line charts. Also introduced the `labelXFrequency` and `labelYFrequency` options, which control how often labels are drawn on each axis.<commit_after>/**
 *  class Chart.Line < Chart.Base
 *
 *  A class for drawing line graphs.
**/

Chart.Line = Class.create(Chart.Base, {
  /**
   *  new Chart.Line(canvas, options)
  **/
  initialize: function($super, canvas, options) {
    $super(canvas);
    this.setOptions(options);
    this.R = Raphael(canvas, this.options.width, this.options.height);
  },

  /**
   *  Chart.Line#draw() -> undefined
  **/
  draw: function() {
    this.clear();

    if (this._datasets.length === 0) {
      throw new Krang.Error("No dataset!");
    }

    var opt = this.options, R = this.R, g = opt.gutter, max;
    if (opt.grid.maxY === 'auto') {
      max = Math.max.apply(Math, this._datasets.invoke('maxValue'));
    } else {
      max = opt.grid.maxY;
    }

    var dataLength = this._datasets.first().dataLength();
    if (dataLength <= 1) {
      // Not enough for a line chart.
      throw new Krang.Error("Not enough data points!")
    }

    // Horizontal space between each node.
    var xScale = (opt.width - (g.left + g.right)) /
     (this._datasets.first().dataLength() - 1);

    // Vertical scale
    var yRange = opt.height - (g.top + g.bottom);
    var yScale = yRange / max;

    var columns = opt.grid.vertical.enabled   ? opt.grid.vertical.lines   : 0;
    var rows    = opt.grid.horizontal.enabled ? opt.grid.horizontal.lines : 0;

    // Do a separate background fill before creating the grid.
    R.rect(
      g.left,
      g.top,
      opt.width  - (g.left + g.right),
      opt.height - (g.top + g.bottom)
    ).attr({ fill: opt.grid.backgroundColor });

    // Create the background grid.
    R.drawGrid(
      g.left,                           /* X                 */
      g.top,                            /* Y                 */
      opt.width  - (g.left + g.right),  /* width             */
      opt.height - (g.top + g.bottom),  /* height            */
      columns,                          /* number of columns */
      rows,                             /* number of rows    */
      opt.grid.color                    /* color             */
    );

    R.rect(
      g.left,
      g.top,
      opt.width  - (g.left + g.right),
      opt.height - (g.top + g.bottom)
    ).attr({ stroke: opt.border.color });

    var $color = opt.line.color;
    if ($color instanceof Krang.Colorset) {
      $color.setLength(this._datasets.length);
    }

    var blanket = R.set();

    function plotDataset(dataset, index) {
      var data = dataset.toArray();

      // Create the path objects for the line and the fill.

      // Fill color, if left unspecified, will be the same as the line
      // color (with lower opacity).
      var fillColor = opt.fill.color || opt.line.color;
      if (opt.fill.color && opt.fill.color instanceof Krang.Colorset) {
        fillColor.setLength(this._datasets.length);
      }

      var lineColor = $color.toString();

      // Mark the color on the dataset so we can read it later (e.g., for
      // building a graph legend).
      dataset.color = lineColor;

      var line = R.path({
        'stroke':          lineColor,
        'stroke-width':    opt.line.width,
        'stroke-linejoin': 'round'
      });

      var fillColor = opt.fill.color || lineColor;
      if (opt.fill.color && opt.fill.color instanceof Krang.Colorset) {
        fillColor.setLength(this._datasets.length);
      }

      var fill = R.path({
        fill:    fillColor.toString(),
        opacity: opt.fill.opacity,
        stroke:  'none'
      });

      fill.moveTo(g.left, opt.height - g.bottom);

      var x = 0, labelPointer = 1, label, value, y, dot, rect;

      // Plot the values.
      for (var i = 0, l = data.length; i < l; i++) {
        label = data[i].label; value = data[i].value;

        y = Math.round(opt.height - g.bottom - (yScale * value));
        x = Math.round(g.left + (xScale * i));

        // Draw the line segment.
        if (i == 0) {
          line.moveTo(x, y, 10);
          fill.lineTo(x, y, 10);
        } else {
          line.cplineTo(x, y, opt.line.curve);
          fill.cplineTo(x, y, opt.line.curve);
        }

        // Draw the dot.
        dot = R.circle(x, y, opt.dot.radius).attr({
          fill:   opt.dot.color || lineColor,
          stroke: opt.dot.stroke
        });

        // Create an invisible, roughly-square box that will serve as the
        // hover zone for this point.
        rect = R.rect(
          g.left + (xScale * i) - (xScale / 16), /* x      */
          y - (xScale / 16),                     /* y      */
          xScale / 8,                            /* width  */
          xScale / 8                             /* height */
        ).attr({
          stroke:  '#000',
          fill:    '#fff',
          opacity: 0
        });

        // Run all the labels through the specified filter.
        var datum = data[i];
        datum.label = opt.grid.labelX(datum.label);

        Krang.Data.store(rect, {
          dot:  dot,
          data: data[i]
        });

        this._createObservers(rect, dot);

        blanket.push(rect);

        // Draw X labels.
        if (!index) {
          // Skip drawing the label if the options call for it.
          if (labelPointerX++ % opt.grid.labelXFrequency !== 0)
            continue;

          var textStartY = g.top + yRange + Math.round(g.bottom / 2);
          label = opt.grid.labelX(label);
          var text = R.text(x, textStartY, label).attr({
            font: '#{0} "#{1}"'.interpolate([opt.text.fontSize, opt.text.fontFamily]),
            stroke: 'none',
            fill: opt.text.color
          });
        }
      }

      // Since the fill path is drawing a shape, not just a line, we need to
      // close the path.
      fill.lineTo(x, opt.height - g.bottom).andClose();

      // We want the fill to appear _behind_ the line, so that the bottom
      // half of the line isn't obscured.
      fill.insertBefore(line);
    }

    this._datasets.each(plotDataset, this);

    // Draw Y labels.
    var yStart = g.bottom, yEnd = opt.height - g.top, labelPointerY = 1;

    var value, yLabel, yPlot, text;
    for (var j = 0, yLabel; j <= rows; j++) {
      value  = (j / rows) * max;
      yLabel = opt.grid.labelY(value);
      yPlot  = yStart + ((j * yRange) / rows);

      // Skip drawing the label if the options call for it.
      if (labelPointerY++ % opt.grid.labelYFrequency !== 0)
        continue;

      text = R.text(Math.round(g.left / 2), (opt.height - yPlot), yLabel).attr({
        font: '#{0} "#{1}"'.interpolate([opt.text.fontSize, opt.text.fontFamily]),
        stroke: 'none',
        fill: opt.text.color
      });

      // Fake right-alignment.
      var textWidth = text.getBBox().width;
      var textX = g.left - 15 - textWidth;

      text.attr({ x: textX, 'text-anchor': 'start' });
    }

    blanket.toFront();
  },

  _createObservers: function(rect, dot) {
    var position = {
      x: dot.attrs.cx,
      y: dot.attrs.cy
    };

    var canvas = this.canvas;

    var over = function() {
      var dot = Krang.Data.retrieve(rect, 'dot'),
         data = Krang.Data.retrieve(rect, 'data');

      Event.fire(canvas, 'krang:mouseover',
       { dot: dot, data: data, position: position });
    };

    var out = function() {
      var dot = Krang.Data.retrieve(rect, 'dot'),
         data = Krang.Data.retrieve(rect, 'data');

      Event.fire(canvas, 'krang:mouseout',
        { dot: dot, data: data, position: position });
    };

    var click = function() {
      var dot = Krang.Data.retrieve(rect, 'dot'),
         data = Krang.Data.retrieve(rect, 'data');

      Event.fire(canvas, 'krang:click',
        { dot: dot, data: data, position: position });
    };

    rect.mouseover(over);
    rect.mouseout(out);
    rect.click(out);
  }
});

Object.extend(Chart.Line, {
  DEFAULT_OPTIONS: {
    width:  800,
    height: 250,
    line: {
      color: new Krang.Colorset({
        vary: 'h',
        saturation: 0.6,
        lightness: 0.4
      }),
      width: 4,
      curve: 10
    },

    fill: {
      opacity: 0.3
    },

    gutter: {
      top:    20,
      bottom: 20,
      left:   30,
      right:  30
    },

    /* The chart's grid. */
    grid: {
      color: '#ddd',            /* Color of the gridlines. */
      backgroundColor: '#fff',  /* Color of the grid's background. */

      horizontal: {
        enabled: true,    /* Whether to draw horizontal gridlines. */
        lines: 10
      },

      vertical: {
        enabled: false,   /* Whether to draw vertical gridlines. */
        lines: 0
      },

      /* Callbacks that format the labels for display. */
      labelX: Prototype.K,
      labelY: function(value) {
        return value.toFixed(1);
      },

      /*
       * How often to draw labels. If set to `3`, for instance, only every
       * third label will be drawn.
       */
      labelXFrequency: 1,
      labelYFrequency: 1,

      /*
       * If set to 'auto', will determine a good max value based on the
       * scale of the chart. Otherwise one can specify a max value to use.
       */
      maxY: 'auto'
    },

    dot: {
      stroke: '#fff',
      radius: 4
    },

    /* Border around the chart itself. */
    border: {
      color: '#999'
    },

    text: {
      fontFamily: 'Lucida Grande',
      fontSize:   '12px',
      color:      "#000"
    }
  }
});<|endoftext|>