UNPKG

@kabeep/forex-cli

Version:

A Node.js Library to convert foreign exchange in terminal

958 lines (926 loc) 35.3 kB
#!/usr/bin/env node import fs, { readFileSync as readFileSync$1 } from 'node:fs'; import process from 'node:process'; import updateNotifier from 'update-notifier'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import { ForexClient } from '@kabeep/forex'; import ora from 'ora'; import envPaths from 'env-paths'; import { osLocaleSync } from 'os-locale'; import confirm from '@inquirer/confirm'; import search from '@inquirer/search'; import ISO3166 from 'iso-3166-1'; import translate from '@kabeep/node-translate'; import path from 'node:path'; import { isDate } from 'node:util/types'; import clipboard from 'clipboardy'; import Table from 'terminal-table'; var LOCALE_CODE = osLocaleSync(); var cacheDirectory = envPaths("@kabeep/forex-cli").cache; // src/locale/en-US.ts var en_US_default = { CMD_USAGE: "$0 <command> [options]", CMD_CONVERT_USAGE: "Convert currency amounts", CMD_CONVERT_USAGE_EG: "Convert 1000 USD to Euros", CMD_CONVERT_USAGE_EG_SPECIFIC: "Specify base and target currencies", CMD_CONVERT_USAGE_EG_AUTO: "Specify one of the currencies and automatically fill in the other currency through the OS's locale", CMD_CONVERT_USAGE_EG_CURRENCY: "Currency codes using ISO 4217", CMD_CONVERT_USAGE_EG_LOCALE: "Use locale codes from ISO 3166-1", CMD_CONVERT_USAGE_EG_COUNTRY: "Use country or region name", CMD_CURRENCY_USAGE: "Get available currency codes and names", CMD_CURRENCY_USAGE_EG_CODE: "Obtained via ISO-3166-1-alpha-2 region code", CMD_CURRENCY_USAGE_EG_CURRENCY: "Obtained via ISO 4217 currency code", CMD_CURRENCY_USAGE_EG_COUNTRY: "Get by country or region name", CMD_LIST_USAGE: "Print list of available currencies", CMD_LIST_USAGE_EG_LATEST: "Shows the latest list of available currencies", CMD_LIST_USAGE_EG_PRETTY: "Use unicode table to prettier printed content", CMD_OPTION_DATE: 'The date for the conversion rate, or "latest" for the most recent', CMD_OPTION_TIMEOUT: "Request timeout (milliseconds)", CMD_OPTION_CLIPBOARD: "Write (copy) the result to the clipboard", CMD_OPTION_TRANSLATE: "Translate occurrences of currency or region names", CMD_OPTION_VERBOSE: "Output verbose messages on internal operations", CMD_OPTION_FROM: "The base currency code or locale code", CMD_OPTION_TO: "The destination currency code or locale code", CMD_OPTION_PRETTY: "Pretty output format", CMD_OPTION_USAGE_EG_DATE: "Use the exchange rate for the specified date", CMD_OPTION_USAGE_EG_TIMEOUT: "Set request timeout", CMD_OPTION_USAGE_EG_TRANSLATE: "Print the translated currency name or region name according to the operating system locale", CMD_MSG_CHOICE_COUNTRY: "Please select a country or region", CMD_MSG_CONFIRM_COUNTRY: "Did you mean {{region}}?", CMD_MSG_FETCH_CURRENCIES: "Get the list of available currencies on {{date}} {{time}}", CMD_MSG_FETCH_TRANSLATION: "Get currency name translation (via Google) {{time}}", CMD_MSG_FETCH_RATE: "Get the currency exchange rate between {{base}} and {{dest}} on {{date}} {{time}}", CMD_ERR_UNKNOWN: "Unknown Error: If it occurs repeatedly, please submit an issue in https://github.com/kabeep/forex-cli/issues", CMD_ERR_UNMEANING: "Meaningless currency group, please enter a different currency code", CMD_ERR_INVALID_DATE: "Invalid date, please check --date parameter", CMD_ERR_INVALID_AMOUNT: '"{{amount}}" \u4E0D\u662F\u6709\u6548\u91D1\u989D, \u8BF7\u8F93\u5165\u6709\u6548\u91D1\u989D, \u4F8B\u5982 "1000" \u6216 "1e3", \u518D\u6BD4\u5982 "101,001,001.01" \u6216 "1b1m1k1.01"', CMD_ERR_CREATE_CACHE_DIR: "The cache directory cannot be created, please check CLI permissions", CMD_ERR_WRITE_CACHE_FILE: "Unable to write cached files, please check CLI permissions", CMD_ERR_TIMEOUT_CURRENCIES: "Request for list of available currencies timed out", CMD_ERR_CLIPBOARD_WRITE: "Clipboard writing failed", CMD_ERR_INVALID_CURRENCIES: "The list of available currencies for the specified date is empty", CMD_ERR_INVALID_FROM: "Invalid base currency or locale code", CMD_ERR_INVALID_TO: "Invalid target currency or locale code", CMD_ERR_TIMEOUT_RATE: "Request currency exchange rate timed out", CMD_ERR_INVALID_RATE: "Currency code that is not available on the specified date", CMD_ERR_TIMEOUT_CONVERT: "Request for exchange amount timed out", CMD_ERR_INVALID_CONVERT: "Unsupported currency code on the specified date", CMD_ERR_INVALID_COUNTRY: "Country or region names not yet supported", CMD_ERR_USER_CANCEL: "User actively cancels" }; // src/locale/zh-CN.ts var zh_CN_default = { CMD_USAGE: "$0 <\u547D\u4EE4> [\u9009\u9879]", CMD_CONVERT_USAGE: "\u8F6C\u6362\u8D27\u5E01\u91D1\u989D", CMD_CONVERT_USAGE_EG: "\u5C06 1000 \u7F8E\u5143\u8F6C\u6362\u4E3A\u6B27\u5143", CMD_CONVERT_USAGE_EG_SPECIFIC: "\u6307\u5B9A\u57FA\u51C6\u8D27\u5E01\u548C\u76EE\u6807\u8D27\u5E01", CMD_CONVERT_USAGE_EG_AUTO: "\u6307\u5B9A\u5176\u4E00\u5E01\u79CD\uFF0C\u901A\u8FC7\u64CD\u4F5C\u7CFB\u7EDF\u8BED\u8A00\u73AF\u5883\u81EA\u52A8\u586B\u5165\u53E6\u4E00\u5E01\u79CD", CMD_CONVERT_USAGE_EG_CURRENCY: "\u4F7F\u7528 ISO 4217 \u7684\u8D27\u5E01\u4EE3\u7801", CMD_CONVERT_USAGE_EG_LOCALE: "\u4F7F\u7528 ISO 3166-1 \u7684\u5730\u533A\u4EE3\u7801", CMD_CONVERT_USAGE_EG_COUNTRY: "\u4F7F\u7528\u56FD\u5BB6\u6216\u5730\u533A\u540D\u79F0", CMD_CURRENCY_USAGE: "\u83B7\u53D6\u53EF\u7528\u7684\u8D27\u5E01\u4EE3\u7801\u548C\u540D\u79F0", CMD_CURRENCY_USAGE_EG_CODE: "\u901A\u8FC7 ISO-3166-1-alpha-2 \u5730\u533A\u4EE3\u7801\u83B7\u53D6", CMD_CURRENCY_USAGE_EG_CURRENCY: "\u901A\u8FC7 ISO 4217 \u8D27\u5E01\u4EE3\u7801\u83B7\u53D6", CMD_CURRENCY_USAGE_EG_COUNTRY: "\u901A\u8FC7\u56FD\u5BB6\u6216\u5730\u533A\u540D\u79F0\u83B7\u53D6", CMD_LIST_USAGE: "\u5C55\u793A\u53EF\u7528\u8D27\u5E01\u5217\u8868", CMD_LIST_USAGE_EG_LATEST: "\u5C55\u793A\u6700\u65B0\u7684\u53EF\u7528\u8D27\u5E01\u5217\u8868", CMD_LIST_USAGE_EG_PRETTY: "\u4F7F\u7528 unicode \u8868\u683C\u7F8E\u5316\u6253\u5370\u5185\u5BB9", CMD_OPTION_DATE: '\u6307\u5B9A\u6C47\u7387\u7684\u65E5\u671F\uFF0C\u6216\u6700\u65B0\u7684 "latest"', CMD_OPTION_TIMEOUT: "\u8BF7\u6C42\u8D85\u65F6\u65F6\u95F4 (\u6BEB\u79D2)", CMD_OPTION_CLIPBOARD: "\u5199\u5165 (\u590D\u5236) \u7ED3\u679C\u5230\u526A\u8D34\u677F", CMD_OPTION_TRANSLATE: "\u7FFB\u8BD1\u51FA\u73B0\u7684\u8D27\u5E01\u6216\u5730\u533A\u540D\u79F0", CMD_OPTION_VERBOSE: "\u8F93\u51FA\u7A0B\u5E8F\u5185\u90E8\u6267\u884C\u7684\u8BE6\u7EC6\u6B65\u9AA4\u4FE1\u606F", CMD_OPTION_FROM: "\u57FA\u51C6\u8D27\u5E01\u4EE3\u7801\u6216\u533A\u57DF\u4EE3\u7801", CMD_OPTION_TO: "\u76EE\u6807\u8D27\u5E01\u4EE3\u7801\u6216\u533A\u57DF\u4EE3\u7801", CMD_OPTION_PRETTY: "\u7F8E\u5316\u8F93\u51FA\u683C\u5F0F", CMD_OPTION_USAGE_EG_DATE: "\u4F7F\u7528\u6307\u5B9A\u65E5\u671F\u7684\u6C47\u7387", CMD_OPTION_USAGE_EG_TIMEOUT: "\u8BBE\u7F6E\u8BF7\u6C42\u8D85\u65F6\u65F6\u95F4", CMD_OPTION_USAGE_EG_TRANSLATE: "\u6839\u636E\u64CD\u4F5C\u7CFB\u7EDF\u8BED\u8A00\u73AF\u5883\u6253\u5370\u7FFB\u8BD1\u540E\u7684\u8D27\u5E01\u540D\u79F0\u6216\u5730\u533A\u540D\u79F0", CMD_MSG_CHOICE_COUNTRY: "\u8BF7\u9009\u62E9\u4E00\u4E2A\u56FD\u5BB6\u6216\u5730\u533A", CMD_MSG_CONFIRM_COUNTRY: "\u60A8\u662F\u6307 {{region}} \u5417\uFF1F", CMD_MSG_FETCH_CURRENCIES: "\u83B7\u53D6 {{date}} \u53EF\u7528\u8D27\u5E01\u5217\u8868 {{time}}", CMD_MSG_FETCH_TRANSLATION: "\u83B7\u53D6\u8D27\u5E01\u540D\u79F0\u7FFB\u8BD1 (\u4F7F\u7528\u8C37\u6B4C\u7FFB\u8BD1\uFF0C\u5927\u9646\u7F51\u7EDC\u4FE1\u9053\u6682\u4E0D\u652F\u6301) {{time}}", CMD_MSG_FETCH_RATE: "\u83B7\u53D6 {{date}} \u8D27\u5E01 {{base}} \u4E0E {{dest}} \u7684\u6C47\u7387 {{time}}", CMD_ERR_UNKNOWN: "\u672A\u77E5\u5F02\u5E38\uFF0C\u82E5\u91CD\u590D\u51FA\u73B0\u8BF7\u901A\u8FC7 https://github.com/kabeep/forex-cli/issues \u63D0\u4EA4 issue", CMD_ERR_UNMEANING: "\u65E0\u610F\u4E49\u7684\u8D27\u5E01\u7EC4\uFF0C\u8BF7\u8F93\u5165\u4E0D\u540C\u7684\u8D27\u5E01\u4EE3\u7801", CMD_ERR_INVALID_DATE: "\u65E0\u6548\u7684\u65E5\u671F\uFF0C\u8BF7\u68C0\u67E5 --date \u53C2\u6570", CMD_ERR_INVALID_AMOUNT: '"{{amount}}" \u4E0D\u662F\u6709\u6548\u91D1\u989D, \u8BF7\u8F93\u5165\u6709\u6548\u91D1\u989D, \u4F8B\u5982 "1000" \u6216 "1e3", \u518D\u6BD4\u5982 "101,001,001.01" \u6216 "1b1m1k1.01"', CMD_ERR_CREATE_CACHE_DIR: "\u65E0\u6CD5\u521B\u5EFA\u7F13\u5B58\u76EE\u5F55, \u8BF7\u68C0\u67E5 CLI \u6743\u9650", CMD_ERR_WRITE_CACHE_FILE: "\u65E0\u6CD5\u5199\u5165\u7F13\u5B58\u6587\u4EF6, \u8BF7\u68C0\u67E5 CLI \u6743\u9650", CMD_ERR_TIMEOUT_CURRENCIES: "\u8BF7\u6C42\u53EF\u7528\u8D27\u5E01\u5217\u8868\u8D85\u65F6", CMD_ERR_CLIPBOARD_WRITE: "\u5199\u5165\u526A\u8D34\u677F\u5931\u8D25", CMD_ERR_INVALID_CURRENCIES: "\u6307\u5B9A\u65E5\u671F\u7684\u53EF\u7528\u8D27\u5E01\u5217\u8868\u4E3A\u7A7A", CMD_ERR_INVALID_FROM: "\u65E0\u6548\u7684\u57FA\u51C6\u8D27\u5E01\u6216\u533A\u57DF\u4EE3\u7801", CMD_ERR_INVALID_TO: "\u65E0\u6548\u7684\u76EE\u6807\u8D27\u5E01\u6216\u533A\u57DF\u4EE3\u7801", CMD_ERR_TIMEOUT_RATE: "\u83B7\u53D6\u8D27\u5E01\u6C47\u7387\u8D85\u65F6", CMD_ERR_INVALID_RATE: "\u5728\u6307\u5B9A\u65E5\u671F\u4E0D\u53EF\u7528\u7684\u8D27\u5E01\u4EE3\u7801", CMD_ERR_TIMEOUT_CONVERT: "\u83B7\u53D6\u5151\u6362\u91D1\u989D\u8D85\u65F6", CMD_ERR_INVALID_CONVERT: "\u5728\u6307\u5B9A\u65E5\u671F\u4E0D\u53D7\u53EF\u7528\u7684\u8D27\u5E01\u4EE3\u7801", CMD_ERR_INVALID_COUNTRY: "\u5C1A\u672A\u652F\u6301\u7528\u7684\u56FD\u5BB6\u6216\u5730\u533A\u540D\u79F0", CMD_ERR_USER_CANCEL: "\u7528\u6237\u4E3B\u52A8\u53D6\u6D88" }; // src/locale/index.ts function getLocale() { const localeAbbr = LOCALE_CODE.split("-")[0]; switch (localeAbbr) { case "zh": { return zh_CN_default; } default: { return en_US_default; } } } var locale_default = { ...en_US_default, ...getLocale() }; // src/helper/i18n.ts var I18n = class _I18n { static instance; dictionary = {}; constructor() { } static getInstance() { if (!_I18n.instance) { _I18n.instance = new _I18n(); } return _I18n.instance; } static escapeHtml(value) { return value.replace( /[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[char] || char ); } load(locale) { this.dictionary = locale; } t(path2, variables, defaultValue) { const value = this.get(this.dictionary, path2); if (typeof value === "object") return this.t(`${path2}.DEFAULT`, variables, defaultValue); return typeof value === "string" ? this.compiled(value, variables) : defaultValue || path2; } get(obj, path2) { return path2.split(".").reduce( (acc, key) => { if (acc && typeof acc === "object" && key in acc) { return acc[key]; } return void 0; }, obj ); } compiled(value, variables) { if (variables) { return value.replace( /{{\s*(\w+)\s*}}/g, (_, key) => _I18n.escapeHtml(variables[key] || "") ); } return value; } }; var i18n = I18n.getInstance(); i18n.load(locale_default); var i18n_default = i18n; // src/helper/boundary.ts function boundary(function_) { return async (...arguments_) => { const spinner = ora(); const [error, result] = await to_default(function_(...arguments_, spinner)); if (error) { spinner.fail( i18n_default.t( `CMD_ERR_${error?.message ?? "UNKNOWN"}`, error?.data ) ); return; } if (!result) { spinner.fail(result?.toString() ?? i18n_default.t("CMD_ERR_UNKNOWN")); return; } spinner.stop(); console.log(`\u2728 ${result}`); }; } var boundary_default = boundary; // src/helper/create-message.ts function createMessage(baseName, baseCode, destName, destCode) { return (source, result) => { return result ? `${source} ${baseName} (${baseCode}) \u2248 ${result} ${destName} (${destCode})` : `${baseName} (${baseCode}) -> ${destName} (${destCode}) \u2248 ${source}`; }; } var create_message_default = createMessage; // src/helper/ensure.ts function ensure(condition, message, data) { if (condition) { return; } const providedMessage = typeof message === "function" ? message() : message; const error = new Error(providedMessage ?? "UNKNOWN"); if (data) error.data = data; throw error; } var ensure_default = ensure; // src/helper/_internal/padded-date.ts function paddedDate(value, length = 2, padding = "0") { return `${value}`.padStart(length, padding); } var padded_date_default = paddedDate; // src/helper/format-date.ts function formatDate(value, pattern = "YYYY-MM-DD") { const tokens = { YYYY: value.getFullYear().toString(), MM: padded_date_default(value.getMonth() + 1), DD: padded_date_default(value.getDate()) }; return pattern.replaceAll(/YYYY|MM|DD/g, (match) => tokens[match]); } var format_date_default = formatDate; // src/helper/_internal/fuzzy-search.ts function containsChar(haystack, nch) { let index = 0; while (index < haystack.length) { if (haystack.codePointAt(index++) === nch) { return true; } } return false; } function fuzzySearch(needle, haystack) { const haystackLength = haystack.length; const needleLength = needle.length; if (needleLength > haystackLength) { return false; } if (needleLength === haystackLength) { return needle === haystack; } for (let i = 0; i < needleLength; i++) { const nch = needle.codePointAt(i); if (!nch || !containsChar(haystack, nch)) { return false; } } return true; } var fuzzy_search_default = fuzzySearch; // src/helper/to.ts function to(promise, errorExt) { return promise.then((data) => [null, data]).catch((err) => { if (errorExt) { const parsedError = Object.assign({}, err, errorExt); return [parsedError, void 0]; } return [err, void 0]; }); } var to_default = to; // src/helper/_internal/use-translate.ts var translateOptions = { from: "en", timeout: 3e3 }; var translateAdapter = (translation) => translation?.to?.text?.value; async function useTranslate(text, options = {}) { const isMultiple = Array.isArray(text); if (!text || isMultiple && !text.length) return ""; const sourceList = isMultiple ? text : [text]; const protection = "\n{%}\n"; const compositeText = sourceList.join(protection); const [error, result] = await to_default( translate(compositeText, { ...translateOptions, ...options }) ); if (error) return text; const translation = translateAdapter(result); const translationList = translation.split(protection); if (translationList.length !== sourceList.length) return text; return isMultiple ? translationList : translationList[0] || text; } var use_translate_default = useTranslate; // src/helper/get-code.ts var translateCountriesName = async (list2, timeout) => { const [_, translation] = await to_default( use_translate_default( list2.map((item) => item.name), { timeout } ) ); if (_ || !translation) return list2; return list2.map((item, index) => ({ ...item, name: translation[index] || item.name })); }; var translateCountryName = async (name, timeout) => { const [_, translation] = await to_default( use_translate_default(name, { timeout }) ); return translation || name; }; async function getCode(code, translate2 = false, timeout = 1e4) { const client = new ForexClient(); if (!code || code === "auto") return client.getCode(LOCALE_CODE.split("-")[1]); if (code.length >= 2 && code.length <= 3) return client.getCode(code) || code; const uppercaseCode = code.toUpperCase(); const countryList = ISO3166.all(); const matchingList = countryList.filter( (options) => options.country.toUpperCase().includes(uppercaseCode) ).map((options) => ({ name: options.country, value: options.alpha2, description: `Numberic: ${options.numeric} ISO-3166-alpha-2: ${options.alpha2} ISO-3166-alpha-3: ${options.alpha3}` })); if (!matchingList.length) return client.getCode(code) || code; if (matchingList.length > 1) { const searchList = translate2 ? await translateCountriesName(matchingList, timeout) : matchingList; const [err, countryCode2] = await to_default( search({ message: i18n_default.t("CMD_MSG_CHOICE_COUNTRY"), pageSize: 10, source: (term) => { if (!term) return searchList; return searchList.filter( (options) => fuzzy_search_default( term.toLowerCase(), options.name.toLowerCase() ) ); } }) ); ensure_default(!err, "USER_CANCEL"); return client.getCode(countryCode2); } const countryCode = matchingList[0].value; const countryName = matchingList[0].name; if (countryCode !== uppercaseCode) { const confirmName = translate2 ? await translateCountryName(countryName, timeout) : countryName; const [err, isConfirm] = await to_default( confirm({ message: i18n_default.t("CMD_MSG_CONFIRM_COUNTRY", { region: confirmName }), default: true }) ); ensure_default(!err, "USER_CANCEL"); ensure_default(isConfirm, "INVALID_COUNTRY"); return client.getCode(countryCode); } return client.getCode(matchingList[0].value); } var get_code_default = getCode; // src/helper/get-code-name.ts async function getCodeName(code, list2, translate2 = false, timeout = 1e4) { const name = list2.find((item) => item.code === code.toLowerCase())?.name; if (!name) return code; if (!translate2) return name; const [err, translation] = await to_default(use_translate_default(name, { timeout })); if (err) return name; return translation || name; } var get_code_name_default = getCodeName; function pathExist(filepath) { try { fs.accessSync(filepath, fs.constants.R_OK); return true; } catch { return false; } } var path_exist_default = pathExist; // src/helper/_internal/ensure-directory.ts function ensureDirectory(directoryPath) { if (path_exist_default(directoryPath)) return; try { fs.mkdirSync(directoryPath, { recursive: true }); return; } catch (err) { return err; } } var ensure_directory_default = ensureDirectory; function readFileSync(filepath) { const result = fs.readFileSync(filepath, { encoding: "utf8" }); try { return JSON.parse(result); } catch { return result; } } var read_file_sync_default = readFileSync; function writeFileSync(filepath, content) { try { fs.writeFileSync(filepath, JSON.stringify(content), "utf8"); } catch (err) { return err; } } var write_file_sync_default = writeFileSync; // src/helper/get-currencies.ts async function getCurrencies(client, date) { const unexpected = ensure_directory_default(cacheDirectory); if (unexpected) throw new Error("CREATE_CACHE_DIR"); const formatDateString = (/* @__PURE__ */ new Date()).toISOString().split("T")[0]; const cachePath = path.join(cacheDirectory, "currencies.json"); const isExisted = path_exist_default(cachePath); if (isExisted) { const { date: date2, currencies } = read_file_sync_default(cachePath); const isExpired = date2 !== formatDateString; if (!isExpired) return currencies; } const [err, result] = await to_default(client.getCurrencies(date)); ensure_default(!err, "TIMEOUT_CURRENCIES"); ensure_default(result.data?.length, "INVALID_CURRENCIES"); const disabled = write_file_sync_default(cachePath, { date: formatDateString, currencies: result.data }); if (disabled) throw new Error("WRITE_CACHE_DIR"); return result.data; } var get_currencies_default = getCurrencies; // src/helper/is-valid-code.ts function isValidCode(code, currencies) { const lowercaseCode = code?.toLowerCase(); return !!code && currencies.some((item) => item.code === lowercaseCode); } var is_valid_code_default = isValidCode; // src/helper/_internal/is-digit-formatting.ts function isDigitFormatting(input) { if (!input.includes("_")) return false; return !Number.isNaN(Number(input.replaceAll("_", ""))); } var is_digit_formatting_default = isDigitFormatting; // src/helper/_internal/is-scientific-notation.ts function isScientificNotation(input) { if (!input.includes("e")) return false; return !Number.isNaN(Number(input)); } var is_scientific_notation_default = isScientificNotation; // src/helper/_internal/is-statistical-formatting.ts function isStatisticalFormatting(input) { if (!input.includes(",")) return false; return !Number.isNaN(Number(input.replaceAll(",", ""))); } var is_statistical_formatting_default = isStatisticalFormatting; // src/helper/_internal/parse-financial-string.ts function parseFinancialString(input) { const value = input.trim().toLowerCase(); let total = 0; let currentValue = ""; for (let i = 0; i < value.length; i++) { const char = value[i]; if (/[0-9.]/.test(char)) { currentValue += char; } else if (/[kmb]/.test(char)) { const numberPart = Number.parseFloat(currentValue); if (Number.isNaN(numberPart)) return Number.NaN; switch (char) { case "k": total += numberPart * 1e3; break; case "m": total += numberPart * 1e6; break; case "b": total += numberPart * 1e9; break; } currentValue = ""; } else { return Number.NaN; } } if (currentValue) { const numberPart = Number.parseFloat(currentValue); if (Number.isNaN(numberPart)) return Number.NaN; total += numberPart; } return total; } var parse_financial_string_default = parseFinancialString; // src/helper/normalize-amount.ts function normalizeAmount(input) { if (typeof input === "number") return input; if (is_digit_formatting_default(input)) return Number(input.replaceAll("_", "")); if (is_scientific_notation_default(input)) return Number(input); if (is_statistical_formatting_default(input)) return Number(input.replaceAll(",", "")); return parse_financial_string_default(input); } var normalize_amount_default = normalizeAmount; // src/helper/_internal/create-palette.ts function createPalette(open, close) { return (text) => `\x1B[${open}m${text}\x1B[${close}m`; } var create_palette_default = createPalette; // src/helper/palette.ts var colorful = (code) => create_palette_default(code, 39); var palette = Object.assign( (open, close) => create_palette_default(open, close), { dim: create_palette_default(2, 22), black: colorful(30), red: colorful(31), green: colorful(32), yellow: colorful(33), blue: colorful(34), magenta: colorful(35), cyan: colorful(36), white: colorful(37), grey: colorful(90) } ); var palette_default = palette; function isValidDate(date) { return isDate(date) && !Number.isNaN(date.getTime()); } var is_valid_date_default = isValidDate; // src/helper/to-date.ts function toDate(value) { const date = new Date(value); ensure_default(is_valid_date_default(date), "INVALID_DATE"); return date; } var to_date_default = toDate; function useClipboard(value, spinner) { return use_handler_default( "CMD_OPTION_CLIPBOARD", async () => { const [err] = await to_default(clipboard.write(`${value}`)); ensure_default(!err, "CMD_ERR_CLIPBOARD_WRITE"); }, {}, spinner ); } var use_clipboard_default = useClipboard; // src/helper/_internal/use-duration.ts function useDuration(timestamp) { const startTimestamp = Number(timestamp) || Date.now(); return () => palette_default.grey(`${Date.now() - startTimestamp}ms`); } var use_duration_default = useDuration; // src/helper/use-handler.ts async function useHandler(title, handler, options = {}, spinner) { spinner?.start(i18n_default.t(title, { ...options, time: palette_default.grey("- ms") })); const timer = use_duration_default(); const result = await handler(); spinner?.succeed(i18n_default.t(title, { ...options, time: timer() })); return result; } var use_handler_default = useHandler; // src/helper/use-statistic.ts function useStatistic(value, { groupSeparator = ",", decimalSeparator = ".", precision } = {}) { const val = String(value); const cells = val.match(/^(-?)(\d*)(\.(\d+))?$/); if (!cells) return "Invalid Number"; const negative = cells[1]; let int = cells[2] || "0"; let decimal = cells[4] || ""; int = int.replace(/\B(?=(\d{3})+(?!\d))/g, groupSeparator); if (typeof precision === "number") { decimal = decimal.padEnd(precision, "0").slice(0, precision > 0 ? precision : 0); } if (decimal) { decimal = `${decimalSeparator}${decimal}`; } return `${negative}${int}${decimal}`; } var use_statistic_default = useStatistic; // src/commands/currency.ts async function currency({ code: input = "auto", date: dateString = "latest", timeout = 1e4, clipboard: clipboard2 = false, translate: translate2 = false, verbose = false }, spinner) { !verbose && spinner.start(); const isLatest = dateString === "latest"; const date = isLatest ? "latest" : to_date_default(dateString); const formatDateString = isLatest ? date : format_date_default(date); const client = new ForexClient({ timeout }); const code = await get_code_default(input, translate2, timeout); const currencies = await use_handler_default( "CMD_MSG_FETCH_CURRENCIES", () => get_currencies_default(client, date), { date: formatDateString }, verbose ? spinner : void 0 ); ensure_default(is_valid_code_default(code, currencies), "INVALID_FROM"); const name = await use_handler_default( "CMD_MSG_FETCH_TRANSLATION", () => get_code_name_default(code, currencies, translate2, timeout), { date: formatDateString }, verbose && translate2 ? spinner : void 0 ); clipboard2 && await use_clipboard_default( `${name} (${code})`, verbose ? spinner : void 0 ); return `${palette_default.yellow(name)} (${palette_default.blue(code)})`; } var currency_default = boundary_default(currency); async function convert({ from: fromCurrency = "auto", to: toCurrency = "auto", date: dateString = "latest", timeout = 1e4, clipboard: clipboard2 = false, translate: translate2 = false, verbose = false, amount }, spinner) { !verbose && spinner.start(); const isLatest = dateString === "latest"; const date = isLatest ? "latest" : to_date_default(dateString); const formatDateString = isLatest ? date : format_date_default(date); const client = new ForexClient({ timeout }); const baseCode = await get_code_default(fromCurrency, translate2, timeout); const destCode = await get_code_default(toCurrency, translate2, timeout); ensure_default(baseCode !== destCode, "UNMEANING"); const currencies = await use_handler_default( "CMD_MSG_FETCH_CURRENCIES", () => get_currencies_default(client, date), { date: formatDateString }, verbose ? spinner : void 0 ); ensure_default(is_valid_code_default(baseCode, currencies), "INVALID_FROM"); ensure_default(is_valid_code_default(destCode, currencies), "INVALID_TO"); const { baseName, destName } = await use_handler_default( "CMD_MSG_FETCH_TRANSLATION", async () => { const baseName2 = await get_code_name_default( baseCode, currencies, translate2, timeout ); const destName2 = await get_code_name_default( destCode, currencies, translate2, timeout ); return { baseName: baseName2, destName: destName2 }; }, { date: formatDateString }, verbose && translate2 ? spinner : void 0 ); const print = create_message_default( baseName, palette_default.blue(baseCode), destName, palette_default.blue(destCode) ); if (!amount) { const rate = await use_handler_default( "CMD_MSG_FETCH_RATE", async () => { const [err, result] = await to_default( client.getRate(baseCode, destCode, date) ); ensure_default(!err, "TIMEOUT_RATE"); ensure_default(result.data, "INVALID_RATE"); return result.data; }, { date: formatDateString, base: baseCode, dest: destCode }, verbose ? spinner : void 0 ); clipboard2 && await use_clipboard_default(rate, verbose ? spinner : void 0); return print(palette_default.yellow(use_statistic_default(rate))); } const numericAmount = normalize_amount_default(amount); ensure_default(!Number.isNaN(numericAmount), "INVALID_AMOUNT", { amount }); const resultData = await use_handler_default( "CMD_MSG_FETCH_RATE", async () => { const [err, result] = await to_default( client.convert(baseCode, destCode, numericAmount, date) ); ensure_default(!err, "TIMEOUT_CONVERT"); ensure_default(result.data, "INVALID_CONVERT"); return result.data; }, { date: formatDateString, base: baseCode, dest: destCode }, verbose ? spinner : void 0 ); clipboard2 && await use_clipboard_default(resultData, verbose ? spinner : void 0); return print( palette_default.yellow(use_statistic_default(numericAmount, { precision: 2 })), palette_default.yellow(use_statistic_default(resultData, { precision: 2 })) ); } var convert_default = boundary_default(convert); async function list({ pretty = false, date: dateString = "latest", timeout = 1e4, clipboard: clipboard2 = false, translate: translate2 = false, verbose = false }, spinner) { const isLatest = dateString === "latest"; const date = isLatest ? "latest" : to_date_default(dateString); const formatDateString = isLatest ? date : format_date_default(date); const client = new ForexClient({ timeout }); !verbose && spinner.start(); const currencies = await use_handler_default( "CMD_MSG_FETCH_CURRENCIES", () => get_currencies_default(client, date), { date: formatDateString }, verbose ? spinner : void 0 ); const translation = await use_handler_default( "CMD_MSG_FETCH_TRANSLATION", async () => { const [_, result] = translate2 ? await to_default( use_translate_default( currencies.map((item) => item.name), { timeout } ) ) : [void 0, void 0]; return result; }, { date: formatDateString }, verbose && translate2 ? spinner : void 0 ); clipboard2 && await use_clipboard_default( currencies.map( (item, index) => `${item.name || "-"}${translation?.[index] ? ` / ${translation?.[index]}` : ""} (${item.code})` ).join("\n"), verbose ? spinner : void 0 ); if (!pretty) { return currencies.map((item, index) => { const uppercaseCode = item.code.toUpperCase(); return `${palette_default.yellow(item.name || uppercaseCode)} / ${translation?.[index] || " - "} (${palette_default.blue(uppercaseCode)})`; }).join("\n"); } const data = currencies.map(({ name, code }, index) => [ palette_default.blue(code.toUpperCase()), palette_default.yellow(name || " - "), translation?.[index] || " - " ]); const table = new Table({ width: [6, 32, 42], borderStyle: 2, horizontalLine: true }); table.push( [ palette_default.green("CODE"), palette_default.green("NAME"), palette_default.green("TRANSLATION") ], ...data ); return ` ${table}`; } var list_default = boundary_default(list); // bin/cli.ts process.on("SIGINT", () => { process.exit(0); }); var pkg = JSON.parse( readFileSync$1(new URL("../package.json", import.meta.url)).toString("utf8") ); updateNotifier({ pkg }).notify({ isGlobal: true }); yargs(hideBin(process.argv)).scriptName("forex").usage(i18n_default.t("CMD_USAGE")).options("date", { alias: "d", type: "string", desc: i18n_default.t("CMD_OPTION_DATE"), default: "latest" }).options("timeout", { type: "number", desc: i18n_default.t("CMD_OPTION_TIMEOUT"), default: 1e4 }).options("clipboard", { alias: "c", type: "boolean", desc: i18n_default.t("CMD_OPTION_CLIPBOARD"), default: false }).options("translate", { alias: "T", type: "boolean", desc: i18n_default.t("CMD_OPTION_TRANSLATE"), default: false }).options("verbose", { alias: "v", type: "boolean", desc: i18n_default.t("CMD_OPTION_VERBOSE"), default: false }).command( ["convert [amount]", "to", "$0"], i18n_default.t("CMD_CONVERT_USAGE"), (yargs2) => { return yargs2.options("from", { alias: "f", type: "string", desc: i18n_default.t("CMD_OPTION_FROM"), default: "auto" }).options("to", { alias: "t", type: "string", desc: i18n_default.t("CMD_OPTION_TO"), default: "auto" }).example( palette_default.yellow("forex 1000 -f USD -t EUR"), i18n_default.t("CMD_CONVERT_USAGE_EG") ).example(palette_default.grey("-------"), "").example( palette_default.yellow("forex -t USD"), i18n_default.t("CMD_CONVERT_USAGE_EG_CURRENCY") ).example( palette_default.yellow("forex -t US"), i18n_default.t("CMD_CONVERT_USAGE_EG_LOCALE") ).example( palette_default.yellow("forex -t America"), i18n_default.t("CMD_CONVERT_USAGE_EG_COUNTRY") ).example(palette_default.grey("-------"), "").example( palette_default.yellow("forex --from USD --to EUR"), i18n_default.t("CMD_CONVERT_USAGE_EG_SPECIFIC") ).example( palette_default.yellow("forex --from USD"), i18n_default.t("CMD_CONVERT_USAGE_EG_AUTO") ).example( palette_default.yellow("forex --to USD"), i18n_default.t("CMD_CONVERT_USAGE_EG_AUTO") ); }, convert_default ).command( ["currency [code]", "cur", "ccy", "cy"], i18n_default.t("CMD_CURRENCY_USAGE"), (yargs2) => { return yargs2.example( palette_default.yellow("forex cy US"), i18n_default.t("CMD_CURRENCY_USAGE_EG_CODE") ).example( palette_default.yellow("forex cy USD"), i18n_default.t("CMD_CURRENCY_USAGE_EG_CURRENCY") ).example( palette_default.yellow("forex cy America"), i18n_default.t("CMD_CURRENCY_USAGE_EG_COUNTRY") ); }, currency_default ).command( ["list", "ls"], i18n_default.t("CMD_LIST_USAGE"), (yargs2) => { return yargs2.options("pretty", { alias: "p", type: "boolean", desc: i18n_default.t("CMD_OPTION_PRETTY"), default: false }).example( palette_default.yellow("forex ls"), i18n_default.t("CMD_LIST_USAGE_EG_LATEST") ).example( palette_default.yellow("forex ls -p"), i18n_default.t("CMD_LIST_USAGE_EG_PRETTY") ); }, list_default ).example( palette_default.yellow("forex list -t US -d 2024-12-01"), i18n_default.t("CMD_OPTION_USAGE_EG_DATE") ).example( palette_default.yellow('forex convert -t US -d "Dec 01, 2024"'), i18n_default.t("CMD_OPTION_USAGE_EG_DATE") ).example(palette_default.grey("-------"), "").example( palette_default.yellow("forex convert -t US --timeout 30000"), i18n_default.t("CMD_OPTION_USAGE_EG_TIMEOUT") ).example( palette_default.yellow("forex currency -T -t US"), i18n_default.t("CMD_OPTION_USAGE_EG_TRANSLATE") ).alias("h", "help").alias("V", "version").demandCommand().strictCommands().recommendCommands().completion().parse();