diff --git a/cjs/index.js b/cjs/index.js index 2b2d1c2..0877a5f 100644 --- a/cjs/index.js +++ b/cjs/index.js @@ -32,7 +32,10 @@ let detector; const isPromise = (o) => typeof o.then === "function"; let cacheLocal = ""; /** - * @type {NsOsLocale.TInternalLocaleDetectorSig} + * @template {true | void} IsAsync + * @template {Conditional>} R + * @param {IsAsync=} async + * @returns {(options?: NsOsLocale.LocaleDetectorOptions) => R} */ const detectorBase = (async) => (options = {}) => { /* eslint-disable indent */ @@ -42,7 +45,7 @@ let detector; return (async ? Promise.resolve(cacheLocal) : cacheLocal); } const functions = localeGetters[+(!!async)]; - /** @type {NsOsLocale.TInternalLocaleDetectorResult} */ + /** @type {R} */ let locale; /** * @param {string} l @@ -52,18 +55,18 @@ let detector; const withCache = (l, mustPromise) => { l = l.replace(/_/, "-"); cacheLocal = cache ? l : ""; - return (mustPromise ? Promise.resolve(l) : l); + return /** @type {NsOsLocale.TInternalLocaleDetectorResult} */ (mustPromise ? Promise.resolve(l) : l); }; const envLocale = getEnvLocale(); if (envLocale || !options.spawn) { - locale = purgeExtraToken(envLocale); + locale = /** @type {R} */ (purgeExtraToken(envLocale)); } else { let { platform } = process; if (platform !== "win32" && platform !== "darwin") { platform = "linux"; } - locale = functions[platform](); + locale = /** @type {R} */ (functions[platform]()); } return (isPromise(locale) ? locale.then(withCache) : withCache(locale, async === true || void 0)); }; @@ -77,7 +80,7 @@ let detector; enumerable: false, }, version: { - value: "v1.0.19", + value: "v1.0.20", enumerable: true, }, }); diff --git a/cjs/lib.js b/cjs/lib.js index 47fbc9d..09318ac 100644 --- a/cjs/lib.js +++ b/cjs/lib.js @@ -181,8 +181,8 @@ const [getUnixLocale, getUnixLocaleSync] = /** @type {(cmd: TLocalCmdToken) => T * @see {@link module:lcid} */ const parseLCID = (result) => { - const lcidCode = parseInt(result.replace("Locale", ""), 16); - return lcid.from(lcidCode) || defaultLocale; + const lcidCode = +("0x" + result.replace(/Locale|\s/g, "")); + return lcid.from(lcidCode) || /* istanbul ignore next */ defaultLocale; }; const [getWinLocale, getWinLocaleSync] = /** @type {(a: TLocalCmdToken, b: string[]) => TAsyncSyncPair} */ ((command, args) => { return [ diff --git a/esm/index.js b/esm/index.mjs similarity index 82% rename from esm/index.js rename to esm/index.mjs index 5bb7c95..7fdba08 100644 --- a/esm/index.js +++ b/esm/index.mjs @@ -14,7 +14,7 @@ // @ts-ignore /// /// -import * as lib from "./lib"; +import * as lib from "./lib.mjs"; /** * @typedef {(o: any) => o is Promise} TPromiseChecker */ @@ -29,7 +29,10 @@ let detector; const isPromise = (o) => typeof o.then === "function"; let cacheLocal = ""; /** - * @type {NsOsLocale.TInternalLocaleDetectorSig} + * @template {true | void} IsAsync + * @template {Conditional>} R + * @param {IsAsync=} async + * @returns {(options?: NsOsLocale.LocaleDetectorOptions) => R} */ const detectorBase = (async) => (options = {}) => { /* eslint-disable indent */ @@ -39,7 +42,7 @@ let detector; return (async ? Promise.resolve(cacheLocal) : cacheLocal); } const functions = localeGetters[+(!!async)]; - /** @type {NsOsLocale.TInternalLocaleDetectorResult} */ + /** @type {R} */ let locale; /** * @param {string} l @@ -49,18 +52,18 @@ let detector; const withCache = (l, mustPromise) => { l = l.replace(/_/, "-"); cacheLocal = cache ? l : ""; - return (mustPromise ? Promise.resolve(l) : l); + return /** @type {NsOsLocale.TInternalLocaleDetectorResult} */ (mustPromise ? Promise.resolve(l) : l); }; const envLocale = getEnvLocale(); if (envLocale || !options.spawn) { - locale = purgeExtraToken(envLocale); + locale = /** @type {R} */ (purgeExtraToken(envLocale)); } else { let { platform } = process; if (platform !== "win32" && platform !== "darwin") { platform = "linux"; } - locale = functions[platform](); + locale = /** @type {R} */ (functions[platform]()); } return (isPromise(locale) ? locale.then(withCache) : withCache(locale, async === true || void 0)); }; @@ -74,7 +77,7 @@ let detector; enumerable: false, }, version: { - value: "v1.0.19", + value: "v1.0.20", enumerable: true, }, }); diff --git a/esm/lib.js b/esm/lib.mjs similarity index 97% rename from esm/lib.js rename to esm/lib.mjs index 8feb91f..23e88a2 100644 --- a/esm/lib.js +++ b/esm/lib.mjs @@ -178,8 +178,8 @@ const [getUnixLocale, getUnixLocaleSync] = /** @type {(cmd: TLocalCmdToken) => T * @see {@link module:lcid} */ const parseLCID = (result) => { - const lcidCode = parseInt(result.replace("Locale", ""), 16); - return lcid.from(lcidCode) || defaultLocale; + const lcidCode = +("0x" + result.replace(/Locale|\s/g, "")); + return lcid.from(lcidCode) || /* istanbul ignore next */ defaultLocale; }; const [getWinLocale, getWinLocaleSync] = /** @type {(a: TLocalCmdToken, b: string[]) => TAsyncSyncPair} */ ((command, args) => { return [ diff --git a/package.json b/package.json index eba0294..17fbe90 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "os-locale-s", - "version": "1.0.19", + "version": "1.0.20", "description": "Its a light weight version that minimizes the dependency module of `os-locale`", "private": false, "main": "./cjs/index.js", - "module": "./esm/index.js", + "module": "./esm/index.mjs", "types": "./index.d.ts", "sideEffects": false, "author": { @@ -51,6 +51,7 @@ "lcid": "^4.0.1" }, "devDependencies": { + "@types/node": "^20.10.6", "jest": "29.7.0" }, "scripts": { diff --git a/test.js b/test.js index c27c3df..afd1bcd 100644 --- a/test.js +++ b/test.js @@ -1,116 +1,171 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const ifDefined = (varName, fallback) => typeof global[varName] !== "undefined" ? global[varName] : fallback; -const debug = ifDefined("printSync", false); -const asyncDebug = ifDefined("printAsync", false); -const DEFAULT = 1; -const WIN = 1; -const LINUX = 1; -const DARWIN = 1; -const reLocale = /^(?:[a-z]{2}-[A-Z]{2}|C|POSIX)$/; -const cache = {}; -const setPlatform = (platform) => { - Object.defineProperty(process, "platform", { value: platform }); -}; -const emitPlatformSetter = (platform, altProcess) => () => { - return new Promise(resolve => { - if (typeof altProcess === "function") - altProcess(); - setPlatform(platform); - osLocale.purge(); - resolve(); - }); -}; -const isBool = (b) => typeof b === "boolean"; -function makeOption(bools) { - const opt = {}; - const [spawn, cache] = bools || []; - isBool(spawn) && (opt.spawn = spawn); - isBool(cache) && (opt.cache = cache); - return (isBool(spawn) || isBool(cache)) ? opt : void 0; -} -let osLocale; -const tryMatch = (lc) => { - try { - expect(lc).toMatch(reLocale); - } - catch (e) { - console.warn("There are no locales available in this environment."); - } -}; -const asyncCallbackEmitter = (plat, bools) => async () => { - const opt = makeOption(bools); - const locale = await osLocale(opt); - asyncDebug && console.log(`async [${plat}]: ${locale}`); - tryMatch(locale); -}; -const syncCallbackEmitter = (plat, bools) => () => { - const opt = makeOption(bools); - const locale = osLocale.sync(opt); - debug && console.log(`[${plat}]: ${locale}`); - tryMatch(locale); -}; -beforeAll(async () => { - return new Promise(resolve => { - cache.env = process.env; - cache.platform = process.platform; - resolve(); - }); -}); -// eachModule("../src/"); -eachModule("./"); -function eachModule(path) { - describe(`[os-locale-s], module - "${path}"`, function () { - beforeAll(() => { - process.env = {}; - return new Promise(resolve => { - Promise.resolve().then(() => require(path)).then((m) => { - ({ osLocale } = m); - resolve(); - }); - }); - }); - describe.each([ - ["linux", LINUX], ["win32", WIN], ["darwin", DARWIN] - ])("Platform: %s (process.env.platform)", (name, enable) => { - if (enable) { - beforeEach(emitPlatformSetter(name)); - describe("locale detection with default options", function () { - it("async detection", asyncCallbackEmitter(name)); - it("sync detection", syncCallbackEmitter(name)); - }); - describe("locale detection with default options (no spawn)", function () { - it("async detection", asyncCallbackEmitter(name, [false])); - it("sync detection", syncCallbackEmitter(name, [false])); - }); - describe("locale detection with default options (no cache)", function () { - it("async detection", asyncCallbackEmitter(name, [true, false])); - it("sync detection", syncCallbackEmitter(name, [true, false])); - }); - describe("locale detection with no spawn, no cache", function () { - it("async detection", asyncCallbackEmitter(name, [false, false])); - it("sync detection", syncCallbackEmitter(name, [false, false])); - }); - } - }); - DEFAULT && describe("OS: default (**Test depending on the actual platform)", () => { - beforeAll(emitPlatformSetter(cache.platform, () => process.env = cache.env)); - describe("locale detection with default options", function () { - it("async detection", asyncCallbackEmitter("default")); - it("sync detection", syncCallbackEmitter("default")); - }); - describe("locale detection with default options (no spawn)", function () { - it("async detection", asyncCallbackEmitter("default", [false])); - it("sync detection", syncCallbackEmitter("default", [false])); - }); - describe("locale detection with default options (no cache)", function () { - it("async detection", asyncCallbackEmitter("default", [true, false])); - it("sync detection", syncCallbackEmitter("default", [true, false])); - }); - describe("locale detection with no spawn, no cache", function () { - it("async detection", asyncCallbackEmitter("default", [false, false])); - it("sync detection", syncCallbackEmitter("default", [false, false])); - }); - }); - }); -} +"use strict"; +/*! + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Copyright (C) 2020 jeffy-g + Released under the MIT license + https://opensource.org/licenses/mit-license.php + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +*/ +// @ts-ignore +/// +// @ts-ignore +/// +/** + * @typedef {typeof process.platform} TOSTokens + * @typedef {keyof typeof global} TGThisKeys + * @typedef {[spawn?: boolean, cache?: boolean]} TDetectorOptValues + * @typedef TProcessCache + * @prop {typeof process["env"]} env + * @prop {TOSTokens} platform + */ +/** + * @template {*} T + * @typedef {{-readonly [P in keyof T]: T[P]}} XReadonly + */ +/** + * @typedef LocaleDetectorOptions + * @prop {boolean} [spawn] Set to `false` to avoid spawning subprocesses and instead only resolve the locale from environment variables.@default true + * @prop {boolean} [cache] @default true + */ +/** + * @type {NsOsLocale.LocaleDetector} + */ +let osLocale; +// @ts-ignore +const ifDefined = (varName, fallback) => typeof global[varName] !== "undefined" ? global[varName] : fallback; +const debug = ifDefined("printSync", false); +const asyncDebug = ifDefined("printAsync", false); +const DEFAULT = 1; +const WIN = 1; +const LINUX = 1; +const DARWIN = 1; +const reLocale = /^(?:[a-z]{2}-[A-Z]{2}|C|POSIX)$/; +/** @type {TProcessCache} */ +const cache = { + env: process.env, + platform: process.platform +}; +/** + * + * @param {TOSTokens} platform "darwin", "linux", "win32" + */ +const setPlatform = (platform) => { + Object.defineProperty(process, "platform", { value: platform }); +}; +/** + * @param {TOSTokens} platform + * @param {() => void} [extraProcess] + * @returns {() => Promise} + */ +const setPlatformOf = (platform, extraProcess) => () => { + return new Promise(resolve => { + extraProcess && extraProcess(); + setPlatform(platform); + // @ts-ignore @internal + osLocale.purge(); + resolve(); + }); +}; +/** + * @param {boolean=} b + * @returns {b is boolean} + */ +const isBool = (b) => typeof b === "boolean"; +/** + * @param {TDetectorOptValues=} bools + * @returns + */ +function makeOption(bools) { + /** @type {XReadonly} */ + const opt = {}; + const [spawn, cache] = bools || []; + isBool(spawn) && (opt.spawn = spawn); + isBool(cache) && (opt.cache = cache); + return (isBool(spawn) || isBool(cache)) ? opt : void 0; +} +const tryMatch = (lc) => { + try { + expect(lc).toMatch(reLocale); + } + catch (e) { + console.warn("There are no locales available in this environment."); + } +}; +/** + * @param {string} prefix + * @param {string} locale + * @param {XReadonly=} opt + */ +const printInfo = (prefix, locale, opt) => console.log(`${prefix}[platform: ${process.platform}, options: ${opt ? JSON.stringify(opt) : "use default(undefined)"}]: ${locale}`); +/** + * @param {TDetectorOptValues=} detectorOpt [spawn, cache] + * @param {true=} async + */ +const emitCallback = (detectorOpt, async) => async () => { + const opt = makeOption(detectorOpt); + const fn = async ? osLocale : osLocale.sync; + const result = fn(opt); + const locale = /** @type {string} */ (async ? await result : result); + debug && printInfo(async ? "async " : "", locale, opt); + tryMatch(locale); +}; + +eachModule("."); +/** + * @param {string} path module path `"../src/"(.ts)` or `"../dist/"(.js)` + */ +function eachModule(path) { + describe(` ====================== running test: [os-locale-s], module - "${path}" ======================`, function () { + beforeAll(/** @type {() => Promise} */ () => { + process.env = {}; + return new Promise(resolve => { + Promise.resolve(`${path}`).then(s => require(s)).then((m) => { + ({ osLocale } = m); + resolve(); + }); + }); + }); + describe.each([ + [/** @type {TOSTokens} */ ("linux"), LINUX], [/** @type {TOSTokens} */ ("win32"), WIN], [/** @type {TOSTokens} */ ("darwin"), DARWIN] + ])("[[[ Platform: %s (process.env.platform) ]]]", (name, enable) => { + if (enable) { + beforeEach(setPlatformOf(name)); + describe("locale detection with default options", function () { + it("async detection", emitCallback(void 0, true)); + it("sync detection", emitCallback()); + }); + describe("locale detection with default options (no spawn)", function () { + it("async detection", emitCallback([false], true)); + it("sync detection", emitCallback([false])); + }); + describe("locale detection with default options (no cache)", function () { + it("async detection", emitCallback([true, false], true)); + it("sync detection", emitCallback([true, false])); + }); + describe("locale detection with no spawn, no cache", function () { + it("async detection", emitCallback([false, false], true)); + it("sync detection", emitCallback([false, false])); + }); + } + }); + DEFAULT && describe("[[[ OS: default (**Test depending on the actual platform) ]]]", () => { + beforeAll(setPlatformOf(cache.platform, () => process.env = cache.env)); + describe("locale detection with default options", function () { + it("async detection", emitCallback(void 0, true)); + it("sync detection", emitCallback()); + }); + describe("locale detection with default options (no spawn)", function () { + it("async detection", emitCallback([false], true)); + it("sync detection", emitCallback([false])); + }); + describe("locale detection with default options (no cache)", function () { + it("async detection", emitCallback([true, false], true)); + it("sync detection", emitCallback([true, false])); + }); + describe("locale detection with no spawn, no cache", function () { + it("async detection", emitCallback([false, false], true)); + it("sync detection", emitCallback([false, false])); + }); + }); + }); +} \ No newline at end of file