@kabeep/forex-cli
Version:
A Node.js Library to convert foreign exchange in terminal
958 lines (926 loc) • 35.3 kB
JavaScript
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) => ({
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'"
})[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();