<commit_before>import {ref} from '../firebase'

let playersArr = Object.keys(ref.players) //should give only player property names (not entire obj)
let currentTurn = ref.players[ref.turn] //should give the entire current player object

export const farmersMarket = {
  rollValue: 1,
  cost: 1,
  industry: 'wheat',
  cardDescription: "Get 1 coin from the bank, on anyone's turn",
  action: () => {
    playersArr.forEach(player => {
      let playerObj = ref.players[player]
      let numCards = playerObj.cards.farmersMarket
      playerObj.money += numCards //is this a proper way to update the firebase database?
    })
  },
  imgURL: ''
};
export const river = {
  rollValue: 1,
  cost: 1,
  industry: 'cow',
  cardDescription: "Get 1 coin from the bank on anyone's turn",
  action: () => {
    playersArr.forEach(player => {
      let playerObj = ref.players[player]
      if(playerObj.cards.river){
        let numCards = playerObj.cards.river
        playerObj.money += numCards //is this a proper way to update the firebase database?
      }
    })
  },
  imgURL: ''
};
export const bakery = {
  rollValue: 2 || 3,
  cost: 1,
  industry: 'building',
  cardDescription: "Get 1 coin from the bank, on your turn only",
  action: () => {
    if (currentTurn.cards.bakery){
      let numCards = currentTurn.cards.bakery
      currentTurn.money += numCards //is this a proper way to update the firebase database?
    }
  },
  imgURL: ''
};
export const cafe = {
  rollValue: 3,
  cost: 2,
  industry: 'mug',
  cardDescription: "Get 1 coin from the player who rolled the dice",
  action: () => {
    let sum = 0
    playersArr.forEach(player => {
      if (player !== ref.turn) {
        let playerObj = ref.players[player]
        if (playerObj.cards.cafe){
          let numCards = playerObj.cards.cafe
          playerObj.money += numCards //is this a proper way to update the firebase database?
          sum += numCards
        }
      }
    })
    if (currentTurn.money - sum >= 0){
      currentTurn.money -= sum //is this a proper way to update the firebase database?
    } else { currentTurn.money = 0  } //is this a proper way to update the firebase database?
  },
  imgURL: ''
};
export const convenienceStore = {
  rollValue: 4,
  cost: 2,
  industry: 'building',
  cardDescription: "Get 3 coins from the bank, on your turn only",
  action: () => {
    if (currentTurn.cards.convenienceStore){
      let numCards = currentTurn.cards.convenienceStore
      currentTurn.money += numCards * 3 //is this a proper way to update the firebase database?
    }
  },
  imgURL: ''
};
export const museum = {
  rollValue: 5,
  cost: 3,
  industry: 'gear',
  cardDescription: "Get 1 coin from the bank, on anyone's turn",
  action: () => {
    playersArr.forEach(player => {
      let playerObj = ref.players[player]
      if (playerObj.cards.museum){
        let numCards = playerObj.cards.museum
        playerObj.money += numCards //is this a proper way to update the firebase database?
      }
    })
  },
  imgURL: ''
};
export const businessCenter = {
  rollValue: 6,
  cost: 8,
  industry: 'antenna',
  cardDescription: "Trade one non [antenna icon] establishment with another player, on your turn only",
  imgURL: ''
};
export const stadium = {
  rollValue: 6,
  cost: 6,
  industry: 'antenna',
  cardDescription: "Get 2 coins from all players, on your turn only",
  action: () => {
    let sum = 0
    playersArr.forEach(player => {
      if (player !== ref.turn){
        let playerObj = ref.players[player]
        if (playerObj.money >= 2) {
          sum += 2
          playerObj.money -= 2 //is this a proper way to update the firebase database?
        } else {
          sum += playerObj.money
          playerObj.money = 0 //is this a proper way to update the firebase database?
        }
      }
      currentTurn.money += sum //is this a proper way to update the firebase database?
    })
  },
  imgURL: ''
};
export const tvStation = {
  rollValue: 6,
  cost: 7,
  industry: 'antenna',
  cardDescription: "Take 5 coins from any one player, on your turn only",
  imgURL: ''
};
export const powerPlant = {
  rollValue: 7,
  cost: 5,
  industry: 'factory',
  cardDescription: "Get 3 coins from the bank for each [cow icon] establishment that you own, on your turn only",
  action: () => {
    let numCards
    if (currentTurn.cards.river) { //river is the only card type with the cow icon
      numCards = currentTurn.cards.river
      currentTurn.money += numCards * 3 //is this a proper way to update the firebase database?
    }
  },
  imgURL: ''
};
export const touristBus = {
  rollValue: 8,
  cost: 3,
  industry: 'factory',
  cardDescription: "Get 3 coins from the bank for each [gear icon] establishment that you own. On your turn only",
  action: () => {
    let numCards = 0
    if (currentTurn.cards.museum) { //museum and theater are the only card types with gear icon
      numCards += currentTurn.cards.museum
    }
    if (currentTurn.cards.theater) {
      numCards += currentTurn.cards.theater
    }
      currentTurn.money += numCards * 3 //is this a proper way to update the firebase database?
  },
  imgURL: ''
};
export const theater = {
  rollValue: 9,
  cost: 6,
  industry: 'gear',
  cardDescription: "Get 5 coins from the bank, on anyone's turn",
  action: () => {
    playersArr.forEach(player => {
      let playerObj = ref.players[player]
      if (playerObj.cards.theater){
        let numCards = playerObj.cards.theater
        playerObj.money += numCards * 5 //is this a proper way to update the firebase database?
      }
    })
  },
  imgURL: ''
};
export const bodega = {
  rollValue: 9 || 10,
  cost: 3,
  industry: 'mug',
  cardDescription: "Get 2 coins from the player who rolled the dice",
  action: () => {
    let sum = 0
    playersArr.forEach(player => {
      if (player !== ref.turn) {
        let playerObj = ref.players[player]
        if (playerObj.cards.bodega){
          let numCards = playerObj.cards.bodega
          playerObj.money += numCards * 2 //is this a proper way to update the firebase database?
          sum += numCards * 2
        }
      }
    })
    if (currentTurn.money - sum >= 0){
      currentTurn.money -= sum //is this a proper way to update the firebase database?
    } else { currentTurn.money = 0  } //is this a proper way to update the firebase database?
  },
  imgURL: ''
};
export const wineShop = {
  rollValue: 10,
  cost: 3,
  industry: 'wheat',
  cardDescription: "Get 3 coins from the bank,on anyone's turn",
  action: () => {
    playersArr.forEach(player => {
      let playerObj = ref.players[player]
      if (playerObj.cards.wineShop){
        let numCards = playerObj.cards.wineShop
        playerObj.money += numCards * 3 //is this a proper way to update the firebase database?
      }
    })
  },
  imgURL: ''
};
export const restaurant = {
  rollValue: 11 || 12,
  cost: 2,
  industry: 'fruit',
  cardDescription: "Get 2 coins from the bank for each [wheat icon] establishment that you own. On your turn only",
  action: () => {
    let numCards = 0
    if (currentTurn.cards.farmersMarket) { //farmersMarket and wine are the only card types with wheat icon
      numCards += currentTurn.cards.farmersMarket
    }
    if (currentTurn.cards.wineShop) {
      numCards += currentTurn.cards.wineShop
    }
    currentTurn.money += numCards * 2 //is this a proper way to update the firebase database?
  },
  imgURL: ''
};

//UNLOCKABLE CARDS BELOW

export const radioTower = {
  cost: 22,
  cardDescription: 'Once Every Turn You can Choose To Re-Roll Your Dice'
};
export const shoppingMall = {
  cost: 10,
  cardDescription: 'If you roll doubles, take another turn after this one'
};
export const coneyIsland = {
  cost: 16,
  cardDescription: 'Each of your [mug icon] and [building icon] establishments earn +1 coin'
};
export const subwayStation = {
  cost: 4,
  cardDescription: 'You may roll 1 or 2 dice'
};
<commit_msg>mostly formatting changes<commit_after>import {ref} from '../firebase'

let playersArr = Object.keys(ref.players) //should give only player property names (not entire obj)
let currentTurn = ref.players[ref.turn] //should give the entire current player object

export const farmersMarket = {
  rollValue: 1,
  cost: 1,
  industry: 'wheat',
  cardDescription: "Get 1 coin from the bank, on anyone's turn",
  action: () => {
    playersArr.forEach(player => {
      let playerObj = ref.players[player]
      let numCards = playerObj.cards.farmersMarket
      playerObj.money += numCards //is this a proper way to update the firebase database?
    })
  },
  imgURL: ''
};
export const river = {
  rollValue: 1,
  cost: 1,
  industry: 'cow',
  cardDescription: "Get 1 coin from the bank on anyone's turn",
  action: () => {
    playersArr.forEach(player => {
      let playerObj = ref.players[player]
      let numCards = playerObj.cards.river || 0
      playerObj.money += numCards //is this a proper way to update the firebase database?
    })
  },
  imgURL: ''
};
export const bakery = {
  rollValue: 2 || 3,
  cost: 1,
  industry: 'building',
  cardDescription: "Get 1 coin from the bank, on your turn only",
  action: () => {
    let numCards = currentTurn.cards.bakery || 0
    currentTurn.money += numCards //is this a proper way to update the firebase database?
  },
  imgURL: ''
};
export const cafe = {
  rollValue: 3,
  cost: 2,
  industry: 'mug',
  cardDescription: "Get 1 coin from the player who rolled the dice",
  action: () => {
    playersArr.forEach(player => {
      let numCards
      let playerObj
      if (player !== ref.turn) {
        playerObj = ref.players[player]
        numCards = playerObj.cards.cafe || 0
        playerObj.money += numCards //is this a proper way to update the firebase database?
      }
      if (currentTurn.money - numCards >= 0){
        currentTurn.money -= numCards //is this a proper way to update the firebase database?
      } else { currentTurn.money = 0  } //is this a proper way to update the firebase database?
    })
  },
  imgURL: ''
};
export const convenienceStore = {
  rollValue: 4,
  cost: 2,
  industry: 'building',
  cardDescription: "Get 3 coins from the bank, on your turn only",
  action: () => {
    let numCards = currentTurn.cards.convenienceStore || 0
    currentTurn.money += numCards * 3 //is this a proper way to update the firebase database?
  },
  imgURL: ''
};
export const museum = {
  rollValue: 5,
  cost: 3,
  industry: 'gear',
  cardDescription: "Get 1 coin from the bank, on anyone's turn",
  action: () => {
    playersArr.forEach(player => {
      let playerObj = ref.players[player]
      let numCards = playerObj.cards.museum || 0
      playerObj.money += numCards //is this a proper way to update the firebase database?
    })
  },
  imgURL: ''
};
export const businessCenter = {
  rollValue: 6,
  cost: 8,
  industry: 'antenna',
  cardDescription: "Trade one non [antenna icon] establishment with another player, on your turn only",
  imgURL: ''
};
export const stadium = {
  rollValue: 6,
  cost: 6,
  industry: 'antenna',
  cardDescription: "Get 2 coins from all players, on your turn only",
  action: () => {
    playersArr.forEach(player => {
      let gain = 0
      if (player !== ref.turn){
        let playerObj = ref.players[player]
        if (playerObj.money >= 2) {
          gain += 2
          playerObj.money -= 2 //is this a proper way to update the firebase database?
        } else {
          gain += playerObj.money
          playerObj.money = 0 //is this a proper way to update the firebase database?
        }
      }
      currentTurn.money += gain //is this a proper way to update the firebase database?
    })
  },
  imgURL: ''
};
export const tvStation = {
  rollValue: 6,
  cost: 7,
  industry: 'antenna',
  cardDescription: "Take 5 coins from any one player, on your turn only",
  imgURL: ''
};
export const powerPlant = {
  rollValue: 7,
  cost: 5,
  industry: 'factory',
  cardDescription: "Get 3 coins from the bank for each [cow icon] establishment that you own, on your turn only",
  action: () => {
    let numCards = currentTurn.cards.river || 0 //river is the only card type with the cow icon
    currentTurn.money += numCards * 3 //is this a proper way to update the firebase database?
  },
  imgURL: ''
};
export const touristBus = {
  rollValue: 8,
  cost: 3,
  industry: 'factory',
  cardDescription: "Get 3 coins from the bank for each [gear icon] establishment that you own. On your turn only",
  action: () => {
    let numCards = 0
    numCards += currentTurn.cards.museum || 0 //museum and theater are the only card types with gear icon
    numCards += currentTurn.cards.theater || 0
    currentTurn.money += numCards * 3 //is this a proper way to update the firebase database?
  },
  imgURL: ''
};
export const theater = {
  rollValue: 9,
  cost: 6,
  industry: 'gear',
  cardDescription: "Get 5 coins from the bank, on anyone's turn",
  action: () => {
    playersArr.forEach(player => {
      let playerObj = ref.players[player]
      let numCards = playerObj.cards.theater || 0
      playerObj.money += numCards * 5 //is this a proper way to update the firebase database?
    })
  },
  imgURL: ''
};
export const bodega = {
  rollValue: 9 || 10,
  cost: 3,
  industry: 'mug',
  cardDescription: "Get 2 coins from the player who rolled the dice",
  action: () => {
    playersArr.forEach(player => {
      let numCards
      if (player !== ref.turn) {
        let playerObj = ref.players[player]
          numCards = playerObj.cards.bodega || 0
          playerObj.money += numCards * 2 //is this a proper way to update the firebase database?
      }
      if (currentTurn.money - (numCards * 2) >= 0){
        currentTurn.money -= (numCards * 2) //is this a proper way to update the firebase database?
      } else { currentTurn.money = 0  } //is this a proper way to update the firebase database?
    })
  },
  imgURL: ''
};
export const wineShop = {
  rollValue: 10,
  cost: 3,
  industry: 'wheat',
  cardDescription: "Get 3 coins from the bank,on anyone's turn",
  action: () => {
    playersArr.forEach(player => {
      let playerObj = ref.players[player]
      let numCards = playerObj.cards.wineShop || 0
      playerObj.money += numCards * 3 //is this a proper way to update the firebase database?
    })
  },
  imgURL: ''
};
export const restaurant = {
  rollValue: 11 || 12,
  cost: 2,
  industry: 'fruit',
  cardDescription: "Get 2 coins from the bank for each [wheat icon] establishment that you own. On your turn only",
  action: () => {
    let numCards = 0
    numCards += currentTurn.cards.farmersMarket || 0 //farmersMarket and wine are the only card types
    numCards += currentTurn.cards.wineShop || 0
    currentTurn.money += numCards * 2 //is this a proper way to update the firebase database?
  },
  imgURL: ''
};

//UNLOCKABLE CARDS BELOW

export const radioTower = {
  cost: 22,
  cardDescription: 'Once Every Turn You can Choose To Re-Roll Your Dice'
};
export const shoppingMall = {
  cost: 10,
  cardDescription: 'If you roll doubles, take another turn after this one'
};
export const coneyIsland = {
  cost: 16,
  cardDescription: 'Each of your [mug icon] and [building icon] establishments earn +1 coin',

  //  THIS FUNCTION NEEDS WORK
  // action: () => {
  //   playersArr.forEach(player => {
  //     let sumToLose = 0
  //     let numCards = 0
  //     if (player !== ref.turn){
  //       let playerObj = ref.players[player]
  //       //bakery card calcs
  //       numCards += playerObj.cards.bakery || 0
  //       //cafe card calcs
  //       numCards += playerObj.cards.cafe || 0
  //       sumToLose += playerObj.cards.cafe || 0
  //       //convenienceStore calcs
  //       numCards += playerObj.cards.convenienceStore || 0
  //       //bodega calcs
  //       numCards += playerObj.cards.bodega || 0
  //       sumToLose += playerObj.cards.bodega || 0

  //       currentTurn.money += numCards //is this a proper way to update the firebase database?
  //       if (currentTurn.money >= sumToLose) {
  //         currentTurn.money -= sumToLose //is this a proper way to update the firebase database?
  //       } else {
  //         currentTurn.money = 0 //is this a proper way to update the firebase database?
  //       }
  //     }
  //   })
  // }
};
export const subwayStation = {
  cost: 4,
  cardDescription: 'You may roll 1 or 2 dice'
};
<|endoftext|>


<commit_before>/*
Core database initiation and connection
 */

const async = require('async'),
	config = require('../config'),
	redisDB = require('redis'),
	r = require('rethinkdb');

const dbVersion = 2;
let rcon;

// Buffers commands, so no need for callback
function redisClient() {
	const client = redisDB.createClient({
		host: config.redis_host,
		port: config.REDIS_PORT
	});
	client.select(config.redis_database || 0);
	return client;
}
exports.redisClient = redisClient;
const redis = global.redis = redisClient();
redis.on('error', err => winston.error('Redis error:', err));

// Establish rethinkDB connection and intialize the database
function init(cb) {
	async.waterfall([
		next =>
			r.connect({
				host: config.rethink_host,
				port: config.rethinkdb_port
			}, next),
		(conn, next) => {
			rcon = global.rcon = conn;

			// Check if database exists
			r.dbList().contains('meguca').do(exists =>
				r.branch(exists,
					{dbs_created: 0},
					r.dbCreate('meguca'))
			).run(rcon, next);
		},
		(res, next) => {
			rcon.use('meguca');
			createTables(['main'], next);
		},
		(res, next) =>
			r.table('main').get('info').run(rcon, next),
		// Intialize main table or check version
		(info, next) => {
			if (info) {
				verifyVersion(info.dbVersion, 'RethinkDB');
				next(null, null);
			}
			else {
				r.table('main').insert([
					{id: 'info', dbVersion},
					{id: 'post_ctr'},
					{id: 'cache'}
				]).run(rcon, next);
			}
		},
		// Check redis version
		(res, next) =>
			redis.get('dbVersion', next),
		(version, next) => {
			if (version)
				verifyVersion(parseInt(version), 'Redis');
			next();
		},
		initBoards
		// Pass connection to callback
	], err => cb(err, rcon));
}
exports.init = init;

// Create tables, if they don't exist
function createTables(tables, cb) {
	r.expr(tables)
		.difference(r.tableList())
		.forEach(name => r.tableCreate(name))
		.run(rcon, cb);
}

function verifyVersion(version, dbms) {
	if (version !== dbVersion) {
		throw new Error(`Incompatible ${dbms} database version: ${version} ;`
			+ 'See docs/migration.md');
	}
}

function initBoards(cb) {
	createTables(config.BOARDS.map((board => '_' + board)), err => cb(err))
}
<commit_msg>Change database table initialisation and naming<commit_after>/*
Core database initiation and connection
 */

const async = require('async'),
	config = require('../config'),
	redisDB = require('redis'),
	r = require('rethinkdb');

const dbVersion = 2;
let rcon;

// Buffers commands, so no need for callback
function redisClient() {
	const client = redisDB.createClient({
		host: config.redis_host,
		port: config.REDIS_PORT
	});
	client.select(config.redis_database || 0);
	return client;
}
exports.redisClient = redisClient;
const redis = global.redis = redisClient();
redis.on('error', err => winston.error('Redis error:', err));

// Establish rethinkDB connection and intialize the database
function init(cb) {
	async.waterfall([
		next =>
			r.connect({
				host: config.rethink_host,
				port: config.rethinkdb_port
			}, next),
		(conn, next) => {
			rcon = global.rcon = conn;

			// Check if database exists
			r.dbList().contains('meguca').do(exists =>
				r.branch(exists, {}, r.dbCreate('meguca'))
			).run(rcon, next);
		},
		(res, next) => {
			rcon.use('meguca');

			// Create all tables at once
			createTables(['_main'].concat(config.BOARDS), next);
		},
		(res, next) =>
			r.table('_main').get('info').run(rcon, next),
		// Intialize main table or check version
		(info, next) => {
			if (info) {
				verifyVersion(info.dbVersion, 'RethinkDB');
				next(null, null);
			}
			else {
				r.table('_main').insert({id: 'info', dbVersion})
					.run(rcon, next);
			}
		},
		// Check redis version
		(res, next) =>
			redis.get('dbVersion', next),
		(version, next) => {
			if (version)
				verifyVersion(parseInt(version), 'Redis');
			next();
		}
		// Pass connection to callback
	], err => cb(err, rcon));
}
exports.init = init;

// Create tables, if they don't exist
function createTables(tables, cb) {
	r.expr(tables)
		.difference(r.tableList())
		.forEach(name => r.tableCreate(name))
		.run(rcon, cb);
}

function verifyVersion(version, dbms) {
	if (version !== dbVersion) {
		throw new Error(`Incompatible ${dbms} database version: ${version}; `
			+ 'See docs/migration.md');
	}
}
<|endoftext|>


<commit_before>(function () {
  var moduleName = 'feeds';

  RemoteStorage.defineModule(moduleName, function(privateClient, publicClient) {

    var md5 = function(str) {
      //
      // http://www.myersdaily.org/joseph/javascript/md5.js
      //
      function md5cycle(x, k) {
        var a = x[0], b = x[1], c = x[2], d = x[3];

        a = ff(a, b, c, d, k[0], 7, -680876936);
        d = ff(d, a, b, c, k[1], 12, -389564586);
        c = ff(c, d, a, b, k[2], 17,  606105819);
        b = ff(b, c, d, a, k[3], 22, -1044525330);
        a = ff(a, b, c, d, k[4], 7, -176418897);
        d = ff(d, a, b, c, k[5], 12,  1200080426);
        c = ff(c, d, a, b, k[6], 17, -1473231341);
        b = ff(b, c, d, a, k[7], 22, -45705983);
        a = ff(a, b, c, d, k[8], 7,  1770035416);
        d = ff(d, a, b, c, k[9], 12, -1958414417);
        c = ff(c, d, a, b, k[10], 17, -42063);
        b = ff(b, c, d, a, k[11], 22, -1990404162);
        a = ff(a, b, c, d, k[12], 7,  1804603682);
        d = ff(d, a, b, c, k[13], 12, -40341101);
        c = ff(c, d, a, b, k[14], 17, -1502002290);
        b = ff(b, c, d, a, k[15], 22,  1236535329);

        a = gg(a, b, c, d, k[1], 5, -165796510);
        d = gg(d, a, b, c, k[6], 9, -1069501632);
        c = gg(c, d, a, b, k[11], 14,  643717713);
        b = gg(b, c, d, a, k[0], 20, -373897302);
        a = gg(a, b, c, d, k[5], 5, -701558691);
        d = gg(d, a, b, c, k[10], 9,  38016083);
        c = gg(c, d, a, b, k[15], 14, -660478335);
        b = gg(b, c, d, a, k[4], 20, -405537848);
        a = gg(a, b, c, d, k[9], 5,  568446438);
        d = gg(d, a, b, c, k[14], 9, -1019803690);
        c = gg(c, d, a, b, k[3], 14, -187363961);
        b = gg(b, c, d, a, k[8], 20,  1163531501);
        a = gg(a, b, c, d, k[13], 5, -1444681467);
        d = gg(d, a, b, c, k[2], 9, -51403784);
        c = gg(c, d, a, b, k[7], 14,  1735328473);
        b = gg(b, c, d, a, k[12], 20, -1926607734);

        a = hh(a, b, c, d, k[5], 4, -378558);
        d = hh(d, a, b, c, k[8], 11, -2022574463);
        c = hh(c, d, a, b, k[11], 16,  1839030562);
        b = hh(b, c, d, a, k[14], 23, -35309556);
        a = hh(a, b, c, d, k[1], 4, -1530992060);
        d = hh(d, a, b, c, k[4], 11,  1272893353);
        c = hh(c, d, a, b, k[7], 16, -155497632);
        b = hh(b, c, d, a, k[10], 23, -1094730640);
        a = hh(a, b, c, d, k[13], 4,  681279174);
        d = hh(d, a, b, c, k[0], 11, -358537222);
        c = hh(c, d, a, b, k[3], 16, -722521979);
        b = hh(b, c, d, a, k[6], 23,  76029189);
        a = hh(a, b, c, d, k[9], 4, -640364487);
        d = hh(d, a, b, c, k[12], 11, -421815835);
        c = hh(c, d, a, b, k[15], 16,  530742520);
        b = hh(b, c, d, a, k[2], 23, -995338651);

        a = ii(a, b, c, d, k[0], 6, -198630844);
        d = ii(d, a, b, c, k[7], 10,  1126891415);
        c = ii(c, d, a, b, k[14], 15, -1416354905);
        b = ii(b, c, d, a, k[5], 21, -57434055);
        a = ii(a, b, c, d, k[12], 6,  1700485571);
        d = ii(d, a, b, c, k[3], 10, -1894986606);
        c = ii(c, d, a, b, k[10], 15, -1051523);
        b = ii(b, c, d, a, k[1], 21, -2054922799);
        a = ii(a, b, c, d, k[8], 6,  1873313359);
        d = ii(d, a, b, c, k[15], 10, -30611744);
        c = ii(c, d, a, b, k[6], 15, -1560198380);
        b = ii(b, c, d, a, k[13], 21,  1309151649);
        a = ii(a, b, c, d, k[4], 6, -145523070);
        d = ii(d, a, b, c, k[11], 10, -1120210379);
        c = ii(c, d, a, b, k[2], 15,  718787259);
        b = ii(b, c, d, a, k[9], 21, -343485551);

        x[0] = add32(a, x[0]);
        x[1] = add32(b, x[1]);
        x[2] = add32(c, x[2]);
        x[3] = add32(d, x[3]);

      }

      function cmn(q, a, b, x, s, t) {
        a = add32(add32(a, q), add32(x, t));
        return add32((a << s) | (a >>> (32 - s)), b);
      }

      function ff(a, b, c, d, x, s, t) {
        return cmn((b & c) | ((~b) & d), a, b, x, s, t);
      }

      function gg(a, b, c, d, x, s, t) {
        return cmn((b & d) | (c & (~d)), a, b, x, s, t);
      }

      function hh(a, b, c, d, x, s, t) {
        return cmn(b ^ c ^ d, a, b, x, s, t);
      }

      function ii(a, b, c, d, x, s, t) {
        return cmn(c ^ (b | (~d)), a, b, x, s, t);
      }

      function md51(s) {
        txt = '';
        var n = s.length,
            state = [1732584193, -271733879, -1732584194, 271733878], i;
        for (i=64; i<=s.length; i+=64) {
          md5cycle(state, md5blk(s.substring(i-64, i)));
        }
        s = s.substring(i-64);
        var tail = [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0];
        for (i=0; i<s.length; i++)
          tail[i>>2] |= s.charCodeAt(i) << ((i%4) << 3);
        tail[i>>2] |= 0x80 << ((i%4) << 3);
        if (i > 55) {
          md5cycle(state, tail);
          for (i=0; i<16; i++) tail[i] = 0;
        }
        tail[14] = n*8;
        md5cycle(state, tail);
        return state;
      }

      function md5blk(s) {
        var md5blks = [], i;
        for (i=0; i<64; i+=4) {
          md5blks[i>>2] = s.charCodeAt(i)
            + (s.charCodeAt(i+1) << 8)
            + (s.charCodeAt(i+2) << 16)
            + (s.charCodeAt(i+3) << 24);
        }
        return md5blks;
      }

      var hex_chr = '0123456789abcdef'.split('');

      function rhex(n)
      {
        var s='', j=0;
        for(; j<4; j++)
          s += hex_chr[(n >> (j * 8 + 4)) & 0x0F]
            + hex_chr[(n >> (j * 8)) & 0x0F];
        return s;
      }

      function hex(x) {
        for (var i=0; i<x.length; i++)
          x[i] = rhex(x[i]);
        return x.join('');
      }

      function md5(s) {
        return hex(md51(s));
      }

      function add32(a, b) {
        return (a + b) & 0xFFFFFFFF;
      }

      if (md5('hello') != '5d41402abc4b2a76b9719d911017c592') {
        function add32(x, y) {
          var lsw = (x & 0xFFFF) + (y & 0xFFFF),
              msw = (x >> 16) + (y >> 16) + (lsw >> 16);
          return (msw << 16) | (lsw & 0xFFFF);
        }
      }

      return md5(str);
    };

    return {
      exports: {
        init: function () {
          privateClient.declareType(moduleName, {
            description: 'RSS Feed URLs and related settings',
            key: 'id',
            type: 'object',
            properties: {
              id : {
                type: 'string',
                description: 'md5sum of link as ID',
                required: true
              },
              url: {
                type: 'string',
                description: 'URL of the RSS feed',
                required: true,
                format: "uri"
              },
              name: {
                type: 'string',
                description: 'human readable name of feed',
                required: false
              },
              description: {
                type: 'string',
                description: 'human readable description of feed',
                required: false
              },
              image: {
                type: 'string',
                description: 'url of feed image (logo)',
                required: false
              },
              favicon: {
                type: 'string',
                description: 'url of feeds favicon',
                required: false
              },
              language: {
                type: 'string',
                description: 'language of feed',
                required: false
              },
              author: {
                type: 'string',
                description: 'author of feed',
                required: false
              },
              cache_articles: {
                type: 'number',
                description: 'number of articles to store',
                required: false
              },
              last_fetched: {
                type: 'number',
                description: 'last time feed was fetched',
                required: false
              },
              date_added: {
                type: 'number',
                description: 'date feed was added',
                required: true
              },
              date_updated: {
                type: 'number',
                description: 'date feed was added',
                required: true
              }
            }
          });
        },

        on: privateClient.on.bind(privateClient),

        remove: function (url) {
          if (typeof url === 'string') {
            url = md5(url);
          } else {
            return false;
          }
          return privateClient.remove(url);
        },

        add: function (obj) {
          if (typeof obj.url === 'string') {
            obj.id = md5(obj.url);
          } else {
            return false;
            //obj.id = privateClient.uuid();
          }
          if (!obj.date_added) {
            obj.date_added = new Date().getTime();
          }
          obj.date_updated = new Date().getTime();

          return privateClient.storeObject(moduleName, obj.id, obj);
        },

        md5sum: md5,

        get: privateClient.getObject.bind(privateClient),

        getAll: privateClient.getAll.bind(privateClient),

        getListing: privateClient.getListing.bind(privateClient)
      }
    };

  });
})();<commit_msg>whitespace<commit_after>(function () {
  var moduleName = 'feeds';

  RemoteStorage.defineModule(moduleName, function(privateClient, publicClient) {

    var md5 = function(str) {
      //
      // http://www.myersdaily.org/joseph/javascript/md5.js
      //
      function md5cycle(x, k) {
        var a = x[0], b = x[1], c = x[2], d = x[3];

        a = ff(a, b, c, d, k[0], 7, -680876936);
        d = ff(d, a, b, c, k[1], 12, -389564586);
        c = ff(c, d, a, b, k[2], 17,  606105819);
        b = ff(b, c, d, a, k[3], 22, -1044525330);
        a = ff(a, b, c, d, k[4], 7, -176418897);
        d = ff(d, a, b, c, k[5], 12,  1200080426);
        c = ff(c, d, a, b, k[6], 17, -1473231341);
        b = ff(b, c, d, a, k[7], 22, -45705983);
        a = ff(a, b, c, d, k[8], 7,  1770035416);
        d = ff(d, a, b, c, k[9], 12, -1958414417);
        c = ff(c, d, a, b, k[10], 17, -42063);
        b = ff(b, c, d, a, k[11], 22, -1990404162);
        a = ff(a, b, c, d, k[12], 7,  1804603682);
        d = ff(d, a, b, c, k[13], 12, -40341101);
        c = ff(c, d, a, b, k[14], 17, -1502002290);
        b = ff(b, c, d, a, k[15], 22,  1236535329);

        a = gg(a, b, c, d, k[1], 5, -165796510);
        d = gg(d, a, b, c, k[6], 9, -1069501632);
        c = gg(c, d, a, b, k[11], 14,  643717713);
        b = gg(b, c, d, a, k[0], 20, -373897302);
        a = gg(a, b, c, d, k[5], 5, -701558691);
        d = gg(d, a, b, c, k[10], 9,  38016083);
        c = gg(c, d, a, b, k[15], 14, -660478335);
        b = gg(b, c, d, a, k[4], 20, -405537848);
        a = gg(a, b, c, d, k[9], 5,  568446438);
        d = gg(d, a, b, c, k[14], 9, -1019803690);
        c = gg(c, d, a, b, k[3], 14, -187363961);
        b = gg(b, c, d, a, k[8], 20,  1163531501);
        a = gg(a, b, c, d, k[13], 5, -1444681467);
        d = gg(d, a, b, c, k[2], 9, -51403784);
        c = gg(c, d, a, b, k[7], 14,  1735328473);
        b = gg(b, c, d, a, k[12], 20, -1926607734);

        a = hh(a, b, c, d, k[5], 4, -378558);
        d = hh(d, a, b, c, k[8], 11, -2022574463);
        c = hh(c, d, a, b, k[11], 16,  1839030562);
        b = hh(b, c, d, a, k[14], 23, -35309556);
        a = hh(a, b, c, d, k[1], 4, -1530992060);
        d = hh(d, a, b, c, k[4], 11,  1272893353);
        c = hh(c, d, a, b, k[7], 16, -155497632);
        b = hh(b, c, d, a, k[10], 23, -1094730640);
        a = hh(a, b, c, d, k[13], 4,  681279174);
        d = hh(d, a, b, c, k[0], 11, -358537222);
        c = hh(c, d, a, b, k[3], 16, -722521979);
        b = hh(b, c, d, a, k[6], 23,  76029189);
        a = hh(a, b, c, d, k[9], 4, -640364487);
        d = hh(d, a, b, c, k[12], 11, -421815835);
        c = hh(c, d, a, b, k[15], 16,  530742520);
        b = hh(b, c, d, a, k[2], 23, -995338651);

        a = ii(a, b, c, d, k[0], 6, -198630844);
        d = ii(d, a, b, c, k[7], 10,  1126891415);
        c = ii(c, d, a, b, k[14], 15, -1416354905);
        b = ii(b, c, d, a, k[5], 21, -57434055);
        a = ii(a, b, c, d, k[12], 6,  1700485571);
        d = ii(d, a, b, c, k[3], 10, -1894986606);
        c = ii(c, d, a, b, k[10], 15, -1051523);
        b = ii(b, c, d, a, k[1], 21, -2054922799);
        a = ii(a, b, c, d, k[8], 6,  1873313359);
        d = ii(d, a, b, c, k[15], 10, -30611744);
        c = ii(c, d, a, b, k[6], 15, -1560198380);
        b = ii(b, c, d, a, k[13], 21,  1309151649);
        a = ii(a, b, c, d, k[4], 6, -145523070);
        d = ii(d, a, b, c, k[11], 10, -1120210379);
        c = ii(c, d, a, b, k[2], 15,  718787259);
        b = ii(b, c, d, a, k[9], 21, -343485551);

        x[0] = add32(a, x[0]);
        x[1] = add32(b, x[1]);
        x[2] = add32(c, x[2]);
        x[3] = add32(d, x[3]);

      }

      function cmn(q, a, b, x, s, t) {
        a = add32(add32(a, q), add32(x, t));
        return add32((a << s) | (a >>> (32 - s)), b);
      }

      function ff(a, b, c, d, x, s, t) {
        return cmn((b & c) | ((~b) & d), a, b, x, s, t);
      }

      function gg(a, b, c, d, x, s, t) {
        return cmn((b & d) | (c & (~d)), a, b, x, s, t);
      }

      function hh(a, b, c, d, x, s, t) {
        return cmn(b ^ c ^ d, a, b, x, s, t);
      }

      function ii(a, b, c, d, x, s, t) {
        return cmn(c ^ (b | (~d)), a, b, x, s, t);
      }

      function md51(s) {
        txt = '';
        var n = s.length,
            state = [1732584193, -271733879, -1732584194, 271733878], i;
        for (i=64; i<=s.length; i+=64) {
          md5cycle(state, md5blk(s.substring(i-64, i)));
        }
        s = s.substring(i-64);
        var tail = [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0];
        for (i=0; i<s.length; i++)
          tail[i>>2] |= s.charCodeAt(i) << ((i%4) << 3);
        tail[i>>2] |= 0x80 << ((i%4) << 3);
        if (i > 55) {
          md5cycle(state, tail);
          for (i=0; i<16; i++) tail[i] = 0;
        }
        tail[14] = n*8;
        md5cycle(state, tail);
        return state;
      }

      function md5blk(s) {
        var md5blks = [], i;
        for (i=0; i<64; i+=4) {
          md5blks[i>>2] = s.charCodeAt(i)
            + (s.charCodeAt(i+1) << 8)
            + (s.charCodeAt(i+2) << 16)
            + (s.charCodeAt(i+3) << 24);
        }
        return md5blks;
      }

      var hex_chr = '0123456789abcdef'.split('');

      function rhex(n)
      {
        var s='', j=0;
        for(; j<4; j++)
          s += hex_chr[(n >> (j * 8 + 4)) & 0x0F]
            + hex_chr[(n >> (j * 8)) & 0x0F];
        return s;
      }

      function hex(x) {
        for (var i=0; i<x.length; i++)
          x[i] = rhex(x[i]);
        return x.join('');
      }

      function md5(s) {
        return hex(md51(s));
      }

      function add32(a, b) {
        return (a + b) & 0xFFFFFFFF;
      }

      if (md5('hello') != '5d41402abc4b2a76b9719d911017c592') {
        function add32(x, y) {
          var lsw = (x & 0xFFFF) + (y & 0xFFFF),
              msw = (x >> 16) + (y >> 16) + (lsw >> 16);
          return (msw << 16) | (lsw & 0xFFFF);
        }
      }

      return md5(str);
    };

    return {
      exports: {
        init: function () {
          privateClient.declareType(moduleName, {
            description: 'RSS Feed URLs and related settings',
            key: 'id',
            type: 'object',
            properties: {
              id : {
                type: 'string',
                description: 'md5sum of link as ID',
                required: true
              },
              url: {
                type: 'string',
                description: 'URL of the RSS feed',
                required: true,
                format: "uri"
              },
              name: {
                type: 'string',
                description: 'human readable name of feed',
                required: false
              },
              description: {
                type: 'string',
                description: 'human readable description of feed',
                required: false
              },
              image: {
                type: 'string',
                description: 'url of feed image (logo)',
                required: false
              },
              favicon: {
                type: 'string',
                description: 'url of feeds favicon',
                required: false
              },
              language: {
                type: 'string',
                description: 'language of feed',
                required: false
              },
              author: {
                type: 'string',
                description: 'author of feed',
                required: false
              },
              cache_articles: {
                type: 'number',
                description: 'number of articles to store',
                required: false
              },
              last_fetched: {
                type: 'number',
                description: 'last time feed was fetched',
                required: false
              },
              date_added: {
                type: 'number',
                description: 'date feed was added',
                required: true
              },
              date_updated: {
                type: 'number',
                description: 'date feed was added',
                required: true
              }
            }
          });
        },

        on: privateClient.on.bind(privateClient),

        remove: function (url) {
          if (typeof url === 'string') {
            url = md5(url);
          } else {
            return false;
          }
          return privateClient.remove(url);
        },

        add: function (obj) {
          if (typeof obj.url === 'string') {
            obj.id = md5(obj.url);
          } else {
            return false;
            //obj.id = privateClient.uuid();
          }
          if (!obj.date_added) {
            obj.date_added = new Date().getTime();
          }
          obj.date_updated = new Date().getTime();

          return privateClient.storeObject(moduleName, obj.id, obj);
        },

        md5sum: md5,

        get: privateClient.getObject.bind(privateClient),

        getAll: privateClient.getAll.bind(privateClient),

        getListing: privateClient.getListing.bind(privateClient)
      }
    };

  });
})();<|endoftext|>


<commit_before>/* global _su, strWrap, Point, HexGrid */

class SkillPlanner {
  constructor () {
    this.checkDependencies()

    // Skill tree data
    this.skills = {}
    this.coords = {}
    this.links = {}
    this.tree = {}
    this.locale = {}
    this.colors = {}

    this.reset()

    // DEBUG
    this.DEBUG = false
    // END DEBUG
    this.initGrid()
  }

  checkDependencies () {
    if (typeof HexGrid !== 'function' || !_su) {
      console.log('Warning! Required dependencies not found. Please contact the dev for more info.')
    }
  }

  reset () {
    this.category = 'Firepower'
    this.active = new Set()
    this.orphan = new Set()
    this.possible = new Set()
    this.visibleActive = new Set()
    this.visibleOrphan = new Set()
    this.visiblePossible = new Set()
    this.effects = {}
    this.mouseDown = false
    this.prevHexId = null
  }

  initGrid () {
    let radius = 48
    let padding = 16
    let origin = new Point(-96, 128)

    let hexGrid = new HexGrid(radius, padding, origin)
    hexGrid.bindCanvas(document.getElementById('hex'))
    this.hexGrid = hexGrid

    let linkGrid = new HexGrid(radius, padding, origin)
    linkGrid.bindCanvas(document.getElementById('link'))
    this.linkGrid = linkGrid

    this.initEvents()
    this.initInterface()
  }

  initEvents () {
    let hexGrid = this.hexGrid
    let onMouseDown = (e) => {
      e.preventDefault()
      this.mouseDown = true
    }

    let onMouseMove = (e) => {
      e.preventDefault()
      if (this.mouseDown) {
        let point = hexGrid.getCursorCanvasPoint(e)
        let hex = hexGrid.getHexAtPoint(point)

        if (hex) {
          let hexId = this.skillHexToId(hex)

          if (hexId !== this.prevHexId) {
            this.toggleNode(hexId)
            this.prevHexId = hexId
          }
        }
      }
    }

    let onMouseUp = (e) => {
      e.preventDefault()
      let point = hexGrid.getCursorCanvasPoint(e)
      let hex = hexGrid.getHexAtPoint(point)

      if (hex) {
        let hexId = this.skillHexToId(hex)

        if (hexId !== this.prevHexId) {
          this.toggleNode(hexId)
          this.prevHexId = hexId
        }
      }
      this.prevHexId = null
      this.mouseDown = false
    }

    this.bind('mousedown', onMouseDown, this)
    this.bind('mousemove', onMouseMove, this)
    this.bind('mouseup', onMouseUp, this)
  }

  initInterface () {
    let bindEvent = (element, event, fn) => {
      document.getElementById(element).addEventListener('click', fn, false)
    }

    bindEvent('toggle-firepower', 'click', () => {
      this.category = 'Firepower'
      this.draw()
    })

    bindEvent('toggle-survival', 'click', () => {
      this.category = 'Survival'
      this.draw()
    })

    bindEvent('toggle-mobility', 'click', () => {
      this.category = 'Mobility'
      this.draw()
    })

    bindEvent('toggle-jumpjets', 'click', () => {
      this.category = 'Jump Jets'
      this.draw()
    })

    bindEvent('toggle-operations', 'click', () => {
      this.category = 'Operations'
      this.draw()
    })

    bindEvent('toggle-sensors', 'click', () => {
      this.category = 'Sensors'
      this.draw()
    })

    bindEvent('toggle-auxiliary', 'click', () => {
      this.category = 'Auxiliary'
      this.draw()
    })

    bindEvent('add-section', 'click', () => {
      this.active = _su.union(this.active, this.tree[this.category].nodes)
      this.draw()
    })

    bindEvent('clear-section', 'click', () => {
      this.active = _su.difference(this.active, this.tree[this.category].nodes)
      this.draw()
    })

    bindEvent('clear-all', 'click', () => {
      this.active = new Set()
      this.draw()
    })
  }

  loadData (cb) {
    let loadColors = this.getJSON('./data/colors.json')
      .then((colors) => {
        this.colors = colors
      })
      .catch((err) => {
        console.error(err)
      })

    let loadSkills = this.getJSON('./data/latest.json')
      .then((tree) => {
        for (let category in tree) {
          let subtree = tree[category]
          // Load all skill nodes
          Object.assign(this.skills, subtree.nodes)

          this.tree[category] = {
            'root': subtree['root'],
            'nodes': new Set()
          }

          // Hex coord to node lookup
          this.coords[category] = {}
          for (let id in subtree.nodes) {
            let skill = this.skills[id]
            let coord = `(${skill.col}, ${skill.row})`
            this.coords[category][coord] = id

            // Add node to subtree
            this.tree[category]['nodes'].add(id)
          }
        }

        for (let id in this.skills) {
          let skill = this.skills[id]
          // Process node links
          this.links[id] = []
          let links = skill.links
          for (let i = 0, len = links.length; i < len; i++) {
            if (links[i]) {
              this.links[id].push(links[i])
            }
          }
        }
      })
      .catch((err) => {
        console.error(err)
      })

    let loadLocale = this.getJSON('./data/locale.json')
      .then((locale) => {
        this.locale = locale
      })
      .catch((err) => {
        console.error(err)
      })

    Promise.all([loadColors, loadSkills, loadLocale])
      .then(() => {
        typeof cb === 'function' && cb()
      })
  }

  bind (action, fn, ctx) {
    document.addEventListener(action, fn.bind(ctx || this), false)
  }

  getJSON (url) {
    return new Promise((resolve, reject) => {
      let req = new window.XMLHttpRequest()
      req.open('GET', url, true)

      req.onload = function () {
        if (this.status >= 200 && this.status < 400) {
          resolve(JSON.parse(this.response))
        } else {
          reject(new Error('Failed to retrieve URL!'))
        }
      }

      req.send()
    })
  }

  skillIdToHex (id) {
    let col = this.skills[id].col
    let row = this.skills[id].row
    return `(${col}, ${row})`
  }

  skillHexToId (hex) {
    let col = hex.col
    let row = hex.row
    return this.coords[this.category][`(${col}, ${row})`]
  }

  // Toggle node state by ID
  toggleNode (nodeId) {
    // Ignore invalid nodes
    if (!nodeId || !this.tree[this.category].nodes.has(nodeId)) { return }

    if (this.active.has(nodeId)) {
      this.active.delete(nodeId)
    } else {
      this.active.add(nodeId)
    }
    this.draw()
  }

  // Draw skill node and label
  drawNode (skill, state) {
    let col = skill.col
    let row = skill.row
    this.hexGrid.drawHexCell(col, row, this.colors['cell'][state])

    let name = skill.name
    let text = (name.length > 12) ? strWrap(name) : name
    this.hexGrid.drawHexLabel(col, row, text, undefined, this.colors['label'][state])
  }

  // Recalculate state using DFS
  update () {
    let updateNodeState = () => {
      let active = new Set(this.active)
      let possible = new Set()
      let visited = new Set()

      let root = this.tree[this.category].root
      let stack = [root]

      if (!active.has(root)) {
        this.orphan = this.active
        this.possible = new Set(stack)
        return
      }

      while (stack.length) {
        let cur = stack.pop()
        active.delete(cur)
        visited.add(cur)
        let neighbors = planner.links[cur]
        for (let i = 0, len = neighbors.length; i < len; i++) {
          let neighbor = neighbors[i]
          // Neighbor not visited nor queued
          if (!visited.has(neighbor) && stack.indexOf(neighbor) < 0) {
            // Neighbor active and reachable
            if (active.has(neighbor)) {
              stack.push(neighbor)
            } else {
              possible.add(neighbor)
            }
          }
        }
      }

      this.orphan = active
      this.possible = possible
    }

    let updateNodeVisibility = () => {
      let categoryNodes = this.tree[this.category].nodes
      this.visibleActive = _su.intersect(this.active, categoryNodes)
      this.visibleOrphan = _su.intersect(this.orphan, categoryNodes)
      this.visiblePossible = _su.intersect(this.possible, categoryNodes)
    }

    let updateNodeEffects = () => {
      this.effects = {}

      for (let id of this.active) {
        let effects = this.skills[id]['effects']

        for (let i = 0, len = effects.length; i < len; i++) {
          let effect = effects[i]
          if (!this.effects[effect]) {
            this.effects[effect] = 0
          }
          this.effects[effect] += 1
        }
      }
    }

    // TODO: make interface updates a separate step
    let updateInterface = () => {
      document.getElementById('active-nodes').value = `${this.active.size} / 91`
      document.getElementById('category-nodes').value = `${this.visibleActive.size} / ${this.tree[this.category].nodes.size}`

      let effectText = ''
      for (let effect in this.effects) {
        // TODO: load proper values later!
        let effectValue = 1
        effectText += `${this.locale[effect]}: ${this.effects[effect] * effectValue}\n`
      }
      document.getElementById('effects-display').value = effectText
    }

    updateNodeState()
    updateNodeVisibility()
    updateNodeEffects()
    updateInterface()
  }

  // Draw planner; calls state change internally
  draw () {
    this.update()
    this.hexGrid.clear()
    this.linkGrid.clear()
    let categoryNodes = this.tree[this.category].nodes

    // Draw nodes of current category
    for (let id of categoryNodes) {
      if (id) {
        let node = this.skills[id]
        planner.drawNode(node, 'inactive')

        let links = this.links[id]
        for (let i = 0, len = links.length; i < len; i++) {
          let dest = this.skills[links[i]]
          if (dest) {
            this.linkGrid.drawHexLink(node.col, node.row, dest.col, dest.row, this.colors['link']['inactive'])
          }
        }

        // BEGIN DEBUG
        if (this.DEBUG) {
          this.hexGrid.drawHexCell(node.col, node.row, this.colors['cell']['inactive'])
          this.hexGrid.drawHexLabel(node.col, node.row, id, this.colors['label']['inactive'])
        }  // END DEBUG
      }
    }

    // Draw visible active nodes and links
    for (let id of this.visibleActive) {
      if (id) {
        let node = this.skills[id]
        planner.drawNode(node, 'active')

        let links = this.links[id]
        for (let i = 0, len = links.length; i < len; i++) {
          let dest = this.skills[links[i]]
          if (dest) {
            this.linkGrid.drawHexLink(node.col, node.row, dest.col, dest.row, this.colors['link']['active'])
          }
        }

        // BEGIN DEBUG
        if (this.DEBUG) {
          this.hexGrid.drawHexCell(node.col, node.row, this.colors['cell']['active'])
          this.hexGrid.drawHexLabel(node.col, node.row, id, undefined, this.colors['cell']['active'])
        }  // END DEBUG
      }
    }

    // Draw visible possible nodes and links
    for (let id of this.visiblePossible) {
      if (id) {
        let node = this.skills[id]
        planner.drawNode(node, 'possible')

        let links = this.links[id]
        for (let i = 0, len = links.length; i < len; i++) {
          let dest = this.skills[links[i]]
          if (dest) {
            this.linkGrid.drawHexLink(node.col, node.row, dest.col, dest.row, this.colors['link']['possible'])
          }
        }

        // BEGIN DEBUG
        if (this.DEBUG) {
          this.hexGrid.drawHexCell(node.col, node.row, this.colors['cell']['possible'])
          this.hexGrid.drawHexLabel(node.col, node.row, id, undefined, this.colors['cell']['possible'])
        }  // END DEBUG
      }
    }

    // Draw visible orphan nodes and links
    for (let id of this.visibleOrphan) {
      if (id) {
        let node = this.skills[id]
        planner.drawNode(node, 'orphan')

        let links = this.links[id]
        for (let i = 0, len = links.length; i < len; i++) {
          let dest = this.skills[links[i]]
          if (dest) {
            this.linkGrid.drawHexLink(node.col, node.row, dest.col, dest.row, this.colors['link']['orphan'])
          }
        }

        // BEGIN DEBUG
        if (this.DEBUG) {
          this.hexGrid.drawHexCell(node.col, node.row, this.colors['cell']['orphan'])
          this.hexGrid.drawHexLabel(node.col, node.row, id, undefined, this.colors['cell']['orphan'])
        }  // END DEBUG
      }
    }
  }

  exportImage () {
    let image = this.hexGrid.canvas.toDataURL('image/png')
    let file = image.replace('image/png', 'image/octet-stream')
    window.location.href = file
  }
}

const planner = new SkillPlanner()
planner.loadData(() => {
  planner.draw()
})
<commit_msg>Added sessionStorage support<commit_after>/* global _su, strWrap, Point, HexGrid */

class SkillPlanner {
  constructor () {
    this.checkDependencies()

    // Skill tree data
    this.skills = {}
    this.coords = {}
    this.links = {}
    this.tree = {}
    this.locale = {}
    this.colors = {}

    this.reset()

    // DEBUG
    this.DEBUG = false
    // END DEBUG
    this.initGrid()
  }

  checkDependencies () {
    if (typeof HexGrid !== 'function' || !_su) {
      console.log('Warning! Required dependencies not found. Please contact the dev for more info.')
    }
  }

  reset () {
    this.category = 'Firepower'
    this.active = new Set()
    this.orphan = new Set()
    this.possible = new Set()
    this.visibleActive = new Set()
    this.visibleOrphan = new Set()
    this.visiblePossible = new Set()
    this.effects = {}
    this.mouseDown = false
    this.prevHexId = null
  }

  initGrid () {
    let radius = 48
    let padding = 16
    let origin = new Point(-96, 128)

    let hexGrid = new HexGrid(radius, padding, origin)
    hexGrid.bindCanvas(document.getElementById('hex'))
    this.hexGrid = hexGrid

    let linkGrid = new HexGrid(radius, padding, origin)
    linkGrid.bindCanvas(document.getElementById('link'))
    this.linkGrid = linkGrid

    this.initEvents()
    this.initInterface()
  }

  initEvents () {
    let hexGrid = this.hexGrid
    let onMouseDown = (e) => {
      e.preventDefault()
      this.mouseDown = true
    }

    let onMouseMove = (e) => {
      e.preventDefault()
      if (this.mouseDown) {
        let point = hexGrid.getCursorCanvasPoint(e)
        let hex = hexGrid.getHexAtPoint(point)

        if (hex) {
          let hexId = this.skillHexToId(hex)

          if (hexId !== this.prevHexId) {
            this.toggleNode(hexId)
            this.prevHexId = hexId
          }
        }
      }
    }

    let onMouseUp = (e) => {
      e.preventDefault()
      let point = hexGrid.getCursorCanvasPoint(e)
      let hex = hexGrid.getHexAtPoint(point)

      if (hex) {
        let hexId = this.skillHexToId(hex)

        if (hexId !== this.prevHexId) {
          this.toggleNode(hexId)
          this.prevHexId = hexId
        }
      }
      this.prevHexId = null
      this.mouseDown = false
    }

    this.bind('mousedown', onMouseDown, this)
    this.bind('mousemove', onMouseMove, this)
    this.bind('mouseup', onMouseUp, this)
  }

  initInterface () {
    let bindEvent = (element, event, fn) => {
      document.getElementById(element).addEventListener('click', fn, false)
    }

    bindEvent('toggle-firepower', 'click', () => {
      this.category = 'Firepower'
      this.draw()
    })

    bindEvent('toggle-survival', 'click', () => {
      this.category = 'Survival'
      this.draw()
    })

    bindEvent('toggle-mobility', 'click', () => {
      this.category = 'Mobility'
      this.draw()
    })

    bindEvent('toggle-jumpjets', 'click', () => {
      this.category = 'Jump Jets'
      this.draw()
    })

    bindEvent('toggle-operations', 'click', () => {
      this.category = 'Operations'
      this.draw()
    })

    bindEvent('toggle-sensors', 'click', () => {
      this.category = 'Sensors'
      this.draw()
    })

    bindEvent('toggle-auxiliary', 'click', () => {
      this.category = 'Auxiliary'
      this.draw()
    })

    bindEvent('add-section', 'click', () => {
      this.active = _su.union(this.active, this.tree[this.category].nodes)
      this.draw()
    })

    bindEvent('clear-section', 'click', () => {
      this.active = _su.difference(this.active, this.tree[this.category].nodes)
      this.draw()
    })

    bindEvent('clear-all', 'click', () => {
      this.active = new Set()
      this.draw()
    })
  }

  loadData (cb) {
    let loadColors = this.getJSON('./data/colors.json')
      .then((colors) => {
        this.colors = colors
      })
      .catch((err) => {
        console.error(err)
      })

    let loadSkills = this.getJSON('./data/latest.json')
      .then((tree) => {
        for (let category in tree) {
          let subtree = tree[category]
          // Load all skill nodes
          Object.assign(this.skills, subtree.nodes)

          this.tree[category] = {
            'root': subtree['root'],
            'nodes': new Set()
          }

          // Hex coord to node lookup
          this.coords[category] = {}
          for (let id in subtree.nodes) {
            let skill = this.skills[id]
            let coord = `(${skill.col}, ${skill.row})`
            this.coords[category][coord] = id

            // Add node to subtree
            this.tree[category]['nodes'].add(id)
          }
        }

        for (let id in this.skills) {
          let skill = this.skills[id]
          // Process node links
          this.links[id] = []
          let links = skill.links
          for (let i = 0, len = links.length; i < len; i++) {
            if (links[i]) {
              this.links[id].push(links[i])
            }
          }
        }
      })
      .catch((err) => {
        console.error(err)
      })

    let loadLocale = this.getJSON('./data/locale.json')
      .then((locale) => {
        this.locale = locale
      })
      .catch((err) => {
        console.error(err)
      })

    Promise.all([loadColors, loadSkills, loadLocale])
      .then(() => {
        typeof cb === 'function' && cb()
      })
  }

  bind (action, fn, ctx) {
    document.addEventListener(action, fn.bind(ctx || this), false)
  }

  getJSON (url) {
    return new Promise((resolve, reject) => {
      let req = new window.XMLHttpRequest()
      req.open('GET', url, true)

      req.onload = function () {
        if (this.status >= 200 && this.status < 400) {
          resolve(JSON.parse(this.response))
        } else {
          reject(new Error('Failed to retrieve URL!'))
        }
      }

      req.send()
    })
  }

  skillIdToHex (id) {
    let col = this.skills[id].col
    let row = this.skills[id].row
    return `(${col}, ${row})`
  }

  skillHexToId (hex) {
    let col = hex.col
    let row = hex.row
    return this.coords[this.category][`(${col}, ${row})`]
  }

  // Toggle node state by ID
  toggleNode (nodeId) {
    // Ignore invalid nodes
    if (!nodeId || !this.tree[this.category].nodes.has(nodeId)) { return }

    if (this.active.has(nodeId)) {
      this.active.delete(nodeId)
    } else {
      this.active.add(nodeId)
    }
    this.draw()
  }

  // Draw skill node and label
  drawNode (skill, state) {
    let col = skill.col
    let row = skill.row
    this.hexGrid.drawHexCell(col, row, this.colors['cell'][state])

    let name = skill.name
    let text = (name.length > 12) ? strWrap(name) : name
    this.hexGrid.drawHexLabel(col, row, text, undefined, this.colors['label'][state])
  }

  // Recalculate state using DFS
  update () {
    let updateNodeState = () => {
      let active = new Set(this.active)
      let possible = new Set()
      let visited = new Set()

      let root = this.tree[this.category].root
      let stack = [root]

      if (!active.has(root)) {
        this.orphan = this.active
        this.possible = new Set(stack)
        return
      }

      while (stack.length) {
        let cur = stack.pop()
        active.delete(cur)
        visited.add(cur)
        let neighbors = planner.links[cur]
        for (let i = 0, len = neighbors.length; i < len; i++) {
          let neighbor = neighbors[i]
          // Neighbor not visited nor queued
          if (!visited.has(neighbor) && stack.indexOf(neighbor) < 0) {
            // Neighbor active and reachable
            if (active.has(neighbor)) {
              stack.push(neighbor)
            } else {
              possible.add(neighbor)
            }
          }
        }
      }

      this.orphan = active
      this.possible = possible
    }

    let updateNodeVisibility = () => {
      let categoryNodes = this.tree[this.category].nodes
      this.visibleActive = _su.intersect(this.active, categoryNodes)
      this.visibleOrphan = _su.intersect(this.orphan, categoryNodes)
      this.visiblePossible = _su.intersect(this.possible, categoryNodes)
    }

    let updateNodeEffects = () => {
      this.effects = {}

      for (let id of this.active) {
        let effects = this.skills[id]['effects']

        for (let i = 0, len = effects.length; i < len; i++) {
          let effect = effects[i]
          if (!this.effects[effect]) {
            this.effects[effect] = 0
          }
          this.effects[effect] += 1
        }
      }
    }

    // TODO: make interface updates a separate step
    let updateInterface = () => {
      document.getElementById('active-nodes').value = `${this.active.size} / 91`
      document.getElementById('category-nodes').value = `${this.visibleActive.size} / ${this.tree[this.category].nodes.size}`

      let effectText = ''
      for (let effect in this.effects) {
        // TODO: load proper values later!
        let effectValue = 1
        effectText += `${this.locale[effect]}: ${this.effects[effect] * effectValue}\n`
      }
      document.getElementById('effects-display').value = effectText
    }

    updateNodeState()
    updateNodeVisibility()
    updateNodeEffects()
    updateInterface()
    this.saveSession()
  }

  // Draw planner; calls state change internally
  draw () {
    this.update()
    this.hexGrid.clear()
    this.linkGrid.clear()
    let categoryNodes = this.tree[this.category].nodes

    // Draw nodes of current category
    for (let id of categoryNodes) {
      if (id) {
        let node = this.skills[id]
        planner.drawNode(node, 'inactive')

        let links = this.links[id]
        for (let i = 0, len = links.length; i < len; i++) {
          let dest = this.skills[links[i]]
          if (dest) {
            this.linkGrid.drawHexLink(node.col, node.row, dest.col, dest.row, this.colors['link']['inactive'])
          }
        }

        // BEGIN DEBUG
        if (this.DEBUG) {
          this.hexGrid.drawHexCell(node.col, node.row, this.colors['cell']['inactive'])
          this.hexGrid.drawHexLabel(node.col, node.row, id, this.colors['label']['inactive'])
        }  // END DEBUG
      }
    }

    // Draw visible active nodes and links
    for (let id of this.visibleActive) {
      if (id) {
        let node = this.skills[id]
        planner.drawNode(node, 'active')

        let links = this.links[id]
        for (let i = 0, len = links.length; i < len; i++) {
          let dest = this.skills[links[i]]
          if (dest) {
            this.linkGrid.drawHexLink(node.col, node.row, dest.col, dest.row, this.colors['link']['active'])
          }
        }

        // BEGIN DEBUG
        if (this.DEBUG) {
          this.hexGrid.drawHexCell(node.col, node.row, this.colors['cell']['active'])
          this.hexGrid.drawHexLabel(node.col, node.row, id, undefined, this.colors['cell']['active'])
        }  // END DEBUG
      }
    }

    // Draw visible possible nodes and links
    for (let id of this.visiblePossible) {
      if (id) {
        let node = this.skills[id]
        planner.drawNode(node, 'possible')

        let links = this.links[id]
        for (let i = 0, len = links.length; i < len; i++) {
          let dest = this.skills[links[i]]
          if (dest) {
            this.linkGrid.drawHexLink(node.col, node.row, dest.col, dest.row, this.colors['link']['possible'])
          }
        }

        // BEGIN DEBUG
        if (this.DEBUG) {
          this.hexGrid.drawHexCell(node.col, node.row, this.colors['cell']['possible'])
          this.hexGrid.drawHexLabel(node.col, node.row, id, undefined, this.colors['cell']['possible'])
        }  // END DEBUG
      }
    }

    // Draw visible orphan nodes and links
    for (let id of this.visibleOrphan) {
      if (id) {
        let node = this.skills[id]
        planner.drawNode(node, 'orphan')

        let links = this.links[id]
        for (let i = 0, len = links.length; i < len; i++) {
          let dest = this.skills[links[i]]
          if (dest) {
            this.linkGrid.drawHexLink(node.col, node.row, dest.col, dest.row, this.colors['link']['orphan'])
          }
        }

        // BEGIN DEBUG
        if (this.DEBUG) {
          this.hexGrid.drawHexCell(node.col, node.row, this.colors['cell']['orphan'])
          this.hexGrid.drawHexLabel(node.col, node.row, id, undefined, this.colors['cell']['orphan'])
        }  // END DEBUG
      }
    }
  }

  exportImage () {
    let image = this.hexGrid.canvas.toDataURL('image/png')
    let file = image.replace('image/png', 'image/octet-stream')
    window.location.href = file
  }

  saveSession () {
    let session = JSON.stringify([...this.active])
    window.sessionStorage.setItem('active', session)
  }

  loadSesssion () {
    let session = window.sessionStorage.getItem('active')
    if (session) {
      this.active = new Set(JSON.parse(session))
    }
  }
}

const planner = new SkillPlanner()
planner.loadData(() => {
  planner.loadSesssion()
  planner.draw()
})
<|endoftext|>


<commit_before>// Copyright

'use strict';

var fs = require('fs')
var mkdirp = require('mkdirp')
var spawn = require('child_process').spawnSync;
var path = require('path');
var temp = require('temp').track();
var _ = require('underscore');

module.exports = function (grunt) {

  grunt.registerMultiTask('validate_html', 'W3C nu html validation', function() {

    var options = this.options({
      customtags: [],
      customattrs: [],
      templateSandwich: false,
    });

    var files = grunt.file.expand(this.filesSrc);

    var tmpFiles = _.map(files, function(item) {
      var tmpPath = path.join(temp.dir, item);
      mkdirp.sync(path.dirname(tmpPath))

      var itemContent = fs.readFileSync(item, {encoding: 'UTF-8'})
      if (options.templateSandwich === true) {
        var buns = '<!DOCTYPE html><title>NA</title>'+
                   '';
        itemContent = buns.replace('', itemContent);
      }
      fs.writeFileSync(tmpPath, itemContent);
      return tmpPath;
    });

    options.customtags = _.map(options.customtags, function(tag) {
        return tag.replace(/\*/g, '(.*)');
    });

    options.customattrs = _.map(options.customattrs, function(tag) {
        return tag.replace(/\*/g, '(.*)');
    });

    var vnuPath = ['-jar', path.normalize(path.join(__dirname, '../vnu/vnu.jar'))]
    var vnuOptions = ['--format', 'json']

    var args = [].concat(vnuPath).concat(vnuOptions).concat(tmpFiles);

    var child = spawn('java', args, {encoding:'UTF-8'});

    var errorReport = JSON.parse(child.stderr); //.replace(
            //new RegExp('file:' + temp.dir + '/', 'g'), '');

    errorReport.messages = _.filter(errorReport.messages, function(message) {
        var text = message.message;

        var customtag = _.find(options.customtags, function(tag) {
            var re = new RegExp('Element (.?)' + tag + '(.?) not allowed as child of element (.*)');

            if (re.test(text)) {
                return true;
            } else {
                return false;
            }
        })

        if (customtag === undefined) {
            return true;
        } else {
            return false;
        }
    });

    errorReport.messages = _.filter(errorReport.messages, function(message) {
        var text = message.message;

        var customattr = _.find(options.customattrs, function(attr) {
            var re = new RegExp('Attribute (.?)' + attr + '(.?) not allowed on element (.*) at this point.(.*)');

            if (re.test(text)) {
                return true;
            } else {
                return false;
            }
        })

        if (customattr === undefined) {
            return true;
        } else {
            return false;
        }
    })

    var prefixdir = temp.dir.replace(new RegExp('\\\\', 'g'), '/');

    if (errorReport.messages.length > 0) {
      _.each(errorReport.messages, function(message) {
        var file = message.url.replace(new RegExp('file:(.*)' + prefixdir + '/', 'g'), '');
        var msg = file + ':' + message.lastLine + ':' + message.firstColumn + ': ' + message.message;
        grunt.log.writeln(msg);
      });
      grunt.warn('HTML validation failed.')
    }
  });

};
<commit_msg>Report error for older versions of node<commit_after>// Copyright

'use strict';

var fs = require('fs')
var mkdirp = require('mkdirp')
var spawn = require('child_process').spawnSync;
var path = require('path');
var temp = require('temp').track();
var _ = require('underscore');

module.exports = function (grunt) {

  grunt.registerMultiTask('validate_html', 'W3C nu html validation', function() {

    if (spawn === undefined) {
      grunt.warn('Cannot find child_process.spawnSync - Update node.js');
    }

    var options = this.options({
      customtags: [],
      customattrs: [],
      templateSandwich: false,
    });

    var files = grunt.file.expand(this.filesSrc);

    var tmpFiles = _.map(files, function(item) {
      var tmpPath = path.join(temp.dir, item);
      mkdirp.sync(path.dirname(tmpPath))

      var itemContent = fs.readFileSync(item, {encoding: 'UTF-8'})
      if (options.templateSandwich === true) {
        var buns = '<!DOCTYPE html><title>NA</title>'+
                   '';
        itemContent = buns.replace('', itemContent);
      }
      fs.writeFileSync(tmpPath, itemContent);
      return tmpPath;
    });

    options.customtags = _.map(options.customtags, function(tag) {
        return tag.replace(/\*/g, '(.*)');
    });

    options.customattrs = _.map(options.customattrs, function(tag) {
        return tag.replace(/\*/g, '(.*)');
    });

    var vnuPath = ['-jar', path.normalize(path.join(__dirname, '../vnu/vnu.jar'))]
    var vnuOptions = ['--format', 'json']

    var args = [].concat(vnuPath).concat(vnuOptions).concat(tmpFiles);

    var child = spawn('java', args, {encoding:'UTF-8'});

    var errorReport = JSON.parse(child.stderr); //.replace(
            //new RegExp('file:' + temp.dir + '/', 'g'), '');

    errorReport.messages = _.filter(errorReport.messages, function(message) {
        var text = message.message;

        var customtag = _.find(options.customtags, function(tag) {
            var re = new RegExp('Element (.?)' + tag + '(.?) not allowed as child of element (.*)');

            if (re.test(text)) {
                return true;
            } else {
                return false;
            }
        })

        if (customtag === undefined) {
            return true;
        } else {
            return false;
        }
    });

    errorReport.messages = _.filter(errorReport.messages, function(message) {
        var text = message.message;

        var customattr = _.find(options.customattrs, function(attr) {
            var re = new RegExp('Attribute (.?)' + attr + '(.?) not allowed on element (.*) at this point.(.*)');

            if (re.test(text)) {
                return true;
            } else {
                return false;
            }
        })

        if (customattr === undefined) {
            return true;
        } else {
            return false;
        }
    })

    var prefixdir = temp.dir.replace(new RegExp('\\\\', 'g'), '/');

    if (errorReport.messages.length > 0) {
      _.each(errorReport.messages, function(message) {
        var file = message.url.replace(new RegExp('file:(.*)' + prefixdir + '/', 'g'), '');
        var msg = file + ':' + message.lastLine + ':' + message.firstColumn + ': ' + message.message;
        grunt.log.writeln(msg);
      });
      grunt.warn('HTML validation failed.')
    }
  });

};
<|endoftext|>


<commit_before>/*
 * grunt-update-json
 * https://github.com/andreaspizsa/grunt-update-json
 *
 * Copyright (c) 2013
 * Licensed under the MIT license.
 */
'use strict';

var _ = require('lodash'),
  pointer = require('json-pointer'),
  // avoid the lint demons
  jsonPath = require('JSONPath')["eval".slice()];

var re = {
  PATH_POINT: /^(\\)?((\$\.|\/)?.*)/,
  DELIM: /\s*,\s*/,
  RENAME: /^(.*?)\s*>\s*(.*?)$/
};

var taskName = 'update_json',
  taskDescription = 'Update JSON files with data from other JSON files';

// Parse a fieldspec, like ["field", "to<from", {"to": "from"}]
function mergeField(memo, key){
  var result = {},
    match;

  if(_.isPlainObject(key)){
    result = key;
  }else if(_.isString(key) && (match = key.match(re.RENAME))){
    result[match[2]] = match[1];
  }else{
    result[key] = null;
  }

  return _.merge(memo, result);
}

// factory for a reduce function, bound to the input, that can get
// the value out of the input
function expandField(input, grunt){
  var get = pointer(input);

  return function(memo, fin, fout){
    if(_.isString(fin)){
      var match = fin.match(re.PATH_POINT);
      // matched  ...with a `$.`       ...but not with a `\`
      if(match && match[3] === '$.' && !match[1]){
        // field name, starts with an unescaped `$`, treat as JSON Path
        memo[fout] = jsonPath(input, match[2]);
      }else if(match && match[3] === '/' && !match[1]){
        // field name, treat as a JSON pointer
        memo[fout] = get(match[2]);
      }else{
        memo[fout] = input[match[2]];
      }
    }else if(_.isFunction(fin)){
      // call a function
      memo[fout] = fin(input);
    }else if(_.isArray(fin)){
      // pick out the values
      memo[fout] = _.map(fin, function(value){
        return expandField(input)({}, value, "dummy")["dummy"];
      });
    }else if(_.isObject(fin)){
      // build up an object of something else
      memo[fout] = _.reduce(fin, expandField(input, grunt), {});
    }else if(_.isNull(fin)){
      // copy the value
      memo[fout] = input[fout];
    }else{
      grunt.fail.warn('Could not map `' + JSON.stringify(fin) + '` to `' +
        JSON.stringify(fout) + '`');
    }
    return memo;
  };
}

function normalizeFields(fields){
  // put fields in canonical (key-value object) form
  // first, break up a single string, if found
  if(_.isString(fields)){
    fields = fields.split(re.DELIM);
  }

  // then, turn an array of fieldspecs:
  //  ["field", "from>to", {"from": "to"}]
  if(_.isArray(fields)){
    fields = _.reduce(fields, mergeField, {});
  }

  return fields;
}

function updateJSON(grunt, files, fields, options){
  fields = normalizeFields(fields);
  options = options || {};
  files.forEach(function(file){
    var src = file.src;
    if(!src || _.isEmpty(src)){
      src = options.src;
      if(!src || _.isEmpty(src = grunt.file.expand(src))){
        grunt.fail.warn('No data found from which to update.');
      }
    }

    // load the current output, if it exists
    var output = grunt.file.exists(file.dest) ?
        grunt.file.readJSON(file.dest) : {},
      // build up a union object of src files
      input = src.reduce(function(data, src){
          return _.merge(data, grunt.file.readJSON(src));
        }, {});
    var copied = _.reduce(fields, expandField(input, grunt), {});

    grunt.file.write(
      file.dest,
      JSON.stringify(_.merge(output, copied), null, options.indent) + '\n'
    );
  });
}

updateJSON.taskName = taskName;
updateJSON.taskDescription = taskDescription;

module.exports = exports = updateJSON;
<commit_msg>tasks/lib/update_json.js: support sorting of keys<commit_after>/*
 * grunt-update-json
 * https://github.com/andreaspizsa/grunt-update-json
 *
 * Copyright (c) 2013
 * Licensed under the MIT license.
 */
'use strict';

var _ = require('lodash'),
  pointer = require('json-pointer'),
  // avoid the lint demons
  jsonPath = require('JSONPath')["eval".slice()],
  stringify = require('json-stable-stringify');

var re = {
  PATH_POINT: /^(\\)?((\$\.|\/)?.*)/,
  DELIM: /\s*,\s*/,
  RENAME: /^(.*?)\s*>\s*(.*?)$/
};

var taskName = 'update_json',
  taskDescription = 'Update JSON files with data from other JSON files';

// Parse a fieldspec, like ["field", "to<from", {"to": "from"}]
function mergeField(memo, key){
  var result = {},
    match;

  if(_.isPlainObject(key)){
    result = key;
  }else if(_.isString(key) && (match = key.match(re.RENAME))){
    result[match[2]] = match[1];
  }else{
    result[key] = null;
  }

  return _.merge(memo, result);
}

// factory for a reduce function, bound to the input, that can get
// the value out of the input
function expandField(input, grunt){
  var get = pointer(input);

  return function(memo, fin, fout){
    if(_.isString(fin)){
      var match = fin.match(re.PATH_POINT);
      // matched  ...with a `$.`       ...but not with a `\`
      if(match && match[3] === '$.' && !match[1]){
        // field name, starts with an unescaped `$`, treat as JSON Path
        memo[fout] = jsonPath(input, match[2]);
      }else if(match && match[3] === '/' && !match[1]){
        // field name, treat as a JSON pointer
        memo[fout] = get(match[2]);
      }else{
        memo[fout] = input[match[2]];
      }
    }else if(_.isFunction(fin)){
      // call a function
      memo[fout] = fin(input);
    }else if(_.isArray(fin)){
      // pick out the values
      memo[fout] = _.map(fin, function(value){
        return expandField(input)({}, value, "dummy")["dummy"];
      });
    }else if(_.isObject(fin)){
      // build up an object of something else
      memo[fout] = _.reduce(fin, expandField(input, grunt), {});
    }else if(_.isNull(fin)){
      // copy the value
      memo[fout] = input[fout];
    }else{
      grunt.fail.warn('Could not map `' + JSON.stringify(fin) + '` to `' +
        JSON.stringify(fout) + '`');
    }
    return memo;
  };
}

function normalizeFields(fields){
  // put fields in canonical (key-value object) form
  // first, break up a single string, if found
  if(_.isString(fields)){
    fields = fields.split(re.DELIM);
  }

  // then, turn an array of fieldspecs:
  //  ["field", "from>to", {"from": "to"}]
  if(_.isArray(fields)){
    fields = _.reduce(fields, mergeField, {});
  }

  return fields;
}

function updateJSON(grunt, files, fields, options){
  fields = normalizeFields(fields);
  options = options || {};
  files.forEach(function(file){
    var src = file.src;
    if(!src || _.isEmpty(src)){
      src = options.src;
      if(!src || _.isEmpty(src = grunt.file.expand(src))){
        grunt.fail.warn('No data found from which to update.');
      }
    }

    // load the current output, if it exists
    var output = grunt.file.exists(file.dest) ?
        grunt.file.readJSON(file.dest) : {},
      // build up a union object of src files
      input = src.reduce(function(data, src){
          return _.merge(data, grunt.file.readJSON(src));
        }, {});
    var copied = _.reduce(fields, expandField(input, grunt), {}),
      jsonStr;

    // if sorting was requested
    if (options.sort) {
      // do stringify of the passed object with sort
      jsonStr = stringify(_.merge(output, copied), { space: options.indent });
    } else {
      // do stringify only
      jsonStr = JSON.stringify(_.merge(output, copied), null, options.indent);
    }
    grunt.file.write(file.dest, jsonStr + '\n');
  });
}

updateJSON.taskName = taskName;
updateJSON.taskDescription = taskDescription;

module.exports = exports = updateJSON;
<|endoftext|>