diff --git a/githooks/update b/githooks/update index 242e7729f..d0c9ebf92 100755 --- a/githooks/update +++ b/githooks/update @@ -15,6 +15,7 @@ var crypto = require('crypto'); var exec = require('child_process').exec; var sugar = require('sugar'); +var thisFile = __filename; var thisDir = __dirname; var rootDir = path.resolve(thisDir, '..'); @@ -27,7 +28,7 @@ function updateIndex () { var indexContents = fs.readFileSync(path.resolve(rootDir, 'index.template.html'), { encoding: 'utf8' }); // add hashes to js and css files - console.log('Updating hashes...'); + console.log("Updating hashes..."); indexContents = indexContents.replace(/(src|href)="\/(.*?)\?[a-z0-9]*?"/g, function (a, b, c) { var hash = Math.random(); // just in case creating the hash fails try { @@ -38,10 +39,10 @@ function updateIndex () { return b + '="/' + c + '?' + hash + '"'; }); - console.log('Hashes updated.'); + console.log("Hashes updated."); // add news - console.log('Updating news...'); + console.log("Updating news..."); exec('php ' + path.resolve(thisDir, 'news-data.php'), function (error, stdout, stderr) { var newsData = [0, '[failed to retrieve news]']; if (!error && !stderr) { @@ -56,119 +57,191 @@ function updateIndex () { indexContents = indexContents.replace(//g, newsData[0]); indexContents = indexContents.replace(//g, newsData[1]); + console.log("News updated."); + console.log("Writing new `index.html` file..."); fs.writeFileSync(path.resolve(rootDir, 'index.html'), indexContents); - console.log('News updated.'); + console.log("File `index.html` successfully updated."); }); } +function updateLearnsets (callback) { + var reservedKeywords = ['return']; // `return` is the only ES3+ reserved keyword that (currently) raises conflicts + var numberRegExp = new RegExp('^[0-9]*'); + var reservedRegExp = new RegExp('^(' + reservedKeywords.join('|') + ')$', 'g'); + var alphabetize = function (a, b) {return a.localeCompare(b);}; + var getLsetGen = function (lset) {return lset.charAt(0);}; + + var padNumString = function (str) { + switch (str.length) { + case 1: return '00' + str; + case 2: return '0' + str; + case 3: return '' + str; + } + }; + var inLearnset = function (lset, learnset) { + var secondChar = lset.charAt(1); + if (secondChar !== 'L') return learnset.indexOf(lset) >= 0; + + var firstFragment = lset.substr(0, 2); + var levelFragment = lset.substr(2); + var paddedLevel = padNumString(levelFragment); + for (var i = 0, len = learnset.length; i < len; i++) { + if (learnset[i].substring(0, 2) !== firstFragment) continue; + // ignore PSdex starter moves sorting data + if (learnset[i].substring(2, 5) === paddedLevel) return true; + } + return false; + }; + var formatLset = function (lset) { + var secondChar = lset.charAt(1); + if (secondChar !== 'L') return lset; + var firstFragment = lset.substr(0, 2); + var levelFragment = lset.substr(2).match(numberRegExp)[0]; + var sortFragment = lset.substr(2 + levelFragment.length); + return firstFragment + padNumString(levelFragment) + sortFragment; + }; + + var Pokedex, Learnsets, oldLearnsetsG6, newLearnsetsG6; + newLearnsetsG6 = {}; + try { + Pokedex = require(path.join(rootDir, 'data', 'pokedex.js')).BattlePokedex; + Learnsets = require(path.join(rootDir, 'data', 'learnsets.js')).BattleLearnsets; + } catch (err) { + return callback(err); + } + try { + oldLearnsetsG6 = require(path.join(rootDir, 'data', 'learnsets-g6.js')).BattleLearnsets; + } catch (err) { + if (err.code !== 'MODULE_NOT_FOUND') return callback(err); + oldLearnsetsG6 = {}; + } + + for (var speciesid in Learnsets) { + if (!oldLearnsetsG6[speciesid] || !oldLearnsetsG6[speciesid].learnset) { + console.log("NEW ENTRY at learnsets-g6.js: " + Pokedex[speciesid].species + "."); + oldLearnsetsG6[speciesid] = {learnset: {}}; + } + } + for (var speciesid in oldLearnsetsG6) { + if (!oldLearnsetsG6[speciesid] || !oldLearnsetsG6[speciesid].learnset) return callback(new TypeError("Invalid `learnsets-g6.js` entry for " + speciesid + ".")); + if (!Learnsets[speciesid]) { + console.log("REMOVED ENTRY at learnsets-g6.js: " + Pokedex[speciesid].species + "."); + continue; + } + var newLearnset = {}; + var oldLearnset = oldLearnsetsG6[speciesid].learnset; + var fullLearnset = Learnsets[speciesid].learnset; + if (!fullLearnset) return callback(new TypeError("Invalid data at `learnsets.js` for " + speciesid + ".")); + + // clone + for (var moveid in oldLearnset) { + if (!Array.isArray(oldLearnset[moveid])) return callback(new TypeError("Invalid data at `learnsets-g6.js` for " + speciesid + ":" + moveid + ".")); + newLearnset[moveid] = oldLearnset[moveid].slice(); + } + + for (var moveid in fullLearnset) { + if (!Array.isArray(fullLearnset[moveid])) return callback(new TypeError("Invalid data at `learnsets.js` for " + speciesid + ":" + moveid + ".")); + + if (!newLearnset[moveid]) newLearnset[moveid] = []; + newLearnset[moveid] = newLearnset[moveid].map(formatLset); + for (var i = 0, len = fullLearnset[moveid].length; i < len; i++) { + if (getLsetGen(fullLearnset[moveid][i]) !== '6') continue; + if (inLearnset(fullLearnset[moveid][i], newLearnset[moveid])) continue; + newLearnset[moveid].push(formatLset(fullLearnset[moveid][i])); + } + newLearnset[moveid].sort(alphabetize); + } + + newLearnsetsG6[speciesid] = {learnset: newLearnset}; + } + + var buf = []; + var pokemonList = Object.values(Pokedex).sort(function (a, b) { + // Missingno. goes first (zeroth); afterwards, CAP in descending dex order (increasingly negative) + // Finally, standard Pokémon in ascending dex order + if (a.num <= 0 && b.num > 0) return -1; + if (b.num <= 0 && a.num > 0) return 1; + if (a.num <= 0 && b.num <= 0) return b.num - a.num; + return a.num - b.num; + }).map('species').map(toId); + + for (var i = 0, len = pokemonList.length; i < len; i++) { + var entry = newLearnsetsG6[pokemonList[i]]; + if (!entry || !entry.learnset) continue; + var lsetSerialized = '{' + Object.keys(entry.learnset).sort(alphabetize).map(function (moveid) { + return moveid.replace(reservedRegExp, '"$1"') + ':' + JSON.stringify(entry.learnset[moveid]); + }).join(',') + '}'; + buf.push(pokemonList[i] + ':{learnset:' + lsetSerialized + '}'); + } + + var writeStream = fs.createWriteStream(path.join(rootDir, 'data', 'learnsets-g6.js')).on('error', callback); + writeStream.write('exports.BattleLearnsets = {\n\t' + buf.join(',\n\t') + '\n};\n'); + writeStream.end(callback); +} + var pendingFiles = 0; +var indexStats, updateStats, indexMTime, updateMTime; +try { + indexStats = fs.statSync(path.join(rootDir, 'index.html')); + indexMTime = indexStats.mtime.getTime(); +} catch (err) { + if (err.code !== 'ENOENT') throw err; + // It doesn't exist currently, but it will by the end of the script execution. + // Any other error is unacceptable and will throw. +} +try { + updateStats = fs.statSync(thisFile); + updateMTime = indexStats.mtime.getTime(); +} catch (err) { + throw err; // !! +} // update learnsets-g6 +console.log("Updating file `data/learnsets-g6`..."); + pendingFiles++; -console.log('Updating file `data/learnsets-g6`...'); -var reservedKeywords = ['return']; // `return` is the only ES3+ reserved keyword that (currently) raises conflicts -var numberRegExp = new RegExp('^[0-9]*'); -var reservedRegExp = new RegExp('^(' + reservedKeywords.join('|') + ')$', 'g'); -var alphabetize = function (a, b) {return a.localeCompare(b)}; -var getLsetGen = function (lset) {return lset.charAt(0)}; +var learnsetsStats; +var learnsetsG6Stats; +var learnsetsG6ToUpdate = true; -var padNumString = function (str) { - switch (str.length) { - case 1: return '00' + str; - case 2: return '0' + str; - case 3: return '' + str; - } -}; -var inLearnset = function (lset, learnset) { - var secondChar = lset.charAt(1); - if (secondChar !== 'L') return learnset.indexOf(lset) >= 0; - - var firstFragment = lset.substr(0, 2); - var levelFragment = lset.substr(2); - var paddedLevel = padNumString(levelFragment); - for (var i = 0, len = learnset.length; i < len; i++) { - if (learnset[i].substring(0, 2) !== firstFragment) continue; - // ignore PSdex starter moves sorting data - if (learnset[i].substring(2, 5) === paddedLevel) return true; - } - return false; -}; -var formatLset = function (lset) { - var secondChar = lset.charAt(1); - if (secondChar !== 'L') return lset; - var firstFragment = lset.substr(0, 2); - var levelFragment = lset.substr(2).match(numberRegExp)[0]; - var sortFragment = lset.substr(2 + levelFragment.length); - return firstFragment + padNumString(levelFragment) + sortFragment; -}; - -var Learnsets = require(path.join(rootDir, 'data', 'learnsets.js')).BattleLearnsets; -var oldLearnsetsG6 = require(path.join(rootDir, 'data', 'learnsets-g6.js')).BattleLearnsets; -var Pokedex = require(path.join(rootDir, 'data', 'pokedex.js')).BattlePokedex; -var newLearnsetsG6 = {}; - -for (var speciesid in Learnsets) { - if (!oldLearnsetsG6[speciesid] || !oldLearnsetsG6[speciesid].learnset) { - console.log("NEW: " + Pokedex[speciesid].species + "."); - oldLearnsetsG6[speciesid] = {learnset: {}}; - } +try { + learnsetsStats = fs.statSync(path.join(rootDir, 'data', 'learnsets.js')); +} catch (err) { + // Couldn't find learnsets.js, but that's not the end of the world: skip to next task. + console.error("Couldn't find `data/learnsets.js`. Task aborted."); + learnsetsG6ToUpdate = false; + if (!--pendingFiles) updateIndex(); } -for (var speciesid in oldLearnsetsG6) { - if (!Learnsets[speciesid] || !Learnsets[speciesid].learnset) throw new Error("Missing learnsets.js entry for " + speciesid + "."); - if (!oldLearnsetsG6[speciesid] || !oldLearnsetsG6[speciesid].learnset) throw new Error("Invalid learnsets-g6.js entry for " + speciesid + "."); - - var fullLearnset = Learnsets[speciesid].learnset; - var oldLearnset = oldLearnsetsG6[speciesid].learnset; - var newLearnset = {}; - - // clone - for (var moveid in oldLearnset) { - if (!Array.isArray(oldLearnset[moveid])) throw new TypeError("Invalid data at learnsets-g6.js for " + speciesid + ":" + moveid + "."); - newLearnset[moveid] = oldLearnset[moveid].slice(); - } - - for (var moveid in fullLearnset) { - if (!Array.isArray(fullLearnset[moveid])) throw new TypeError("Invalid data at learnsets.js for " + speciesid + ":" + moveid + "."); - - if (!newLearnset[moveid]) newLearnset[moveid] = []; - newLearnset[moveid] = newLearnset[moveid].map(formatLset); - for (var i = 0, len = fullLearnset[moveid].length; i < len; i++) { - if (getLsetGen(fullLearnset[moveid][i]) !== '6') continue; - if (inLearnset(fullLearnset[moveid][i], newLearnset[moveid])) continue; - newLearnset[moveid].push(formatLset(fullLearnset[moveid][i])); +if (learnsetsG6ToUpdate) { + try { + learnsetsG6Stats = fs.statSync(path.join(rootDir, 'data', 'learnsets-g6.js')); + } catch (err) { + if (err.code === 'ENOENT') { + // It doesn't exist currently, but it will by the end of the script execution. + } else { + // Any other error is unacceptable for learnsets-g6.js update: skip to next task. + console.error("Failed to read `data/learnsets-g6.js`. Task aborted."); + learnsetsG6ToUpdate = false; + if (!--pendingFiles) updateIndex(); } - newLearnset[moveid].sort(alphabetize); } - - newLearnsetsG6[speciesid] = {learnset: newLearnset}; } -var buf = []; -var pokemonList = Object.values(Pokedex).sort(function (a, b) { - // Missingno. goes first (zeroth); afterwards, CAP in descending dex order (increasingly negative) - // Finally, standard Pokémon in ascending dex order - if (a.num <= 0 && b.num > 0) return -1; - if (b.num <= 0 && a.num > 0) return 1; - if (a.num <= 0 && b.num <= 0) return b.num - a.num; - return a.num - b.num; -}).map('species').map(toId); +if (learnsetsG6ToUpdate && (!indexStats || !learnsetsG6Stats || indexMTime < updateMTime || indexMTime < learnsetsStats.mtime.getTime() || indexMTime < learnsetsG6Stats.mtime.getTime())) { + // Only merge learnsets.js with learnsets-g6.js if any of those files, or this one, have been modified recently (or if we don't know what "recently" means) -for (var i = 0, len = pokemonList.length; i < len; i++) { - var entry = newLearnsetsG6[pokemonList[i]]; - if (!entry || !entry.learnset) continue; - var lsetSerialized = '{' + Object.keys(entry.learnset).sort(alphabetize).map(function (moveid) { - return moveid.replace(reservedRegExp, '"$1"') + ':' + JSON.stringify(entry.learnset[moveid]); - }).join(',') + '}'; - buf.push(pokemonList[i] + ':{learnset:' + lsetSerialized + '}'); + updateLearnsets(function (err) { + if (err) { + var stack = err.stack || ''; + stack = "File `data/learnsets-g6` failed to update.\n" + stack; + console.error(stack); + } else { + console.log("File `data/learnsets-g6` updated."); + } + if (!--pendingFiles) updateIndex(); + }); +} else { + console.log("Input files not modified. Task aborted."); + if (!--pendingFiles) updateIndex(); } - -var writeStream = fs.createWriteStream(path.join(rootDir, 'data', 'learnsets-g6.js')).on('error', function (err) { - console.error(err.stack || err); -}); -writeStream.write('exports.BattleLearnsets = {\n\t' + buf.join(',\n\t') + '\n};\n'); -writeStream.end(function () { - console.log('File `data/learnsets-g6` updated.'); - pendingFiles--; - if (!pendingFiles) updateIndex(); -});