<commit_before>"use strict";

var http = require('http');
var fs = require('fs');

function HttpAPI(discovery, port, presets) {

  var lockVolumes = {};
  var pauseState = {};

  // This is to handle setTimeout
  function pauseAll() {
    pauseState = {};
    discovery.getZones().forEach(function (zone) {
      pauseState[zone.uuid] = zone.coordinator.state.currentState;
      if (pauseState[zone.uuid] == "PLAYING") {
        var player = discovery.getPlayerByUUID(zone.uuid);
        player.pause();
      }
    });
  }

  function resumeAll() {
        // save state for resume
    for (var uuid in pauseState) {
      if (pauseState[uuid] == "PLAYING") {
        var player = discovery.getPlayerByUUID(uuid);
        player.play();
      }
    }

    // Clear the pauseState to prevent a second resume to raise hell
    pauseState = {};
  }

  var server = http.createServer(function (req, res) {
    res.writeHead(200, {
      'Content-Type': 'text/plain;charset=utf8',
      'Cache-Control': 'no-cache'
    });

    var params = req.url.substring(1).split('/');

    if (params.length < 1 || params[0] == "favicon.ico") {
      // This is faulty.
      res.end();
      return;
    } else if (params.length == 2 && ["preset", "pauseall", "resumeall"].some(function (i) { return params[0] == i; })) {
      // Handle presets
      var opt = {
        action: params[0],
        value: params[1]
      };
    } else if (params.length > 1) {

      var opt = {
        room: params[0],
        action: params[1],
        value: params[2]
      };

    } else {
      // guessing zones
      var opt = {
        action: params[0]
      }
    }

    var response = handleAction(opt, function (response) {
      if (response) {
        var jsonResponse = JSON.stringify(response);
        res.write(new Buffer(jsonResponse));
      }
      res.end();
    });
  });

  function restrictVolume(info) {
    console.log("should revert volume to", lockVolumes[info.uuid]);
    var player = discovery.getPlayerByUUID(info.uuid);
    // Only do this if volume differs
    if (player.state.volume != lockVolumes[info.uuid])
      player.setVolume(lockVolumes[info.uuid]);
  }

  function handleAction(options, callback) {
    console.log(options)

    if (options.action === "zones") {
      callback(discovery.getZones());
      return;
    }

    if (options.action == "preset") {
      // Apply preset
      var value = decodeURIComponent(options.value);
      if (value.startsWith('{'))
        var preset = JSON.parse(value);
      else
        var preset = presets[value];

      console.log("applying preset", preset)

      if (preset)
        discovery.applyPreset(preset);

      callback();
      return;
    }

    if (options.action == "lockvolumes") {
        console.log("locking volumes");
        // Locate all volumes
        for (var i in discovery.players) {
          var player = discovery.players[i];
          lockVolumes[i] = player.state.volume;
        }
        // prevent duplicates, will ignore if no event listener is here
        discovery.removeListener("volume", restrictVolume);
        discovery.on("volume", restrictVolume);
        callback();
        return;
    }

     if (options.action == "unlockvolumes") {
        console.log("unlocking volumes");
        discovery.removeListener("volume", restrictVolume);
        callback();
        return;
    }

    if (options.action == "pauseall") {

      console.log("pausing all players");
      // save state for resume

      if (options.value && options.value > 0) {
        console.log("in", options.value, "minutes");
        setTimeout(function () { pauseAll(); }, options.value*1000*60);
      } else {
        pauseAll();
      }

      callback();

    }

    if (options.action == "resumeall") {
        console.log("resuming all players");

        if (options.value && options.value > 0) {
        console.log("in", options.value, "minutes");
        setTimeout(function () { resumeAll(); }, options.value*1000*60);
      } else {
        resumeAll();
      }

      callback();
      return;
    }

    var roomName = decodeURIComponent(options.room);
    var player = discovery.getPlayer(roomName);
    if (!player) {
      callback();
      return;
    }

    switch (options.action.toLowerCase()) {
      case "play":
        player.coordinator.play();
        break;
      case "pause":
        player.coordinator.pause();
        break;
      case "playpause":
        if(player.coordinator.state['currentState'] == "PLAYING") {
          player.coordinator.pause();
        } else {
          player.coordinator.play();
        }
        break;
      case "volume":
        player.setVolume(options.value);
        break;
      case "groupvolume":
        player.coordinator.groupSetVolume(options.value);
        break;
      case "mute":
        player.mute(true);
        break;
      case "unmute":
        player.mute(false);
        break;
      case "groupmute":
        player.coordinator.groupMute(true);
        break;
      case "groupunmute":
        player.coordinator.groupMute(false);
        break;
      case "state":
        var state = player.getState();
        callback(state);
        return;
        break;
      case "seek":
        player.coordinator.seek(options.value);
        break;
      case "trackseek":
        player.coordinator.trackSeek(options.value*1);
        break;
      case "next":
        player.coordinator.nextTrack();
        break;
      case "previous":
        player.coordinator.previousTrack();
        break;
      case "setavtransport":
        player.setAVTransportURI(options.value);
        break;
      case "favorite":
        player.coordinator.replaceWithFavorite(options.value, function (success) {
          if (success)
            player.coordinator.play();
        });
        break;
      case "repeat":
        player.coordinator.repeat(options.value == "on" ? true : false);
        break;
      case "shuffle":
        player.coordinator.shuffle(options.value == "on" ? true : false);
        break;
      case "favorites":
        player.getFavorites(function (success, favorites) {
          // only present relevant data
          var simpleFavorites = [];
          console.log(favorites)
          favorites.forEach(function (i) {
            simpleFavorites.push(i.title);
          });
          callback(simpleFavorites);
        });
        return;
    }

    callback();

  }

  server.listen(port);

  console.log("http server listening on port", port);
}

module.exports = HttpAPI;<commit_msg>Fixed Content-Type response header.<commit_after>"use strict";

var http = require('http');
var fs = require('fs');

function HttpAPI(discovery, port, presets) {

  var lockVolumes = {};
  var pauseState = {};

  // This is to handle setTimeout
  function pauseAll() {
    pauseState = {};
    discovery.getZones().forEach(function (zone) {
      pauseState[zone.uuid] = zone.coordinator.state.currentState;
      if (pauseState[zone.uuid] == "PLAYING") {
        var player = discovery.getPlayerByUUID(zone.uuid);
        player.pause();
      }
    });
  }

  function resumeAll() {
        // save state for resume
    for (var uuid in pauseState) {
      if (pauseState[uuid] == "PLAYING") {
        var player = discovery.getPlayerByUUID(uuid);
        player.play();
      }
    }

    // Clear the pauseState to prevent a second resume to raise hell
    pauseState = {};
  }

  var server = http.createServer(function (req, res) {
    res.writeHead(200, {
      'Content-Type': 'application/json;charset=utf8',
      'Cache-Control': 'no-cache'
    });

    var params = req.url.substring(1).split('/');

    if (params.length < 1 || params[0] == "favicon.ico") {
      // This is faulty.
      res.end();
      return;
    } else if (params.length == 2 && ["preset", "pauseall", "resumeall"].some(function (i) { return params[0] == i; })) {
      // Handle presets
      var opt = {
        action: params[0],
        value: params[1]
      };
    } else if (params.length > 1) {

      var opt = {
        room: params[0],
        action: params[1],
        value: params[2]
      };

    } else {
      // guessing zones
      var opt = {
        action: params[0]
      }
    }

    var response = handleAction(opt, function (response) {
      if (response) {
        var jsonResponse = JSON.stringify(response);
        res.write(new Buffer(jsonResponse));
      }
      res.end();
    });
  });

  function restrictVolume(info) {
    console.log("should revert volume to", lockVolumes[info.uuid]);
    var player = discovery.getPlayerByUUID(info.uuid);
    // Only do this if volume differs
    if (player.state.volume != lockVolumes[info.uuid])
      player.setVolume(lockVolumes[info.uuid]);
  }

  function handleAction(options, callback) {
    console.log(options)

    if (options.action === "zones") {
      callback(discovery.getZones());
      return;
    }

    if (options.action == "preset") {
      // Apply preset
      var value = decodeURIComponent(options.value);
      if (value.startsWith('{'))
        var preset = JSON.parse(value);
      else
        var preset = presets[value];

      console.log("applying preset", preset)

      if (preset)
        discovery.applyPreset(preset);

      callback();
      return;
    }

    if (options.action == "lockvolumes") {
        console.log("locking volumes");
        // Locate all volumes
        for (var i in discovery.players) {
          var player = discovery.players[i];
          lockVolumes[i] = player.state.volume;
        }
        // prevent duplicates, will ignore if no event listener is here
        discovery.removeListener("volume", restrictVolume);
        discovery.on("volume", restrictVolume);
        callback();
        return;
    }

     if (options.action == "unlockvolumes") {
        console.log("unlocking volumes");
        discovery.removeListener("volume", restrictVolume);
        callback();
        return;
    }

    if (options.action == "pauseall") {

      console.log("pausing all players");
      // save state for resume

      if (options.value && options.value > 0) {
        console.log("in", options.value, "minutes");
        setTimeout(function () { pauseAll(); }, options.value*1000*60);
      } else {
        pauseAll();
      }

      callback();

    }

    if (options.action == "resumeall") {
        console.log("resuming all players");

        if (options.value && options.value > 0) {
        console.log("in", options.value, "minutes");
        setTimeout(function () { resumeAll(); }, options.value*1000*60);
      } else {
        resumeAll();
      }

      callback();
      return;
    }

    var roomName = decodeURIComponent(options.room);
    var player = discovery.getPlayer(roomName);
    if (!player) {
      callback();
      return;
    }

    switch (options.action.toLowerCase()) {
      case "play":
        player.coordinator.play();
        break;
      case "pause":
        player.coordinator.pause();
        break;
      case "playpause":
        if(player.coordinator.state['currentState'] == "PLAYING") {
          player.coordinator.pause();
        } else {
          player.coordinator.play();
        }
        break;
      case "volume":
        player.setVolume(options.value);
        break;
      case "groupvolume":
        player.coordinator.groupSetVolume(options.value);
        break;
      case "mute":
        player.mute(true);
        break;
      case "unmute":
        player.mute(false);
        break;
      case "groupmute":
        player.coordinator.groupMute(true);
        break;
      case "groupunmute":
        player.coordinator.groupMute(false);
        break;
      case "state":
        var state = player.getState();
        callback(state);
        return;
        break;
      case "seek":
        player.coordinator.seek(options.value);
        break;
      case "trackseek":
        player.coordinator.trackSeek(options.value*1);
        break;
      case "next":
        player.coordinator.nextTrack();
        break;
      case "previous":
        player.coordinator.previousTrack();
        break;
      case "setavtransport":
        player.setAVTransportURI(options.value);
        break;
      case "favorite":
        player.coordinator.replaceWithFavorite(options.value, function (success) {
          if (success)
            player.coordinator.play();
        });
        break;
      case "repeat":
        player.coordinator.repeat(options.value == "on" ? true : false);
        break;
      case "shuffle":
        player.coordinator.shuffle(options.value == "on" ? true : false);
        break;
      case "favorites":
        player.getFavorites(function (success, favorites) {
          // only present relevant data
          var simpleFavorites = [];
          console.log(favorites)
          favorites.forEach(function (i) {
            simpleFavorites.push(i.title);
          });
          callback(simpleFavorites);
        });
        return;
    }

    callback();

  }

  server.listen(port);

  console.log("http server listening on port", port);
}

module.exports = HttpAPI;<|endoftext|>


<commit_before>// The text to identify rules for each possible step that can be taken

module.exports = {
  NO_CHANGE: 'NO_CHANGE',

  COLLECT_LIKE_TERMS: 'COLLECT_LIKE_TERMS',
  COLLECT_AND_COMBINE_LIKE_TERMS: 'COLLECT_AND_COMBINE_LIKE_TERMS',

  UNARY_MINUS_TO_NEGATIVE_ONE: 'UNARY_MINUS_TO_NEGATIVE_ONE',
  ADD_COEFFICIENT_OF_ONE: 'ADD_COEFFICIENT_OF_ONE',
  ADD_EXPONENT_OF_ONE: 'ADD_EXPONENT_OF_ONE',
  GROUP_COEFFICIENTS: 'GROUP_COEFFICIENTS',
  COLLECT_EXPONENTS: 'COLLECT_EXPONENTS',
  MULTIPLY_COEFFICIENTS: 'MULTIPLY_COEFFICIENTS',
  ADD_POLYNOMIAL_TERMS: 'ADD_POLYNOMIAL_TERMS',
  MULTIPLY_POLYNOMIAL_TERMS: 'MULTIPLY_POLYNOMIAL_TERMS',
  REARRANGE_COEFF: 'REARRANGE_COEFF',
  REDUCE_EXPONENT_BY_ZERO: 'REDUCE_EXPONENT_BY_ZERO',
  REMOVE_EXPONENT_BY_ONE: 'REMOVE_EXPONENT_BY_ONE',

  SIMPLIFY_ARITHMETIC: 'SIMPLIFY_ARITHMETIC',
  ABSOLUTE_VALUE: 'ABSOLUTE_VALUE',
  SIMPLIFY_DIVISION: 'SIMPLIFY_DIVISION',
  RESOLVE_DOUBLE_MINUS: 'RESOLVE_DOUBLE_MINUS',
  DIVISION_BY_NEGATIVE_ONE: 'DIVISION_BY_NEGATIVE_ONE',
  DIVISION_BY_ONE: 'DIVISION_BY_ONE',
  MULTIPLY_BY_ZERO: 'MULTIPLY_BY_ZERO',
  REMOVE_ADDING_ZERO: 'REMOVE_ADDING_ZERO',
  REMOVE_MULTIPLYING_BY_ONE: 'REMOVE_MULTIPLYING_BY_ONE',
  REMOVE_MULTIPLYING_BY_NEGATIVE_ONE: 'REMOVE_MULTIPLYING_BY_NEGATIVE_ONE',

  DISTRIBUTE_NEGATIVE_ONE: 'DISTRIBUTE_NEGATIVE_ONE',
  DISTRIBUTE: 'DISTRIBUTE',
  SIMPLIFY_TERMS: 'SIMPLIFY_TERMS',

  COMMON_DENOMINATOR: 'COMMON_DENOMINATOR',
  SIMPLIFY_FRACTION: 'SIMPLIFY_FRACTION',
  MULTIPLY_DENOMINATORS: 'MULTIPLY_DENOMINATORS',
  MULTIPLY_NUMERATORS: 'MULTIPLY_NUMERATORS',
  ADD_NUMERATORS: 'ADD_NUMERATORS',
  REDUCE_ZERO_NUMERATOR: 'REDUCE_ZERO_NUMERATOR',
  DIVIDE_FRACTION_FOR_ADDITION: 'DIVIDE_FRACTION_FOR_ADDITION',
  SIMPLIFY_SIGNS: 'SIMPLIFY_SIGNS',
  COMBINE_NUMERATORS: 'COMBINE_NUMERATORS',
  ADD_FRACTIONS: 'ADD_FRACTIONS',
  MULTIPLY_FRACTIONS: 'MULTIPLY_FRACTIONS',
  CONVERT_INTEGER_TO_FRACTION: 'CONVERT_INTEGER_TO_FRACTION',
  MULTIPLY_BY_INVERSE: 'MULTIPLY_BY_INVERSE',
  BREAK_UP_FRACTION: 'BREAK_UP_FRACTION',

  FACTOR_INTO_PRIMES: 'FACTOR_INTO_PRIMES',
  GROUP_TERMS_BY_ROOT: 'GROUP_TERMS_BY_ROOT',
  CONVERT_MULTIPLICATION_TO_EXPONENT: 'CONVERT_MULTIPLICATION_TO_EXPONENT',
  CANCEL_EXPONENT_AND_ROOT: 'CANCEL_EXPONENT_AND_ROOT',
  CANCEL_EXPONENT: 'CANCEL_EXPONENT',
  CANCEL_ROOT: 'CANCEL_ROOT',
  COMBINE_UNDER_ROOT: 'COMBINE_UNDER_ROOT',
  NTH_ROOT_VALUE: 'NTH_ROOT_VALUE',
  DISTRIBUTE_NTH_ROOT: 'DISTRIBUTE_NTH_ROOT',
  EVALUATE_DISTRIBUTED_NTH_ROOT: 'EVALUATE_DISTRIBUTED_NTH_ROOT',
  CANCEL_TERMS: 'CANCEL_TERMS',

  SWAP_SIDES: 'SWAP_SIDES',
  ADD_TO_BOTH_SIDES: 'ADD_TO_BOTH_SIDES',
  SIMPLIFY_LEFT_SIDE: 'SIMPLIFY_LEFT_SIDE',
  SIMPLIFY_RIGHT_SIDE: 'SIMPLIFY_RIGHT_SIDE',
  SUBTRACT_FROM_BOTH_SIDES: 'SUBTRACT_FROM_BOTH_SIDES',
  MULTIPLY_TO_BOTH_SIDES: 'MULTIPLY_TO_BOTH_SIDES',
  DIVIDE_FROM_BOTH_SIDES: 'DIVIDE_FROM_BOTH_SIDES',
  MULTIPLY_BOTH_SIDES_BY_INVERSE_FRACTION: 'MULTIPLY_BOTH_SIDES_BY_INVERSE_FRACTION',
  MULTIPLY_BOTH_SIDES_BY_NEGATIVE_ONE: 'MULTIPLY_BOTH_SIDES_BY_NEGATIVE_ONE',

  STATEMENT_IS_TRUE: 'STATEMENT_IS_TRUE',
  STATEMENT_IS_FALSE: 'STATEMENT_IS_FALSE',
};
<commit_msg>adding an example for each change type<commit_after>// The text to identify rules for each possible step that can be taken

module.exports = {
  NO_CHANGE: 'NO_CHANGE',

  COLLECT_LIKE_TERMS: 'COLLECT_LIKE_TERMS', // e.g. x + 2 + x^2 + x + 4 -> x^2 + (x + x) + (4 + 2)
  COLLECT_AND_COMBINE_LIKE_TERMS: 'COLLECT_AND_COMBINE_LIKE_TERMS', // e.g. 2 + x + 3 + x -> 5 + 2x

  UNARY_MINUS_TO_NEGATIVE_ONE: 'UNARY_MINUS_TO_NEGATIVE_ONE',
  ADD_COEFFICIENT_OF_ONE: 'ADD_COEFFICIENT_OF_ONE', // e.g. 2x + x -> 2x + 1x
  ADD_EXPONENT_OF_ONE: 'ADD_EXPONENT_OF_ONE', // e.g. x^2 * x -> x^2 * x^1
  GROUP_COEFFICIENTS: 'GROUP_COEFFICIENTS', // e.g. 2x^2 + 3x^2 + 5x^2 -> (2+3+5)x^2
  COLLECT_EXPONENTS: 'COLLECT_EXPONENTS', // e.g. x^2 * x^3 * x^1 -> x^(2 + 3 + 1)
  MULTIPLY_COEFFICIENTS: 'MULTIPLY_COEFFICIENTS', // e.g. 2x * 3x -> (2 * 3)(x * x)
  ADD_POLYNOMIAL_TERMS: 'ADD_POLYNOMIAL_TERMS', // e.g. x^2 + x^2 -> 2x^2
  MULTIPLY_POLYNOMIAL_TERMS: 'MULTIPLY_POLYNOMIAL_TERMS', // e.g. 2x * x -> 2x ^ 2
  REARRANGE_COEFF: 'REARRANGE_COEFF', // e.g. x * 2 -> 2x
  REDUCE_EXPONENT_BY_ZERO: 'REDUCE_EXPONENT_BY_ZERO', // e.g. x ^ 0 -> 1
  REMOVE_EXPONENT_BY_ONE: 'REMOVE_EXPONENT_BY_ONE', // e.g. x ^ 1 -> x

  SIMPLIFY_ARITHMETIC: 'SIMPLIFY_ARITHMETIC', // e.g. 2 + 2 -> 4 or 2 * 2 -> 4
  ABSOLUTE_VALUE: 'ABSOLUTE_VALUE', // e.g. |-3| -> 3
  SIMPLIFY_DIVISION: 'SIMPLIFY_DIVISION', // e.g. 2/3/4 -> 2/(3*4)
  RESOLVE_DOUBLE_MINUS: 'RESOLVE_DOUBLE_MINUS', // e.g. 2 - - 3 -> 2 + 3
  DIVISION_BY_NEGATIVE_ONE: 'DIVISION_BY_NEGATIVE_ONE', // e.g. 2/-1 -> -2
  DIVISION_BY_ONE: 'DIVISION_BY_ONE', // e.g. 2/1 -> 2
  MULTIPLY_BY_ZERO: 'MULTIPLY_BY_ZERO', // e.g. x * 0 -> 0
  REMOVE_ADDING_ZERO: 'REMOVE_ADDING_ZERO', // e.g. 2 + 0 -> 2
  REMOVE_MULTIPLYING_BY_ONE: 'REMOVE_MULTIPLYING_BY_ONE', // e.g. x * 1 -> x
  REMOVE_MULTIPLYING_BY_NEGATIVE_ONE: 'REMOVE_MULTIPLYING_BY_NEGATIVE_ONE', // e.g. x * -1 -> -x

  DISTRIBUTE_NEGATIVE_ONE: 'DISTRIBUTE_NEGATIVE_ONE', // e.g. -(2 + x) -> -2 - x
  DISTRIBUTE: 'DISTRIBUTE', // e.g. 2(x + y) -> 2x + 2y
  SIMPLIFY_TERMS: 'SIMPLIFY_TERMS', // e.g. 2 * (4x + 5) + 3 * (4x + 5) -> 8x + 10 + 12x + 15

  COMMON_DENOMINATOR: 'COMMON_DENOMINATOR', // e.g. (2*2)/(6*2) + (1*3)/(4*3)
  SIMPLIFY_FRACTION: 'SIMPLIFY_FRACTION', // e.g. 2/6 -> 1/3
  MULTIPLY_DENOMINATORS: 'MULTIPLY_DENOMINATORS', // e.g. (2*2)/12 + (1*3)/12
  MULTIPLY_NUMERATORS: 'MULTIPLY_NUMERATORS', // e.g. (2*2)/12 + (1*3)/12 -> 4/12 + 3/12
  ADD_NUMERATORS: 'ADD_NUMERATORS', // e.g. (1 + 2)/3 -> 3/3
  REDUCE_ZERO_NUMERATOR: 'REDUCE_ZERO_NUMERATOR', // e.g. 0/1 -> 0
  DIVIDE_FRACTION_FOR_ADDITION: 'DIVIDE_FRACTION_FOR_ADDITION', // 1.2 + 1/2 -> 1.2 + 0.5
  SIMPLIFY_SIGNS: 'SIMPLIFY_SIGNS', // e.g. 2/-3 -> -2/3
  COMBINE_NUMERATORS: 'COMBINE_NUMERATORS', // e.g. (2+1)/5
  ADD_FRACTIONS: 'ADD_FRACTIONS', // e.g. 1/2 + 1/3 -> 5/6
  MULTIPLY_FRACTIONS: 'MULTIPLY_FRACTIONS', // e.g. 1/2 * 2/3 -> 2/6
  CONVERT_INTEGER_TO_FRACTION: 'CONVERT_INTEGER_TO_FRACTION', // e.g. 3 + 1/2 -> 6/2 + 1/2
  MULTIPLY_BY_INVERSE: 'MULTIPLY_BY_INVERSE', // e.g. x/(2/3) -> x * 3/2
  BREAK_UP_FRACTION: 'BREAK_UP_FRACTION', // e.g. (x + 2)/2 -> x/2 + 2/2

  FACTOR_INTO_PRIMES: 'FACTOR_INTO_PRIMES', // e.g. 12 -> 2 * 2 * 3
  GROUP_TERMS_BY_ROOT: 'GROUP_TERMS_BY_ROOT', // e.g. nthRoot(2 * 2 * 2, 2) -> nthRoot((2 * 2) * 2)
  CONVERT_MULTIPLICATION_TO_EXPONENT: 'CONVERT_MULTIPLICATION_TO_EXPONENT', // e.g. 2 * 2 * 2 -> 2 ^ 3
  CANCEL_EXPONENT_AND_ROOT: 'CANCEL_EXPONENT_AND_ROOT', // e.g. nthRoot(x ^ 2, 2) -> x
  CANCEL_EXPONENT: 'CANCEL_EXPONENT', // e.g. nthRoot(x ^ 2, 4) -> nthRoot(x, 2)
  CANCEL_ROOT: 'CANCEL_ROOT', // e.g. nthRoot(x ^ 4, 2) -> x ^ 2
  COMBINE_UNDER_ROOT: 'COMBINE_UNDER_ROOT', // e.g. nthRoot(2, 2) * nthRoot(3, 2) -> nthRoot(2 * 3, 2)
  NTH_ROOT_VALUE: 'NTH_ROOT_VALUE', // e.g. nthRoot(4) -> 2
  DISTRIBUTE_NTH_ROOT: 'DISTRIBUTE_NTH_ROOT', // e.g. nthRoot(2 * x) -> nthRoot(2) * nthRoot(x)
  EVALUATE_DISTRIBUTED_NTH_ROOT: 'EVALUATE_DISTRIBUTED_NTH_ROOT', // e.g. nthRoot(4) * nthRoot(x^2) -> 2 * x
  CANCEL_TERMS: 'CANCEL_TERMS', // e.g. 2x/2 -> x

  SWAP_SIDES: 'SWAP_SIDES', // e.g. 2 = x -> x = 2
  ADD_TO_BOTH_SIDES: 'ADD_TO_BOTH_SIDES', // e.g. x - 3 = 2 -> x - 3 + 3 = 2 + 3
  SIMPLIFY_LEFT_SIDE: 'SIMPLIFY_LEFT_SIDE', // e.g. x + 2 - 1 = 3 -> x + 1 = 3
  SIMPLIFY_RIGHT_SIDE: 'SIMPLIFY_RIGHT_SIDE', // e.g. x = 3 - 1 -> x = 2
  SUBTRACT_FROM_BOTH_SIDES: 'SUBTRACT_FROM_BOTH_SIDES', // e.g. x + 3 = 2 -> x + 3 - 3 = 2 - 3
  MULTIPLY_TO_BOTH_SIDES: 'MULTIPLY_TO_BOTH_SIDES', // e.g. x/2 = 1 -> (x/2) * 2 = 1 * 2
  DIVIDE_FROM_BOTH_SIDES: 'DIVIDE_FROM_BOTH_SIDES', // e.g. 2x = 1 -> (2x)/2 = 1/2
  MULTIPLY_BOTH_SIDES_BY_INVERSE_FRACTION: 'MULTIPLY_BOTH_SIDES_BY_INVERSE_FRACTION', // e.g. (2/3)x = 1 -> (2/3)x * (3/2) = 1 * (3/2)
  MULTIPLY_BOTH_SIDES_BY_NEGATIVE_ONE: 'MULTIPLY_BOTH_SIDES_BY_NEGATIVE_ONE', // e.g. -x = 2 -> -1 * -x = -1 * 2

  STATEMENT_IS_TRUE: 'STATEMENT_IS_TRUE', // e.g. 2 = 2
  STATEMENT_IS_FALSE: 'STATEMENT_IS_FALSE', // e.g. 2 = 3
};
<|endoftext|>


<commit_before>var WritableStream = require('stream').Writable
                     || require('readable-stream').Writable,
    inherits = require('util').inherits,
    inspect = require('util').inspect;

var XRegExp = require('xregexp').XRegExp;

var REX_LISTUNIX = XRegExp.cache('^(?<type>[\\-ld])(?<permission>([\\-r][\\-w][\\-xs]){3})\\s+(?<inodes>\\d+)\\s+(?<owner>\\S+)\\s+(?<group>\\S+)\\s+(?<size>\\d+)\\s+(?<timestamp>((?<month1>\\w{3})\\s+(?<date1>\\d{1,2})\\s+(?<hour>\\d{1,2}):(?<minute>\\d{2}))|((?<month2>\\w{3})\\s+(?<date2>\\d{1,2})\\s+(?<year>\\d{4})))\\s+(?<name>.+)$'),
    REX_LISTMSDOS = XRegExp.cache('^(?<month>\\d{2})(?:\\-|\\/)(?<date>\\d{2})(?:\\-|\\/)(?<year>\\d{2,4})\\s+(?<hour>\\d{2}):(?<minute>\\d{2})\\s{0,1}(?<ampm>[AaMmPp]{1,2})\\s+(?:(?<size>\\d+)|(?<isdir>\\<DIR\\>))\\s+(?<name>.+)$'),
    RE_RES_END = /(?:^|\r?\n)(\d{3}) [^\r\n]*\r?\n/,
    RE_EOL = /\r?\n/g,
    RE_DASH = /\-/g;

var MONTHS = {
      jan: 1, feb: 2, mar: 3, apr: 4, may: 5, jun: 6,
      jul: 7, aug: 8, sep: 9, oct: 10, nov: 11, dec: 12
    };

function Parser(options) {
  if (!(this instanceof Parser))
    return new Parser(options);
  WritableStream.call(this);

  this._buffer = '';
  this._debug = options.debug;
}
inherits(Parser, WritableStream);

Parser.prototype._write = function(chunk, encoding, cb) {
  var m, code, reRmLeadCode, rest = '', debug = this._debug;

  this._buffer += chunk.toString('binary');

  while (m = RE_RES_END.exec(this._buffer)) {
    // support multiple terminating responses in the buffer
    rest = this._buffer.substring(m.index + m[0].length);
    if (rest.length)
      this._buffer = this._buffer.substring(0, m.index + m[0].length);

    debug&&debug('[parser] < ' + inspect(this._buffer));

    // we have a terminating response line
    code = parseInt(m[1], 10);

    // RFC 959 does not require each line in a multi-line response to begin
    // with '<code>-', but many servers will do this.
    //
    // remove this leading '<code>-' (or '<code> ' from last line) from each
    // line in the response ...
    reRmLeadCode = '(^|\\r?\\n)';
    reRmLeadCode += m[1];
    reRmLeadCode += '(?: |\\-)';
    reRmLeadCode = new RegExp(reRmLeadCode, 'g');
    var text = this._buffer.replace(reRmLeadCode, '$1').trim();
    this._buffer = rest;

    debug&&debug('[parser] Response: code=' + code + ', buffer=' + inspect(text));
    this.emit('response', code, text);
  }

  cb();
};

Parser.parseFeat = function(text) {
  var lines = text.split(RE_EOL);
  lines.shift(); // initial response line
  lines.pop(); // final response line

  for (var i = 0, len = lines.length; i < len; ++i)
    lines[i] = lines[i].trim();

  // just return the raw lines for now
  return lines;
};

Parser.parseListEntry = function(line) {
  var ret,
      info,
      month, day, year,
      hour, mins;

  if (ret = XRegExp.exec(line, REX_LISTUNIX)) {
    info = {
      type: ret.type,
      name: undefined,
      target: undefined,
      rights: {
        user: ret.permission.substr(0, 3).replace(RE_DASH, ''),
        group: ret.permission.substr(3, 3).replace(RE_DASH, ''),
        other: ret.permission.substr(6, 3).replace(RE_DASH, '')
      },
      owner: ret.owner,
      group: ret.group,
      size: parseInt(ret.size, 10),
      date: undefined
    };
    if (ret.month1 !== undefined) {
      month = parseInt(MONTHS[ret.month1.toLowerCase()], 10);
      day = parseInt(ret.date1, 10);
      year = (new Date()).getFullYear();
      hour = parseInt(ret.hour, 10);
      mins = parseInt(ret.minute, 10);
      if (month < 10)
        month = '0' + month;
      if (day < 10)
        day = '0' + day;
      if (hour < 10)
        hour = '0' + hour;
      if (mins < 10)
        mins = '0' + mins;
      info.date = new Date(year + '-' + month + '-' + day + 'T'
                           + hour + ':' + mins);
    } else if (ret.month2 !== undefined) {
      month = parseInt(MONTHS[ret.month2.toLowerCase()], 10);
      day = parseInt(ret.date2, 10);
      year = parseInt(ret.year, 10);
      if (month < 10)
        month = '0' + month;
      if (day < 10)
        day = '0' + day;
      info.date = new Date(year + '-' + month + '-' + day);
    }
    if (ret.type === 'l') {
      var pos = ret.name.indexOf(' -> ');
      info.name = ret.name.substring(0, pos);
      info.target = ret.name.substring(pos+4);
    } else
      info.name = ret.name;
    ret = info;
  } else if (ret = XRegExp.exec(line, REX_LISTMSDOS)) {
    info = {
      name: ret.name,
      type: (ret.isdir ? 'd' : '-'),
      size: (ret.isdir ? 0 : parseInt(ret.size, 10)),
      date: undefined,
    };
    month = parseInt(ret.month, 10),
    day = parseInt(ret.date, 10),
    year = parseInt(ret.year, 10),
    hour = parseInt(ret.hour, 10),
    mins = parseInt(ret.minute, 10);

    if (year < 70)
      year += 2000;
    else
      year += 1900;

    if (ret.ampm[0].toLowerCase() === 'p' && hour < 12)
      hour += 12;
    else if (ret.ampm[0].toLowerCase() === 'a' && hour === 12)
      hour = 0;

    info.date = new Date(year, month - 1, day, hour, mins);

    ret = info;
  } else
    ret = line; // could not parse, so at least give the end user a chance to
                // look at the raw listing themselves

  return ret;
};

module.exports = Parser;
<commit_msg>Parser: better year parsing on *NIX lists<commit_after>var WritableStream = require('stream').Writable
                     || require('readable-stream').Writable,
    inherits = require('util').inherits,
    inspect = require('util').inspect;

var XRegExp = require('xregexp').XRegExp;

var REX_LISTUNIX = XRegExp.cache('^(?<type>[\\-ld])(?<permission>([\\-r][\\-w][\\-xs]){3})\\s+(?<inodes>\\d+)\\s+(?<owner>\\S+)\\s+(?<group>\\S+)\\s+(?<size>\\d+)\\s+(?<timestamp>((?<month1>\\w{3})\\s+(?<date1>\\d{1,2})\\s+(?<hour>\\d{1,2}):(?<minute>\\d{2}))|((?<month2>\\w{3})\\s+(?<date2>\\d{1,2})\\s+(?<year>\\d{4})))\\s+(?<name>.+)$'),
    REX_LISTMSDOS = XRegExp.cache('^(?<month>\\d{2})(?:\\-|\\/)(?<date>\\d{2})(?:\\-|\\/)(?<year>\\d{2,4})\\s+(?<hour>\\d{2}):(?<minute>\\d{2})\\s{0,1}(?<ampm>[AaMmPp]{1,2})\\s+(?:(?<size>\\d+)|(?<isdir>\\<DIR\\>))\\s+(?<name>.+)$'),
    RE_RES_END = /(?:^|\r?\n)(\d{3}) [^\r\n]*\r?\n/,
    RE_EOL = /\r?\n/g,
    RE_DASH = /\-/g;

var MONTHS = {
      jan: 1, feb: 2, mar: 3, apr: 4, may: 5, jun: 6,
      jul: 7, aug: 8, sep: 9, oct: 10, nov: 11, dec: 12
    };

function Parser(options) {
  if (!(this instanceof Parser))
    return new Parser(options);
  WritableStream.call(this);

  this._buffer = '';
  this._debug = options.debug;
}
inherits(Parser, WritableStream);

Parser.prototype._write = function(chunk, encoding, cb) {
  var m, code, reRmLeadCode, rest = '', debug = this._debug;

  this._buffer += chunk.toString('binary');

  while (m = RE_RES_END.exec(this._buffer)) {
    // support multiple terminating responses in the buffer
    rest = this._buffer.substring(m.index + m[0].length);
    if (rest.length)
      this._buffer = this._buffer.substring(0, m.index + m[0].length);

    debug&&debug('[parser] < ' + inspect(this._buffer));

    // we have a terminating response line
    code = parseInt(m[1], 10);

    // RFC 959 does not require each line in a multi-line response to begin
    // with '<code>-', but many servers will do this.
    //
    // remove this leading '<code>-' (or '<code> ' from last line) from each
    // line in the response ...
    reRmLeadCode = '(^|\\r?\\n)';
    reRmLeadCode += m[1];
    reRmLeadCode += '(?: |\\-)';
    reRmLeadCode = new RegExp(reRmLeadCode, 'g');
    var text = this._buffer.replace(reRmLeadCode, '$1').trim();
    this._buffer = rest;

    debug&&debug('[parser] Response: code=' + code + ', buffer=' + inspect(text));
    this.emit('response', code, text);
  }

  cb();
};

Parser.parseFeat = function(text) {
  var lines = text.split(RE_EOL);
  lines.shift(); // initial response line
  lines.pop(); // final response line

  for (var i = 0, len = lines.length; i < len; ++i)
    lines[i] = lines[i].trim();

  // just return the raw lines for now
  return lines;
};

Parser.parseListEntry = function(line) {
  var ret,
      info,
      month, day, year,
      hour, mins;

  if (ret = XRegExp.exec(line, REX_LISTUNIX)) {
    info = {
      type: ret.type,
      name: undefined,
      target: undefined,
      rights: {
        user: ret.permission.substr(0, 3).replace(RE_DASH, ''),
        group: ret.permission.substr(3, 3).replace(RE_DASH, ''),
        other: ret.permission.substr(6, 3).replace(RE_DASH, '')
      },
      owner: ret.owner,
      group: ret.group,
      size: parseInt(ret.size, 10),
      date: undefined
    };
    if (ret.month1 !== undefined) {
      month = parseInt(MONTHS[ret.month1.toLowerCase()], 10);
      day = parseInt(ret.date1, 10);
      year = (new Date()).getFullYear();
      hour = parseInt(ret.hour, 10);
      mins = parseInt(ret.minute, 10);
      if (month < 10)
        month = '0' + month;
      if (day < 10)
        day = '0' + day;
      if (hour < 10)
        hour = '0' + hour;
      if (mins < 10)
        mins = '0' + mins;
      info.date = new Date(year + '-' + month + '-' + day + 'T'
                           + hour + ':' + mins);
      // if the date is in the past but not older than 6 months ago, year
      // isn't displayed and doesn't have to be the current year
      //
      // if the date is in the future (less than an hour from now), year
      // isn't displayed and doesn't have to be the current year (if it's
      // December 31st 23:30 for instance).
      // That second case is much more rare than the first and less annoying.
      // It's impossible to fix without knowing about the server's timezone,
      // so we just don't do anything about it.
      //
      // if we're here with a date 28 hours from now in the future (1 hour +
      // maximum timezone offset which is 27 hours),
      // there is a problem : we should be in the second conditional block
      if(info.date.getTime()-new Date().getTime() > 100800000)
        info.date = new Date((year-1) + '-' + month + '-' + day + 'T'
                             + hour + ':' + mins);

      // if we're here with a date older than 6 months in the past, there's
      // a problem as well
      // maybe local & remote servers aren't on the same timezone (with remote
      // ahead of local)
      // For instance, remote is in 2014 while local is still in 2013. In
      // this case, a date like 01/01/13 02:23 could be detected instead of
      // 01/01/14 02:23
      // Our trigger point will be 3600*24*31*6 (since we already use 31
      // as an upper bound, no need to add the 27 hours timezone offset)
      if(new Date().getTime()-info.date.getTime() > 16070400000)
        info.date = new Date((year+1) + '-' + month + '-' + day + 'T'
                             + hour + ':' + mins);
    } else if (ret.month2 !== undefined) {
      month = parseInt(MONTHS[ret.month2.toLowerCase()], 10);
      day = parseInt(ret.date2, 10);
      year = parseInt(ret.year, 10);
      if (month < 10)
        month = '0' + month;
      if (day < 10)
        day = '0' + day;
      info.date = new Date(year + '-' + month + '-' + day);
    }
    if (ret.type === 'l') {
      var pos = ret.name.indexOf(' -> ');
      info.name = ret.name.substring(0, pos);
      info.target = ret.name.substring(pos+4);
    } else
      info.name = ret.name;
    ret = info;
  } else if (ret = XRegExp.exec(line, REX_LISTMSDOS)) {
    info = {
      name: ret.name,
      type: (ret.isdir ? 'd' : '-'),
      size: (ret.isdir ? 0 : parseInt(ret.size, 10)),
      date: undefined,
    };
    month = parseInt(ret.month, 10),
    day = parseInt(ret.date, 10),
    year = parseInt(ret.year, 10),
    hour = parseInt(ret.hour, 10),
    mins = parseInt(ret.minute, 10);

    if (year < 70)
      year += 2000;
    else
      year += 1900;

    if (ret.ampm[0].toLowerCase() === 'p' && hour < 12)
      hour += 12;
    else if (ret.ampm[0].toLowerCase() === 'a' && hour === 12)
      hour = 0;

    info.date = new Date(year, month - 1, day, hour, mins);

    ret = info;
  } else
    ret = line; // could not parse, so at least give the end user a chance to
                // look at the raw listing themselves

  return ret;
};

module.exports = Parser;
<|endoftext|>


<commit_before>
;(function(is_server) {

  var BOOL_ATTR = ('allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,'+
    'defaultchecked,defaultmuted,defaultselected,defer,disabled,draggable,enabled,formnovalidate,hidden,'+
    'indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,'+
    'pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,spellcheck,translate,truespeed,'+
    'typemustmatch,visible').split(',')

  var RIOT_ATTR = ('style,src').split(',')

  var VOID_TAGS = 'area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr'.split(',')

  var HTML_PARSERS = {
    jade: jade
  }

  var JS_PARSERS = {
    coffeescript: coffee,
    none: plainjs,
    cs: coffee,
    es6: es6,
    typescript: typescript,
    livescript: livescript,
    ls: livescript
  }

  var LINE_TAG = /^<([\w\-]+)>(.*)<\/\1>/gim,
      QUOTE = /=({[^}]+})([\s\/\>])/g,
      SET_ATTR = /([\w\-]+)=(["'])([^\2]+?)\2/g,
      EXPR = /{\s*([^}]+)\s*}/g,
      // (tagname) (html) (javascript) endtag
      CUSTOM_TAG = /^<([\w\-]+)>([^\x00]*[\w\/}]>$)?([^\x00]*?)^<\/\1>/gim,
      SCRIPT = /<script(\s+type=['"]?([^>'"]+)['"]?)?>([^\x00]*?)<\/script>/gm,
      STYLE = /<style(\s+type=['"]?([^>'"]+)['"]?)?>([^\x00]*?)<\/style>/gm,
      HTML_COMMENT = /<!--.*?-->/g,
      CLOSED_TAG = /<([\w\-]+)([^>]*)\/\s*>/g,
      LINE_COMMENT = /^\s*\/\/.*$/gm,
      JS_COMMENT = /\/\*[^\x00]*?\*\//gm

  function compileHTML(html, opts, type) {

    var brackets = riot.util.brackets

    // whitespace
    html = opts.whitespace ? html.replace(/\n/g, '\\n') : html.replace(/\s+/g, ' ')

    // strip comments
    html = html.trim().replace(HTML_COMMENT, '')

    // foo={ bar } --> foo="{ bar }"
    html = html.replace(brackets(QUOTE), '="$1"$2')

    // alter special attribute names
    html = html.replace(SET_ATTR, function(full, name, _, expr) {
      if (expr.indexOf(brackets(0)) >= 0) {
        name = name.toLowerCase()

        // <img src> --> <img riot-src>
        if (RIOT_ATTR.indexOf(name) >= 0) name = 'riot-' + name

        // IE8 looses boolean attr values: `checked={ expr }` --> `__checked={ expr }`
        else if (BOOL_ATTR.indexOf(name) >= 0) name = '__' + name
      }

      return name + '="' + expr + '"'
    })

    // run expressions trough parser
    if (opts.expr) {
      html = html.replace(brackets(EXPR), function(_, expr) {
        var ret = compileJS(expr, opts, type).trim().replace(/\r?\n|\r/g, '').trim()
        if (ret.slice(-1) == ';') ret = ret.slice(0, -1)
        return brackets(0) + ret + brackets(1)
      })
    }

    // <foo/> -> <foo></foo>
    html = html.replace(CLOSED_TAG, function(_, name, attr) {
      var tag = '<' + name + (attr ? ' ' + attr.trim() : '') + '>'

      // Do not self-close HTML5 void tags
      if (VOID_TAGS.indexOf(name.toLowerCase()) == -1) tag += '</' + name + '>'
      return tag
    })

    // escape single quotes
    html = html.replace(/'/g, "\\'")

    // \{ jotain \} --> \\{ jotain \\}
    html = html.replace(brackets(/\\{|\\}/g), '\\$&')

    // compact: no whitespace between tags
    if (opts.compact) html = html.replace(/> </g, '><')

    return html

  }

  function coffee(js) {
    return require('coffee-script').compile(js, { bare: true })
  }

  function es6(js) {
    return require('babel').transform(js, { blacklist: ['useStrict'] }).code
  }

  function typescript(js) {
    return require('typescript-simple')(js)
  }

  function livescript(js) {
    return require('LiveScript').compile(js, { bare: true, header: false })
  }

  function plainjs(js) {
    return js
  }

  function jade(html) {
    return require('jade').render(html, {pretty: true})
  }

  function riotjs(js) {

    // strip comments
    js = js.replace(LINE_COMMENT, '').replace(JS_COMMENT, '')

    // ES6 method signatures
    var lines = js.split('\n'),
        es6_ident = ''

    lines.forEach(function(line, i) {
      var l = line.trim()

      // method start
      if (l[0] != '}' && l.indexOf('(') > 0 && l.indexOf('function') == -1) {
        var end = /[{}]/.exec(l.slice(-1)),
            m = end && /(\s+)([\w]+)\s*\(([\w,\s]*)\)\s*\{/.exec(line)

        if (m && !/^(if|while|switch|for)$/.test(m[2])) {
          lines[i] = m[1] + 'this.' + m[2] + ' = function(' + m[3] + ') {'

          // foo() { }
          if (end[0] == '}') {
            lines[i] += ' ' + l.slice(m[0].length - 1, -1) + '}.bind(this)'

          } else {
            es6_ident = m[1]
          }
        }

      }

      // method end
      if (line.slice(0, es6_ident.length + 1) == es6_ident + '}') {
        lines[i] = es6_ident + '}.bind(this);'
        es6_ident = ''
      }

    })

    return lines.join('\n')

  }

  function compileJS(js, opts, type) {
    var parser = opts.parser || (type ? JS_PARSERS[type] : riotjs)
    if (!parser) throw new Error('Parser not found "' + type + '"')
    return parser(js, opts)
  }

  function compileTemplate(lang, html) {
    var parser = HTML_PARSERS[lang]
    if (!parser) throw new Error('Template parser not found "' + lang + '"')
    return parser(html)
  }

  function compileCSS(style, styleType) {
    //TODO: compile LESS, Sass, ...etc.

    return style.replace(/\s+/g, ' ').trim().replace(/\\/g, '\\\\').replace(/'/g, "\\'")
  }

  function mktag(name, html, css, js) {
    return 'riot.tag(\''
      + name + '\', \''
      + html + '\''
      + (css ? ', \'' + css + '\'' : '')
      + ', function(opts) {' + js + '\n});'
  }

  function compile(src, opts) {

    opts = opts || {}

    if (opts.brackets) riot.settings.brackets = opts.brackets

    if (opts.template) src = compileTemplate(opts.template, src)

    src = src.replace(LINE_TAG, function(_, tagName, html) {
      return mktag(tagName, compileHTML(html, opts), '', '')
    })

    return src.replace(CUSTOM_TAG, function(_, tagName, html, js) {

      html = html || ''

      // js wrapped inside <script> tag
      var type = opts.type

      if (!js.trim()) {
        html = html.replace(SCRIPT, function(_, fullType, _type, script) {
          if (_type) type = _type.replace('text/', '')
          js = script
          return ''
        })
      }

      // styles in <style> tag
      var style = ''
      var styleType = 'css'

      html = html.replace(STYLE, function(_, fullType, _type, _style) {
        if (_type) styleType = _type.replace('text/', '')
        style = _style
        return ''
      })

      return mktag(
        tagName,
        compileHTML(html, opts, type),
        compileCSS(style, styleType),
        compileJS(js, opts, type)
      )

    })

  }

  // io.js (node)
  if (is_server) {
    this.riot = require(process.env.RIOT || '../riot')
    return module.exports = {
      html: compileHTML,
      compile: compile
    }
  }

  // browsers
  var doc = document,
      promise,
      ready

  function GET(url, fn) {
    var req = new XMLHttpRequest()

    req.onreadystatechange = function() {
      if (req.readyState == 4 && req.status == 200) fn(req.responseText)
    }
    req.open('GET', url, true)
    req.send('')
  }

  function unindent(src) {
    var ident = /[ \t]+/.exec(src)
    if (ident) src = src.replace(new RegExp('^' + ident[0], 'gm'), '')
    return src
  }

  function globalEval(js) {
    var node = doc.createElement('script'),
        root = doc.documentElement

    node.text = compile(js)
    root.appendChild(node)
    root.removeChild(node)
  }

  function compileScripts(fn) {
    var scripts = doc.querySelectorAll('script[type="riot/tag"]')

    ;[].map.call(scripts, function(script, i) {
      var url = script.getAttribute('src')

      function compileTag(source) {
        globalEval(source)

        if (i + 1 == scripts.length) {
          promise.trigger('ready')
          ready = true
          fn && fn()
        }
      }

      return url ? GET(url, compileTag) : compileTag(unindent(script.innerHTML))
    })

  }

  riot.compile = function(arg, fn) {

    // string
    if (typeof arg == 'string') {

      // compile & return
      if (arg.trim()[0] == '<') {
        var js = unindent(compile(arg))
        if (!fn) globalEval(js)
        return js

      // URL
      } else {
        return GET(arg, function(str) {
          var js = unindent(compile(str))
          globalEval(js)
          fn && fn(js, str)
        })
      }
    }

    // must be a function
    if (typeof arg != 'function') arg = undefined

    // all compiled
    if (ready) return arg && arg()

    // add to queue
    if (promise) {
      arg && promise.on('ready', arg)

    // grab riot/tag elements + load & execute them
    } else {
      promise = riot.observable()
      compileScripts(arg)
    }

  }

  // reassign mount methods
  var mount = riot.mount

  riot.mount = function(a, b, c) {
    var ret
    riot.compile(function() { ret = mount(a, b, c) })
    return ret
  }

  // @depreciated
  riot.mountTo = riot.mount

})(!this.top)
<commit_msg>Added 'd' to attributes who needs prefix to work properly, Chrome gives error if the value is not valid format (https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d). Renamed RIOT_ATTR to PREFIX_ATTR<commit_after>
;(function(is_server) {

  var BOOL_ATTR = ('allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,'+
    'defaultchecked,defaultmuted,defaultselected,defer,disabled,draggable,enabled,formnovalidate,hidden,'+
    'indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,'+
    'pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,spellcheck,translate,truespeed,'+
    'typemustmatch,visible').split(',')

  // 'd' describes the SVG <path>, Chrome gives error if the value is not valid format (https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d)
  var PREFIX_ATTR = ('style,src,d').split(',')

  var VOID_TAGS = 'area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr'.split(',')

  var HTML_PARSERS = {
    jade: jade
  }

  var JS_PARSERS = {
    coffeescript: coffee,
    none: plainjs,
    cs: coffee,
    es6: es6,
    typescript: typescript,
    livescript: livescript,
    ls: livescript
  }

  var LINE_TAG = /^<([\w\-]+)>(.*)<\/\1>/gim,
      QUOTE = /=({[^}]+})([\s\/\>])/g,
      SET_ATTR = /([\w\-]+)=(["'])([^\2]+?)\2/g,
      EXPR = /{\s*([^}]+)\s*}/g,
      // (tagname) (html) (javascript) endtag
      CUSTOM_TAG = /^<([\w\-]+)>([^\x00]*[\w\/}]>$)?([^\x00]*?)^<\/\1>/gim,
      SCRIPT = /<script(\s+type=['"]?([^>'"]+)['"]?)?>([^\x00]*?)<\/script>/gm,
      STYLE = /<style(\s+type=['"]?([^>'"]+)['"]?)?>([^\x00]*?)<\/style>/gm,
      HTML_COMMENT = /<!--.*?-->/g,
      CLOSED_TAG = /<([\w\-]+)([^>]*)\/\s*>/g,
      LINE_COMMENT = /^\s*\/\/.*$/gm,
      JS_COMMENT = /\/\*[^\x00]*?\*\//gm

  function compileHTML(html, opts, type) {

    var brackets = riot.util.brackets

    // whitespace
    html = opts.whitespace ? html.replace(/\n/g, '\\n') : html.replace(/\s+/g, ' ')

    // strip comments
    html = html.trim().replace(HTML_COMMENT, '')

    // foo={ bar } --> foo="{ bar }"
    html = html.replace(brackets(QUOTE), '="$1"$2')

    // alter special attribute names
    html = html.replace(SET_ATTR, function(full, name, _, expr) {
      if (expr.indexOf(brackets(0)) >= 0) {
        name = name.toLowerCase()

        // some attributes needs 'riot-' prefix: <img src> --> <img riot-src>, <path d> --> <path riot-d>
        if (PREFIX_ATTR.indexOf(name) >= 0) name = 'riot-' + name

        // IE8 looses boolean attr values: `checked={ expr }` --> `__checked={ expr }`
        else if (BOOL_ATTR.indexOf(name) >= 0) name = '__' + name
      }

      return name + '="' + expr + '"'
    })

    // run expressions trough parser
    if (opts.expr) {
      html = html.replace(brackets(EXPR), function(_, expr) {
        var ret = compileJS(expr, opts, type).trim().replace(/\r?\n|\r/g, '').trim()
        if (ret.slice(-1) == ';') ret = ret.slice(0, -1)
        return brackets(0) + ret + brackets(1)
      })
    }

    // <foo/> -> <foo></foo>
    html = html.replace(CLOSED_TAG, function(_, name, attr) {
      var tag = '<' + name + (attr ? ' ' + attr.trim() : '') + '>'

      // Do not self-close HTML5 void tags
      if (VOID_TAGS.indexOf(name.toLowerCase()) == -1) tag += '</' + name + '>'
      return tag
    })

    // escape single quotes
    html = html.replace(/'/g, "\\'")

    // \{ jotain \} --> \\{ jotain \\}
    html = html.replace(brackets(/\\{|\\}/g), '\\$&')

    // compact: no whitespace between tags
    if (opts.compact) html = html.replace(/> </g, '><')

    return html

  }

  function coffee(js) {
    return require('coffee-script').compile(js, { bare: true })
  }

  function es6(js) {
    return require('babel').transform(js, { blacklist: ['useStrict'] }).code
  }

  function typescript(js) {
    return require('typescript-simple')(js)
  }

  function livescript(js) {
    return require('LiveScript').compile(js, { bare: true, header: false })
  }

  function plainjs(js) {
    return js
  }

  function jade(html) {
    return require('jade').render(html, {pretty: true})
  }

  function riotjs(js) {

    // strip comments
    js = js.replace(LINE_COMMENT, '').replace(JS_COMMENT, '')

    // ES6 method signatures
    var lines = js.split('\n'),
        es6_ident = ''

    lines.forEach(function(line, i) {
      var l = line.trim()

      // method start
      if (l[0] != '}' && l.indexOf('(') > 0 && l.indexOf('function') == -1) {
        var end = /[{}]/.exec(l.slice(-1)),
            m = end && /(\s+)([\w]+)\s*\(([\w,\s]*)\)\s*\{/.exec(line)

        if (m && !/^(if|while|switch|for)$/.test(m[2])) {
          lines[i] = m[1] + 'this.' + m[2] + ' = function(' + m[3] + ') {'

          // foo() { }
          if (end[0] == '}') {
            lines[i] += ' ' + l.slice(m[0].length - 1, -1) + '}.bind(this)'

          } else {
            es6_ident = m[1]
          }
        }

      }

      // method end
      if (line.slice(0, es6_ident.length + 1) == es6_ident + '}') {
        lines[i] = es6_ident + '}.bind(this);'
        es6_ident = ''
      }

    })

    return lines.join('\n')

  }

  function compileJS(js, opts, type) {
    var parser = opts.parser || (type ? JS_PARSERS[type] : riotjs)
    if (!parser) throw new Error('Parser not found "' + type + '"')
    return parser(js, opts)
  }

  function compileTemplate(lang, html) {
    var parser = HTML_PARSERS[lang]
    if (!parser) throw new Error('Template parser not found "' + lang + '"')
    return parser(html)
  }

  function compileCSS(style, styleType) {
    //TODO: compile LESS, Sass, ...etc.

    return style.replace(/\s+/g, ' ').trim().replace(/\\/g, '\\\\').replace(/'/g, "\\'")
  }

  function mktag(name, html, css, js) {
    return 'riot.tag(\''
      + name + '\', \''
      + html + '\''
      + (css ? ', \'' + css + '\'' : '')
      + ', function(opts) {' + js + '\n});'
  }

  function compile(src, opts) {

    opts = opts || {}

    if (opts.brackets) riot.settings.brackets = opts.brackets

    if (opts.template) src = compileTemplate(opts.template, src)

    src = src.replace(LINE_TAG, function(_, tagName, html) {
      return mktag(tagName, compileHTML(html, opts), '', '')
    })

    return src.replace(CUSTOM_TAG, function(_, tagName, html, js) {

      html = html || ''

      // js wrapped inside <script> tag
      var type = opts.type

      if (!js.trim()) {
        html = html.replace(SCRIPT, function(_, fullType, _type, script) {
          if (_type) type = _type.replace('text/', '')
          js = script
          return ''
        })
      }

      // styles in <style> tag
      var style = ''
      var styleType = 'css'

      html = html.replace(STYLE, function(_, fullType, _type, _style) {
        if (_type) styleType = _type.replace('text/', '')
        style = _style
        return ''
      })

      return mktag(
        tagName,
        compileHTML(html, opts, type),
        compileCSS(style, styleType),
        compileJS(js, opts, type)
      )

    })

  }

  // io.js (node)
  if (is_server) {
    this.riot = require(process.env.RIOT || '../riot')
    return module.exports = {
      html: compileHTML,
      compile: compile
    }
  }

  // browsers
  var doc = document,
      promise,
      ready

  function GET(url, fn) {
    var req = new XMLHttpRequest()

    req.onreadystatechange = function() {
      if (req.readyState == 4 && req.status == 200) fn(req.responseText)
    }
    req.open('GET', url, true)
    req.send('')
  }

  function unindent(src) {
    var ident = /[ \t]+/.exec(src)
    if (ident) src = src.replace(new RegExp('^' + ident[0], 'gm'), '')
    return src
  }

  function globalEval(js) {
    var node = doc.createElement('script'),
        root = doc.documentElement

    node.text = compile(js)
    root.appendChild(node)
    root.removeChild(node)
  }

  function compileScripts(fn) {
    var scripts = doc.querySelectorAll('script[type="riot/tag"]')

    ;[].map.call(scripts, function(script, i) {
      var url = script.getAttribute('src')

      function compileTag(source) {
        globalEval(source)

        if (i + 1 == scripts.length) {
          promise.trigger('ready')
          ready = true
          fn && fn()
        }
      }

      return url ? GET(url, compileTag) : compileTag(unindent(script.innerHTML))
    })

  }

  riot.compile = function(arg, fn) {

    // string
    if (typeof arg == 'string') {

      // compile & return
      if (arg.trim()[0] == '<') {
        var js = unindent(compile(arg))
        if (!fn) globalEval(js)
        return js

      // URL
      } else {
        return GET(arg, function(str) {
          var js = unindent(compile(str))
          globalEval(js)
          fn && fn(js, str)
        })
      }
    }

    // must be a function
    if (typeof arg != 'function') arg = undefined

    // all compiled
    if (ready) return arg && arg()

    // add to queue
    if (promise) {
      arg && promise.on('ready', arg)

    // grab riot/tag elements + load & execute them
    } else {
      promise = riot.observable()
      compileScripts(arg)
    }

  }

  // reassign mount methods
  var mount = riot.mount

  riot.mount = function(a, b, c) {
    var ret
    riot.compile(function() { ret = mount(a, b, c) })
    return ret
  }

  // @depreciated
  riot.mountTo = riot.mount

})(!this.top)
<|endoftext|>


<commit_before>/*jslint node: true */
"use strict";

var http = require('http'),
    express = require('express'),
    exec = require('child_process').exec,
    events = require('events'),
    util = require('util'),
    debug = require('debug')('EphemeralAuthApp'),
    Q = require('q'),
    secrets = require('../client_secrets'),
    clientId = secrets.web.client_id,
    clientName = secrets.web.client_name,
    clientSecret = secrets.web.client_secret,
    redirectUri = "http://localhost:8088/auth/versionone/callback",
    authUri = secrets.web.auth_uri,
    tokenUri = secrets.web.token_uri,
    serverBaseUri = secrets.web.server_base_uri,
    PORT = 8088,
    request = require('superagent');

function ManualAuthApp() {
  var server,
      rootThis = this,
      app = this.app = express();

  app.get('/start', function (req, res) {
    res.redirect(authUri +
      '?response_type=code' +
      '&client_id=' + clientId +
      '&redirect_uri=' + redirectUri +
      '&scope=ap1v1 query-api-1.0' +
      '&state=foobaz');
  });

  app.get('/auth/versionone/callback', function (req, res) {
    var pageRes = res,
        tokenPromise;
    if (req.param('code')) {
      tokenPromise = rootThis.hitTokenUri({code: req.param('code')});
      tokenPromise.then(function fulfilled(tokensJson) {
        rootThis.tokenDfd.resolve(tokensJson);
        pageRes.send('got token');
        pageRes.end();
      }, function rejected(errMessage) {
        rootThis.tokenDfd.reject('error turning code into token', errMessage);
        pageRes.send('failed to get token');
        pageRes.end();
      });
    } else {
      rootThis.tokenDfd.reject("error obtaining authorization code");
      res.send("Didn't get authorization code!");
      res.end();
    }
  });

  server = this.server = http.createServer(app);
  server.listen(PORT);
  this.tokenDfd = Q.defer();
}

/** Resolve an authorization code OR a refresh token into an access token.
 * Must be called with either
 *   {code: "**authorization code from oauth flow calback**"}
 *   -- OR --
 *   {refreshToken: "**refresh token from previous successful flow**"}
 */
ManualAuthApp.prototype.hitTokenUri = function (params) {
  var dfd = Q.defer(),
      tokenRequest = request.post(tokenUri)
        .send('client_id=' + clientId)
        .send('client_secret=' + clientSecret);

  if (params.code) {
    tokenRequest = tokenRequest
      .send('code=' + params.code)
      .send('grant_type=authorization_code')
      .send('redirect_uri=' + redirectUri)
      .send('scope=apiv1 query-api-1.0');
  } else if (params.refreshToken) {
    tokenRequest = tokenRequest
      .send('refresh_token=' + params.refreshToken)
      .send('grant_type=refresh_token');
  } else {
    dfd.reject('must call with code or refreshToken');
  }
  tokenRequest
    .end(function (res) {
      if (res.ok) {
        dfd.resolve(res.body);
      } else {
        dfd.reject("error getting access token", res.text);
      }
    });
  return dfd.promise;
};

ManualAuthApp.prototype.url = function () {
  return "http://localhost:8088/start";
};

ManualAuthApp.prototype.tokenPromise = function () {
  return this.tokenDfd.promise;
}

module.exports = ManualAuthApp;<commit_msg>Remove unused variable.<commit_after>/*jslint node: true */
"use strict";

var http = require('http'),
    express = require('express'),
    events = require('events'),
    util = require('util'),
    debug = require('debug')('EphemeralAuthApp'),
    Q = require('q'),
    secrets = require('../client_secrets'),
    clientId = secrets.web.client_id,
    clientName = secrets.web.client_name,
    clientSecret = secrets.web.client_secret,
    redirectUri = "http://localhost:8088/auth/versionone/callback",
    authUri = secrets.web.auth_uri,
    tokenUri = secrets.web.token_uri,
    serverBaseUri = secrets.web.server_base_uri,
    PORT = 8088,
    request = require('superagent');

function ManualAuthApp() {
  var server,
      rootThis = this,
      app = this.app = express();

  app.get('/start', function (req, res) {
    res.redirect(authUri +
      '?response_type=code' +
      '&client_id=' + clientId +
      '&redirect_uri=' + redirectUri +
      '&scope=ap1v1 query-api-1.0' +
      '&state=foobaz');
  });

  app.get('/auth/versionone/callback', function (req, res) {
    var pageRes = res,
        tokenPromise;
    if (req.param('code')) {
      tokenPromise = rootThis.hitTokenUri({code: req.param('code')});
      tokenPromise.then(function fulfilled(tokensJson) {
        rootThis.tokenDfd.resolve(tokensJson);
        pageRes.send('got token');
        pageRes.end();
      }, function rejected(errMessage) {
        rootThis.tokenDfd.reject('error turning code into token', errMessage);
        pageRes.send('failed to get token');
        pageRes.end();
      });
    } else {
      rootThis.tokenDfd.reject("error obtaining authorization code");
      res.send("Didn't get authorization code!");
      res.end();
    }
  });

  server = this.server = http.createServer(app);
  server.listen(PORT);
  this.tokenDfd = Q.defer();
}

/** Resolve an authorization code OR a refresh token into an access token.
 * Must be called with either
 *   {code: "**authorization code from oauth flow calback**"}
 *   -- OR --
 *   {refreshToken: "**refresh token from previous successful flow**"}
 */
ManualAuthApp.prototype.hitTokenUri = function (params) {
  var dfd = Q.defer(),
      tokenRequest = request.post(tokenUri)
        .send('client_id=' + clientId)
        .send('client_secret=' + clientSecret);

  if (params.code) {
    tokenRequest = tokenRequest
      .send('code=' + params.code)
      .send('grant_type=authorization_code')
      .send('redirect_uri=' + redirectUri)
      .send('scope=apiv1 query-api-1.0');
  } else if (params.refreshToken) {
    tokenRequest = tokenRequest
      .send('refresh_token=' + params.refreshToken)
      .send('grant_type=refresh_token');
  } else {
    dfd.reject('must call with code or refreshToken');
  }
  tokenRequest
    .end(function (res) {
      if (res.ok) {
        dfd.resolve(res.body);
      } else {
        dfd.reject("error getting access token", res.text);
      }
    });
  return dfd.promise;
};

ManualAuthApp.prototype.url = function () {
  return "http://localhost:8088/start";
};

ManualAuthApp.prototype.tokenPromise = function () {
  return this.tokenDfd.promise;
}

module.exports = ManualAuthApp;<|endoftext|>


<commit_before>/**
 * Copyright reelyActive 2015-2016
 * We believe in an open Internet of Things
 */

var nedb = require('nedb');
var reelib = require('reelib');
var sniffypedia = require('sniffypedia');

var SNIFFYPEDIA_ROOT_URL = 'http://sniffypedia.org/';
var DEFAULT_DATA_FOLDER = 'data';
var ASSOCIATION_DB = 'associations.db';
var TEST_TRANSMITTER = { "_id": "001bc50940100000",
                         "url": "http://reelyactive.com/metadata/test.json" };
var TEST_RECEIVER_0 = { "_id": "001bc50940800000", "tags": ["test"] };
var TEST_RECEIVER_1 = { "_id": "001bc50940800001", "tags": ["test"] };
var TEST_RECEIVER_2 = { "_id": "001bc50940810000", "tags": ["test"] };
var TEST_RECEIVER_3 = { "_id": "001bc50940810001", "tags": ["test"] };

var TAGS_SPLIT_CHAR = ',';
var DEFAULT_DEVICE_URL = null;
var DEFAULT_DEVICE_TAGS = [];
var DEFAULT_DEVICE_DIRECTORY = null;

/**
 * AssociationManager Class
 * Manages the association of identifiers with URLs
 * @param {Object} options The options as a JSON object.
 * @constructor
 */
function AssociationManager(options) {
  var self = this;

  self.associationsRootUrl = options.associationsRootUrl ||
                             SNIFFYPEDIA_ROOT_URL;
  var datafolder = options.persistentDataFolder || DEFAULT_DATA_FOLDER;
  var filename = ASSOCIATION_DB;
  if(options.persistentDataFolder !== '') {
    filename = datafolder.concat('/' + filename);
  }

  self.db = new nedb({ filename: filename, autoload: true });
  self.db.insert(TEST_TRANSMITTER);
  self.db.insert(TEST_RECEIVER_0);
  self.db.insert(TEST_RECEIVER_1);
  self.db.insert(TEST_RECEIVER_2);
  self.db.insert(TEST_RECEIVER_3);
}

/**
 * Find and append all associations for each device identifier
 * @param {Object} req The request which originated this function call.
 * @param {Object} state State of devices.
 * @param {function} callback Function to call on completion.
 */
AssociationManager.prototype.addAssociations = function(req, state, callback) {
  var self = this;
  var devices = state.devices;
  var transmitterIDs = Object.keys(devices);
  var decoders = extractRadioDecoders(devices);
  var decoderIDs = Object.keys(decoders);
  var associationIDs = extractAssociationIds(devices);
  var ids = transmitterIDs.concat(decoderIDs).concat(associationIDs);

  self.db.find({ _id: { $in: ids } }, function(err, associations) {
    var lookup = {};

    // Create a lookup table of all registered associations
    for(cAssociation = 0; cAssociation < associations.length; cAssociation++) {
      lookup[associations[cAssociation]._id] = associations[cAssociation];
    }

    // Add each radioDecoder to the list of devices
    for(var cDecoder = 0; cDecoder < decoderIDs.length; cDecoder++) {
      var decoderId = decoderIDs[cDecoder];
      devices[decoderId] = decoders[decoderId];
      devices[decoderId].directory = DEFAULT_DEVICE_DIRECTORY;
    }

    // Add associations to each device and remove the identifier
    for(var deviceId in devices) {
      var device = devices[deviceId];
      var lookupExists = lookup.hasOwnProperty(deviceId);

      // Add explicit associations based on device ID
      if(lookupExists) {
        device.url = lookup[deviceId].url || DEFAULT_DEVICE_URL;
        device.tags = lookup[deviceId].tags || DEFAULT_DEVICE_TAGS;
        if(lookup[deviceId].directory) {
          device.directory = lookup[deviceId].directory;
        }
      }

      // Otherwise, add associations based on association ids
      else {
        var hasAssociationIds = device.hasOwnProperty("associationIds");
        if(hasAssociationIds) {
          lookupExists = false;
          for(var cId = 0; ((cId < device.associationIds.length) &&
                            !lookupExists); cId++) {
            var associationId = device.associationIds[cId];
            lookupExists = lookup.hasOwnProperty(associationId);
            if(lookupExists) {
              device.url = lookup[associationId].url || DEFAULT_DEVICE_URL;
              device.tags = lookup[associationId].tags || DEFAULT_DEVICE_TAGS;
            }
          }
        }

        // No explicit associations on any ids, go implicit
        if(!device.hasOwnProperty("url") ||
           device.url === DEFAULT_DEVICE_URL) {
          device.tags = DEFAULT_DEVICE_TAGS;
          device.url = makeExternalAssociation(device,
                                               self.associationsRootUrl);
        }
      }
      delete device.identifier;

      // Substitute radioDecodings for nearest
      if(device.radioDecodings) {
        device.nearest = [];

        for(var cDecoding = 0; cDecoding < device.radioDecodings.length;
            cDecoding++) {
          var decoding = device.radioDecodings[cDecoding];
          device.nearest[cDecoding] = {
            device: decoding.identifier.value,
            rssi: decoding.rssi
          };
        }
        delete device.radioDecodings;
      }
    }
    callback(state);
  });
};

/**
 * Attempts to associate a device with an external URL
 * @param {Object} device The given device.
 * @param {String} rootUrl The given root URL.
 * @return {String} The externally associated URL, if any.
 */
function makeExternalAssociation(device, rootUrl) {

  // Special case of reelyActive proprietary Active RFID
  if(device.identifier.type === "EUI-64") {
    var oui36 = device.identifier.value.slice(0,9);
    switch(oui36) {
      case "001bc5094": // reelyActive
        if(device.identifier.value.substr(9,2) === '01') {
          return "http://sniffypedia.org/Product/reelyActive_RA-T411/";
        }
      default: // Unknown
        return DEFAULT_DEVICE_URL;
    }
  }

  // Special case of URL embedded in payload
  var advData = device.identifier.advData;
  if(advData) {
    var serviceData = advData.serviceData;
    if(serviceData) {
      switch(serviceData.uuid) {
        case "feaa":  // Eddystone (Google)
          if(serviceData && serviceData.eddystone &&
             serviceData.eddystone.url) {
            return serviceData.eddystone.url;
          }
        case "fed8":  // Physical Web (Google)
          if(serviceData && serviceData.uriBeacon &&
             serviceData.uriBeacon.url) {
            return serviceData.uriBeacon.url;
          }
      }
    }
  }

  // Standard Sniffypedia case
  var hasAssociationIds = device.hasOwnProperty("associationIds");
  if(hasAssociationIds) {
    var lookupExists = false;
    var ble = sniffypedia.index.ble;
    var urls = {};
    for(var cId = 0; cId < device.associationIds.length; cId++) {
      var associationId = device.associationIds[cId];
      if((associationId.length === 40) && // iBeacon (trim major & minor)
         (ble.iBeacons.hasOwnProperty(associationId.substr(0,32)))) {
        urls.iBeacon = assembleUrl(ble.iBeacons[associationId.substr(0,32)],
                                   rootUrl);
      }
      else if((associationId.length === 32) &&
              (ble.uuid128.hasOwnProperty(associationId))) {
        urls.uuid128 = assembleUrl(ble.uuid128[associationId], rootUrl);
      }
      else if(associationId.length === 4) {
        if(ble.uuid16.hasOwnProperty(associationId)) {
          urls.uuid16 = assembleUrl(ble.uuid16[associationId], rootUrl);
        }
        else if(ble.companyIdentifiers.hasOwnProperty(associationId)) {
          urls.companyIdentifier = assembleUrl(
                              ble.companyIdentifiers[associationId], rootUrl);
        }
      }
    }

    if(Object.keys(urls).length !== 0) {
      return urls.iBeacon || urls.uuid128 || urls.uuid16 ||
             urls.companyIdentifier; // In order of precedence
    }
  }

  // Catch-all
  var advHeader = device.identifier.advHeader;
  if(advHeader) {
    var type = advHeader.type;
    if(type === "SCAN_REQ") { // Curious device
      return "http://sniffypedia.org/Product/Any_Curious-Device/";
    }
    else { // Unknown Bluetooth Low Energy device
      return "http://sniffypedia.org/Organization/Bluetooth_SIG_Inc/";
    }
  }
}

/**
 * Assembles a URL from the target and the root URL, if required
 * @param {String} target The given target URL.
 * @param {String} rootUrl The given root URL.
 * @return {String} The full URL, assembled if required.
 */
function assembleUrl(target, rootUrl) {

  // Target is a full URL
  if(target.substr(0,4) === 'http') {
    return target;
  }

  // Target is a URL, add protocol, host and port (rootUrl)
  return rootUrl + target;
}

/**
 * Retrieve associations from the database
 * @param {Object} req The request which originated this function call.
 * @param {function} callback Function to call on completion.
 */
AssociationManager.prototype.retrieveMany = function(req, callback) {
  callback(new Error('Not implemented.'), null);
}

/**
 * Retrieve an association from the database
 * @param {Object} req The request which originated this function call.
 * @param {String} id The id of the association.
 * @param {String} property A specific association property to retrieve.
 * @param {function} callback Function to call on completion.
 */
AssociationManager.prototype.retrieve = function(req, id, property, callback) {
  var self = this;
  var projection;
  var isValidID = reelib.identifier.isValid(id);

  if(!isValidID) {
    callback('Invalid id', id);
    return;
  }

  switch(property) {
    case 'url':
      projection = { url: 1, _id: 0 };
      break;
    case 'directory':
      projection = { directory: 1, _id: 0 };
      break;
    case 'tags':
      projection = { tags: 1, _id: 0 };
      break;
    default:
      projection = { _id: 0 };
  }

  self.db.find({ _id: id }, projection, function(err, devices) {
    var device = devices[0];
    if(err) {
      err = err.message;
    }
    else if(!device || (Object.keys(device).length === 0)) {
      device = null;
    }
    callback(err, id, device);
  });
};

/**
 * Replace an association in the database, create if it doesn't exist
 * @param {Object} req The request which originated this function call.
 * @param {String} id The id of the association.
 * @param {String} url URL to associate.
 * @param {String} directory Directory to associate.
 * @param {Array} tags The tags to associate.
 * @param {function} callback Function to call on completion.
 */
AssociationManager.prototype.replace = function(req, id, url, directory, tags,
                                                callback) {
  var self = this;
  var association = {};
  var isValidID = reelib.identifier.isValid(id);

  if(!isValidID) {
    callback('Invalid id', id);
    return;
  }

  if(url) {
    association.url = url;
  }
  if(directory) {
    association.directory = directory;
  }
  if(tags) {
    association.tags = tags;
  }

  self.db.update({ _id: id }, { $set: association }, { upsert: true },
                 function(err, numReplaced, device) {
    if(err) {
      err = err.message;
    }
    if(device) {
      id = device._id;
      delete device._id;
    }
    else {
      device = association;
    }
    callback(err, id, device);
  });
};

/**
 * Remove an association from the database
 * @param {Object} req The request which originated this function call.
 * @param {String} id Identifier to remove.
 * @param {String} property A specific association property to retrieve.
 * @param {function} callback Function to call on completion.
 */
AssociationManager.prototype.remove = function(req, id, property, callback) {
  var self = this;
  var isValidID = reelib.identifier.isValid(id);

  if(!isValidID) {
    callback('Invalid id');
    return;
  }

  if(!property) {
    self.db.remove({ _id: id }, function(err, numRemoved) {
      if(err) {
        err = err.message;
      }
      else if (numRemoved === 0) {
        err = "Identifier not found";
      }
      callback(err);
    });
  }
  else {
    var unset = {};
    if(property === 'url') {
      unset = { url: true };
    }
    else if(property === 'directory') {
      unset = { directory: true };
    }
    else if(property === 'tags') {
      unset = { tags: true };
    }
    self.db.update({ _id: id }, { $unset: unset }, {},
                   function(err, numReplaced) {
      if(err) {
        err = err.message;
      }
      else if(numReplaced === 0) {
        err = "Identifier not found";
      }
      callback(err);
    });
  }
};

/**
 * Retrieve the ids associated with the given parameters
 * @param {Object} req The request which originated this function call.
 * @param {String} directory The association directory to search for.
 * @param {String} tags The assocation tags to search for.
 * @param {function} callback Function to call on completion.
 */
AssociationManager.prototype.retrieveIdsByParams = function(req, directory,
                                                            tags, callback) {
  var self = this;
  var query = {};
  var ids = [];

  if(directory) {
    var regex = new RegExp('^' + directory + '([:]+|$)');
    query = { directory: { $regex: regex } };
  }
  else if(tags) {
    var tagsArray = tags.split(TAGS_SPLIT_CHAR);
    query = { tags: { $in: tagsArray } };
  }
  else {
    callback(ids);
  }

  self.db.find(query, { _id: 1 }, function(err, devices) {
    if(!devices) {
      callback(ids);
    }
    else {
      for(var cDevice = 0; cDevice < devices.length; cDevice++) {
        ids.push(devices[cDevice]._id);
      }
      callback(ids);
    }
  });
};

/**
 * Extract the radio decoders from the given list of devices.
 * @param {Object} devices The given devices.
 */
function extractRadioDecoders(devices) {
  var radioDecoders = {};

  for(var deviceId in devices) {
    var radioDecodings = devices[deviceId].radioDecodings || [];
    for(var cDecoding = 0; cDecoding < radioDecodings.length; cDecoding++) {
      var radioDecoder = radioDecodings[cDecoding].identifier;
      var radioDecoderId = radioDecoder.value;
      radioDecoders[radioDecoderId] = { identifier: radioDecoder };
    }
  }

  return radioDecoders;
}

/**
 * Extract all non-duplicate association ids from the given list of devices,
 * adding an assocationIds field to each device when absent.
 * @param {Object} devices The given devices.
 * @return {Array} Non-duplicate association identifiers.
 */
function extractAssociationIds(devices) {
  var associationIds = [];

  for(var deviceId in devices) {
    var device = devices[deviceId];
    var hasAssociationIds = device.hasOwnProperty("associationIds");
    if(!hasAssociationIds) {
      device.associationIds = reelib.tiraid.getAssociationIds(device);
    }
    for(var cId = 0; cId < device.associationIds.length; cId++) {
      if(associationIds.indexOf(device.associationIds[cId]) < 0) {
        associationIds.push(device.associationIds[cId]);
      }
    }
  }

  return associationIds;
}

module.exports = AssociationManager;
module.exports.makeExternalAssociation = makeExternalAssociation;
module.exports.extractRadioDecoders = extractRadioDecoders;
<commit_msg>Added UUID-URL support<commit_after>/**
 * Copyright reelyActive 2015-2016
 * We believe in an open Internet of Things
 */

var nedb = require('nedb');
var reelib = require('reelib');
var sniffypedia = require('sniffypedia');

var SNIFFYPEDIA_ROOT_URL = 'http://sniffypedia.org/';
var DEFAULT_DATA_FOLDER = 'data';
var ASSOCIATION_DB = 'associations.db';
var TEST_TRANSMITTER = { "_id": "001bc50940100000",
                         "url": "http://reelyactive.com/metadata/test.json" };
var TEST_RECEIVER_0 = { "_id": "001bc50940800000", "tags": ["test"] };
var TEST_RECEIVER_1 = { "_id": "001bc50940800001", "tags": ["test"] };
var TEST_RECEIVER_2 = { "_id": "001bc50940810000", "tags": ["test"] };
var TEST_RECEIVER_3 = { "_id": "001bc50940810001", "tags": ["test"] };

var TAGS_SPLIT_CHAR = ',';
var DEFAULT_DEVICE_URL = null;
var DEFAULT_DEVICE_TAGS = [];
var DEFAULT_DEVICE_DIRECTORY = null;

var UUID_URL_PREFIX = '53746f727920494420';
var UUID_URL_BASE_URL = 'http://myjson.info/s/';

/**
 * AssociationManager Class
 * Manages the association of identifiers with URLs
 * @param {Object} options The options as a JSON object.
 * @constructor
 */
function AssociationManager(options) {
  var self = this;

  self.associationsRootUrl = options.associationsRootUrl ||
                             SNIFFYPEDIA_ROOT_URL;
  var datafolder = options.persistentDataFolder || DEFAULT_DATA_FOLDER;
  var filename = ASSOCIATION_DB;
  if(options.persistentDataFolder !== '') {
    filename = datafolder.concat('/' + filename);
  }

  self.db = new nedb({ filename: filename, autoload: true });
  self.db.insert(TEST_TRANSMITTER);
  self.db.insert(TEST_RECEIVER_0);
  self.db.insert(TEST_RECEIVER_1);
  self.db.insert(TEST_RECEIVER_2);
  self.db.insert(TEST_RECEIVER_3);
}

/**
 * Find and append all associations for each device identifier
 * @param {Object} req The request which originated this function call.
 * @param {Object} state State of devices.
 * @param {function} callback Function to call on completion.
 */
AssociationManager.prototype.addAssociations = function(req, state, callback) {
  var self = this;
  var devices = state.devices;
  var transmitterIDs = Object.keys(devices);
  var decoders = extractRadioDecoders(devices);
  var decoderIDs = Object.keys(decoders);
  var associationIDs = extractAssociationIds(devices);
  var ids = transmitterIDs.concat(decoderIDs).concat(associationIDs);

  self.db.find({ _id: { $in: ids } }, function(err, associations) {
    var lookup = {};

    // Create a lookup table of all registered associations
    for(cAssociation = 0; cAssociation < associations.length; cAssociation++) {
      lookup[associations[cAssociation]._id] = associations[cAssociation];
    }

    // Add each radioDecoder to the list of devices
    for(var cDecoder = 0; cDecoder < decoderIDs.length; cDecoder++) {
      var decoderId = decoderIDs[cDecoder];
      devices[decoderId] = decoders[decoderId];
      devices[decoderId].directory = DEFAULT_DEVICE_DIRECTORY;
    }

    // Add associations to each device and remove the identifier
    for(var deviceId in devices) {
      var device = devices[deviceId];
      var lookupExists = lookup.hasOwnProperty(deviceId);

      // Add explicit associations based on device ID
      if(lookupExists) {
        device.url = lookup[deviceId].url || DEFAULT_DEVICE_URL;
        device.tags = lookup[deviceId].tags || DEFAULT_DEVICE_TAGS;
        if(lookup[deviceId].directory) {
          device.directory = lookup[deviceId].directory;
        }
      }

      // Otherwise, add associations based on association ids
      else {
        var hasAssociationIds = device.hasOwnProperty("associationIds");
        if(hasAssociationIds) {
          lookupExists = false;
          for(var cId = 0; ((cId < device.associationIds.length) &&
                            !lookupExists); cId++) {
            var associationId = device.associationIds[cId];
            lookupExists = lookup.hasOwnProperty(associationId);
            if(lookupExists) {
              device.url = lookup[associationId].url || DEFAULT_DEVICE_URL;
              device.tags = lookup[associationId].tags || DEFAULT_DEVICE_TAGS;
            }
          }
        }

        // No explicit associations on any ids, go implicit
        if(!device.hasOwnProperty("url") ||
           device.url === DEFAULT_DEVICE_URL) {
          device.tags = DEFAULT_DEVICE_TAGS;
          device.url = makeExternalAssociation(device,
                                               self.associationsRootUrl);
        }
      }
      delete device.identifier;

      // Substitute radioDecodings for nearest
      if(device.radioDecodings) {
        device.nearest = [];

        for(var cDecoding = 0; cDecoding < device.radioDecodings.length;
            cDecoding++) {
          var decoding = device.radioDecodings[cDecoding];
          device.nearest[cDecoding] = {
            device: decoding.identifier.value,
            rssi: decoding.rssi
          };
        }
        delete device.radioDecodings;
      }
    }
    callback(state);
  });
};

/**
 * Attempts to associate a device with an external URL
 * @param {Object} device The given device.
 * @param {String} rootUrl The given root URL.
 * @return {String} The externally associated URL, if any.
 */
function makeExternalAssociation(device, rootUrl) {

  // Special case of reelyActive proprietary Active RFID
  if(device.identifier.type === "EUI-64") {
    var oui36 = device.identifier.value.slice(0,9);
    switch(oui36) {
      case "001bc5094": // reelyActive
        if(device.identifier.value.substr(9,2) === '01') {
          return "http://sniffypedia.org/Product/reelyActive_RA-T411/";
        }
      default: // Unknown
        return DEFAULT_DEVICE_URL;
    }
  }

  // Special case of URL embedded in payload
  var advData = device.identifier.advData;
  if(advData) {
    var uuid = advData.complete128BitUUIDs;
    if(uuid && (uuid.substr(0,18) === UUID_URL_PREFIX)) { // UUID-URL
      var url = UUID_URL_BASE_URL;
      for(var cChar = 9; cChar < 16; cChar++) {
        url += String.fromCharCode(parseInt(uuid.substr(cChar * 2, 2), 16));
      }
      return url;
    }
    var serviceData = advData.serviceData;
    if(serviceData) {
      switch(serviceData.uuid) {
        case "feaa":  // Eddystone (Google)
          if(serviceData && serviceData.eddystone &&
             serviceData.eddystone.url) {
            return serviceData.eddystone.url;
          }
        case "fed8":  // Physical Web (Google)
          if(serviceData && serviceData.uriBeacon &&
             serviceData.uriBeacon.url) {
            return serviceData.uriBeacon.url;
          }
      }
    }
  }

  // Standard Sniffypedia case
  var hasAssociationIds = device.hasOwnProperty("associationIds");
  if(hasAssociationIds) {
    var lookupExists = false;
    var ble = sniffypedia.index.ble;
    var urls = {};
    for(var cId = 0; cId < device.associationIds.length; cId++) {
      var associationId = device.associationIds[cId];
      if((associationId.length === 40) && // iBeacon (trim major & minor)
         (ble.iBeacons.hasOwnProperty(associationId.substr(0,32)))) {
        urls.iBeacon = assembleUrl(ble.iBeacons[associationId.substr(0,32)],
                                   rootUrl);
      }
      else if((associationId.length === 32) &&
              (ble.uuid128.hasOwnProperty(associationId))) {
        urls.uuid128 = assembleUrl(ble.uuid128[associationId], rootUrl);
      }
      else if(associationId.length === 4) {
        if(ble.uuid16.hasOwnProperty(associationId)) {
          urls.uuid16 = assembleUrl(ble.uuid16[associationId], rootUrl);
        }
        else if(ble.companyIdentifiers.hasOwnProperty(associationId)) {
          urls.companyIdentifier = assembleUrl(
                              ble.companyIdentifiers[associationId], rootUrl);
        }
      }
    }

    if(Object.keys(urls).length !== 0) {
      return urls.iBeacon || urls.uuid128 || urls.uuid16 ||
             urls.companyIdentifier; // In order of precedence
    }
  }

  // Catch-all
  var advHeader = device.identifier.advHeader;
  if(advHeader) {
    var type = advHeader.type;
    if(type === "SCAN_REQ") { // Curious device
      return "http://sniffypedia.org/Product/Any_Curious-Device/";
    }
    else { // Unknown Bluetooth Low Energy device
      return "http://sniffypedia.org/Organization/Bluetooth_SIG_Inc/";
    }
  }
}

/**
 * Assembles a URL from the target and the root URL, if required
 * @param {String} target The given target URL.
 * @param {String} rootUrl The given root URL.
 * @return {String} The full URL, assembled if required.
 */
function assembleUrl(target, rootUrl) {

  // Target is a full URL
  if(target.substr(0,4) === 'http') {
    return target;
  }

  // Target is a URL, add protocol, host and port (rootUrl)
  return rootUrl + target;
}

/**
 * Retrieve associations from the database
 * @param {Object} req The request which originated this function call.
 * @param {function} callback Function to call on completion.
 */
AssociationManager.prototype.retrieveMany = function(req, callback) {
  callback(new Error('Not implemented.'), null);
}

/**
 * Retrieve an association from the database
 * @param {Object} req The request which originated this function call.
 * @param {String} id The id of the association.
 * @param {String} property A specific association property to retrieve.
 * @param {function} callback Function to call on completion.
 */
AssociationManager.prototype.retrieve = function(req, id, property, callback) {
  var self = this;
  var projection;
  var isValidID = reelib.identifier.isValid(id);

  if(!isValidID) {
    callback('Invalid id', id);
    return;
  }

  switch(property) {
    case 'url':
      projection = { url: 1, _id: 0 };
      break;
    case 'directory':
      projection = { directory: 1, _id: 0 };
      break;
    case 'tags':
      projection = { tags: 1, _id: 0 };
      break;
    default:
      projection = { _id: 0 };
  }

  self.db.find({ _id: id }, projection, function(err, devices) {
    var device = devices[0];
    if(err) {
      err = err.message;
    }
    else if(!device || (Object.keys(device).length === 0)) {
      device = null;
    }
    callback(err, id, device);
  });
};

/**
 * Replace an association in the database, create if it doesn't exist
 * @param {Object} req The request which originated this function call.
 * @param {String} id The id of the association.
 * @param {String} url URL to associate.
 * @param {String} directory Directory to associate.
 * @param {Array} tags The tags to associate.
 * @param {function} callback Function to call on completion.
 */
AssociationManager.prototype.replace = function(req, id, url, directory, tags,
                                                callback) {
  var self = this;
  var association = {};
  var isValidID = reelib.identifier.isValid(id);

  if(!isValidID) {
    callback('Invalid id', id);
    return;
  }

  if(url) {
    association.url = url;
  }
  if(directory) {
    association.directory = directory;
  }
  if(tags) {
    association.tags = tags;
  }

  self.db.update({ _id: id }, { $set: association }, { upsert: true },
                 function(err, numReplaced, device) {
    if(err) {
      err = err.message;
    }
    if(device) {
      id = device._id;
      delete device._id;
    }
    else {
      device = association;
    }
    callback(err, id, device);
  });
};

/**
 * Remove an association from the database
 * @param {Object} req The request which originated this function call.
 * @param {String} id Identifier to remove.
 * @param {String} property A specific association property to retrieve.
 * @param {function} callback Function to call on completion.
 */
AssociationManager.prototype.remove = function(req, id, property, callback) {
  var self = this;
  var isValidID = reelib.identifier.isValid(id);

  if(!isValidID) {
    callback('Invalid id');
    return;
  }

  if(!property) {
    self.db.remove({ _id: id }, function(err, numRemoved) {
      if(err) {
        err = err.message;
      }
      else if (numRemoved === 0) {
        err = "Identifier not found";
      }
      callback(err);
    });
  }
  else {
    var unset = {};
    if(property === 'url') {
      unset = { url: true };
    }
    else if(property === 'directory') {
      unset = { directory: true };
    }
    else if(property === 'tags') {
      unset = { tags: true };
    }
    self.db.update({ _id: id }, { $unset: unset }, {},
                   function(err, numReplaced) {
      if(err) {
        err = err.message;
      }
      else if(numReplaced === 0) {
        err = "Identifier not found";
      }
      callback(err);
    });
  }
};

/**
 * Retrieve the ids associated with the given parameters
 * @param {Object} req The request which originated this function call.
 * @param {String} directory The association directory to search for.
 * @param {String} tags The assocation tags to search for.
 * @param {function} callback Function to call on completion.
 */
AssociationManager.prototype.retrieveIdsByParams = function(req, directory,
                                                            tags, callback) {
  var self = this;
  var query = {};
  var ids = [];

  if(directory) {
    var regex = new RegExp('^' + directory + '([:]+|$)');
    query = { directory: { $regex: regex } };
  }
  else if(tags) {
    var tagsArray = tags.split(TAGS_SPLIT_CHAR);
    query = { tags: { $in: tagsArray } };
  }
  else {
    callback(ids);
  }

  self.db.find(query, { _id: 1 }, function(err, devices) {
    if(!devices) {
      callback(ids);
    }
    else {
      for(var cDevice = 0; cDevice < devices.length; cDevice++) {
        ids.push(devices[cDevice]._id);
      }
      callback(ids);
    }
  });
};

/**
 * Extract the radio decoders from the given list of devices.
 * @param {Object} devices The given devices.
 */
function extractRadioDecoders(devices) {
  var radioDecoders = {};

  for(var deviceId in devices) {
    var radioDecodings = devices[deviceId].radioDecodings || [];
    for(var cDecoding = 0; cDecoding < radioDecodings.length; cDecoding++) {
      var radioDecoder = radioDecodings[cDecoding].identifier;
      var radioDecoderId = radioDecoder.value;
      radioDecoders[radioDecoderId] = { identifier: radioDecoder };
    }
  }

  return radioDecoders;
}

/**
 * Extract all non-duplicate association ids from the given list of devices,
 * adding an assocationIds field to each device when absent.
 * @param {Object} devices The given devices.
 * @return {Array} Non-duplicate association identifiers.
 */
function extractAssociationIds(devices) {
  var associationIds = [];

  for(var deviceId in devices) {
    var device = devices[deviceId];
    var hasAssociationIds = device.hasOwnProperty("associationIds");
    if(!hasAssociationIds) {
      device.associationIds = reelib.tiraid.getAssociationIds(device);
    }
    for(var cId = 0; cId < device.associationIds.length; cId++) {
      if(associationIds.indexOf(device.associationIds[cId]) < 0) {
        associationIds.push(device.associationIds[cId]);
      }
    }
  }

  return associationIds;
}

module.exports = AssociationManager;
module.exports.makeExternalAssociation = makeExternalAssociation;
module.exports.extractRadioDecoders = extractRadioDecoders;
<|endoftext|>