diff --git a/lib/factorio/index.js b/lib/factorio/index.js index f5f95b85..f682bbd2 100644 --- a/lib/factorio/index.js +++ b/lib/factorio/index.js @@ -1,3 +1,4 @@ module.exports = { + ...require('lib/factorio/locale'), ...require('lib/factorio/server'), }; diff --git a/lib/factorio/locale.js b/lib/factorio/locale.js new file mode 100644 index 00000000..36ee8a1e --- /dev/null +++ b/lib/factorio/locale.js @@ -0,0 +1,28 @@ +/** + * Function for parsing factorio locale from a local factorio install + */ + +"use strict"; +const fs = require("fs-extra"); +const path = require("path"); +const ini = require("ini"); + +/** + * Gets factorios locale as an object, does not respect mods. + * + * @param {string} factorioDirectory + * @param {string} languageCode + * @param {function} callback + */ +async function getLocale(dataDir, languageCode) { + if (typeof dataDir !== "string") throw new TypeError("dataDir must be a string"); + if (typeof languageCode !== "string") throw new TypeError("languageCode must be a string"); + + let localeFilePath = path.join(dataDir, "base", "locale", languageCode, "base.cfg"); + let content = await fs.readFile(localeFilePath, "utf8"); + return ini.parse(content); +} + +module.exports = { + getLocale, +}; diff --git a/lib/getFactorioLocale.js b/lib/getFactorioLocale.js deleted file mode 100644 index 72d84dd0..00000000 --- a/lib/getFactorioLocale.js +++ /dev/null @@ -1,53 +0,0 @@ -/** -Module for grabbing factorio locale from a local factorio install -@module getFactorioLocale -*/ - -const fs = require("fs"); -const ini = require("ini"); - -const cache = {}; - -/** -Gets raw factorio locale from the .ini file. -Not available outside of module through exports. - -@memberof getFactorioLocale -@inner -@param {string} factorioDirectory -@param {string} languageCode -@param {function} callback -*/ -function getLocale(factorioDirectory, languageCode, callback){ - var localeFile = factorioDirectory + '/data/base/locale/' + languageCode + '/base.cfg'; - fs.readFile(localeFile, "utf8", (err, rawLocale) => { - callback(err, rawLocale); - }); -} - -/** -Gets factorios locale as an object, does not respect mods. - -@param {string} factorioDirectory -@param {string} languageCode -@param {function} callback -*/ -module.exports.asObject = function getLocaleAsObject(factorioDirectory, languageCode, callback){ - if(typeof factorioDirectory != "string" || typeof languageCode != "string" || typeof callback != "function"){ - throw new Error("Error: wrong parameters provided"); - } - - if(!cache[languageCode]){ - getLocale(factorioDirectory, languageCode, (err, rawLocale) => { - if(err){ - callback(err); - } else { - let processedLocale = ini.parse(rawLocale); - cache[languageCode] = processedLocale; - callback(undefined, processedLocale); - } - }); - } else { - callback(undefined, cache[languageCode]); - } -} diff --git a/master.js b/master.js index e0a1986c..541752b7 100644 --- a/master.js +++ b/master.js @@ -32,9 +32,9 @@ let config = {}; // homebrew modules const generateSSLcert = require("lib/generateSSLcert"); -const getFactorioLocale = require("lib/getFactorioLocale"); const pluginManager = require("lib/manager/pluginManager.js"); const database = require("lib/database"); +const factorio = require("lib/factorio"); // homemade express middleware for token auth const authenticate = require("lib/authenticate"); @@ -670,19 +670,26 @@ app.post("/api/runCommand", authenticate.middleware, function(req,res) { res.status(400).send({auth: true, message: "Error: invalid request.body"}); } }); + +var localeCache; /** GET endpoint. Returns factorio's base locale as a JSON object. @memberof clusterioMaster @instance @alias api/getFactorioLocale -@returns {object{}} 2 deep nested object with base game factorio locale as key:value pairs +@returns {object} 2 deep nested object with base game factorio locale as key:value pairs */ app.get("/api/getFactorioLocale", function(req,res){ endpointHitCounter.labels(req.route.path).inc(); - getFactorioLocale.asObject(config.factorioDirectory, "en", (err, factorioLocale) => { - res.send(factorioLocale); - }); + if (!localeCache) { + factorio.getLocale(path.join(config.factorioDirectory, "data"), "en").then(locale => { + localeCache = locale; + res.send(localeCache); + }); + } else { + res.send(localeCache); + } }); /* Websockets for plugins */ diff --git a/test/file/factorio/data/base/locale/en/base.cfg b/test/file/factorio/data/base/locale/en/base.cfg new file mode 100644 index 00000000..d5ee03af --- /dev/null +++ b/test/file/factorio/data/base/locale/en/base.cfg @@ -0,0 +1,4 @@ +; Test data for getLocale +[test] +key-a=1 +key-b=2 diff --git a/test/integration/locale.js b/test/integration/locale.js new file mode 100644 index 00000000..8c42981e --- /dev/null +++ b/test/integration/locale.js @@ -0,0 +1,62 @@ +const assert = require("assert").strict; +const path = require("path"); + +const factorio = require("lib/factorio"); + + +describe("Integration of lib/factorio/locale", function() { + describe("getLocale()", function() { + let locale; + before(async function() { + locale = await factorio.getLocale(path.join("factorio", "data"), "en"); + }); + + it("returns a nested object with base game locale information", async function() { + assert.equal(typeof locale, "object"); + assert.equal(locale["entity-name"]["fish"], "Fish"); + assert.equal(locale["entity-name"]["small-lamp"], "Lamp"); + let empty_keys = 0; + Object.keys(locale).forEach(key => { + // first level of the nested object is always an object + assert.equal(typeof locale[key], "object"); + + Object.keys(locale[key]).forEach(key2 => { + // second level of the nested object is always a string, nearly always truthy + if (!locale[key][key2]) { + empty_keys++; + } + assert.equal(typeof locale[key][key2], "string"); + }); + + }); + assert(empty_keys < 10, `Got unusually high amount (${empty_keys}) of empty keys`); + }); + + it("has some duplicate keys", async function() { + let arrayOfKeys = []; + // collect all keys in a single array + Object.keys(locale).forEach(key => { + arrayOfKeys.push(key); + Object.keys(locale[key]).forEach(key2 => { + arrayOfKeys.push(key2); + }); + }); + + // check for duplicates + let duplicateKeys = []; + let i = 0; + arrayOfKeys.forEach(key => { + let o = 0; + arrayOfKeys.forEach(key2 => { + if(i != o && key == key2){ + duplicateKeys.push(key); + } + o++ + }); + i++; + }); + // we got 704 but this is a very unexact number + assert(duplicateKeys.length > 10) + }); + }); +}); diff --git a/test/lib/factorio/locale.js b/test/lib/factorio/locale.js new file mode 100644 index 00000000..f9322ba2 --- /dev/null +++ b/test/lib/factorio/locale.js @@ -0,0 +1,18 @@ +const assert = require("assert").strict; +const path = require("path"); + +const factorio = require("lib/factorio"); + + +describe("lib/factorio/locale", function() { + describe("getLocale()", function() { + it("verifies types of the arguments", async function() { + await assert.rejects(factorio.getLocale(1, "a"), new TypeError("dataDir must be a string")); + await assert.rejects(factorio.getLocale("a", 1), new TypeError("languageCode must be a string")); + }); + it("returns a nested object with locale definitions", async function() { + let locale = await factorio.getLocale(path.join("test", "file", "factorio", "data"), "en"); + assert.deepEqual(locale, { test: { "key-a": "1", "key-b": "2" } }); + }); + });; +}); diff --git a/test/lib/getFactorioLocale.js b/test/lib/getFactorioLocale.js deleted file mode 100644 index 043d32c7..00000000 --- a/test/lib/getFactorioLocale.js +++ /dev/null @@ -1,74 +0,0 @@ -const assert = require("assert"); -const util = require("util"); - -const getFactorioLocale = require("lib/getFactorioLocale"); -const asObjectAsync = util.promisify(getFactorioLocale.asObject); - -describe("getFactorioLocale.js", function() { - describe("asObject()", function() { - it("requires 2 strings and a function as parameters", function() { - function validate(err) { - return err.message == "Error: wrong parameters provided" - } - let message = "getFactorioLocale is responding incorrectly to invalid arguments" - let callback = (err,object) => true; - - assert.throws(() => getFactorioLocale.asObject(), validate, message); - assert.throws(() => getFactorioLocale.asObject("hello", "I forgot my callback"), validate, message); - assert.throws(() => getFactorioLocale.asObject(undefined, undefined, callback), validate, message); - - // the proper way to do it - getFactorioLocale.asObject("factorio_0.15.27", "en", callback); - }); - it("returns a nested object with base game locale information", async function() { - let object = await asObjectAsync("factorio", "en"); - - assert.equal(typeof object, "object"); - assert.equal(object["entity-name"]["fish"], "Fish"); - assert.equal(object["entity-name"]["small-lamp"], "Lamp"); - let empty_keys = 0; - Object.keys(object).forEach(key => { - // first level of the nested object is always an object - assert.equal(typeof object[key], "object"); - - Object.keys(object[key]).forEach(key2 => { - // second level of the nested object is always a string, nearly always truthy - if (!object[key][key2]) { - empty_keys++; - } - assert.equal(typeof object[key][key2], "string"); - }); - - }); - assert(empty_keys < 10, `Got unusually high amount (${empty_keys}) of empty keys`); - }); - it("has some duplicate keys", async function() { - let object = await asObjectAsync("factorio", "en"); - - let arrayOfKeys = []; - // collect all keys in a single array - Object.keys(object).forEach(key => { - arrayOfKeys.push(key); - Object.keys(object[key]).forEach(key2 => { - arrayOfKeys.push(key2); - }); - }); - - // check for duplicates - let duplicateKeys = []; - let i = 0; - arrayOfKeys.forEach(key => { - let o = 0; - arrayOfKeys.forEach(key2 => { - if(i != o && key == key2){ - duplicateKeys.push(key); - } - o++ - }); - i++; - }); - // we got 704 but this is a very unexact number - assert(duplicateKeys.length > 10) - }); - }); -}); \ No newline at end of file