<commit_before>var events = require("events"),
    child = require("child_process"),
    util = require("util"),
    colors = require("colors"),
    Firmata = require("firmata").Board,
    _ = require("lodash"),
    __ = require("../lib/fn.js"),
    Repl = require("../lib/repl.js"),
    board,
    boards,
    grep,
    Serial;

boards = [];

grep = "ls /dev | grep -iE 'usb|acm'";

/**
 * Process Codes
 * SIGHUP        1       Term    Hangup detected on controlling terminal
                              or death of controlling process
 * SIGINT        2       Term    Interrupt from keyboard
 * SIGQUIT       3       Core    Quit from keyboard
 * SIGILL        4       Core    Illegal Instruction
 * SIGABRT       6       Core    Abort signal from abort(3)
 * SIGFPE        8       Core    Floating point exception
 * SIGKILL       9       Term    Kill signal
 * SIGSEGV      11       Core    Invalid memory reference
 * SIGPIPE      13       Term    Broken pipe: write to pipe with no readers
 * SIGALRM      14       Term    Timer signal from alarm(2)
 * SIGTERM      15       Term    Termination signal
 *
 *
 *
 * http://www.slac.stanford.edu/BFROOT/www/Computing/Environment/Tools/Batch/exitcode.html
 *
 */

Serial = {
  used: [],

  detect: function( callback ) {
    var instance = this;

    this.info( "Board", "Connecting..." );
    // TODO:
    // Confirm bluetooth serial connections
    // eg. /dev/tty.RN42-1203-SPP-
    // http://www.rioleo.org/setting-up-the-arduino-pro-mini-and-bluetooth-mate-on-mac.php
    child.exec( grep, function( err, stdout, stderr ) {
      var connected, eventType, found, length, ports, usb;

      ports = stdout.slice( 0, -1 ).split("\n").filter(function( val ) {
        var available = true;

        // Originally, we filtered for "tty"
        // As it turns out, using the cu.* device path saves the program
        // from the dreaded dead-stop
        if ( !(/tty/i).test(val) ) {
          available = false;
        }

        // Don't allow already used/encountered usb device paths
        if ( Serial.used.indexOf(val) > -1 ) {
          available = false;
        }

        return available;
      });

      length = ports.length;

      // If no ports are detected when scanning /dev/, then there is
      // nothing left to do and we can safely exit the program
      if ( !length ) {
        // Alert user that no devices were detected
        this.error( "Board", "No USB devices detected" );

        // Exit the program by sending SIGABRT
        process.exit(3);

        // Return (not that it matters, but this is a good way
        // to indicate to readers of the code that nothing else
        // will happen in this function)
        return;
      }

      // Continue with connection routine attempts
      this.info( "Serial",
        "Found possible serial port" + ( length > 1 ? "s" : "" ),
        ports.toString().grey
      );

      // Get the first available device path from the list of
      // detected ports
      usb = ports[0];

      // Add the usb device path to the list of device paths that
      // are currently in use - this is used by the filter function
      // above to remove any device paths that we've already encountered
      // or used to avoid blindly attempting to reconnect on them.
      Serial.used.push( usb );

      try {
        found = new Firmata( "/dev/" + usb, function() {
          // Execute "ready" callback
          callback.call( this, err, "ready", found, usb );
        }.bind(this));

        // Made this far, safely connected
        connected = true;
      } catch ( error ) {
        err = error;
      }

      if ( err ) {
        err = err.message || err;
      }

      // Determine the type of event that will be passed on to
      // the board emitter in the callback passed to Serial.detect(...)
      eventType = connected ? "connected" : "error";

      // Execute "connected" callback
      callback.call( this, err, eventType, found, usb );

    }.bind(this));
  }
};

// Board constructor: Firmata Board wrapper
function Board( opts ) {

  if ( !(this instanceof Board) ) {
    return new Board( opts );
  }

  // Ensure opts is an object
  opts = opts || {};

  var inject = {},
      keys = Object.keys( opts ),
      key;

  // Copy opts to constructor instance
  while ( keys.length ) {
    key = keys.shift();

    this[ key ] = opts[ key ];
  }

  // Easily track state of hardware
  this.ready = false;

  // Initialize instance property to reference firmata board
  this.firmata = null;

  // Identify for connected hardware cache
  if ( !this.id ) {
    this.id = __.uid();
  }

  // If no debug flag, default to false
  // TODO: Remove override
  this.debug = true;

  if ( !("debug" in this) ) {
    this.debug = false;
  }

  // Create a Repl instance and store as
  // instance property of this firmata/board.
  // This will reduce the amount of boilerplate
  // code required to _always_ have a Repl
  // session available.
  //
  // If a sesssion exists, use it
  // (instead of creating a new session)
  //
  if ( Repl.ref ) {

    inject[ this.id ] = this;

    Repl.ref.on( "ready", function() {
      Repl.ref.inject( inject );
    });

    this.repl = Repl.ref;
  } else {
    inject[ this.id ] = inject.board = this;
    this.repl = new Repl( inject );
  }

  // Used for testing only
  if ( this.mock ) {
    this.firmata = new Firmata( this.mock, function() {
      // Execute "connected" and "ready" callbacks
      this.emit( "connected", null );
      this.emit( "ready", null );
      this.ready = true;
    }.bind(this));
  } else {

    Serial.detect.call( this, function( err, type, board, port ) {
      if ( err ) {
        this.error( "Board", err );
      } else {
        // Assign found board to instance
        this.firmata = board;
        this.info( "Board " + ( type === "connected" ? "->" : "<-" ) + " Serialport", type, port.grey );
      }

      if ( type === "connected" ) {
        process.on( "SIGINT", function() {
          this.warn( "Board", "Closing connection to board, serialport" );
          // On ^c, make sure we close the process after the
          // board and serialport are closed. Approx 100ms
          // TODO: this sucks, need better solution
          setTimeout(function() {
            process.exit();
          }.bind(this), 100);
        }.bind(this));
      }

      if ( type === "ready" ) {
        // Update instance `ready` flag
        this.ready = true;

        this.port = port;

        // Trigger the repl to start
        process.stdin.emit( "data", 1 );
      }

      // emit connect|ready event
      this.emit( type, err );
    });
  }

  // Cache instance to allow access from module constructors
  boards.push( this );
}

// Inherit event api
util.inherits( Board, events.EventEmitter );

/**
 * pinMode, analogWrite, analogRead, digitalWrite, digitalRead
 *
 * Pass through methods
 */
[ "pinMode", "analogWrite", "analogRead", "digitalWrite", "digitalRead" ].forEach(function( method ) {
  Board.prototype[ method ] = function( pin, arg ) {
    this.firmata[ method ]( pin, arg );
  };
});

Board.prototype.log = function( /* type, module, message [, long description] */ ) {
  var args = [].slice.call( arguments ),
      type = args.shift(),
      module = args.shift(),
      message = args.shift(),
      color = Board.prototype.log.types[ type ];

  if ( this.debug ) {
    console.log([
      // Timestamp
      String(+new Date()).grey,
      // Module, color matches type of log
      module.magenta,
      // Message
      message[ color ],
      // Miscellaneous args
      args.join(", ")
    ].join(" "));
  }
};

Board.prototype.log.types = {
  error: "red",
  fail: "orange",
  warn: "yellow",
  info: "cyan"
};

// Make shortcuts to all logging methods
Object.keys( Board.prototype.log.types ).forEach(function( type ) {
  Board.prototype[ type ] = function() {
    var args = [].slice.call( arguments );
    args.unshift( type );

    this.log.apply( this, args );
  };
});

// Aliasing.
// (temporary, while ironing out API details)
// The idea is to match existing hardware programming apis
// or simply find the words that are most intuitive.

// Eventually, there should be a queuing process
// for all new callbacks added

Board.prototype.wait = function( time, callback ) {
  return setTimeout( callback.bind(this), time );
};

Board.prototype.loop = function( time, callback ) {
  return setInterval( callback.bind(this), time );
};

//
// Board.prototype.timeout = Board.prototype.wait;
// Board.prototype.delay = Board.prototype.wait;
// Board.prototype.interval = Board.prototype.loop;

// ----------
// Static API
// ----------

// Board.map( val, fromLow, fromHigh, toLow, toHigh )
//
// Re-maps a number from one range to another.
// Based on arduino map()
Board.map = __.map;

// Board.constrain( val, lower, upper )
//
// Constrains a number to be within a range.
// Based on arduino constrain()
Board.constrain = __.constrain;

// Board.range( upper )
// Board.range( lower, upper )
// Board.range( lower, upper, tick )
//
// Returns a new array range
//
Board.range = __.range;

// Board.range.prefixed( prefix, upper )
// Board.range.prefixed( prefix, lower, upper )
// Board.range.prefixed( prefix, lower, upper, tick )
//
// Returns a new array range, each value prefixed
//
Board.range.prefixed = __.range.prefixed;

// Board.uid()
//
// Returns a reasonably unique id string
//
Board.uid = __.uid;

// Board.mount()
// Board.mount( index )
// Board.mount( object )
//
// Return hardware instance, based on type of param:
// @param {arg}
//   object, user specified
//   number/index, specified in cache
//   none, defaults to first in cache
//
// Notes:
// Used to reduce the amount of boilerplate
// code required in any given module or program, by
// giving the developer the option of omitting an
// explicit Board reference in a module
// constructor's options
Board.mount = function( arg ) {
  var index = typeof arg === "number" && arg,
      hardware;

  // board was explicitly provided
  if ( arg && arg.board ) {
    return arg.board;
  }

  // index specified, attempt to return
  // hardware instance. Return null if not
  // found or not available
  if ( index ) {
    hardware = boards[ index ];
    return hardware && hardware || null;
  }

  // If no arg specified and hardware instances
  // exist in the cache
  if ( boards.length ) {
    return boards[ 0 ];
  }

  // No mountable hardware
  return null;
};

Board.options = function( arg ) {
  var pin,
      isArray = Array.isArray(arg),
      opts = {};

  if ( typeof arg === "number" ||
        typeof arg === "string" ||
        isArray ) {
    // Arrays are on a "pins" property
    // String/Numbers are on a "pin" property
    opts[ isArray ? "pins" : "pin" ] = arg;
  } else {
    opts = arg;
  }

  // Auto-normalize pin values, this reduces boilerplate code
  // inside module constructors
  if ( opts.pin || opts.pins && opts.pins.length ) {

    // When an array of pins is present, attempt to
    // normalize them if necessary
    if ( opts.pins ) {
      opts.pins = opts.pins.map(function( pin ) {
        var p = Board.Pins.analog[ pin ];

        return p != null ? p : pin;
      });
    } else {
      pin = Board.Pins.analog[ opts.pin ];

      opts.pin = pin != null ? pin : opts.pin;
    }
  }

  return opts;
};

Board.Pin = {
  //  Require a pin to be a digital PWM pin
  isPWM: function( pin ) {
    return Board.Pins.pwm[ pin ];
  },
  isAnalog: function( pin ) {
    return !!Board.Pins.analog[ pin ];
  },
  isDigital: function( pin ) {
    return Board.Pins.digital[ pin ];
  },
  isSerial: function( pin ) {
    return Board.Pins.serial[ pin ];
  }
};

// TODO: Make this this all private?, delegate to
// Board.Pin.is___()

// TODO: Improve/Overhaul all docs
Board.Pins = {
  analog: {
    "A0": 0, //14,
    "A1": 1, //15,
    "A2": 2, //16,
    "A3": 3, //17,
    "A4": 4, //18,
    "A5": 5  //19
    //     "A0": 14,
    //     "A1": 15,
    //     "A2": 16,
    //     "A3": 17,
    //     "A4": 18,
    //     "A5": 19
  },
  // Used for HIGH/LOW input / output
  digital: {},

  // PWM (8-bit) output with the analogWrite() function.
  // [SecretsOfArduinoPWM](http://arduino.cc/en/Tutorial/SecretsOfArduinoPWM)
  // Used for Servo (or any PWM hardware)
  pwm: {},
  // Used to receive / transmit TTL serial data
  serial: {
    // RX, Receive
    0: true,
    // TX, Transmit
    1: true
  },
  interrupt: {
    2: true,
    3: true
  },
  // These pins support SPI communication using the SPI library.
  spi: {
    10: true,
    11: true,
    12: true,
    13: true
  },
  // There is a built-in LED connected to digital pin 13.
  // When the pin is HIGH value, the LED is on, when the pin is LOW, it's off.
  led: {
    13: true
  }
};

// Add all Digital pins
Board.range( 0, 13 ).forEach(function( val ) {
  Board.Pins.digital[ val ] = true;
});

// Add all PWM pins
[ 3, 5, 6, 9, 10, 11, 12, 13 ].forEach(function( val ) {
  Board.Pins.pwm[ val ] = true;
});

// Define a user-safe, unwritable hardware cache access
Object.defineProperty( Board, "cache", {
  get: function() {
    return boards;
  }
});

module.exports = Board;

// References:
// http://arduino.cc/en/Main/arduinoBoardUno
<commit_msg>Experimental switch from tty to cu<commit_after>var events = require("events"),
    child = require("child_process"),
    util = require("util"),
    colors = require("colors"),
    Firmata = require("firmata").Board,
    _ = require("lodash"),
    __ = require("../lib/fn.js"),
    Repl = require("../lib/repl.js"),
    board,
    boards,
    grep,
    Serial;

boards = [];

grep = "ls /dev | grep -iE 'usb|acm'";

/**
 * Process Codes
 * SIGHUP        1       Term    Hangup detected on controlling terminal
                              or death of controlling process
 * SIGINT        2       Term    Interrupt from keyboard
 * SIGQUIT       3       Core    Quit from keyboard
 * SIGILL        4       Core    Illegal Instruction
 * SIGABRT       6       Core    Abort signal from abort(3)
 * SIGFPE        8       Core    Floating point exception
 * SIGKILL       9       Term    Kill signal
 * SIGSEGV      11       Core    Invalid memory reference
 * SIGPIPE      13       Term    Broken pipe: write to pipe with no readers
 * SIGALRM      14       Term    Timer signal from alarm(2)
 * SIGTERM      15       Term    Termination signal
 *
 *
 *
 * http://www.slac.stanford.edu/BFROOT/www/Computing/Environment/Tools/Batch/exitcode.html
 *
 */

Serial = {
  used: [],

  detect: function( callback ) {
    var instance = this;

    this.info( "Board", "Connecting..." );
    // TODO:
    // Confirm bluetooth serial connections
    // eg. /dev/tty.RN42-1203-SPP-
    // http://www.rioleo.org/setting-up-the-arduino-pro-mini-and-bluetooth-mate-on-mac.php
    child.exec( grep, function( err, stdout, stderr ) {
      var connected, eventType, found, length, ports, usb;

      ports = stdout.slice( 0, -1 ).split("\n").filter(function( val ) {
        var available = true;

        // Originally, we filtered for "tty"
        // As it turns out, using the cu.* device path saves the program
        // from the dreaded dead-stop
        if ( !(/cu/i).test(val) ) {
          available = false;
        }

        // Don't allow already used/encountered usb device paths
        if ( Serial.used.indexOf(val) > -1 ) {
          available = false;
        }

        return available;
      });

      length = ports.length;

      // If no ports are detected when scanning /dev/, then there is
      // nothing left to do and we can safely exit the program
      if ( !length ) {
        // Alert user that no devices were detected
        this.error( "Board", "No USB devices detected" );

        // Exit the program by sending SIGABRT
        process.exit(3);

        // Return (not that it matters, but this is a good way
        // to indicate to readers of the code that nothing else
        // will happen in this function)
        return;
      }

      // Continue with connection routine attempts
      this.info( "Serial",
        "Found possible serial port" + ( length > 1 ? "s" : "" ),
        ports.toString().grey
      );

      // Get the first available device path from the list of
      // detected ports
      usb = ports[0];

      // Add the usb device path to the list of device paths that
      // are currently in use - this is used by the filter function
      // above to remove any device paths that we've already encountered
      // or used to avoid blindly attempting to reconnect on them.
      Serial.used.push( usb );

      try {
        found = new Firmata( "/dev/" + usb, function() {
          // Execute "ready" callback
          callback.call( this, err, "ready", found, usb );
        }.bind(this));

        // Made this far, safely connected
        connected = true;
      } catch ( error ) {
        err = error;
      }

      if ( err ) {
        err = err.message || err;
      }

      // Determine the type of event that will be passed on to
      // the board emitter in the callback passed to Serial.detect(...)
      eventType = connected ? "connected" : "error";

      // Execute "connected" callback
      callback.call( this, err, eventType, found, usb );

    }.bind(this));
  }
};

// Board constructor: Firmata Board wrapper
function Board( opts ) {

  if ( !(this instanceof Board) ) {
    return new Board( opts );
  }

  // Ensure opts is an object
  opts = opts || {};

  var inject = {},
      keys = Object.keys( opts ),
      key;

  // Copy opts to constructor instance
  while ( keys.length ) {
    key = keys.shift();

    this[ key ] = opts[ key ];
  }

  // Easily track state of hardware
  this.ready = false;

  // Initialize instance property to reference firmata board
  this.firmata = null;

  // Identify for connected hardware cache
  if ( !this.id ) {
    this.id = __.uid();
  }

  // If no debug flag, default to false
  // TODO: Remove override
  this.debug = true;

  if ( !("debug" in this) ) {
    this.debug = false;
  }

  // Create a Repl instance and store as
  // instance property of this firmata/board.
  // This will reduce the amount of boilerplate
  // code required to _always_ have a Repl
  // session available.
  //
  // If a sesssion exists, use it
  // (instead of creating a new session)
  //
  if ( Repl.ref ) {

    inject[ this.id ] = this;

    Repl.ref.on( "ready", function() {
      Repl.ref.inject( inject );
    });

    this.repl = Repl.ref;
  } else {
    inject[ this.id ] = inject.board = this;
    this.repl = new Repl( inject );
  }

  // Used for testing only
  if ( this.mock ) {
    this.firmata = new Firmata( this.mock, function() {
      // Execute "connected" and "ready" callbacks
      this.emit( "connected", null );
      this.emit( "ready", null );
      this.ready = true;
    }.bind(this));
  } else {

    Serial.detect.call( this, function( err, type, board, port ) {
      if ( err ) {
        this.error( "Board", err );
      } else {
        // Assign found board to instance
        this.firmata = board;
        this.info( "Board " + ( type === "connected" ? "->" : "<-" ) + " Serialport", type, port.grey );
      }

      if ( type === "connected" ) {
        process.on( "SIGINT", function() {
          this.warn( "Board", "Closing connection to board, serialport" );
          // On ^c, make sure we close the process after the
          // board and serialport are closed. Approx 100ms
          // TODO: this sucks, need better solution
          setTimeout(function() {
            process.exit();
          }.bind(this), 100);
        }.bind(this));
      }

      if ( type === "ready" ) {
        // Update instance `ready` flag
        this.ready = true;

        this.port = port;

        // Trigger the repl to start
        process.stdin.emit( "data", 1 );
      }

      // emit connect|ready event
      this.emit( type, err );
    });
  }

  // Cache instance to allow access from module constructors
  boards.push( this );
}

// Inherit event api
util.inherits( Board, events.EventEmitter );

/**
 * pinMode, analogWrite, analogRead, digitalWrite, digitalRead
 *
 * Pass through methods
 */
[ "pinMode", "analogWrite", "analogRead", "digitalWrite", "digitalRead" ].forEach(function( method ) {
  Board.prototype[ method ] = function( pin, arg ) {
    this.firmata[ method ]( pin, arg );
  };
});

Board.prototype.log = function( /* type, module, message [, long description] */ ) {
  var args = [].slice.call( arguments ),
      type = args.shift(),
      module = args.shift(),
      message = args.shift(),
      color = Board.prototype.log.types[ type ];

  if ( this.debug ) {
    console.log([
      // Timestamp
      String(+new Date()).grey,
      // Module, color matches type of log
      module.magenta,
      // Message
      message[ color ],
      // Miscellaneous args
      args.join(", ")
    ].join(" "));
  }
};

Board.prototype.log.types = {
  error: "red",
  fail: "orange",
  warn: "yellow",
  info: "cyan"
};

// Make shortcuts to all logging methods
Object.keys( Board.prototype.log.types ).forEach(function( type ) {
  Board.prototype[ type ] = function() {
    var args = [].slice.call( arguments );
    args.unshift( type );

    this.log.apply( this, args );
  };
});

// Aliasing.
// (temporary, while ironing out API details)
// The idea is to match existing hardware programming apis
// or simply find the words that are most intuitive.

// Eventually, there should be a queuing process
// for all new callbacks added

Board.prototype.wait = function( time, callback ) {
  return setTimeout( callback.bind(this), time );
};

Board.prototype.loop = function( time, callback ) {
  return setInterval( callback.bind(this), time );
};

//
// Board.prototype.timeout = Board.prototype.wait;
// Board.prototype.delay = Board.prototype.wait;
// Board.prototype.interval = Board.prototype.loop;

// ----------
// Static API
// ----------

// Board.map( val, fromLow, fromHigh, toLow, toHigh )
//
// Re-maps a number from one range to another.
// Based on arduino map()
Board.map = __.map;

// Board.constrain( val, lower, upper )
//
// Constrains a number to be within a range.
// Based on arduino constrain()
Board.constrain = __.constrain;

// Board.range( upper )
// Board.range( lower, upper )
// Board.range( lower, upper, tick )
//
// Returns a new array range
//
Board.range = __.range;

// Board.range.prefixed( prefix, upper )
// Board.range.prefixed( prefix, lower, upper )
// Board.range.prefixed( prefix, lower, upper, tick )
//
// Returns a new array range, each value prefixed
//
Board.range.prefixed = __.range.prefixed;

// Board.uid()
//
// Returns a reasonably unique id string
//
Board.uid = __.uid;

// Board.mount()
// Board.mount( index )
// Board.mount( object )
//
// Return hardware instance, based on type of param:
// @param {arg}
//   object, user specified
//   number/index, specified in cache
//   none, defaults to first in cache
//
// Notes:
// Used to reduce the amount of boilerplate
// code required in any given module or program, by
// giving the developer the option of omitting an
// explicit Board reference in a module
// constructor's options
Board.mount = function( arg ) {
  var index = typeof arg === "number" && arg,
      hardware;

  // board was explicitly provided
  if ( arg && arg.board ) {
    return arg.board;
  }

  // index specified, attempt to return
  // hardware instance. Return null if not
  // found or not available
  if ( index ) {
    hardware = boards[ index ];
    return hardware && hardware || null;
  }

  // If no arg specified and hardware instances
  // exist in the cache
  if ( boards.length ) {
    return boards[ 0 ];
  }

  // No mountable hardware
  return null;
};

Board.options = function( arg ) {
  var pin,
      isArray = Array.isArray(arg),
      opts = {};

  if ( typeof arg === "number" ||
        typeof arg === "string" ||
        isArray ) {
    // Arrays are on a "pins" property
    // String/Numbers are on a "pin" property
    opts[ isArray ? "pins" : "pin" ] = arg;
  } else {
    opts = arg;
  }

  // Auto-normalize pin values, this reduces boilerplate code
  // inside module constructors
  if ( opts.pin || opts.pins && opts.pins.length ) {

    // When an array of pins is present, attempt to
    // normalize them if necessary
    if ( opts.pins ) {
      opts.pins = opts.pins.map(function( pin ) {
        var p = Board.Pins.analog[ pin ];

        return p != null ? p : pin;
      });
    } else {
      pin = Board.Pins.analog[ opts.pin ];

      opts.pin = pin != null ? pin : opts.pin;
    }
  }

  return opts;
};

Board.Pin = {
  //  Require a pin to be a digital PWM pin
  isPWM: function( pin ) {
    return Board.Pins.pwm[ pin ];
  },
  isAnalog: function( pin ) {
    return !!Board.Pins.analog[ pin ];
  },
  isDigital: function( pin ) {
    return Board.Pins.digital[ pin ];
  },
  isSerial: function( pin ) {
    return Board.Pins.serial[ pin ];
  }
};

// TODO: Make this this all private?, delegate to
// Board.Pin.is___()

// TODO: Improve/Overhaul all docs
Board.Pins = {
  analog: {
    "A0": 0, //14,
    "A1": 1, //15,
    "A2": 2, //16,
    "A3": 3, //17,
    "A4": 4, //18,
    "A5": 5  //19
    //     "A0": 14,
    //     "A1": 15,
    //     "A2": 16,
    //     "A3": 17,
    //     "A4": 18,
    //     "A5": 19
  },
  // Used for HIGH/LOW input / output
  digital: {},

  // PWM (8-bit) output with the analogWrite() function.
  // [SecretsOfArduinoPWM](http://arduino.cc/en/Tutorial/SecretsOfArduinoPWM)
  // Used for Servo (or any PWM hardware)
  pwm: {},
  // Used to receive / transmit TTL serial data
  serial: {
    // RX, Receive
    0: true,
    // TX, Transmit
    1: true
  },
  interrupt: {
    2: true,
    3: true
  },
  // These pins support SPI communication using the SPI library.
  spi: {
    10: true,
    11: true,
    12: true,
    13: true
  },
  // There is a built-in LED connected to digital pin 13.
  // When the pin is HIGH value, the LED is on, when the pin is LOW, it's off.
  led: {
    13: true
  }
};

// Add all Digital pins
Board.range( 0, 13 ).forEach(function( val ) {
  Board.Pins.digital[ val ] = true;
});

// Add all PWM pins
[ 3, 5, 6, 9, 10, 11, 12, 13 ].forEach(function( val ) {
  Board.Pins.pwm[ val ] = true;
});

// Define a user-safe, unwritable hardware cache access
Object.defineProperty( Board, "cache", {
  get: function() {
    return boards;
  }
});

module.exports = Board;

// References:
// http://arduino.cc/en/Main/arduinoBoardUno
<|endoftext|>


<commit_before>var Square = require('./square');
var Rook = require('./pieces/rook');
var Bishop = require('./pieces/bishop');
var Queen = require('./pieces/queen');
var Knight = require('./pieces/knight');
var King = require('./pieces/king');
var Pawn = require('./pieces/pawn');

function Board (context, canvas) {
  this.gameOver = false;
  this.nextTurn = true;
  this.context = context;
  this.canvas = canvas;
  this.squares = [];
};

Board.prototype.createNewBoard = function () {
  this.createSquares();
  this.addPiecesToBoard();
  this.makeBoardClickable();
};

Board.prototype.createSquares = function () {
  var board = this;
  var pattern = ["grey", "white"];
  for (var i = 0; i <= 7; i++) {
    togglePattern();
    for (var j = 0; j <= 7; j++) {
      if (j % 2 === 0) {
        board.squares.push(new Square(this, j, i, pattern[0]));
      } else {
        board.squares.push(new Square(this, j, i, pattern[1]));
      }
    }
  }

  function togglePattern () {
    if (pattern[0] === "white") {
      var temp = pattern.pop();
      pattern.unshift(temp);
    } else if (pattern[0] === "grey") {
      var temp = pattern.pop();
      pattern.unshift(temp);
    }
  }
};

Board.prototype.drawBoard = function () {
  var board = this;

  for (var i = 0; i < board.squares.length; i++) {
    drawSquare (board.squares[i], this.context)
    var square = board.squares[i]
    if (square.piece) {
      addIcon (square, board.context)
    }
  }
};

function drawSquare (square, context) {
  var x = square.xCoordinate * 100;
  var y = square.yCoordinate * 100;
  context.fillStyle = square.pattern;
  context.fillRect(x, y, 100, 100);
  context.fillRect(x, y, 100, 100);
}

function addIcon (square, context) {
  var icon = new Image(80, 80);
  icon.src = square.piece.src;

  icon.onload = function () {
    if (square.board.game.turn === "white") {
      whitePerspective (square, context, icon);
    } else if (square.board.game.turn === "black") {
      blackPerspective (square, context, icon);
    }
  };
}

function whitePerspective (square, context, icon) {
  var x = (square.xCoordinate * 100 + 20)
  var y = (square.yCoordinate * 100 + 10)
  context.drawImage(icon, x, y);
};

function blackPerspective (square, context, icon) {
  var x = (700 - (square.xCoordinate * 100) + 20)
  var y = (700 - (square.yCoordinate * 100) + 10)
  context.drawImage(icon, x, y);
};

Board.prototype.addSquaresToBoard = function () {
  for (var i = 0; i <= 7; i++) {
    for (var j = 0; j <= 7; j++) {
      this.squares.push(new Square(this, j, i));
    };
  };
};

Board.prototype.makeBoardClickable = function () {
  var board = this;
  this.canvas.addEventListener("mousedown", selectSquare);
  var targetSquare;
  var viablePieces;

  function selectSquare (event) {
    var canvas_x = event.pageX;
    var canvas_y = event.pageY;

    if (board.game.turn === "white") {
      var indexOfSquare = (Math.floor(canvas_y / 100) * 8) + Math.floor(canvas_x / 100);
    } else if (board.game.turn === "black") {
      var indexOfSquare = (Math.floor((800 - canvas_y) / 100) * 8) + Math.floor((800 - canvas_x) / 100);
    }

    targetSquare = board.squares[indexOfSquare];

    viablePieces = board.canMoveHere(targetSquare);

    if (viablePieces.length > 0) {
      highlightSelectedSquare(board, targetSquare);
      highlightViableMoves(board, viablePieces);
      board.canvas.removeEventListener("mousedown", selectSquare, false);
      board.canvas.addEventListener("mousedown", selectPiece);
    } else {
      flashRed(board, targetSquare);
    }

  }

  function selectPiece (event) {
    var canvas_x = event.pageX;
    var canvas_y = event.pageY;

    if (board.game.turn === "white") {
      var indexOfSquare = (Math.floor(canvas_y / 100) * 8) + Math.floor(canvas_x / 100);
    } else if (board.game.turn === "black") {
      var indexOfSquare = (Math.floor((800 - canvas_y) / 100) * 8) + Math.floor((800 - canvas_x) / 100);
    }
    var pieceSquare = board.squares[indexOfSquare];

    if (viablePieces.includes(pieceSquare.piece)) {
      board.canvas.removeEventListener("mousedown", selectPiece, false);
      board.canvas.addEventListener("mousedown", selectSquare, false);
      pieceSquare.piece.move(targetSquare);
    }
  }
};

function highlightSelectedSquare (board, targetSquare) {
  var startX = targetSquare.xCoordinate * 100;
  var startY = targetSquare.yCoordinate * 100;
  board.context.fillStyle = "yellow";
  if (board.game.turn === "white") {
    board.context.fillRect(startX, startY, 100, 100);
  } else if (board.game.turn === "black") {
    board.context.fillRect(700 - startX, 700 - startY, 100, 100);
  }

  if (targetSquare.isOccupied()) {
    addIcon(targetSquare, board.context);
  }
};

function highlightViableMoves (board, viablePieces) {
  board.context.fillStyle = "lightblue";

  viablePieces.forEach (function (piece) {
    var x = piece.square.xCoordinate * 100;
    var y = piece.square.yCoordinate * 100;
    if (board.game.turn === "white") {
      board.context.fillRect(x, y, 100, 100);
    } else if (board.game.turn === "black") {
      board.context.fillRect(700 - x, 700 - y, 100, 100);
    }
    addIcon(piece.square, board.context);
  });
};

function flashRed (board, targetSquare) {
  var startX = targetSquare.xCoordinate * 100;
  var startY = targetSquare.yCoordinate * 100;
  board.context.fillStyle = "red";
  if (board.game.turn === "white") {
    board.context.fillRect(startX, startY, 100, 100);
  } else if (board.game.turn === "black") {
    board.context.fillRect(700 - startX, 700 - startY, 100, 100);
  }

  if (targetSquare.isOccupied()) {
    addIcon(targetSquare, board.context);
  }
};

Board.prototype.addPiecesToBoard = function () {
  loadBlack(this);
  loadWhite(this);
}

function loadBlack (board) {
  board.squares[0].piece = new Rook (board.squares[0], "black");
  board.squares[1].piece = new Knight (board.squares[1], "black");
  board.squares[2].piece = new Bishop (board.squares[2], "black");
  board.squares[3].piece = new Queen (board.squares[3], "black");
  board.squares[4].piece = new King (board.squares[4], "black");
  board.squares[5].piece = new Bishop (board.squares[5], "black");
  board.squares[6].piece = new Knight (board.squares[6], "black");
  board.squares[7].piece = new Rook (board.squares[7], "black");
  for (var i = 8; i < 16; i++) {
    board.squares[i].piece = new Pawn (board.squares[i], "black");
  }
};

function loadWhite (board) {
  for (var i = 48; i < 56; i++) {
    board.squares[i].piece = new Pawn (board.squares[i], "white");
  }
  board.squares[56].piece = new Rook (board.squares[56], "white");
  board.squares[57].piece = new Knight (board.squares[57], "white");
  board.squares[58].piece = new Bishop (board.squares[58], "white");
  board.squares[59].piece = new Queen (board.squares[59], "white");
  board.squares[60].piece = new King (board.squares[60], "white");
  board.squares[61].piece = new Bishop (board.squares[61], "white");
  board.squares[62].piece = new Knight (board.squares[62], "white");
  board.squares[63].piece = new Rook (board.squares[63], "white");
};

Board.prototype.canMoveHere = function (square) {
  var turn = this.game.turn;
  var pieces = allPieces(this);
  return pieces.filter(function (piece) {
    return piece.color === turn
      && piece.canMoveTo(square);
  })
};

Board.prototype.opponentCanMoveHere = function (kingsSquare) {
  var turn = this.game.turn;
  var pieces = allPieces(this);
  return pieces.filter(function (piece) {
    return piece.color !== turn
      && piece.canMoveTo(kingsSquare);
  })
};

function allPieces (board) {
  var pieces = [];
  for (var i = 0; i < board.squares.length; i++) {
    if (board.squares[i].piece) {
      pieces.push(board.squares[i].piece);
    }
  }
  return pieces;
};

Board.prototype.pieceCount = function () {
  return this.squares.reduce(function (count, square) {
    if (square.piece) {
      count++;
    }
    return count;
  }, 0);
};

Board.prototype.findSquare = function (x, y) {
  return this.squares[(y * 8) + x];
};

Board.prototype.currentKingSquare = function () {
  var board = this;
  var squaresWithKing = board.squares.filter(function (square) {
    return !!square.isOccupied()
      && square.piece.color === board.game.turn
      && square.piece.type === "king";
  });
  return squaresWithKing[0];
};

module.exports = Board;
<commit_msg>Fixes red square issue... code is ugly.<commit_after>var Square = require('./square');
var Rook = require('./pieces/rook');
var Bishop = require('./pieces/bishop');
var Queen = require('./pieces/queen');
var Knight = require('./pieces/knight');
var King = require('./pieces/king');
var Pawn = require('./pieces/pawn');

function Board (context, canvas) {
  this.gameOver = false;
  this.nextTurn = true;
  this.context = context;
  this.canvas = canvas;
  this.squares = [];
};

Board.prototype.createNewBoard = function () {
  this.createSquares();
  this.addPiecesToBoard();
  this.makeBoardClickable();
};

Board.prototype.createSquares = function () {
  var board = this;
  var pattern = ["grey", "white"];
  for (var i = 0; i <= 7; i++) {
    togglePattern();
    for (var j = 0; j <= 7; j++) {
      if (j % 2 === 0) {
        board.squares.push(new Square(this, j, i, pattern[0]));
      } else {
        board.squares.push(new Square(this, j, i, pattern[1]));
      }
    }
  }

  function togglePattern () {
    if (pattern[0] === "white") {
      var temp = pattern.pop();
      pattern.unshift(temp);
    } else if (pattern[0] === "grey") {
      var temp = pattern.pop();
      pattern.unshift(temp);
    }
  }
};

Board.prototype.drawBoard = function () {
  var board = this;

  for (var i = 0; i < board.squares.length; i++) {
    drawSquare (board.squares[i], this.context)
    var square = board.squares[i]
    if (square.piece) {
      addIcon (square, board.context)
    }
  }
};

function drawSquare (square, context) {
  var x = square.xCoordinate * 100;
  var y = square.yCoordinate * 100;
  context.fillStyle = square.pattern;
  context.fillRect(x, y, 100, 100);
  context.fillRect(x, y, 100, 100);
}

function addIcon (square, context) {
  var icon = new Image(80, 80);
  icon.src = square.piece.src;

  icon.onload = function () {
    if (square.board.game.turn === "white") {
      whitePerspective (square, context, icon);
    } else if (square.board.game.turn === "black") {
      blackPerspective (square, context, icon);
    }
  };
}

function whitePerspective (square, context, icon) {
  var x = (square.xCoordinate * 100 + 20)
  var y = (square.yCoordinate * 100 + 10)
  context.drawImage(icon, x, y);
};

function blackPerspective (square, context, icon) {
  var x = (700 - (square.xCoordinate * 100) + 20)
  var y = (700 - (square.yCoordinate * 100) + 10)
  context.drawImage(icon, x, y);
};

Board.prototype.addSquaresToBoard = function () {
  for (var i = 0; i <= 7; i++) {
    for (var j = 0; j <= 7; j++) {
      this.squares.push(new Square(this, j, i));
    };
  };
};

Board.prototype.makeBoardClickable = function () {
  var board = this;
  this.canvas.addEventListener("mousedown", selectSquare);
  var targetSquare;
  var viablePieces;

  function selectSquare (event) {
    var canvas_x = event.pageX;
    var canvas_y = event.pageY;

    if (board.game.turn === "white") {
      var indexOfSquare = (Math.floor(canvas_y / 100) * 8) + Math.floor(canvas_x / 100);
    } else if (board.game.turn === "black") {
      var indexOfSquare = (Math.floor((800 - canvas_y) / 100) * 8) + Math.floor((800 - canvas_x) / 100);
    }

    targetSquare = board.squares[indexOfSquare];

    viablePieces = board.canMoveHere(targetSquare);

    if (viablePieces.length > 0) {
      highlightSelectedSquare(board, targetSquare);
      highlightViableMoves(board, viablePieces);
      board.canvas.removeEventListener("mousedown", selectSquare, false);
      board.canvas.addEventListener("mousedown", selectPiece);
    } else {
      flashRed(board, targetSquare);
    }

  }

  function selectPiece (event) {
    var canvas_x = event.pageX;
    var canvas_y = event.pageY;

    if (board.game.turn === "white") {
      var indexOfSquare = (Math.floor(canvas_y / 100) * 8) + Math.floor(canvas_x / 100);
    } else if (board.game.turn === "black") {
      var indexOfSquare = (Math.floor((800 - canvas_y) / 100) * 8) + Math.floor((800 - canvas_x) / 100);
    }
    var pieceSquare = board.squares[indexOfSquare];

    if (viablePieces.includes(pieceSquare.piece)) {
      board.canvas.removeEventListener("mousedown", selectPiece, false);
      board.canvas.addEventListener("mousedown", selectSquare, false);
      pieceSquare.piece.move(targetSquare);
    }
  }
};

function highlightSelectedSquare (board, targetSquare) {
  var startX = targetSquare.xCoordinate * 100;
  var startY = targetSquare.yCoordinate * 100;
  board.context.fillStyle = "yellow";
  if (board.game.turn === "white") {
    board.context.fillRect(startX, startY, 100, 100);
  } else if (board.game.turn === "black") {
    board.context.fillRect(700 - startX, 700 - startY, 100, 100);
  }

  if (targetSquare.isOccupied()) {
    addIcon(targetSquare, board.context);
  }
};

function highlightViableMoves (board, viablePieces) {
  board.context.fillStyle = "lightblue";

  viablePieces.forEach (function (piece) {
    var x = piece.square.xCoordinate * 100;
    var y = piece.square.yCoordinate * 100;
    if (board.game.turn === "white") {
      board.context.fillRect(x, y, 100, 100);
    } else if (board.game.turn === "black") {
      board.context.fillRect(700 - x, 700 - y, 100, 100);
    }
    addIcon(piece.square, board.context);
  });
};

function flashRed (board, targetSquare) {
  var startX = targetSquare.xCoordinate * 100;
  var startY = targetSquare.yCoordinate * 100;
  board.context.fillStyle = "red";
  if (board.game.turn === "white") {
    board.context.fillRect(startX, startY, 100, 100);
    setTimeout (function () {
      drawSquare(targetSquare, board.context);
      if (targetSquare.isOccupied()) {
        addIcon(targetSquare, board.context);
      }
    }, 500);
  } else if (board.game.turn === "black") {
    board.context.fillRect(700 - startX, 700 - startY, 100, 100);
    setTimeout (function () {
      board.context.fillStyle = targetSquare.pattern;
      board.context.fillRect(700 - startX, 700 - startY, 100, 100);
      if (targetSquare.isOccupied()) {
        addIcon(targetSquare, board.context);
      }
    }, 500)
  }
  if (targetSquare.isOccupied()) {
    addIcon(targetSquare, board.context);
  }
};

Board.prototype.addPiecesToBoard = function () {
  loadBlack(this);
  loadWhite(this);
}

function loadBlack (board) {
  board.squares[0].piece = new Rook (board.squares[0], "black");
  board.squares[1].piece = new Knight (board.squares[1], "black");
  board.squares[2].piece = new Bishop (board.squares[2], "black");
  board.squares[3].piece = new Queen (board.squares[3], "black");
  board.squares[4].piece = new King (board.squares[4], "black");
  board.squares[5].piece = new Bishop (board.squares[5], "black");
  board.squares[6].piece = new Knight (board.squares[6], "black");
  board.squares[7].piece = new Rook (board.squares[7], "black");
  for (var i = 8; i < 16; i++) {
    board.squares[i].piece = new Pawn (board.squares[i], "black");
  }
};

function loadWhite (board) {
  for (var i = 48; i < 56; i++) {
    board.squares[i].piece = new Pawn (board.squares[i], "white");
  }
  board.squares[56].piece = new Rook (board.squares[56], "white");
  board.squares[57].piece = new Knight (board.squares[57], "white");
  board.squares[58].piece = new Bishop (board.squares[58], "white");
  board.squares[59].piece = new Queen (board.squares[59], "white");
  board.squares[60].piece = new King (board.squares[60], "white");
  board.squares[61].piece = new Bishop (board.squares[61], "white");
  board.squares[62].piece = new Knight (board.squares[62], "white");
  board.squares[63].piece = new Rook (board.squares[63], "white");
};

Board.prototype.canMoveHere = function (square) {
  var turn = this.game.turn;
  var pieces = allPieces(this);
  return pieces.filter(function (piece) {
    return piece.color === turn
      && piece.canMoveTo(square);
  })
};

Board.prototype.opponentCanMoveHere = function (kingsSquare) {
  var turn = this.game.turn;
  var pieces = allPieces(this);
  return pieces.filter(function (piece) {
    return piece.color !== turn
      && piece.canMoveTo(kingsSquare);
  })
};

function allPieces (board) {
  var pieces = [];
  for (var i = 0; i < board.squares.length; i++) {
    if (board.squares[i].piece) {
      pieces.push(board.squares[i].piece);
    }
  }
  return pieces;
};

Board.prototype.pieceCount = function () {
  return this.squares.reduce(function (count, square) {
    if (square.piece) {
      count++;
    }
    return count;
  }, 0);
};

Board.prototype.findSquare = function (x, y) {
  return this.squares[(y * 8) + x];
};

Board.prototype.currentKingSquare = function () {
  var board = this;
  var squaresWithKing = board.squares.filter(function (square) {
    return !!square.isOccupied()
      && square.piece.color === board.game.turn
      && square.piece.type === "king";
  });
  return squaresWithKing[0];
};

module.exports = Board;
<|endoftext|>


<commit_before>'use babel';

export default {
  config: require('./config'),

  activate() {
    if (!/^win/.test(process.platform)) {
      // Manually append /usr/local/bin as it may not be set on some systems,
      // and it's common to have node installed here. Keep it at end so it won't
      // accidentially override any other node installation

      // Note: This should probably be removed in a end-user friendly way...
      process.env.PATH += ':/usr/local/bin';
    }

    require('atom-package-deps').install('build');

    this.tools = [ require('./atom-build') ];
    this.linter = null;

    this.setupTargetManager();
    this.setupBuildView();
    this.setupErrorMatcher();

    atom.commands.add('atom-workspace', 'build:trigger', () => this.build('trigger'));
    atom.commands.add('atom-workspace', 'build:stop', () => this.stop());
    atom.commands.add('atom-workspace', 'build:confirm', () => {
      require('./google-analytics').sendEvent('build', 'confirmed');
      document.activeElement.click();
    });
    atom.commands.add('atom-workspace', 'build:no-confirm', () => {
      if (this.saveConfirmView) {
        require('./google-analytics').sendEvent('build', 'not confirmed');
        this.saveConfirmView.cancel();
      }
    });

    atom.workspace.observeTextEditors((editor) => {
      editor.onDidSave(() => {
        if (atom.config.get('build.buildOnSave')) {
          this.build('save');
        }
      });
    });

    atom.workspace.onDidChangeActivePaneItem(() => this.updateStatusBar());
    atom.packages.onDidActivateInitialPackages(() => this.targetManager.refreshTargets());
  },

  setupTargetManager() {
    const TargetManager = require('./target-manager');
    this.targetManager = new TargetManager();
    this.targetManager.setTools(this.tools);
    this.targetManager.on('refresh-complete', () => {
      this.updateStatusBar();
    });
    this.targetManager.on('new-active-target', (path, target) => {
      this.updateStatusBar();

      if (atom.config.get('build.selectTriggers')) {
        this.build('trigger');
      }
    });
    this.targetManager.on('trigger', atomCommandName => this.build('trigger', atomCommandName));
  },

  setupBuildView() {
    const BuildView = require('./build-view');
    this.buildView = new BuildView();
  },

  setupErrorMatcher() {
    const ErrorMatcher = require('./error-matcher');
    this.errorMatcher = new ErrorMatcher();
    this.errorMatcher.on('error', (message) => {
      atom.notifications.addError('Error matching failed!', { detail: message });
    });
    this.errorMatcher.on('matched', (match) => {
      match[0] && this.buildView.scrollTo(match[0]);
    });
  },

  deactivate() {
    if (this.child) {
      this.child.removeAllListeners();
      require('tree-kill')(this.child.pid, 'SIGKILL');
      this.child = null;
    }

    this.statusBarView && this.statusBarView.destroy();
    this.buildView && this.buildView.destroy();
    this.saveConfirmView && this.saveConfirmView.destroy();
    this.linter && this.linter.destroy();
    this.targetManager.destroy();

    clearTimeout(this.finishedTimer);
  },

  updateStatusBar() {
    const path = require('./utils').activePath();
    const activeTarget = this.targetManager.getActiveTarget(path);
    this.statusBarView && activeTarget && this.statusBarView.setTarget(activeTarget.name);
  },

  startNewBuild(source, atomCommandName) {
    const BuildError = require('./build-error');
    const path = require('./utils').activePath();
    let buildTitle = '';
    this.linter && this.linter.clear();

    Promise.resolve(this.targetManager.getTargets(path)).then(targets => {
      if (!targets || 0 === targets.length) {
        throw new BuildError('No eligible build target.', 'No configuration to build this project exists.');
      }

      let target = targets.find(t => t.atomCommandName === atomCommandName);
      if (!target) {
        target = this.targetManager.getActiveTarget(path);
      }
      require('./google-analytics').sendEvent('build', 'triggered');

      if (!target.exec) {
        throw new BuildError('Invalid build file.', 'No executable command specified.');
      }

      this.statusBarView && this.statusBarView.buildStarted();
      this.busyRegistry && this.busyRegistry.begin(`build.${target.name}`, `${target.name}`);
      this.buildView.buildStarted();
      this.buildView.setHeading('Running preBuild...');

      return Promise.resolve(target.preBuild ? target.preBuild() : null).then(() => target);
    }).then(target => {
      const replace = require('./utils').replace;
      const env = Object.assign({}, process.env, target.env);
      Object.keys(env).forEach(key => {
        env[key] = replace(env[key], target.env);
      });

      const exec = replace(target.exec, target.env);
      const args = target.args.map(arg => replace(arg, target.env));
      const cwd = replace(target.cwd, target.env);
      const isWin = process.platform === 'win32';
      const shCmd = isWin ? 'cmd' : '/bin/sh';
      const shCmdArg = isWin ? '/C' : '-c';

      // Store this as we need to re-set it after postBuild
      buildTitle = [ (target.sh ? `${shCmd} ${shCmdArg} ${exec}` : exec ), ...args, '\n'].join(' ');

      this.buildView.setHeading(buildTitle);
      if (target.sh) {
        this.child = require('child_process').spawn(
          shCmd,
          [ shCmdArg, [ exec ].concat(args).join(' ')],
          { cwd: cwd, env: env }
        );
      } else {
        this.child = require('cross-spawn-async').spawn(
          exec,
          args,
          { cwd: cwd, env: env }
        );
      }

      let stdout = '';
      let stderr = '';
      this.child.stdout.setEncoding('utf8');
      this.child.stderr.setEncoding('utf8');
      this.child.stdout.on('data', d => (stdout += d));
      this.child.stderr.on('data', d => (stderr += d));
      this.child.stdout.pipe(this.buildView.terminal);
      this.child.stderr.pipe(this.buildView.terminal);
      this.child.killSignals = (target.killSignals || [ 'SIGINT', 'SIGTERM', 'SIGKILL' ]).slice();

      this.child.on('error', (err) => {
        this.buildView.terminal.write((target.sh ? 'Unable to execute with shell: ' : 'Unable to execute: ') + exec + '\n');

        if (/\s/.test(exec) && !target.sh) {
          this.buildView.terminal.write('`cmd` cannot contain space. Use `args` for arguments.\n');
        }

        if ('ENOENT' === err.code) {
          this.buildView.terminal.write(`Make sure cmd:'${exec}' and cwd:'${cwd}' exists and have correct access permissions.\n`);
          this.buildView.terminal.write(`Binaries are found in these folders: ${process.env.PATH}\n`);
        }
      });

      this.child.on('close', (exitCode) => {
        this.child = null;
        this.errorMatcher.set(target, cwd, stdout + stderr);

        let success = (0 === exitCode);
        if (atom.config.get('build.matchedErrorFailsBuild')) {
          success = success && !this.errorMatcher.getMatches().some(match => match.type && match.type.toLowerCase() === 'error');
        }

        this.linter && this.linter.processMessages(this.errorMatcher.getMatches(), cwd);

        if (atom.config.get('build.beepWhenDone')) {
          atom.beep();
        }

        this.buildView.setHeading('Running postBuild...');
        return Promise.resolve(target.postBuild ? target.postBuild(success, stdout, stderr) : null).then(() => {
          this.buildView.setHeading(buildTitle);

          this.busyRegistry && this.busyRegistry.end(`build.${target.name}`, success);
          this.buildView.buildFinished(success);
          this.statusBarView && this.statusBarView.setBuildSuccess(success);
          if (success) {
            require('./google-analytics').sendEvent('build', 'succeeded');
            this.finishedTimer = setTimeout(() => {
              this.buildView.detach();
            }, 1200);
          } else {
            if (atom.config.get('build.scrollOnError')) {
              this.errorMatcher.matchFirst();
            }
            require('./google-analytics').sendEvent('build', 'failed');
          }

          this.nextBuild && this.nextBuild();
          this.nextBuild = null;
        });
      });
    }).catch((err) => {
      if (err instanceof BuildError) {
        if (source === 'save') {
          // If there is no eligible build tool, and cause of build was a save, stay quiet.
          return;
        }

        atom.notifications.addWarning(err.name, { detail: err.message });
      } else {
        atom.notifications.addError('Failed to build.', { detail: err.message });
      }
    });
  },

  sendNextSignal() {
    try {
      const signal = this.child.killSignals.shift();
      require('tree-kill')(this.child.pid, signal);
    } catch (e) {
      /* Something may have happened to the child (e.g. terminated by itself). Ignore this. */
    }
  },

  abort(cb) {
    if (!this.child.killed) {
      this.buildView.buildAbortInitiated();
      this.child.killed = true;
      this.child.on('exit', () => {
        this.child = null;
        cb && cb();
      });
    }

    this.sendNextSignal();
  },

  build(source, event) {
    clearTimeout(this.finishedTimer);

    this.doSaveConfirm(this.unsavedTextEditors(), () => {
      const nextBuild = this.startNewBuild.bind(this, source, event ? event.type : null);
      if (this.child) {
        this.nextBuild = nextBuild;
        return this.abort();
      }
      return nextBuild();
    });
  },

  doSaveConfirm(modifiedTextEditors, continuecb, cancelcb) {
    const saveAndContinue = (save) => {
      modifiedTextEditors.forEach((textEditor) => save && textEditor.save());
      continuecb();
    };

    if (0 === modifiedTextEditors.length || atom.config.get('build.saveOnBuild')) {
      saveAndContinue(true);
      return;
    }

    if (this.saveConfirmView) {
      this.saveConfirmView.destroy();
    }

    const SaveConfirmView = require('./save-confirm-view');
    this.saveConfirmView = new SaveConfirmView();
    this.saveConfirmView.show(saveAndContinue, cancelcb);
  },

  unsavedTextEditors() {
    return atom.workspace.getTextEditors().filter((textEditor) => {
      return textEditor.isModified() && (undefined !== textEditor.getPath());
    });
  },

  stop() {
    this.nextBuild = null;
    clearTimeout(this.finishedTimer);
    if (this.child) {
      this.abort(() => {
        this.buildView.buildAborted();
        this.statusBarView && this.statusBarView.buildAborted();
      });
    } else {
      this.buildView.reset();
    }
  },

  consumeLinterRegistry(registry) {
    this.linter && this.linter.destroy();
    const Linter = require('./linter-integration');
    this.linter = new Linter(registry);
  },

  consumeBuilder(builder) {
    if (Array.isArray(builder)) this.tools.push(...builder); else this.tools.push(builder);
    this.targetManager.setTools(this.tools);
    const Disposable = require('atom').Disposable;
    return new Disposable(() => {
      this.tools = this.tools.filter(Array.isArray(builder) ? tool => builder.indexOf(tool) === -1 : tool => tool !== builder);
      this.targetManager.setTools(this.tools);
    });
  },

  consumeStatusBar(statusBar) {
    const StatusBarView = require('./status-bar-view');
    this.statusBarView = new StatusBarView(statusBar);
    this.statusBarView.onClick(() => this.targetManager.selectActiveTarget());
    this.statusBarView.attach();
  },

  consumeBusy(registry) {
    this.busyRegistry = registry;
    this.targetManager.setBusyRegistry(registry);
  }
};
<commit_msg>Replace cross-spawn-async with cross-spawn<commit_after>'use babel';

export default {
  config: require('./config'),

  activate() {
    if (!/^win/.test(process.platform)) {
      // Manually append /usr/local/bin as it may not be set on some systems,
      // and it's common to have node installed here. Keep it at end so it won't
      // accidentially override any other node installation

      // Note: This should probably be removed in a end-user friendly way...
      process.env.PATH += ':/usr/local/bin';
    }

    require('atom-package-deps').install('build');

    this.tools = [ require('./atom-build') ];
    this.linter = null;

    this.setupTargetManager();
    this.setupBuildView();
    this.setupErrorMatcher();

    atom.commands.add('atom-workspace', 'build:trigger', () => this.build('trigger'));
    atom.commands.add('atom-workspace', 'build:stop', () => this.stop());
    atom.commands.add('atom-workspace', 'build:confirm', () => {
      require('./google-analytics').sendEvent('build', 'confirmed');
      document.activeElement.click();
    });
    atom.commands.add('atom-workspace', 'build:no-confirm', () => {
      if (this.saveConfirmView) {
        require('./google-analytics').sendEvent('build', 'not confirmed');
        this.saveConfirmView.cancel();
      }
    });

    atom.workspace.observeTextEditors((editor) => {
      editor.onDidSave(() => {
        if (atom.config.get('build.buildOnSave')) {
          this.build('save');
        }
      });
    });

    atom.workspace.onDidChangeActivePaneItem(() => this.updateStatusBar());
    atom.packages.onDidActivateInitialPackages(() => this.targetManager.refreshTargets());
  },

  setupTargetManager() {
    const TargetManager = require('./target-manager');
    this.targetManager = new TargetManager();
    this.targetManager.setTools(this.tools);
    this.targetManager.on('refresh-complete', () => {
      this.updateStatusBar();
    });
    this.targetManager.on('new-active-target', (path, target) => {
      this.updateStatusBar();

      if (atom.config.get('build.selectTriggers')) {
        this.build('trigger');
      }
    });
    this.targetManager.on('trigger', atomCommandName => this.build('trigger', atomCommandName));
  },

  setupBuildView() {
    const BuildView = require('./build-view');
    this.buildView = new BuildView();
  },

  setupErrorMatcher() {
    const ErrorMatcher = require('./error-matcher');
    this.errorMatcher = new ErrorMatcher();
    this.errorMatcher.on('error', (message) => {
      atom.notifications.addError('Error matching failed!', { detail: message });
    });
    this.errorMatcher.on('matched', (match) => {
      match[0] && this.buildView.scrollTo(match[0]);
    });
  },

  deactivate() {
    if (this.child) {
      this.child.removeAllListeners();
      require('tree-kill')(this.child.pid, 'SIGKILL');
      this.child = null;
    }

    this.statusBarView && this.statusBarView.destroy();
    this.buildView && this.buildView.destroy();
    this.saveConfirmView && this.saveConfirmView.destroy();
    this.linter && this.linter.destroy();
    this.targetManager.destroy();

    clearTimeout(this.finishedTimer);
  },

  updateStatusBar() {
    const path = require('./utils').activePath();
    const activeTarget = this.targetManager.getActiveTarget(path);
    this.statusBarView && activeTarget && this.statusBarView.setTarget(activeTarget.name);
  },

  startNewBuild(source, atomCommandName) {
    const BuildError = require('./build-error');
    const path = require('./utils').activePath();
    let buildTitle = '';
    this.linter && this.linter.clear();

    Promise.resolve(this.targetManager.getTargets(path)).then(targets => {
      if (!targets || 0 === targets.length) {
        throw new BuildError('No eligible build target.', 'No configuration to build this project exists.');
      }

      let target = targets.find(t => t.atomCommandName === atomCommandName);
      if (!target) {
        target = this.targetManager.getActiveTarget(path);
      }
      require('./google-analytics').sendEvent('build', 'triggered');

      if (!target.exec) {
        throw new BuildError('Invalid build file.', 'No executable command specified.');
      }

      this.statusBarView && this.statusBarView.buildStarted();
      this.busyRegistry && this.busyRegistry.begin(`build.${target.name}`, `${target.name}`);
      this.buildView.buildStarted();
      this.buildView.setHeading('Running preBuild...');

      return Promise.resolve(target.preBuild ? target.preBuild() : null).then(() => target);
    }).then(target => {
      const replace = require('./utils').replace;
      const env = Object.assign({}, process.env, target.env);
      Object.keys(env).forEach(key => {
        env[key] = replace(env[key], target.env);
      });

      const exec = replace(target.exec, target.env);
      const args = target.args.map(arg => replace(arg, target.env));
      const cwd = replace(target.cwd, target.env);
      const isWin = process.platform === 'win32';
      const shCmd = isWin ? 'cmd' : '/bin/sh';
      const shCmdArg = isWin ? '/C' : '-c';

      // Store this as we need to re-set it after postBuild
      buildTitle = [ (target.sh ? `${shCmd} ${shCmdArg} ${exec}` : exec ), ...args, '\n'].join(' ');

      this.buildView.setHeading(buildTitle);
      if (target.sh) {
        this.child = require('child_process').spawn(
          shCmd,
          [ shCmdArg, [ exec ].concat(args).join(' ')],
          { cwd: cwd, env: env }
        );
      } else {
        this.child = require('cross-spawn').spawn(
          exec,
          args,
          { cwd: cwd, env: env }
        );
      }

      let stdout = '';
      let stderr = '';
      this.child.stdout.setEncoding('utf8');
      this.child.stderr.setEncoding('utf8');
      this.child.stdout.on('data', d => (stdout += d));
      this.child.stderr.on('data', d => (stderr += d));
      this.child.stdout.pipe(this.buildView.terminal);
      this.child.stderr.pipe(this.buildView.terminal);
      this.child.killSignals = (target.killSignals || [ 'SIGINT', 'SIGTERM', 'SIGKILL' ]).slice();

      this.child.on('error', (err) => {
        this.buildView.terminal.write((target.sh ? 'Unable to execute with shell: ' : 'Unable to execute: ') + exec + '\n');

        if (/\s/.test(exec) && !target.sh) {
          this.buildView.terminal.write('`cmd` cannot contain space. Use `args` for arguments.\n');
        }

        if ('ENOENT' === err.code) {
          this.buildView.terminal.write(`Make sure cmd:'${exec}' and cwd:'${cwd}' exists and have correct access permissions.\n`);
          this.buildView.terminal.write(`Binaries are found in these folders: ${process.env.PATH}\n`);
        }
      });

      this.child.on('close', (exitCode) => {
        this.child = null;
        this.errorMatcher.set(target, cwd, stdout + stderr);

        let success = (0 === exitCode);
        if (atom.config.get('build.matchedErrorFailsBuild')) {
          success = success && !this.errorMatcher.getMatches().some(match => match.type && match.type.toLowerCase() === 'error');
        }

        this.linter && this.linter.processMessages(this.errorMatcher.getMatches(), cwd);

        if (atom.config.get('build.beepWhenDone')) {
          atom.beep();
        }

        this.buildView.setHeading('Running postBuild...');
        return Promise.resolve(target.postBuild ? target.postBuild(success, stdout, stderr) : null).then(() => {
          this.buildView.setHeading(buildTitle);

          this.busyRegistry && this.busyRegistry.end(`build.${target.name}`, success);
          this.buildView.buildFinished(success);
          this.statusBarView && this.statusBarView.setBuildSuccess(success);
          if (success) {
            require('./google-analytics').sendEvent('build', 'succeeded');
            this.finishedTimer = setTimeout(() => {
              this.buildView.detach();
            }, 1200);
          } else {
            if (atom.config.get('build.scrollOnError')) {
              this.errorMatcher.matchFirst();
            }
            require('./google-analytics').sendEvent('build', 'failed');
          }

          this.nextBuild && this.nextBuild();
          this.nextBuild = null;
        });
      });
    }).catch((err) => {
      if (err instanceof BuildError) {
        if (source === 'save') {
          // If there is no eligible build tool, and cause of build was a save, stay quiet.
          return;
        }

        atom.notifications.addWarning(err.name, { detail: err.message });
      } else {
        atom.notifications.addError('Failed to build.', { detail: err.message });
      }
    });
  },

  sendNextSignal() {
    try {
      const signal = this.child.killSignals.shift();
      require('tree-kill')(this.child.pid, signal);
    } catch (e) {
      /* Something may have happened to the child (e.g. terminated by itself). Ignore this. */
    }
  },

  abort(cb) {
    if (!this.child.killed) {
      this.buildView.buildAbortInitiated();
      this.child.killed = true;
      this.child.on('exit', () => {
        this.child = null;
        cb && cb();
      });
    }

    this.sendNextSignal();
  },

  build(source, event) {
    clearTimeout(this.finishedTimer);

    this.doSaveConfirm(this.unsavedTextEditors(), () => {
      const nextBuild = this.startNewBuild.bind(this, source, event ? event.type : null);
      if (this.child) {
        this.nextBuild = nextBuild;
        return this.abort();
      }
      return nextBuild();
    });
  },

  doSaveConfirm(modifiedTextEditors, continuecb, cancelcb) {
    const saveAndContinue = (save) => {
      modifiedTextEditors.forEach((textEditor) => save && textEditor.save());
      continuecb();
    };

    if (0 === modifiedTextEditors.length || atom.config.get('build.saveOnBuild')) {
      saveAndContinue(true);
      return;
    }

    if (this.saveConfirmView) {
      this.saveConfirmView.destroy();
    }

    const SaveConfirmView = require('./save-confirm-view');
    this.saveConfirmView = new SaveConfirmView();
    this.saveConfirmView.show(saveAndContinue, cancelcb);
  },

  unsavedTextEditors() {
    return atom.workspace.getTextEditors().filter((textEditor) => {
      return textEditor.isModified() && (undefined !== textEditor.getPath());
    });
  },

  stop() {
    this.nextBuild = null;
    clearTimeout(this.finishedTimer);
    if (this.child) {
      this.abort(() => {
        this.buildView.buildAborted();
        this.statusBarView && this.statusBarView.buildAborted();
      });
    } else {
      this.buildView.reset();
    }
  },

  consumeLinterRegistry(registry) {
    this.linter && this.linter.destroy();
    const Linter = require('./linter-integration');
    this.linter = new Linter(registry);
  },

  consumeBuilder(builder) {
    if (Array.isArray(builder)) this.tools.push(...builder); else this.tools.push(builder);
    this.targetManager.setTools(this.tools);
    const Disposable = require('atom').Disposable;
    return new Disposable(() => {
      this.tools = this.tools.filter(Array.isArray(builder) ? tool => builder.indexOf(tool) === -1 : tool => tool !== builder);
      this.targetManager.setTools(this.tools);
    });
  },

  consumeStatusBar(statusBar) {
    const StatusBarView = require('./status-bar-view');
    this.statusBarView = new StatusBarView(statusBar);
    this.statusBarView.onClick(() => this.targetManager.selectActiveTarget());
    this.statusBarView.attach();
  },

  consumeBusy(registry) {
    this.busyRegistry = registry;
    this.targetManager.setBusyRegistry(registry);
  }
};
<|endoftext|>


<commit_before>// This reports the tracks and exercise count.
// Also a good example of how to make a report...
module.exports = function(configfile, targets){
  // configfile is in uniconfig format
  // targets is variadic: basically whatever args are left on the command line

  // initialize return array with header row
  let returner = [['Track', 'Exercise Count']];
  for(let slug in configfile.tracks){
    let track = configfile.tracks[slug]
    returner.push([slug, track.exercises.length]);
  }

  // returning a two dimensional array which will allow the program
  // to output as CSV or tabular.
  return returner;
};
<commit_msg>exercise-count: show in descending count order<commit_after>// This reports the tracks and exercise count.
// Also a good example of how to make a report...
module.exports = function(configfile, targets){
  // configfile is in uniconfig format
  // targets is variadic: basically whatever args are left on the command line

  // initialize return array with header row
  let returner = [['Track', 'Exercise Count']];
  for(let slug in configfile.tracks){
    let track = configfile.tracks[slug]
    returner.push([slug, track.exercises.length]);
  }

  returner.sort((a, b) => b[1] - a[1]);
  // returning a two dimensional array which will allow the program
  // to output as CSV or tabular.
  return returner;
};
<|endoftext|>


<commit_before>var payload = require('./payload'),
	pubsub = require('node-internal-pubsub'),
	exec = require('child_process').exec;

var build_channel = "builds";

function determineBuildType(dir) {
	if( dir.indexOf("pom.xml") > -1 ) { //maven
		return "mvn package; rm -f target; ln -s dist target"
	} else if( dir.indexOf("Gemfile") > -1 ) { //ruby
		return "bundle install --path vendor/bundle";
	} else if( dir.indexOf("package.json") > -1 ) {  //node
		return "npm install";
	} else {  //uncrecognized project type...
		return "";
	}
}

function view(target, callback) {
	exec("ls "+target, function(error, stdout, stderr) {
		var cmd = payload.build_cmd.replace("{{target}}",target)
								   .replace("{{build_spec}}",determineBuildType(stdout));
		callback(cmd);
	});
}

function build(target) {
	view(target, function(cmd){
		console.log(cmd);a
		var ps = pubsub.createPublisher();
		exec(cmd, function(error, stdout, stderr) {
			ps.publish(build_channel, [stdout,stderr]);
		});
	});
}

module.exports = function() {

	return {
		run: function(target) {
			return build(target);
		}
	}
}<commit_msg>bugs!  fixed.<commit_after>var payload = require('./payload'),
	pubsub = require('node-internal-pubsub'),
	exec = require('child_process').exec;

var build_channel = "builds";

function determineBuildType(dir) {
	if( dir.indexOf("pom.xml") > -1 ) { //maven
		return "mvn package; rm -f target; ln -s dist target"
	} else if( dir.indexOf("Gemfile") > -1 ) { //ruby
		return "bundle install --path vendor/bundle";
	} else if( dir.indexOf("package.json") > -1 ) {  //node
		return "npm install";
	} else {  //uncrecognized project type...
		return "";
	}
}

function ls(target, callback) {
	exec("ls "+target, function(error, stdout, stderr) {
		var cmd = payload.build_cmd.replace("{{target}}",target)
								   .replace("{{build_spec}}",determineBuildType(stdout));
		callback(cmd);
	});
}

function build(target) {
	ls(target, function(cmd){
		console.log(cmd);
		var ps = pubsub.createPublisher();
		exec(cmd, function(error, stdout, stderr) {
			ps.publish(build_channel, [stdout,stderr]);
		});
	});
}

module.exports = function() {

	return {
		run: function(target) {
			return build(target);
		}
	}
}<|endoftext|>


<commit_before>var _ = require('lodash');
var qs = require('qs');

 /**
 * build
 * @augments Adds a method providing a convenient means of building URLs with
 * query string parameters as well as attractive URLs with path components
 */

module.exports = function(self) {

  // Add and modify query parameters of a url. data is an object whose properties
  // become new query parameters. These parameters override any existing
  // parameters of the same name in the URL. If you pass a property with
  // a value of undefined, null or an empty string, that parameter is removed from the
  // URL if already present (note that the number 0 does not do this). This is very
  // useful for maintaining filter parameters in a query string without redundant code.
  //
  // PRETTY URLS
  //
  // If the optional `path` argument is present, it must be an array. (You
  // may skip this argument if you are just adding query parameters.) Any
  // properties of `data` whose names appear in `path` are concatenated
  // to the URL directly, separated by slashes, in the order they appear in that
  // array. The first missing or empty value for a property in `path` stops
  // this process to prevent an ambiguous URL.
  //
  // Note that there is no automatic detection that this has
  // already happened in an existing URL, so you can't override existing
  // components of the path. Typically this is used with a snippet index page,
  // on which the URL of the page is available as a starting point for
  // building the next URL.
  //
  // If a property's value is not equal to the slugification of itself
  // (apos.slugify), then a query parameter is set instead. This ensures your
  // URLs are not rejected by the browser. If you don't want to handle a
  // property as a query parameter, make sure it is always slug-safe.
  //
  // OVERRIDES: MULTIPLE DATA OBJECTS
  //
  // You may pass additional data objects. The last one wins, so you can
  // pass your existing parameters first and pass new parameters you are changing
  // as a second data object.

  self.build = function(url, path, data) {
    var hash;
    // Preserve hash separately
    var matches = url.match(/^(.*)?\#(.*)$/);
    if (matches) {
      url = matches[1];
      hash = matches[2];
    }
    // Sometimes necessary with nunjucks, we may otherwise be
    // exposed to a SafeString object and throw an exception. Not
    // able to boil this down to a simple test case for jlongster so far
    url = url.toString();
    var qat = url.indexOf('?');
    var base = url;
    var dataObjects = [];
    var pathKeys;
    var original;
    var query = {};
    var i, j;
    var key;

    if (qat !== -1) {
      original = qs.parse(url.substr(qat + 1));
      base = url.substr(0, qat);
    }
    var dataStart = 1;
    if (path && Array.isArray(path)) {
      pathKeys = path;
      dataStart = 2;
    } else {
      pathKeys = [];
    }
    // Process data objects in reverse order so the last override wins
    for (i = arguments.length - 1; (i >= dataStart); i--) {
      dataObjects.push(arguments[i]);
    }
    if (original) {
      dataObjects.push(original);
    }
    var done = {};
    var stop = false;
    var dataObject;
    var value;

    for (i = 0; (i < pathKeys.length); i++) {
      if (stop) {
        break;
      }
      key = pathKeys[i];
      for (j = 0; (j < dataObjects.length); j++) {
        dataObject = dataObjects[j];
        if (dataObject[key] !== undefined) {
          value = dataObject[key];
          // If we hit an empty value we need to stop all path processing to avoid
          // ambiguous URLs
          if ((value === undefined) || (value === null) || (value === '')) {
            done[key] = true;
            stop = true;
            break;
          }
          // If the value is an object it can't be stored in the path,
          // so stop path processing, but don't mark this key 'done'
          // because we can still store it as a query parameter
          if (typeof(value) === 'object') {
            stop = true;
            break;
          }
          var s = dataObject[key].toString();
          if (s === self.slugify(s)) {
            // Don't append double /
            if (base !== '/') {
              base += '/' + s;
            } else {
              base += s;
            }
            done[key] = true;
            break;
          } else {
            // A value that cannot be slugified also forces an end to
            // path processing
            stop = true;
            break;
          }
        }
      }
    }

    for (i = 0; (i < dataObjects.length); i++) {
      dataObject = dataObjects[i];
      for (key in dataObject) {
        value = dataObject[key];
        if (done[key]) {
          continue;
        }
        done[key] = true;
        if ((value === undefined) || (value === null) || (value === '')) {
          delete query[key];
        } else {
          query[key] = value;
        }
      }
    }

    function restoreHash(url) {
      if (hash !== undefined) {
        return url + '#' + hash;
      } else {
        return url;
      }
    }

    if (_.size(query)) {
      return restoreHash(base + '?' + qs.stringify(query));
    } else {
      return restoreHash(base);
    }
  };
};

<commit_msg>javascript hates me so it sets the first matched clause to undefined even though the regex does match<commit_after>var _ = require('lodash');
var qs = require('qs');

 /**
 * build
 * @augments Adds a method providing a convenient means of building URLs with
 * query string parameters as well as attractive URLs with path components
 */

module.exports = function(self) {

  // Add and modify query parameters of a url. data is an object whose properties
  // become new query parameters. These parameters override any existing
  // parameters of the same name in the URL. If you pass a property with
  // a value of undefined, null or an empty string, that parameter is removed from the
  // URL if already present (note that the number 0 does not do this). This is very
  // useful for maintaining filter parameters in a query string without redundant code.
  //
  // PRETTY URLS
  //
  // If the optional `path` argument is present, it must be an array. (You
  // may skip this argument if you are just adding query parameters.) Any
  // properties of `data` whose names appear in `path` are concatenated
  // to the URL directly, separated by slashes, in the order they appear in that
  // array. The first missing or empty value for a property in `path` stops
  // this process to prevent an ambiguous URL.
  //
  // Note that there is no automatic detection that this has
  // already happened in an existing URL, so you can't override existing
  // components of the path. Typically this is used with a snippet index page,
  // on which the URL of the page is available as a starting point for
  // building the next URL.
  //
  // If a property's value is not equal to the slugification of itself
  // (apos.slugify), then a query parameter is set instead. This ensures your
  // URLs are not rejected by the browser. If you don't want to handle a
  // property as a query parameter, make sure it is always slug-safe.
  //
  // OVERRIDES: MULTIPLE DATA OBJECTS
  //
  // You may pass additional data objects. The last one wins, so you can
  // pass your existing parameters first and pass new parameters you are changing
  // as a second data object.

  self.build = function(url, path, data) {
    var hash;
    // Preserve hash separately
    var matches = url.match(/^(.*)?\#(.*)$/);
    if (matches) {
      url = matches[1];
      hash = matches[2];
      if (url === undefined) {
        // Why, JavaScript? Why? -Tom
        url = '';
      }
    }
    // Sometimes necessary with nunjucks, we may otherwise be
    // exposed to a SafeString object and throw an exception. Not
    // able to boil this down to a simple test case for jlongster so far
    url = url.toString();
    var qat = url.indexOf('?');
    var base = url;
    var dataObjects = [];
    var pathKeys;
    var original;
    var query = {};
    var i, j;
    var key;

    if (qat !== -1) {
      original = qs.parse(url.substr(qat + 1));
      base = url.substr(0, qat);
    }
    var dataStart = 1;
    if (path && Array.isArray(path)) {
      pathKeys = path;
      dataStart = 2;
    } else {
      pathKeys = [];
    }
    // Process data objects in reverse order so the last override wins
    for (i = arguments.length - 1; (i >= dataStart); i--) {
      dataObjects.push(arguments[i]);
    }
    if (original) {
      dataObjects.push(original);
    }
    var done = {};
    var stop = false;
    var dataObject;
    var value;

    for (i = 0; (i < pathKeys.length); i++) {
      if (stop) {
        break;
      }
      key = pathKeys[i];
      for (j = 0; (j < dataObjects.length); j++) {
        dataObject = dataObjects[j];
        if (dataObject[key] !== undefined) {
          value = dataObject[key];
          // If we hit an empty value we need to stop all path processing to avoid
          // ambiguous URLs
          if ((value === undefined) || (value === null) || (value === '')) {
            done[key] = true;
            stop = true;
            break;
          }
          // If the value is an object it can't be stored in the path,
          // so stop path processing, but don't mark this key 'done'
          // because we can still store it as a query parameter
          if (typeof(value) === 'object') {
            stop = true;
            break;
          }
          var s = dataObject[key].toString();
          if (s === self.slugify(s)) {
            // Don't append double /
            if (base !== '/') {
              base += '/' + s;
            } else {
              base += s;
            }
            done[key] = true;
            break;
          } else {
            // A value that cannot be slugified also forces an end to
            // path processing
            stop = true;
            break;
          }
        }
      }
    }

    for (i = 0; (i < dataObjects.length); i++) {
      dataObject = dataObjects[i];
      for (key in dataObject) {
        value = dataObject[key];
        if (done[key]) {
          continue;
        }
        done[key] = true;
        if ((value === undefined) || (value === null) || (value === '')) {
          delete query[key];
        } else {
          query[key] = value;
        }
      }
    }

    function restoreHash(url) {
      if (hash !== undefined) {
        return url + '#' + hash;
      } else {
        return url;
      }
    }

    if (_.size(query)) {
      return restoreHash(base + '?' + qs.stringify(query));
    } else {
      return restoreHash(base);
    }
  };
};

<|endoftext|>


<commit_before>"use strict";

function Cache(options) {
  this.map = new Map;
  this.newest = null;
  this.oldest = null;
  this.max = options && options.max;
  this.dispose = options && options.dispose;
}

exports.Cache = Cache;

var Cp = Cache.prototype;

Cp.has = function (key) {
  return this.map.has(key);
};

Cp.get = function (key) {
  var entry = getEntry(this, key);
  return entry && entry.value;
};

function getEntry(cache, key) {
  var entry = cache.map.get(key);
  if (entry &&
      entry !== cache.newest) {
    var older = entry.older;
    var newer = entry.newer;

    if (newer) {
      newer.older = older;
    }

    if (older) {
      older.newer = newer;
    }

    entry.older = cache.newest;
    entry.older.newer = entry;

    entry.newer = null;
    cache.newest = entry;

    if (entry === cache.oldest) {
      cache.oldest = newer;
    }
  }

  return entry;
}

Cp.set = function (key, value) {
  var entry = getEntry(this, key);
  if (entry) {
    return entry.value = value;
  }

  entry = {
    key: key,
    value: value,
    newer: null,
    older: this.newest
  };

  if (this.newest) {
    this.newest.newer = entry;
  }

  this.newest = entry;
  this.oldest = this.oldest || entry;

  this.map.set(key, entry);

  if (typeof this.max === "number") {
    while (this.oldest &&
           this.map.size > this.max) {
      this.delete(this.oldest.key);
    }
  }
};

Cp.delete = function (key) {
  var entry = this.map.get(key);
  if (entry) {
    if (entry === this.newest) {
      this.newest = entry.older;
    }

    if (entry === this.oldest) {
      this.oldest = entry.newer;
    }

    if (entry.newer) {
      entry.newer.older = entry.older;
    }

    if (entry.older) {
      entry.older.newer = entry.newer;
    }

    this.map.delete(key);

    if (typeof this.dispose === "function") {
      this.dispose(key, entry.value);
    }

    return true;
  }

  return false;
};
<commit_msg>Make sure Cache#set always returns entry.value.<commit_after>"use strict";

function Cache(options) {
  this.map = new Map;
  this.newest = null;
  this.oldest = null;
  this.max = options && options.max;
  this.dispose = options && options.dispose;
}

exports.Cache = Cache;

var Cp = Cache.prototype;

Cp.has = function (key) {
  return this.map.has(key);
};

Cp.get = function (key) {
  var entry = getEntry(this, key);
  return entry && entry.value;
};

function getEntry(cache, key) {
  var entry = cache.map.get(key);
  if (entry &&
      entry !== cache.newest) {
    var older = entry.older;
    var newer = entry.newer;

    if (newer) {
      newer.older = older;
    }

    if (older) {
      older.newer = newer;
    }

    entry.older = cache.newest;
    entry.older.newer = entry;

    entry.newer = null;
    cache.newest = entry;

    if (entry === cache.oldest) {
      cache.oldest = newer;
    }
  }

  return entry;
}

Cp.set = function (key, value) {
  var entry = getEntry(this, key);
  if (entry) {
    return entry.value = value;
  }

  entry = {
    key: key,
    value: value,
    newer: null,
    older: this.newest
  };

  if (this.newest) {
    this.newest.newer = entry;
  }

  this.newest = entry;
  this.oldest = this.oldest || entry;

  this.map.set(key, entry);

  if (typeof this.max === "number") {
    while (this.oldest &&
           this.map.size > this.max) {
      this.delete(this.oldest.key);
    }
  }

  return entry.value;
};

Cp.delete = function (key) {
  var entry = this.map.get(key);
  if (entry) {
    if (entry === this.newest) {
      this.newest = entry.older;
    }

    if (entry === this.oldest) {
      this.oldest = entry.newer;
    }

    if (entry.newer) {
      entry.newer.older = entry.older;
    }

    if (entry.older) {
      entry.older.newer = entry.newer;
    }

    this.map.delete(key);

    if (typeof this.dispose === "function") {
      this.dispose(key, entry.value);
    }

    return true;
  }

  return false;
};
<|endoftext|>