Include parse and stringify functions for American short-count locale.master
@@ -0,0 +1,11 @@ | |||
root = true | |||
[*] | |||
charset = utf-8 | |||
end_of_line = lf | |||
indent_size = tab | |||
indent_style = tab | |||
insert_final_newline = true | |||
max_line_length = 120 | |||
tab_width = 2 | |||
trim_trailing_whitespace = true |
@@ -1,2 +1,3 @@ | |||
node_modules | |||
.DS_Store | |||
.idea/ | |||
scripts/*.out.* |
@@ -1,24 +0,0 @@ | |||
- [ ] Improve test case organization | |||
- [ ] Implement stream support (not tested) | |||
- [X] Implement `BigInt` support | |||
- [X] Fully localizable number systems (e.g. custom rules for combining fragments of number words) | |||
- [ ] Reimplement tl-PH | |||
- [ ] Exponential syntax support | |||
- [ ] Optimizations for fractions | |||
- [ ] Check invalid string inputs | |||
- [X] Ordinals (e.g. `twentieth`, `first`, `two millionth`) | |||
- [ ] Implement other `fractionType`s, implement `lazy` fractions, e.g. `0.05` => `zero point zero five`, | |||
will implement `ratio` (`zero and five over one hundred`) and `part` (`zero and five hundredths`) | |||
- [ ] Special names? (e.g. `googol` for `1e+100`) | |||
Some inputs to consider: | |||
1. `1/2` converts to `one half`. When only `lazy` is enabled, it converts to `one over two`. | |||
2. `5/10` or `0.5` or `.5` (on en-US or en-PH) converts to `five tenths`. When `simplify` is `true`, it converts to | |||
`one half`. When only `lazy` is enabled, it converts to `five over ten`. | |||
3. `1e-2` converts to `one hundredth`. It follows by `one thousandth`, `one millionth`, `one billionth`, `one trillionth` etc. | |||
4. `2/1e+2` converts to `two hundredths`. `2/2e+2` converts to `two two-hundredths`. | |||
5. Should we enable `1e+(1e+100)` inputs? "googolplex" | |||
1. Should we enable `1e+1/10`? | |||
2. Should we enable `1e+1/1e+2`? | |||
3. Should we enable `1e+(1e+1)/10`? |
@@ -0,0 +1,9 @@ | |||
{ | |||
"root": true, | |||
"extends": [ | |||
"lxsmnsyc/typescript" | |||
], | |||
"parserOptions": { | |||
"project": "./tsconfig.eslint.json" | |||
} | |||
} |
@@ -0,0 +1,107 @@ | |||
# Logs | |||
logs | |||
*.log | |||
npm-debug.log* | |||
yarn-debug.log* | |||
yarn-error.log* | |||
lerna-debug.log* | |||
# Diagnostic reports (https://nodejs.org/api/report.html) | |||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json | |||
# Runtime data | |||
pids | |||
*.pid | |||
*.seed | |||
*.pid.lock | |||
# Directory for instrumented libs generated by jscoverage/JSCover | |||
lib-cov | |||
# Coverage directory used by tools like istanbul | |||
coverage | |||
*.lcov | |||
# nyc test coverage | |||
.nyc_output | |||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) | |||
.grunt | |||
# Bower dependency directory (https://bower.io/) | |||
bower_components | |||
# node-waf configuration | |||
.lock-wscript | |||
# Compiled binary addons (https://nodejs.org/api/addons.html) | |||
build/Release | |||
# Dependency directories | |||
node_modules/ | |||
jspm_packages/ | |||
# TypeScript v1 declaration files | |||
typings/ | |||
# TypeScript cache | |||
*.tsbuildinfo | |||
# Optional npm cache directory | |||
.npm | |||
# Optional eslint cache | |||
.eslintcache | |||
# Microbundle cache | |||
.rpt2_cache/ | |||
.rts2_cache_cjs/ | |||
.rts2_cache_es/ | |||
.rts2_cache_umd/ | |||
# Optional REPL history | |||
.node_repl_history | |||
# Output of 'npm pack' | |||
*.tgz | |||
# Yarn Integrity file | |||
.yarn-integrity | |||
# dotenv environment variables file | |||
.env | |||
.env.production | |||
.env.development | |||
# parcel-bundler cache (https://parceljs.org/) | |||
.cache | |||
# Next.js build output | |||
.next | |||
# Nuxt.js build / generate output | |||
.nuxt | |||
dist | |||
# Gatsby files | |||
.cache/ | |||
# Comment in the public line in if your project uses Gatsby and *not* Next.js | |||
# https://nextjs.org/blog/next-9-1#public-directory-support | |||
# public | |||
# vuepress build output | |||
.vuepress/dist | |||
# Serverless directories | |||
.serverless/ | |||
# FuseBox cache | |||
.fusebox/ | |||
# DynamoDB Local files | |||
.dynamodb/ | |||
# TernJS port file | |||
.tern-port | |||
.npmrc |
@@ -0,0 +1,73 @@ | |||
{ | |||
"name": "@modal-sh/numerica-cli", | |||
"version": "0.0.0", | |||
"files": [ | |||
"dist", | |||
"src" | |||
], | |||
"engines": { | |||
"node": ">=12" | |||
}, | |||
"license": "UNLICENSED", | |||
"keywords": [ | |||
"pridepack" | |||
], | |||
"devDependencies": { | |||
"@types/node": "^18.14.1", | |||
"@types/yargs": "^17.0.24", | |||
"eslint": "^8.35.0", | |||
"eslint-config-lxsmnsyc": "^0.5.0", | |||
"pridepack": "2.4.4", | |||
"tslib": "^2.5.0", | |||
"typescript": "^4.9.5", | |||
"vitest": "^0.28.1" | |||
}, | |||
"scripts": { | |||
"prepublishOnly": "pridepack clean && pridepack build", | |||
"build": "pridepack build", | |||
"type-check": "pridepack check", | |||
"lint": "pridepack lint", | |||
"clean": "pridepack clean", | |||
"watch": "pridepack watch", | |||
"start": "pridepack start", | |||
"dev": "pridepack dev", | |||
"test": "vitest" | |||
}, | |||
"private": true, | |||
"description": "CLI app.", | |||
"repository": { | |||
"url": "", | |||
"type": "git" | |||
}, | |||
"homepage": "", | |||
"bugs": { | |||
"url": "" | |||
}, | |||
"author": "TheoryOfNekomata <allan.crisostomo@outlook.com>", | |||
"publishConfig": { | |||
"access": "restricted" | |||
}, | |||
"types": "./dist/types/index.d.ts", | |||
"main": "./dist/cjs/production/index.js", | |||
"module": "./dist/esm/production/index.js", | |||
"exports": { | |||
".": { | |||
"development": { | |||
"require": "./dist/cjs/development/index.js", | |||
"import": "./dist/esm/development/index.js" | |||
}, | |||
"require": "./dist/cjs/production/index.js", | |||
"import": "./dist/esm/production/index.js", | |||
"types": "./dist/types/index.d.ts" | |||
} | |||
}, | |||
"typesVersions": { | |||
"*": {} | |||
}, | |||
"dependencies": { | |||
"@clack/prompts": "^0.6.3", | |||
"@modal-sh/core": "workspace:*", | |||
"pino": "^8.14.1", | |||
"yargs": "^17.7.2" | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
{ | |||
"target": "es2018" | |||
} |
@@ -0,0 +1,9 @@ | |||
import { Cli, CliOptions } from './packages/cli-wrapper'; | |||
export const createCli = (options: CliOptions) => { | |||
const cli = new Cli(options); | |||
// TODO setup CLI here | |||
return cli; | |||
}; |
@@ -0,0 +1,22 @@ | |||
import { Cli } from './packages/cli-wrapper'; | |||
import { AdderController, AdderControllerImpl } from './modules/adder'; | |||
export const addCommands = (cli: Cli) => { | |||
const adderController: AdderController = new AdderControllerImpl(); | |||
return cli | |||
.command({ | |||
aliases: ['a'], | |||
command: 'add', | |||
parameters: ['<a> <b>'], | |||
describe: 'Add two numbers', | |||
handler: adderController.addNumbers, | |||
}) | |||
.command({ | |||
aliases: ['s'], | |||
command: 'subtract', | |||
parameters: ['<a> <b>'], | |||
describe: 'Subtract two numbers', | |||
handler: adderController.subtractNumbers, | |||
}); | |||
}; |
@@ -0,0 +1,11 @@ | |||
import { addCommands } from '@/commands'; | |||
import { createCli } from '@/cli'; | |||
import { hideBin } from 'yargs/helpers'; | |||
const cli = createCli({ | |||
name: 'cli', | |||
}); | |||
addCommands(cli); | |||
cli.run(hideBin(process.argv)); |
@@ -0,0 +1,82 @@ | |||
import { | |||
AdderService, | |||
AdderServiceImpl, | |||
ArgumentOutOfRangeError, | |||
InvalidArgumentTypeError, | |||
} from './adder.service'; | |||
import { CommandHandler } from '../../packages/cli-wrapper'; | |||
export interface AdderController { | |||
addNumbers: CommandHandler; | |||
subtractNumbers: CommandHandler; | |||
} | |||
export class AdderControllerImpl implements AdderController { | |||
constructor( | |||
private readonly adderService: AdderService = new AdderServiceImpl(), | |||
) { | |||
// noop | |||
} | |||
readonly addNumbers: CommandHandler = (params) => { | |||
if (!params.interactive) { | |||
const checkArgs = params.args as Record<string, unknown>; | |||
if (typeof checkArgs.a === 'undefined') { | |||
params.logger.error('Missing required argument: a'); | |||
return -1; | |||
} | |||
if (typeof checkArgs.b === 'undefined') { | |||
params.logger.error('Missing required argument: b'); | |||
return -1; | |||
} | |||
} | |||
const { a, b } = params.args; | |||
try { | |||
const response = this.adderService.addNumbers({ a: Number(a), b: Number(b) }); | |||
params.logger.info(response); | |||
} catch (errorRaw) { | |||
const error = errorRaw as Error; | |||
params.logger.error(error.message); | |||
if (error instanceof InvalidArgumentTypeError) { | |||
return -1; | |||
} | |||
if (error instanceof ArgumentOutOfRangeError) { | |||
return -2; | |||
} | |||
return -3; | |||
} | |||
return 0; | |||
} | |||
readonly subtractNumbers: CommandHandler = (params) => { | |||
if (!params.interactive) { | |||
const checkArgs = params.args as Record<string, unknown>; | |||
if (typeof checkArgs.a === 'undefined') { | |||
params.logger.error('Missing required argument: a'); | |||
return -1; | |||
} | |||
if (typeof checkArgs.b === 'undefined') { | |||
params.logger.error('Missing required argument: b'); | |||
return -1; | |||
} | |||
} | |||
const { a, b } = params.args; | |||
try { | |||
const response = this.adderService.addNumbers({ a: Number(a), b: -(Number(b)) }); | |||
params.logger.info(response); | |||
} catch (errorRaw) { | |||
const error = errorRaw as Error; | |||
params.logger.error(error.message); | |||
if (error instanceof InvalidArgumentTypeError) { | |||
return -1; | |||
} | |||
if (error instanceof ArgumentOutOfRangeError) { | |||
return -2; | |||
} | |||
return -3; | |||
} | |||
return 0; | |||
} | |||
} |
@@ -0,0 +1,21 @@ | |||
import { | |||
add, | |||
AddFunctionOptions, | |||
ArgumentOutOfRangeError, | |||
InvalidArgumentTypeError, | |||
} from '@modal-sh/core'; | |||
export interface AdderService { | |||
addNumbers(options: AddFunctionOptions): number; | |||
} | |||
export class AdderServiceImpl implements AdderService { | |||
addNumbers(options: AddFunctionOptions) { | |||
return add(options); | |||
} | |||
} | |||
export { | |||
ArgumentOutOfRangeError, | |||
InvalidArgumentTypeError, | |||
}; |
@@ -0,0 +1,2 @@ | |||
export * from './adder.controller'; | |||
export * from './adder.service'; |
@@ -0,0 +1,337 @@ | |||
import yargs, { Argv } from 'yargs'; | |||
import * as clack from '@clack/prompts'; | |||
import { DummyWriteStream } from './write-stream'; | |||
import pino, {LogFn} from 'pino'; | |||
import * as util from 'util'; | |||
export interface Logger extends pino.BaseLogger {} | |||
export interface CommandHandlerArgs { | |||
self: any; | |||
interactive: boolean; | |||
send: (message: number) => void; | |||
logger: Logger; | |||
args: any; | |||
} | |||
export type CommandHandler = (args: CommandHandlerArgs) => void | number | Promise<number> | Promise<void>; | |||
export interface CommandArgs { | |||
aliases?: string[]; | |||
command: string; | |||
parameters: string[]; | |||
describe: string; | |||
handler: CommandHandler; | |||
interactiveMode?: boolean; | |||
} | |||
type PromptValueArgs = [Record<string, string>, ...never[]] | |||
| [string, string] | |||
| [[string, string], ...never[]] | |||
| [[string, string][], ...never[]]; | |||
export interface TestModeResult { | |||
exitCode?: number; | |||
stdout: DummyWriteStream; | |||
stderr: DummyWriteStream; | |||
} | |||
interface InteractiveModeOptions { | |||
option: string; | |||
alias: string; | |||
intro: string; | |||
cancelled: string; | |||
outro: string; | |||
describe: string; | |||
} | |||
export interface CliOptions { | |||
name: string; | |||
interactiveMode?: Partial<InteractiveModeOptions>; | |||
logger?: Logger | boolean; | |||
} | |||
export class Cli { | |||
private testMode = false; | |||
private promptValues = {} as Record<string, unknown>; | |||
private testModeResult: TestModeResult = { | |||
exitCode: undefined as number | undefined, | |||
stdout: new DummyWriteStream(), | |||
stderr: new DummyWriteStream(), | |||
}; | |||
private readonly cli: Argv; | |||
constructor(private readonly options: CliOptions) { | |||
this.cli = yargs() | |||
.scriptName(options.name) | |||
.help() | |||
.fail(false) | |||
.strict(false); | |||
} | |||
private exit(code: number) { | |||
if (this.testMode) { | |||
this.testModeResult.exitCode = code; | |||
return; | |||
} | |||
process.exit(code); | |||
} | |||
private async prompt(messages: Record<string, string>, interactiveModeOptions: InteractiveModeOptions) { | |||
const { | |||
intro, | |||
cancelled, | |||
outro, | |||
} = interactiveModeOptions; | |||
const messagesEntries = Object.entries(messages); | |||
if (messagesEntries.length > 0) { | |||
clack.intro(intro); | |||
const values = await clack.group( | |||
Object.fromEntries( | |||
messagesEntries.map(([key, value]) => [key, () => clack.text({ message: value })]) | |||
), | |||
{ | |||
onCancel: () => { | |||
clack.cancel(cancelled); | |||
this.exit(1); | |||
} | |||
} | |||
); | |||
clack.outro(outro); | |||
return values; | |||
} | |||
return {}; | |||
} | |||
private async generateCommandArgs( | |||
context: any, | |||
originalCommandArgs: Record<string, unknown>, | |||
interactive: boolean, | |||
interactiveModeOptions: InteractiveModeOptions, | |||
) { | |||
if (!interactive) { | |||
return originalCommandArgs; | |||
} | |||
// TODO properly filter attributes | |||
const clackGroup = context.optional | |||
.filter((optional: { cmd: string[] }) => { | |||
const { | |||
'$0': _$0, | |||
i: _i, | |||
interactive: _interactive, | |||
test: _test, | |||
_: __, | |||
...rest | |||
} = originalCommandArgs; | |||
return !Object.keys(rest).includes(optional.cmd[0]); | |||
}) | |||
.reduce( | |||
(group: Record<string, string>, optional: { cmd: string[], describe: string }) => ({ | |||
...group, | |||
[optional.cmd[0]]: optional.describe ?? optional.cmd[0], | |||
}), | |||
{} as Record<string, string>, | |||
); | |||
const promptedArgs = this.testMode | |||
? this.promptValues | |||
: await this.prompt(clackGroup, interactiveModeOptions); | |||
return { | |||
...originalCommandArgs, | |||
...promptedArgs, | |||
}; | |||
} | |||
private buildHandler(handlerFn: Function, interactiveModeOptions: InteractiveModeOptions) { | |||
const thisCli = this; | |||
return async function handler(this: any, commandArgs: Record<string, unknown>) { | |||
const self = this; | |||
const { option, alias } = interactiveModeOptions; | |||
const interactiveLong = option in commandArgs ? Boolean(commandArgs.interactive) : false; | |||
const interactiveShort = alias in commandArgs ? Boolean(commandArgs.i) : false; | |||
const interactive = interactiveLong || interactiveShort; | |||
const buildArgs = await thisCli.generateCommandArgs( | |||
self, | |||
commandArgs, | |||
interactive, | |||
interactiveModeOptions, | |||
); | |||
const stdoutLogFn: LogFn = (...args: unknown[]) => { | |||
const stream = thisCli.testMode ? thisCli.testModeResult.stdout : process.stdout; | |||
const [arg0, arg1, ...etcArgs] = args; | |||
if (typeof arg0 === 'string' || typeof arg0 === 'number') { | |||
if (arg1) { | |||
if (etcArgs.length > 0) { | |||
stream.write(util.format(`${arg0}\n`, arg1, ...etcArgs)); | |||
} else { | |||
stream.write(util.format(`${arg0}\n`, arg1)); | |||
} | |||
} else { | |||
stream.write(util.format(`${arg0}\n`)); | |||
} | |||
} else if (typeof arg0 === 'object' && (typeof arg1 === 'string' || typeof arg1 === 'number')) { | |||
if (etcArgs.length > 0) { | |||
stream.write(util.format(`${arg1}\n`, ...etcArgs)); | |||
} else { | |||
stream.write(util.format(`${arg1}\n`)); | |||
} | |||
} | |||
}; | |||
const stderrLogFn: LogFn = (...args: unknown[]) => { | |||
const stream = thisCli.testMode ? thisCli.testModeResult.stderr : process.stderr; | |||
const [arg0, arg1, ...etcArgs] = args; | |||
if (typeof arg0 === 'string' || typeof arg0 === 'number') { | |||
if (arg1) { | |||
if (etcArgs.length > 0) { | |||
stream.write(util.format(`${arg0}\n`, arg1, ...etcArgs)); | |||
} else { | |||
stream.write(util.format(`${arg0}\n`, arg1)); | |||
} | |||
} else { | |||
stream.write(util.format(`${arg0}\n`)); | |||
} | |||
} else if (typeof arg0 === 'object' && (typeof arg1 === 'string' || typeof arg1 === 'number')) { | |||
if (etcArgs.length > 0) { | |||
stream.write(util.format(`${arg1}\n`, ...etcArgs)); | |||
} else { | |||
stream.write(util.format(`${arg1}\n`)); | |||
} | |||
} | |||
}; | |||
const loggerFn = thisCli.options.logger; | |||
const defaultLogger = { | |||
level: 'info', | |||
enabled: typeof loggerFn === 'boolean' ? loggerFn : true, | |||
debug: stdoutLogFn, | |||
info: stdoutLogFn, | |||
warn: stdoutLogFn, | |||
error: stderrLogFn, | |||
fatal: stderrLogFn, | |||
trace: stdoutLogFn, | |||
silent: () => {}, | |||
} as Logger; | |||
const loggerBooleanFn = typeof loggerFn === 'boolean' && loggerFn ? defaultLogger : { | |||
level: 'silent', | |||
debug: () => {}, | |||
info: () => {}, | |||
warn: () => {}, | |||
error: () => {}, | |||
fatal: () => {}, | |||
trace: () => {}, | |||
silent: () => {}, | |||
} as Logger; | |||
const logger = typeof loggerFn === 'undefined' ? defaultLogger : (typeof loggerFn === 'function' ? loggerFn : loggerBooleanFn); | |||
let exited = false; | |||
const returnCode = await handlerFn({ | |||
self, | |||
interactive, | |||
logger, | |||
send: (code: number) => { | |||
exited = true; | |||
thisCli.exit(code); | |||
}, | |||
args: buildArgs, | |||
}); | |||
if (!exited) { | |||
thisCli.exit(returnCode ? returnCode : 0); | |||
} | |||
}; | |||
} | |||
command({ parameters, command, interactiveMode = true, handler, ...args }: CommandArgs): Cli { | |||
// TODO type-safe declaration of positional/optional arguments | |||
const { | |||
option = 'interactive', | |||
alias = 'i', | |||
describe = 'Interactive mode', | |||
intro = 'Please provide the following values:', | |||
cancelled = 'Operation cancelled.', | |||
outro = 'Thank you!', | |||
} = this.options?.interactiveMode ?? {}; | |||
const commandArgs = { | |||
...args, | |||
command: `${command} ${parameters.join(' ').replace(/</g, '[').replace(/>/g, ']')}`, | |||
usage: parameters.map((p) => `${command} ${p}`).join('\n'), | |||
handler: this.buildHandler(handler, { | |||
option, | |||
alias, | |||
intro, | |||
cancelled, | |||
outro, | |||
describe, | |||
}), | |||
} as Record<string, unknown> | |||
if (interactiveMode) { | |||
commandArgs.options = { | |||
...(commandArgs.options ?? {}), | |||
[option]: { | |||
alias, | |||
describe, | |||
type: 'boolean', | |||
default: false, | |||
hidden: true, | |||
}, | |||
}; | |||
} | |||
this.cli.command(commandArgs as unknown as Parameters<typeof this.cli.command>[0]); | |||
return this; | |||
} | |||
async run(args: string[]): Promise<void> { | |||
if (args.length > 0) { | |||
await this.cli.parse(args); | |||
return; | |||
} | |||
this.cli.showHelp(); | |||
} | |||
test() { | |||
this.testMode = true; | |||
this.promptValues = {}; | |||
const thisCli = this; | |||
return { | |||
promptValue(...args: PromptValueArgs) { | |||
const [testArg] = args; | |||
if (typeof testArg === 'string') { | |||
const [key, value] = args as [string, unknown]; | |||
thisCli.promptValues[key] = thisCli.promptValues[key] ?? value; | |||
} | |||
if (Array.isArray(testArg)) { | |||
const [key, value] = testArg as [string, unknown]; | |||
thisCli.promptValues[key] = thisCli.promptValues[key] ?? value; | |||
} | |||
if (typeof testArg === 'object' && testArg !== null) { | |||
Object.entries(testArg).forEach(([key, value]) => { | |||
thisCli.promptValues[key] = thisCli.promptValues[key] ?? value; | |||
}); | |||
} | |||
return this; | |||
}, | |||
async run(args: string[]): Promise<TestModeResult> { | |||
await thisCli.cli.parse(args); | |||
return thisCli.testModeResult; | |||
}, | |||
}; | |||
} | |||
} |
@@ -0,0 +1,19 @@ | |||
import {WriteStream} from 'tty'; | |||
export class DummyWriteStream extends WriteStream { | |||
private bufferInternal = Buffer.from(''); | |||
constructor() { | |||
super(0); | |||
} | |||
write(input: Uint8Array | string, _encoding?: BufferEncoding | ((err?: Error) => void), _cb?: (err?: Error) => void): boolean { | |||
this.bufferInternal = Buffer.concat([this.bufferInternal, Buffer.from(input)]); | |||
// noop | |||
return true; | |||
} | |||
get buffer(): Buffer { | |||
return this.bufferInternal; | |||
} | |||
} |
@@ -0,0 +1,68 @@ | |||
import { describe, it, expect, vi, beforeAll } from 'vitest'; | |||
import { createCli } from '../src/cli'; | |||
import { addCommands } from '../src/commands'; | |||
import { AdderServiceImpl, InvalidArgumentTypeError, ArgumentOutOfRangeError } from '../src/modules/adder'; | |||
import { Cli } from '../src/packages/cli-wrapper'; | |||
vi.mock('process'); | |||
describe('blah', () => { | |||
let cli: Cli; | |||
beforeAll(() => { | |||
cli = createCli({ | |||
name: 'cli-test', | |||
logger: false, | |||
}); | |||
addCommands(cli); | |||
}); | |||
it('returns result when successful', async () => { | |||
const response = await cli.test().run(['add', '1', '2']); | |||
expect(response.exitCode).toBe(0); | |||
}); | |||
it('returns error when given invalid inputs', async () => { | |||
vi.spyOn(AdderServiceImpl.prototype, 'addNumbers').mockImplementationOnce(() => { | |||
throw new InvalidArgumentTypeError('Invalid input'); | |||
}); | |||
const response = await cli.test().run(['add', '1', '2']); | |||
expect(response.exitCode).toBe(-1); | |||
}); | |||
it('returns error when given out-of-range inputs', async () => { | |||
vi.spyOn(AdderServiceImpl.prototype, 'addNumbers').mockImplementationOnce(() => { | |||
throw new ArgumentOutOfRangeError('Out of range'); | |||
}); | |||
const response = await cli.test().run(['add', '1', '2']); | |||
expect(response.exitCode).toBe(-2); | |||
}); | |||
it('returns error when an unexpected error occurs', async () => { | |||
vi.spyOn(AdderServiceImpl.prototype, 'addNumbers').mockImplementationOnce(() => { | |||
throw new Error('Unexpected error'); | |||
}); | |||
const response = await cli.test().run(['add', '1', '2']); | |||
expect(response.exitCode).toBe(-3); | |||
}); | |||
it('returns error when given insufficient arguments', async () => { | |||
const response = await cli.test().run(['add', '1']); | |||
expect(response.exitCode).toBe(-1); | |||
}); | |||
describe('on interactive mode', () => { | |||
it('prompts values for insufficient arguments', async () => { | |||
const response = await cli | |||
.test() | |||
.promptValue({ | |||
a: '1', | |||
b: '2', | |||
}) | |||
.run(['add', '-i']); | |||
expect(response.exitCode).not.toBe(-1); | |||
}); | |||
}); | |||
}); |
@@ -0,0 +1,24 @@ | |||
{ | |||
"exclude": ["node_modules"], | |||
"include": ["src", "types", "test"], | |||
"compilerOptions": { | |||
"module": "ESNext", | |||
"lib": ["ESNext"], | |||
"importHelpers": true, | |||
"declaration": true, | |||
"sourceMap": true, | |||
"rootDir": "./", | |||
"strict": true, | |||
"noUnusedLocals": true, | |||
"noUnusedParameters": true, | |||
"noImplicitReturns": true, | |||
"noFallthroughCasesInSwitch": true, | |||
"moduleResolution": "node", | |||
"jsx": "react", | |||
"esModuleInterop": true, | |||
"target": "es2018", | |||
"paths": { | |||
"@/*": ["./src/*"], | |||
} | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
{ | |||
"exclude": ["node_modules"], | |||
"include": ["src", "types"], | |||
"compilerOptions": { | |||
"module": "ESNext", | |||
"lib": ["ESNext"], | |||
"importHelpers": true, | |||
"declaration": true, | |||
"sourceMap": true, | |||
"rootDir": "./src", | |||
"strict": true, | |||
"noUnusedLocals": true, | |||
"noUnusedParameters": true, | |||
"noImplicitReturns": true, | |||
"noFallthroughCasesInSwitch": true, | |||
"moduleResolution": "node", | |||
"jsx": "react", | |||
"esModuleInterop": true, | |||
"target": "es2018", | |||
"paths": { | |||
"@/*": ["./src/*"], | |||
} | |||
} | |||
} |
@@ -1,47 +1,70 @@ | |||
{ | |||
"name": "@modal-sh/numerica-core", | |||
"version": "0.0.0", | |||
"types": "dist/types/index.d.ts", | |||
"main": "dist/cjs/index.js", | |||
"module": "dist/esm/index.js", | |||
"exports": { | |||
"require": "./dist/cjs/index.js", | |||
"import": "./dist/esm/index.js" | |||
}, | |||
"files": [ | |||
"dist" | |||
"dist", | |||
"src" | |||
], | |||
"engines": { | |||
"node": ">=10" | |||
"node": ">=12" | |||
}, | |||
"license": "MIT", | |||
"license": "UNLICENSED", | |||
"keywords": [ | |||
"pridepack", | |||
"number", | |||
"name" | |||
"language", | |||
"conversion", | |||
"name", | |||
"words" | |||
], | |||
"name": "@theoryofnekomata/numerica", | |||
"description": "Gets the name of a number, even if it's stupidly big.", | |||
"devDependencies": { | |||
"@types/bignumber.js": "^5.0.0", | |||
"@types/jest": "^26.0.24", | |||
"@types/node": "^16.3.3", | |||
"eslint": "^7.31.0", | |||
"eslint-config-lxsmnsyc": "^0.2.3", | |||
"pridepack": "^0.10.0", | |||
"tslib": "^2.3.0", | |||
"typescript": "^4.3.5" | |||
"@types/node": "^18.14.1", | |||
"eslint": "^8.35.0", | |||
"eslint-config-lxsmnsyc": "^0.5.0", | |||
"pridepack": "2.4.4", | |||
"tslib": "^2.5.0", | |||
"typescript": "^4.9.5", | |||
"vitest": "^0.28.1" | |||
}, | |||
"peerDependencies": {}, | |||
"scripts": { | |||
"prepublish": "pridepack clean && pridepack build", | |||
"prepublishOnly": "pridepack clean && pridepack build", | |||
"build": "pridepack build", | |||
"type-check": "pridepack check", | |||
"lint": "pridepack lint", | |||
"test": "pridepack test --passWithNoTests", | |||
"clean": "pridepack clean", | |||
"watch": "pridepack watch" | |||
"watch": "pridepack watch", | |||
"start": "pridepack start", | |||
"dev": "pridepack dev", | |||
"test": "vitest" | |||
}, | |||
"private": true, | |||
"description": "Core library.", | |||
"repository": { | |||
"url": "", | |||
"type": "git" | |||
}, | |||
"homepage": "", | |||
"bugs": { | |||
"url": "" | |||
}, | |||
"author": "TheoryOfNekomata <allan.crisostomo@outlook.com>", | |||
"publishConfig": { | |||
"access": "restricted" | |||
}, | |||
"types": "./dist/types/index.d.ts", | |||
"main": "./dist/cjs/production/index.js", | |||
"module": "./dist/esm/production/index.js", | |||
"exports": { | |||
".": { | |||
"development": { | |||
"require": "./dist/cjs/development/index.js", | |||
"import": "./dist/esm/development/index.js" | |||
}, | |||
"require": "./dist/cjs/production/index.js", | |||
"import": "./dist/esm/production/index.js", | |||
"types": "./dist/types/index.d.ts" | |||
} | |||
}, | |||
"dependencies": { | |||
"bignumber.js": "^9.0.1" | |||
"typesVersions": { | |||
"*": {} | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
{ | |||
"target": "es2018" | |||
} |
@@ -0,0 +1,15 @@ | |||
type GroupDigits = string; | |||
type GroupPlace = number; | |||
export type Group = [GroupDigits, GroupPlace]; | |||
export interface StringifySystem { | |||
makeNegative: (s: string) => string; | |||
makeGroup: (group: string, place?: GroupPlace, options?: Record<string, unknown>) => string; | |||
group: (value: string) => Group[]; | |||
finalize: (tokens: string[]) => string; | |||
tokenize: (value: string) => string[]; | |||
parseGroups: (value: string[]) => Group[]; | |||
combineGroups: (value: Group[]) => string; | |||
} |
@@ -0,0 +1,59 @@ | |||
import { enUS } from './systems'; | |||
import { StringifySystem } from './common'; | |||
type StringifyValue = string | number | bigint; | |||
export interface StringifyOptions { | |||
system?: StringifySystem; | |||
makeGroupOptions?: Record<string, unknown>; | |||
} | |||
export const stringify = ( | |||
valueRaw: StringifyValue, | |||
options = {} as StringifyOptions | |||
): string => { | |||
if (!(['bigint', 'number', 'string'].includes(typeof (valueRaw as unknown)))) { | |||
throw new TypeError('value must be a string, number, or bigint'); | |||
} | |||
const value = valueRaw.toString().replace(/\s/g, ''); | |||
const { system = enUS, makeGroupOptions} = options; | |||
if (value.startsWith('-')) { | |||
return system.makeNegative(stringify(value.slice(1), options)); | |||
} | |||
const groups = system | |||
.group(value) | |||
.map(([group, place]) => ( | |||
system.makeGroup(group, place, makeGroupOptions) | |||
)); | |||
return system.finalize(groups); | |||
}; | |||
type ParseType = 'string' | 'number' | 'bigint'; | |||
export interface ParseOptions { | |||
system?: StringifySystem; | |||
type?: ParseType; | |||
} | |||
export const parse = (value: string, options = {} as ParseOptions) => { | |||
const { system = enUS, type = 'string' } = options; | |||
const tokens = system.tokenize(value); | |||
const groups = system.parseGroups(tokens); | |||
const stringValue = system.combineGroups(groups); | |||
switch (type) { | |||
case 'number': | |||
return Number(stringValue); | |||
case 'bigint': | |||
return BigInt(stringValue); | |||
default: | |||
break; | |||
} | |||
return stringValue; | |||
}; |
@@ -0,0 +1,123 @@ | |||
/** | |||
* Valid values that can be converted to exponential notation. | |||
*/ | |||
export type ValidValue = string | number | bigint; | |||
/** | |||
* Options to use when converting a number to exponential notation. | |||
*/ | |||
export interface NumberToExponentialOptions { | |||
/** | |||
* The decimal point character to use. Defaults to ".". | |||
*/ | |||
decimalPoint?: string; | |||
/** | |||
* The grouping symbol to use. Defaults to ",". | |||
*/ | |||
groupingSymbol?: string; | |||
/** | |||
* Exponent character to use. Defaults to "e". | |||
*/ | |||
exponentDelimiter?: string; | |||
} | |||
/** | |||
* Extracts the integer, fractional, and exponent components of a string in exponential notation. | |||
* @param value - The string value to extract components from. | |||
* @param options - Options to use when extracting components. | |||
*/ | |||
export const extractExponentialComponents = (value: string, options = {} as NumberToExponentialOptions) => { | |||
const { | |||
decimalPoint = '.', | |||
groupingSymbol = ',', | |||
exponentDelimiter = 'e', | |||
} = options; | |||
const valueWithoutGroupingSymbols = value.replace(new RegExp(`${groupingSymbol}`, 'g'), ''); | |||
const exponentDelimiterIndex = valueWithoutGroupingSymbols.indexOf(exponentDelimiter); | |||
if (exponentDelimiterIndex < 0) { | |||
// We force the value to have decimal point so that we can extract the integer and fractional | |||
// components. | |||
const stringValueWithDecimal = valueWithoutGroupingSymbols.includes(decimalPoint) | |||
? valueWithoutGroupingSymbols | |||
: `${valueWithoutGroupingSymbols}${decimalPoint}0`; | |||
const [integerRaw, fractionalRaw] = stringValueWithDecimal.split(decimalPoint); | |||
const integer = integerRaw.replace(/^0+/g, ''); | |||
const exponentValue = BigInt(integer.length - 1); | |||
return { | |||
integer, | |||
exponent: exponentValue < 0 ? exponentValue.toString() : `+${exponentValue}`, | |||
fractional: fractionalRaw.replace(/0+$/g, ''), | |||
}; | |||
} | |||
if (exponentDelimiterIndex !== valueWithoutGroupingSymbols.lastIndexOf(exponentDelimiter)) { | |||
throw new TypeError('Value must not contain more than one exponent character'); | |||
} | |||
const [base, exponentRaw] = valueWithoutGroupingSymbols.split(exponentDelimiter); | |||
const [integerRaw, fractionalRaw = ''] = base.split(decimalPoint); | |||
const integerWithoutZeroes = integerRaw.replace(/^0+/g, ''); | |||
const integer = integerWithoutZeroes[0] ?? '0'; | |||
const extraIntegerDigits = integerWithoutZeroes.slice(1); | |||
const fractional = `${extraIntegerDigits}${fractionalRaw.replace(/0+$/g, '')}`; | |||
const exponentValue = BigInt(exponentRaw) + BigInt(extraIntegerDigits.length); | |||
return { | |||
integer, | |||
exponent: exponentValue < 0 ? exponentValue.toString() : `+${exponentValue}`, | |||
fractional, | |||
}; | |||
} | |||
/** | |||
* Converts a numeric value to a string in exponential notation. Supports numbers of all types. | |||
* @param value - The value to convert. | |||
* @param options - Options to use when extracting components. | |||
*/ | |||
export const numberToExponential = (value: ValidValue, options = {} as NumberToExponentialOptions): string => { | |||
const stringValueRaw = value as unknown | |||
if (typeof stringValueRaw === 'bigint' || typeof stringValueRaw === 'number') { | |||
return numberToExponential(stringValueRaw.toString(), options); | |||
} | |||
if (typeof stringValueRaw !== 'string') { | |||
throw new TypeError(`Value must be a string, number, or bigint. Received: ${typeof stringValueRaw}`); | |||
} | |||
if (stringValueRaw.startsWith('-')) { | |||
return `-${numberToExponential(stringValueRaw.slice(1), options)}}`; | |||
} | |||
const { | |||
decimalPoint = '.', | |||
groupingSymbol = ',', | |||
exponentDelimiter = 'e', | |||
} = options; | |||
const stringValue = stringValueRaw | |||
.replace(new RegExp(`${groupingSymbol}`, 'g'), '') | |||
.toLowerCase() | |||
.replace(/\s/g, ''); | |||
const { | |||
integer, | |||
fractional, | |||
exponent, | |||
} = extractExponentialComponents(stringValue, options); | |||
const significantDigits = `${integer}${fractional}`; | |||
if (significantDigits.length === 0) { | |||
// We copy the behavior from `Number.prototype.toExponential` here. | |||
return `0${exponentDelimiter}+0`; | |||
} | |||
const significandInteger = significantDigits[0]; | |||
const significandFractional = significantDigits.slice(1).replace(/0+$/g, ''); | |||
if (significandFractional.length === 0) { | |||
return `${significandInteger}${exponentDelimiter}${exponent}`; | |||
} | |||
return `${significandInteger}${decimalPoint}${significandFractional}${exponentDelimiter}${exponent}`; | |||
}; |
@@ -1,19 +1,3 @@ | |||
import enPH from './locales/variants/en-PH'; | |||
import { | |||
Numeric, | |||
} from './utils/numeric'; | |||
type Options = { | |||
groupSeparator: string, | |||
ordinal: boolean, | |||
locale?: (xRaw: Numeric, options?: Partial<Omit<Options, 'locale'>>) => string, | |||
} | |||
type GetNumberName = (number: Numeric, options?: Partial<Options>) => string | |||
const getNumberName: GetNumberName = (number, options = {} as Partial<Options>): string => { | |||
const {locale = enPH, ...etcOptions} = options; | |||
return locale(number, etcOptions); | |||
}; | |||
export default getNumberName; | |||
export * as systems from './systems'; | |||
export * from './converter'; | |||
export * from './common'; |
@@ -1,93 +0,0 @@ | |||
import BigNumber from 'bignumber.js'; | |||
import {BLANK_DIGIT} from '../../../utils/numeric'; | |||
const config = { | |||
hundredName: 'hundert', | |||
hundredOrdinalName: 'hundertste', | |||
and: 'und', | |||
onesNames: ['zero', 'ein', 'zwei', 'drei', 'vier', 'fünf', 'sechs', 'sieben', 'acht', 'neun'], | |||
onesOrdinalNames: [ | |||
'nullth', | |||
'zuerst', | |||
'zweite', | |||
'dritte', | |||
'vierte', | |||
'fünfte', | |||
'sechste', | |||
'siebte', | |||
'achte', | |||
'neunte', | |||
], | |||
teensNames: [ | |||
'zehn', | |||
'elf', | |||
'zwölf', | |||
'dreizehn', | |||
'vierzehn', | |||
'fünfzehn', | |||
'sechzehn', | |||
'siebzehn', | |||
'achtzehn', | |||
'neunzehn', | |||
], | |||
teensOrdinalNames: [ | |||
'zehnte', | |||
'elfte', | |||
'zwölfte', | |||
'dreizehnte', | |||
'vierzehnte', | |||
'fünfzehnte', | |||
'sechzehnte', | |||
'siebzehnte', | |||
'achtzehnte', | |||
'neunzehnte', | |||
], | |||
tensNames: ['zero', 'zehn', 'zwanzig', 'dreißig', 'vierzig', 'fünfzig', 'sechzig', 'siebzig', 'achtzig', 'neunzig'], | |||
tensOrdinalNames: [ | |||
'nullth', | |||
'zehnte', | |||
'zwanzigste', | |||
'dreißigste', | |||
'vierzigste', | |||
'fünfzigste', | |||
'sechzigste', | |||
'siebzigste', | |||
'achtzigste', | |||
'neunzigste', | |||
], | |||
grouping: 3, | |||
}; | |||
export const getGroupDigitsName = (digitsRaw: string, index: BigNumber, ordinal: boolean) => { | |||
const digits = digitsRaw.padStart(config.grouping, BLANK_DIGIT); | |||
const [hundreds, tens, ones] = digits.split('').map(s => Number(s)); | |||
const names = []; | |||
if (hundreds !== 0) { | |||
names.push(config.onesNames[hundreds]); | |||
if (!ordinal || (tens > 0 || ones > 0)) { | |||
names.push(config.hundredName); | |||
} | |||
} | |||
if (tens === 1) { | |||
names.push(config.teensNames[ones]); | |||
} else if (tens > 1) { | |||
if (ones > 0) { | |||
names.push(config.onesNames[ones]); | |||
names.push(config.and); | |||
} | |||
names.push(config.tensNames[tens]); | |||
} else { | |||
if (hundreds === 0) { | |||
if (ones === 1 && index.gte(2)) { | |||
names.push(ordinal ? config.onesOrdinalNames[ones] : config.onesNames[ones] + 'e'); | |||
} else { | |||
names.push(ordinal ? config.onesOrdinalNames[ones] : config.onesNames[ones]); | |||
} | |||
} else if (ones > 0) { | |||
names.push(ordinal ? config.onesOrdinalNames[ones] : config.onesNames[ones]); | |||
} else if (ordinal) { | |||
names.push(config.hundredOrdinalName); | |||
} | |||
} | |||
return names.join(''); | |||
}; |
@@ -1,77 +0,0 @@ | |||
import {BLANK_DIGIT} from '../../../utils/numeric'; | |||
const config = { | |||
onesNames: ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'], | |||
onesOrdinalNames: ['zeroth', 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth'], | |||
teensNames: [ | |||
'ten', | |||
'eleven', | |||
'twelve', | |||
'thirteen', | |||
'fourteen', | |||
'fifteen', | |||
'sixteen', | |||
'seventeen', | |||
'eighteen', | |||
'nineteen', | |||
], | |||
teensOrdinalNames: [ | |||
'tenth', | |||
'eleventh', | |||
'twelfth', | |||
'thirteenth', | |||
'fourteenth', | |||
'fifteenth', | |||
'sixteenth', | |||
'seventeenth', | |||
'eighteenth', | |||
'nineteenth', | |||
], | |||
tensNames: ['zero', 'ten', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'], | |||
tensOrdinalNames: [ | |||
'zeroth', | |||
'tenth', | |||
'twentieth', | |||
'thirtieth', | |||
'fortieth', | |||
'fiftieth', | |||
'sixtieth', | |||
'seventieth', | |||
'eightieth', | |||
'ninetieth', | |||
], | |||
hundredName: 'hundred', | |||
hundredOrdinalName: 'hundredth', | |||
grouping: 3, | |||
}; | |||
export const getGroupDigitsName = (digitsRaw: string, ordinal: boolean) => { | |||
const digits = digitsRaw.padStart(config.grouping, BLANK_DIGIT); | |||
const [hundreds, tens, ones] = digits.split('').map(s => Number(s)); | |||
const names = []; | |||
if (hundreds !== 0) { | |||
names.push(config.onesNames[hundreds]); | |||
if (!ordinal || (tens > 0 || ones > 0)) { | |||
names.push(config.hundredName); | |||
} | |||
} | |||
if (tens === 1) { | |||
names.push(ordinal ? config.teensOrdinalNames[ones] : config.teensNames[ones]); | |||
} else if (tens > 1) { | |||
if (ones > 0) { | |||
names.push(config.tensNames[tens]); | |||
names.push(ordinal ? config.onesOrdinalNames[ones] : config.onesNames[ones]); | |||
} else { | |||
names.push(ordinal ? config.tensOrdinalNames[tens] : config.tensNames[tens]); | |||
} | |||
} else { | |||
if (hundreds === 0) { | |||
names.push(ordinal ? config.onesOrdinalNames[ones] : config.onesNames[ones]); | |||
} else if (ones > 0) { | |||
names.push(ordinal ? config.onesOrdinalNames[ones] : config.onesNames[ones]); | |||
} else if (ordinal) { | |||
names.push(config.hundredOrdinalName); | |||
} | |||
} | |||
return names.join(' '); | |||
}; |
@@ -1,48 +0,0 @@ | |||
import getNumberName from '../../../index'; | |||
import getLocalizedNumberName from './index'; | |||
describe('Landon\'s original test cases', () => { | |||
describe('Basic conversions', () => { | |||
it.each` | |||
value | traditionalEuropeanName | |||
${1} | ${'ein'} | |||
${1000} | ${'eintausend'} | |||
${1000000} | ${'eine Million'} | |||
${1000000000} | ${'eine Milliarde'} | |||
${1000000000000} | ${'eine Billion'} | |||
${1000000000000000} | ${'eine Billiarde'} | |||
${1000000000000000000} | ${'eine Trillion'} | |||
`('converts $value to $traditionalEuropeanName', ({value, traditionalEuropeanName}) => { | |||
expect(getNumberName(value, {locale: getLocalizedNumberName})).toBe(traditionalEuropeanName); | |||
}); | |||
}); | |||
describe('Medium size numbers (<= 1e+63)', () => { | |||
describe('Table 1', () => { | |||
it.each` | |||
value | traditionalEuropeanName | |||
${'1e+9'} | ${'Milliarde'} | |||
${'1e+12'} | ${'Billion'} | |||
${'1e+15'} | ${'Billiarde'} | |||
${'1e+18'} | ${'Trillion'} | |||
${'1e+21'} | ${'Trilliarde'} | |||
${'1e+24'} | ${'Quadrillion'} | |||
${'1e+27'} | ${'Quadrilliarde'} | |||
${'1e+30'} | ${'Quintillion'} | |||
${'1e+33'} | ${'Quintilliarde'} | |||
${'1e+36'} | ${'Sextillion'} | |||
${'1e+39'} | ${'Sextilliarde'} | |||
${'1e+42'} | ${'Septillion'} | |||
${'1e+45'} | ${'Septilliarde'} | |||
${'1e+48'} | ${'Octillion'} | |||
${'1e+51'} | ${'Octilliarde'} | |||
${'1e+54'} | ${'Nonillion'} | |||
${'1e+57'} | ${'Nonilliarde'} | |||
${'1e+60'} | ${'Decillion'} | |||
${'1e+63'} | ${'Decilliarde'} | |||
`('converts $value to $traditionalEuropeanName', ({value, traditionalEuropeanName}) => { | |||
expect(getNumberName(value, {locale: getLocalizedNumberName})).toBe(`eine ${traditionalEuropeanName}`); | |||
}); | |||
}); | |||
}); | |||
}); |
@@ -1,204 +0,0 @@ | |||
import getNumberName from '../../../index'; | |||
import getLocalizedNumberName from './index'; | |||
describe('Number group conversion', () => { | |||
describe('0 in hundreds place', () => { | |||
describe('0 in tens place', () => { | |||
describe.each` | |||
ones | onesName | |||
${0} | ${'zero'} | |||
${1} | ${'ein'} | |||
${2} | ${'zwei'} | |||
${3} | ${'drei'} | |||
${4} | ${'vier'} | |||
${5} | ${'fünf'} | |||
${6} | ${'sechs'} | |||
${7} | ${'sieben'} | |||
${8} | ${'acht'} | |||
${9} | ${'neun'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
it(`converts ${ones} to ${onesName}`, () => { | |||
expect(getNumberName(ones, {locale: getLocalizedNumberName})).toBe(onesName); | |||
}); | |||
}); | |||
}); | |||
describe('1 in tens place', () => { | |||
describe.each` | |||
ones | onesName | |||
${0} | ${'zehn'} | |||
${1} | ${'elf'} | |||
${2} | ${'zwölf'} | |||
${3} | ${'dreizehn'} | |||
${4} | ${'vierzehn'} | |||
${5} | ${'fünfzehn'} | |||
${6} | ${'sechzehn'} | |||
${7} | ${'siebzehn'} | |||
${8} | ${'achtzehn'} | |||
${9} | ${'neunzehn'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
it(`converts 1${ones} to ${onesName}`, () => { | |||
expect(getNumberName(10 + ones, {locale: getLocalizedNumberName})).toBe(onesName); | |||
}); | |||
}); | |||
}); | |||
describe.each` | |||
tens | tensName | |||
${2} | ${'zwanzig'} | |||
${3} | ${'dreißig'} | |||
${4} | ${'vierzig'} | |||
${5} | ${'fünfzig'} | |||
${6} | ${'sechzig'} | |||
${7} | ${'siebzig'} | |||
${8} | ${'achtzig'} | |||
${9} | ${'neunzig'} | |||
`('$tens in tens place', ({tens, tensName}) => { | |||
describe('0 in ones place', () => { | |||
const value = tens * 10; | |||
const name = tensName; | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {locale: getLocalizedNumberName})).toBe(name); | |||
}); | |||
}); | |||
describe.each` | |||
ones | onesName | |||
${1} | ${'ein'} | |||
${2} | ${'zwei'} | |||
${3} | ${'drei'} | |||
${4} | ${'vier'} | |||
${5} | ${'fünf'} | |||
${6} | ${'sechs'} | |||
${7} | ${'sieben'} | |||
${8} | ${'acht'} | |||
${9} | ${'neun'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
const value = (tens * 10) + ones; | |||
const name = [onesName, tensName].join('und').trim(); | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {locale: getLocalizedNumberName})).toBe(name); | |||
}); | |||
}); | |||
}); | |||
}); | |||
describe.each` | |||
hundreds | hundredsName | |||
${1} | ${'einhundert'} | |||
${2} | ${'zweihundert'} | |||
${3} | ${'dreihundert'} | |||
${4} | ${'vierhundert'} | |||
${5} | ${'fünfhundert'} | |||
${6} | ${'sechshundert'} | |||
${7} | ${'siebenhundert'} | |||
${8} | ${'achthundert'} | |||
${9} | ${'neunhundert'} | |||
`('$hundreds in hundreds place', ({ | |||
hundreds, | |||
hundredsName, | |||
}) => { | |||
describe('0 in tens place', () => { | |||
describe.each` | |||
ones | onesName | |||
${0} | ${''} | |||
${1} | ${'ein'} | |||
${2} | ${'zwei'} | |||
${3} | ${'drei'} | |||
${4} | ${'vier'} | |||
${5} | ${'fünf'} | |||
${6} | ${'sechs'} | |||
${7} | ${'sieben'} | |||
${8} | ${'acht'} | |||
${9} | ${'neun'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
const value = (hundreds * 100) + ones; | |||
const name = [hundredsName, onesName].join('').trim(); | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {locale: getLocalizedNumberName})).toBe(name); | |||
}); | |||
}); | |||
}); | |||
describe('1 in tens place', () => { | |||
describe.each` | |||
ones | onesName | |||
${0} | ${'zehn'} | |||
${1} | ${'elf'} | |||
${2} | ${'zwölf'} | |||
${3} | ${'dreizehn'} | |||
${4} | ${'vierzehn'} | |||
${5} | ${'fünfzehn'} | |||
${6} | ${'sechzehn'} | |||
${7} | ${'siebzehn'} | |||
${8} | ${'achtzehn'} | |||
${9} | ${'neunzehn'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
const value = (hundreds * 100) + 10 + ones; | |||
const name = [hundredsName, onesName].join('').trim(); | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {locale: getLocalizedNumberName})).toBe(name); | |||
}); | |||
}); | |||
}); | |||
describe.each` | |||
tens | tensName | |||
${2} | ${'zwanzig'} | |||
${3} | ${'dreißig'} | |||
${4} | ${'vierzig'} | |||
${5} | ${'fünfzig'} | |||
${6} | ${'sechzig'} | |||
${7} | ${'siebzig'} | |||
${8} | ${'achtzig'} | |||
${9} | ${'neunzig'} | |||
`('$tens in tens place', ({tens, tensName}) => { | |||
describe('0 in ones place', () => { | |||
const value = (hundreds * 100) + (tens * 10); | |||
const name = [hundredsName, tensName].join('').trim(); | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {locale: getLocalizedNumberName})).toBe(name); | |||
}); | |||
}); | |||
describe.each` | |||
ones | onesName | |||
${1} | ${'ein'} | |||
${2} | ${'zwei'} | |||
${3} | ${'drei'} | |||
${4} | ${'vier'} | |||
${5} | ${'fünf'} | |||
${6} | ${'sechs'} | |||
${7} | ${'sieben'} | |||
${8} | ${'acht'} | |||
${9} | ${'neun'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
const value = (hundreds * 100) + (tens * 10) + ones; | |||
const name = [hundredsName, [onesName, tensName].join('und')].join('').trim(); | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {locale: getLocalizedNumberName})).toBe(name); | |||
}); | |||
}); | |||
}); | |||
}); | |||
}); |
@@ -1,113 +0,0 @@ | |||
import BigNumber from 'bignumber.js'; | |||
import { | |||
BLANK_DIGIT, | |||
createBlankDigits, | |||
deconstructNumeric, | |||
groupDigits, | |||
NEGATIVE_SIGN, | |||
normalizeNumeric, | |||
Numeric, | |||
} from '../../../utils/numeric'; | |||
import getLatinPowerName from '../../../utils/common/latinPowers'; | |||
import {getGroupDigitsName} from '../../common/de'; | |||
const config = { | |||
thousandName: 'tausend', | |||
thousandOrdinalName: 'tausendste', | |||
millia: 'millia', | |||
illion: 'illion', | |||
illionth: 'illionste', | |||
illiard: 'illiarde', | |||
illiardth: 'illiardste', | |||
hundredsLatinNames: [ | |||
'', | |||
'cen', | |||
'duocen', | |||
'trecen', | |||
'quadringen', | |||
'quingen', | |||
'sescen', | |||
'septingen', | |||
'octingen', | |||
'nongen', | |||
], | |||
onesLatinNames: ['', 'un', 'duo', 'tre', 'quattuor', 'quin', 'sex', 'septen', 'octo', 'novem'], | |||
tensLatinNames: [ | |||
'', | |||
'dec', | |||
'vigin', | |||
'trigin', | |||
'quadragin', | |||
'quinquagin', | |||
'sexagin', | |||
'septuagin', | |||
'octogin', | |||
'nonagin', | |||
], | |||
onesSpecialLatinNames: ['', 'm', 'b', 'tr', 'quadr', 'quin', 'sex', 'sep', 'oct', 'non'], | |||
negative: 'negative', | |||
grouping: 3, | |||
}; | |||
const getGroupIndexName = (index: BigNumber, digits: string, ordinal: boolean) => { | |||
if (index.eq(1)) { | |||
return ordinal ? config.thousandOrdinalName : config.thousandName; | |||
} | |||
const basicIndex = index.dividedToIntegerBy(2); | |||
const isOdd = index.mod(2).eq(1); | |||
const latinPowerName = getLatinPowerName(basicIndex, isOdd, ordinal, config); | |||
const latinPowerNameWithCase = latinPowerName.slice(0, 1).toUpperCase() + latinPowerName.slice(1); | |||
if (digits.padStart(config.grouping, BLANK_DIGIT) === '001' || ordinal) { | |||
return latinPowerNameWithCase; | |||
} | |||
if (latinPowerNameWithCase.endsWith('e')) { | |||
return latinPowerNameWithCase + 'n'; | |||
} | |||
return latinPowerNameWithCase + 'en'; | |||
}; | |||
const getGroupName = (g: [string, BigNumber], ordinal: boolean) => { | |||
const [digits, index] = g; | |||
if (index.lt(1)) { | |||
return getGroupDigitsName(digits, index, ordinal); | |||
} | |||
if (index.lt(2)) { | |||
return [getGroupDigitsName(digits, index, false), getGroupIndexName(index, digits, ordinal)].join(''); | |||
} | |||
return [getGroupDigitsName(digits, index, false), getGroupIndexName(index, digits, ordinal)].join(' '); | |||
}; | |||
type Options = { | |||
groupSeparator: string, | |||
ordinal: boolean, | |||
} | |||
const getLocalizedNumberName = (xRaw: Numeric, options = {} as Partial<Options>) => { | |||
const { | |||
groupSeparator = ' ', | |||
ordinal = false, | |||
} = options; | |||
const x = normalizeNumeric(xRaw); | |||
const {significandDigits, exponent} = deconstructNumeric(x); | |||
const blankDigits = createBlankDigits(config.grouping); | |||
const groups = groupDigits(significandDigits, exponent, config.grouping); | |||
if (groups.length === 1) { | |||
return getGroupName(groups[0], ordinal); | |||
} | |||
const base = groups | |||
.filter(([digits]) => digits !== blankDigits) | |||
.map((g, i, gg) => getGroupName(g, ordinal ? i === gg.length - 1 : false)) | |||
.join(groupSeparator); | |||
if (x.startsWith(NEGATIVE_SIGN)) { | |||
return [config.negative, base].join(' '); | |||
} | |||
return base; | |||
}; | |||
export default getLocalizedNumberName; |
@@ -1,62 +0,0 @@ | |||
import getNumberName from '../../../index'; | |||
import getLocalizedNumberName from './index'; | |||
describe('Plurals', () => { | |||
describe('1 in millions place', () => { | |||
const value = 1000000; | |||
const name = 'eine Million'; | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {locale: getLocalizedNumberName})).toBe(name); | |||
}); | |||
}); | |||
describe.each` | |||
ones | onesName | |||
${2} | ${'zwei'} | |||
${3} | ${'drei'} | |||
${4} | ${'vier'} | |||
${5} | ${'fünf'} | |||
${6} | ${'sechs'} | |||
${7} | ${'sieben'} | |||
${8} | ${'acht'} | |||
${9} | ${'neun'} | |||
`('$ones in millions place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
const value = ones * 1000000; | |||
const name = `${onesName} Millionen`; | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {locale: getLocalizedNumberName})).toBe(name); | |||
}); | |||
}); | |||
describe('1 in billions place', () => { | |||
const value = 1000000000; | |||
const name = 'eine Milliarde'; | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {locale: getLocalizedNumberName})).toBe(name); | |||
}); | |||
}); | |||
describe.each` | |||
ones | onesName | |||
${2} | ${'zwei'} | |||
${3} | ${'drei'} | |||
${4} | ${'vier'} | |||
${5} | ${'fünf'} | |||
${6} | ${'sechs'} | |||
${7} | ${'sieben'} | |||
${8} | ${'acht'} | |||
${9} | ${'neun'} | |||
`('$ones in billions place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
const value = ones * 1000000000; | |||
const name = `${onesName} Milliarden`; | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {locale: getLocalizedNumberName})).toBe(name); | |||
}); | |||
}); | |||
}); |
@@ -1,48 +0,0 @@ | |||
import getNumberName from '../../../index'; | |||
import getLocalizedNumberName from './index'; | |||
describe('Landon\'s original test cases', () => { | |||
describe('Basic conversions', () => { | |||
it.each` | |||
value | traditionalBritishName | |||
${1} | ${'one'} | |||
${1000} | ${'one thousand'} | |||
${1000000} | ${'one million'} | |||
${1000000000} | ${'one thousand million'} | |||
${1000000000000} | ${'one billion'} | |||
${1000000000000000} | ${'one thousand billion'} | |||
${1000000000000000000} | ${'one trillion'} | |||
`('converts $value to $traditionalBritishName', ({value, traditionalBritishName}) => { | |||
expect(getNumberName(value, {locale: getLocalizedNumberName})).toBe(traditionalBritishName); | |||
}); | |||
}); | |||
describe('Medium size numbers (<= 1e+63)', () => { | |||
describe('Table 1', () => { | |||
it.each` | |||
value | traditionalBritishName | |||
${'1e+9'} | ${'thousand million'} | |||
${'1e+12'} | ${'billion'} | |||
${'1e+15'} | ${'thousand billion'} | |||
${'1e+18'} | ${'trillion'} | |||
${'1e+21'} | ${'thousand trillion'} | |||
${'1e+24'} | ${'quadrillion'} | |||
${'1e+27'} | ${'thousand quadrillion'} | |||
${'1e+30'} | ${'quintillion'} | |||
${'1e+33'} | ${'thousand quintillion'} | |||
${'1e+36'} | ${'sextillion'} | |||
${'1e+39'} | ${'thousand sextillion'} | |||
${'1e+42'} | ${'septillion'} | |||
${'1e+45'} | ${'thousand septillion'} | |||
${'1e+48'} | ${'octillion'} | |||
${'1e+51'} | ${'thousand octillion'} | |||
${'1e+54'} | ${'nonillion'} | |||
${'1e+57'} | ${'thousand nonillion'} | |||
${'1e+60'} | ${'decillion'} | |||
${'1e+63'} | ${'thousand decillion'} | |||
`('converts $value to $traditionalBritishName', ({value, traditionalBritishName}) => { | |||
expect(getNumberName(value, {locale: getLocalizedNumberName})).toBe(`one ${traditionalBritishName}`); | |||
}); | |||
}); | |||
}); | |||
}); |
@@ -1,104 +0,0 @@ | |||
import BigNumber from 'bignumber.js'; | |||
import { | |||
createBlankDigits, | |||
deconstructNumeric, | |||
groupDigits, | |||
NEGATIVE_SIGN, | |||
normalizeNumeric, | |||
Numeric, | |||
} from '../../../utils/numeric'; | |||
import getLatinPowerName from '../../../utils/common/latinPowers'; | |||
import {getGroupDigitsName} from '../../common/en'; | |||
const config = { | |||
thousandName: 'thousand', | |||
thousandOrdinalName: 'thousandth', | |||
millia: 'millia', | |||
illion: 'illion', | |||
illionth: 'illionth', | |||
hundredsLatinNames: [ | |||
'', | |||
'cen', | |||
'duocen', | |||
'trecen', | |||
'quadringen', | |||
'quingen', | |||
'sescen', | |||
'septingen', | |||
'octingen', | |||
'nongen', | |||
], | |||
onesLatinNames: ['', 'un', 'duo', 'tre', 'quattuor', 'quin', 'sex', 'septen', 'octo', 'novem'], | |||
tensLatinNames: [ | |||
'', | |||
'dec', | |||
'vigin', | |||
'trigin', | |||
'quadragin', | |||
'quinquagin', | |||
'sexagin', | |||
'septuagin', | |||
'octogin', | |||
'nonagin', | |||
], | |||
onesSpecialLatinNames: ['', 'm', 'b', 'tr', 'quadr', 'quin', 'sex', 'sep', 'oct', 'non'], | |||
negative: 'negative', | |||
grouping: 3, | |||
}; | |||
const getGroupIndexName = (index: BigNumber, ordinal: boolean) => { | |||
if (index.eq(1)) { | |||
return ordinal ? config.thousandOrdinalName : config.thousandName; | |||
} | |||
const basicIndex = index.dividedToIntegerBy(2); | |||
const isOdd = false; | |||
const latinPowerName = getLatinPowerName(basicIndex, isOdd, ordinal, config); | |||
if (index.mod(2).eq(1)) { | |||
return [config.thousandName, latinPowerName].join(' '); | |||
} | |||
return latinPowerName; | |||
}; | |||
const getGroupName = (g: [string, BigNumber], ordinal: boolean) => { | |||
const [digits, index] = g; | |||
if (index.lt(1)) { | |||
return getGroupDigitsName(digits, ordinal); | |||
} | |||
return [getGroupDigitsName(digits, false), getGroupIndexName(index, ordinal)].join(' '); | |||
}; | |||
type Options = { | |||
groupSeparator: string, | |||
ordinal: boolean, | |||
} | |||
const getLocalizedNumberName = (xRaw: Numeric, options = {} as Partial<Options>) => { | |||
const { | |||
groupSeparator = ' ', | |||
ordinal = false, | |||
} = options; | |||
const x = normalizeNumeric(xRaw); | |||
const {significandDigits, exponent} = deconstructNumeric(x); | |||
const blankDigits = createBlankDigits(config.grouping); | |||
const groups = groupDigits(significandDigits, exponent, config.grouping); | |||
if (groups.length === 1) { | |||
return getGroupName(groups[0], ordinal); | |||
} | |||
const base = groups | |||
.filter(([digits]) => digits !== blankDigits) | |||
.map((g, i, gg) => getGroupName(g, ordinal ? i === gg.length - 1 : false)) | |||
.join(groupSeparator); | |||
if (x.startsWith(NEGATIVE_SIGN)) { | |||
return [config.negative, base].join(' '); | |||
} | |||
return base; | |||
}; | |||
export default getLocalizedNumberName; |
@@ -1,34 +0,0 @@ | |||
import getNumberName from '../../../index'; | |||
describe('Custom numbers', () => { | |||
it('converts 123456000 to one hundred twenty three million four hundred fifty six thousand', () => { | |||
expect(getNumberName(123456000)).toBe('one hundred twenty three million four hundred fifty six thousand'); | |||
}); | |||
it('converts 123456000 to one hundred twenty three million four hundred fifty six thousand nine', () => { | |||
expect(getNumberName(123456009)).toBe('one hundred twenty three million four hundred fifty six thousand nine'); | |||
}); | |||
it('converts 123000789 to one hundred twenty three million seven hundred eighty nine', () => { | |||
expect(getNumberName(123000789)).toBe('one hundred twenty three million seven hundred eighty nine'); | |||
}); | |||
it('converts 123050789 to one hundred twenty three million fifty thousand seven hundred eighty nine', () => { | |||
expect(getNumberName(123050789)).toBe('one hundred twenty three million fifty thousand seven hundred eighty nine'); | |||
}); | |||
it( | |||
'converts 123456789 to one hundred twenty three million four hundred fifty six thousand seven hundred eighty nine', | |||
() => { | |||
expect(getNumberName(123456789)) | |||
.toBe('one hundred twenty three million four hundred fifty six thousand seven hundred eighty nine'); | |||
}, | |||
); | |||
it( | |||
'converts -123456789 to negative one hundred twenty three million four hundred fifty six thousand seven hundred eighty nine', | |||
() => { | |||
expect(getNumberName(-123456789)) | |||
.toBe('negative one hundred twenty three million four hundred fifty six thousand seven hundred eighty nine'); | |||
}, | |||
); | |||
}); |
@@ -1,189 +0,0 @@ | |||
import getNumberName from '../../../index'; | |||
describe('Number group conversion', () => { | |||
describe('0 in hundreds place', () => { | |||
describe('0 in tens place', () => { | |||
describe.each` | |||
ones | onesName | |||
${0} | ${'zero'} | |||
${1} | ${'one'} | |||
${2} | ${'two'} | |||
${3} | ${'three'} | |||
${4} | ${'four'} | |||
${5} | ${'five'} | |||
${6} | ${'six'} | |||
${7} | ${'seven'} | |||
${8} | ${'eight'} | |||
${9} | ${'nine'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
it(`converts ${ones} to ${onesName}`, () => { | |||
expect(getNumberName(ones)).toBe(onesName); | |||
}); | |||
}); | |||
}); | |||
describe('1 in tens place', () => { | |||
describe.each` | |||
ones | onesName | |||
${0} | ${'ten'} | |||
${1} | ${'eleven'} | |||
${2} | ${'twelve'} | |||
${3} | ${'thirteen'} | |||
${4} | ${'fourteen'} | |||
${5} | ${'fifteen'} | |||
${6} | ${'sixteen'} | |||
${7} | ${'seventeen'} | |||
${8} | ${'eighteen'} | |||
${9} | ${'nineteen'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
it(`converts 1${ones} to ${onesName}`, () => { | |||
expect(getNumberName(10 + ones)).toBe(onesName); | |||
}); | |||
}); | |||
}); | |||
describe.each` | |||
tens | tensName | |||
${2} | ${'twenty'} | |||
${3} | ${'thirty'} | |||
${4} | ${'forty'} | |||
${5} | ${'fifty'} | |||
${6} | ${'sixty'} | |||
${7} | ${'seventy'} | |||
${8} | ${'eighty'} | |||
${9} | ${'ninety'} | |||
`('$tens in tens place', ({tens, tensName}) => { | |||
describe.each` | |||
ones | onesName | |||
${0} | ${''} | |||
${1} | ${'one'} | |||
${2} | ${'two'} | |||
${3} | ${'three'} | |||
${4} | ${'four'} | |||
${5} | ${'five'} | |||
${6} | ${'six'} | |||
${7} | ${'seven'} | |||
${8} | ${'eight'} | |||
${9} | ${'nine'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
const value = (tens * 10) + ones; | |||
const name = [tensName, onesName].join(' ').trim(); | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value)).toBe(name); | |||
}); | |||
}); | |||
}); | |||
}); | |||
describe.each` | |||
hundreds | hundredsName | |||
${1} | ${'one hundred'} | |||
${2} | ${'two hundred'} | |||
${3} | ${'three hundred'} | |||
${4} | ${'four hundred'} | |||
${5} | ${'five hundred'} | |||
${6} | ${'six hundred'} | |||
${7} | ${'seven hundred'} | |||
${8} | ${'eight hundred'} | |||
${9} | ${'nine hundred'} | |||
`('$hundreds in hundreds place', ({ | |||
hundreds, | |||
hundredsName, | |||
}) => { | |||
describe('0 in tens place', () => { | |||
describe.each` | |||
ones | onesName | |||
${0} | ${''} | |||
${1} | ${'one'} | |||
${2} | ${'two'} | |||
${3} | ${'three'} | |||
${4} | ${'four'} | |||
${5} | ${'five'} | |||
${6} | ${'six'} | |||
${7} | ${'seven'} | |||
${8} | ${'eight'} | |||
${9} | ${'nine'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
const value = (hundreds * 100) + ones; | |||
const name = [hundredsName, onesName].join(' ').trim(); | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value)).toBe(name); | |||
}); | |||
}); | |||
}); | |||
describe('1 in tens place', () => { | |||
describe.each` | |||
ones | onesName | |||
${0} | ${'ten'} | |||
${1} | ${'eleven'} | |||
${2} | ${'twelve'} | |||
${3} | ${'thirteen'} | |||
${4} | ${'fourteen'} | |||
${5} | ${'fifteen'} | |||
${6} | ${'sixteen'} | |||
${7} | ${'seventeen'} | |||
${8} | ${'eighteen'} | |||
${9} | ${'nineteen'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
const value = (hundreds * 100) + 10 + ones; | |||
const name = [hundredsName, onesName].join(' ').trim(); | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value)).toBe(name); | |||
}); | |||
}); | |||
}); | |||
describe.each` | |||
tens | tensName | |||
${2} | ${'twenty'} | |||
${3} | ${'thirty'} | |||
${4} | ${'forty'} | |||
${5} | ${'fifty'} | |||
${6} | ${'sixty'} | |||
${7} | ${'seventy'} | |||
${8} | ${'eighty'} | |||
${9} | ${'ninety'} | |||
`('$tens in tens place', ({tens, tensName}) => { | |||
describe.each` | |||
ones | onesName | |||
${0} | ${''} | |||
${1} | ${'one'} | |||
${2} | ${'two'} | |||
${3} | ${'three'} | |||
${4} | ${'four'} | |||
${5} | ${'five'} | |||
${6} | ${'six'} | |||
${7} | ${'seven'} | |||
${8} | ${'eight'} | |||
${9} | ${'nine'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
const value = (hundreds * 100) + (tens * 10) + ones; | |||
const name = [hundredsName, tensName, onesName].join(' ').trim(); | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value)).toBe(name); | |||
}); | |||
}); | |||
}); | |||
}); | |||
}); |
@@ -1,99 +0,0 @@ | |||
import BigNumber from 'bignumber.js'; | |||
import { | |||
createBlankDigits, | |||
deconstructNumeric, | |||
groupDigits, | |||
NEGATIVE_SIGN, | |||
normalizeNumeric, | |||
Numeric, | |||
} from '../../../utils/numeric'; | |||
import getLatinPowerName from '../../../utils/common/latinPowers'; | |||
import {getGroupDigitsName} from '../../common/en'; | |||
const config = { | |||
thousandName: 'thousand', | |||
thousandOrdinalName: 'thousandth', | |||
millia: 'millia', | |||
illion: 'illion', | |||
illionth: 'illionth', | |||
hundredsLatinNames: [ | |||
'', | |||
'cen', | |||
'duocen', | |||
'trecen', | |||
'quadringen', | |||
'quingen', | |||
'sescen', | |||
'septingen', | |||
'octingen', | |||
'nongen', | |||
], | |||
onesLatinNames: ['', 'un', 'duo', 'tre', 'quattuor', 'quin', 'sex', 'septen', 'octo', 'novem'], | |||
tensLatinNames: [ | |||
'', | |||
'dec', | |||
'vigin', | |||
'trigin', | |||
'quadragin', | |||
'quinquagin', | |||
'sexagin', | |||
'septuagin', | |||
'octogin', | |||
'nonagin', | |||
], | |||
onesSpecialLatinNames: ['', 'm', 'b', 'tr', 'quadr', 'quin', 'sex', 'sep', 'oct', 'non'], | |||
negative: 'negative', | |||
grouping: 3, | |||
}; | |||
const getGroupIndexName = (index: BigNumber, ordinal: boolean) => { | |||
if (index.eq(1)) { | |||
return ordinal ? config.thousandOrdinalName : config.thousandName; | |||
} | |||
const basicIndex = index.minus(1); | |||
const isOdd = false; | |||
return getLatinPowerName(basicIndex, isOdd, ordinal, config); | |||
}; | |||
const getGroupName = (g: [string, BigNumber], ordinal: boolean) => { | |||
const [digits, index] = g; | |||
if (index.lt(1)) { | |||
return getGroupDigitsName(digits, ordinal); | |||
} | |||
return [getGroupDigitsName(digits, false), getGroupIndexName(index, ordinal)].join(' '); | |||
}; | |||
type Options = { | |||
groupSeparator: string, | |||
ordinal: boolean, | |||
} | |||
const getLocalizedNumberName = (xRaw: Numeric, options = {} as Partial<Options>) => { | |||
const { | |||
groupSeparator = ' ', | |||
ordinal = false, | |||
} = options; | |||
const x = normalizeNumeric(xRaw); | |||
const {significandDigits, exponent} = deconstructNumeric(x); | |||
const blankDigits = createBlankDigits(config.grouping); | |||
const groups = groupDigits(significandDigits, exponent, config.grouping); | |||
if (groups.length === 1) { | |||
return getGroupName(groups[0], ordinal); | |||
} | |||
const base = groups | |||
.filter(([digits]) => digits !== blankDigits) | |||
.map((g, i, gg) => getGroupName(g, ordinal ? i === gg.length - 1 : false)) | |||
.join(groupSeparator); | |||
if (x.startsWith(NEGATIVE_SIGN)) { | |||
return [config.negative, base].join(' '); | |||
} | |||
return base; | |||
}; | |||
export default getLocalizedNumberName; |
@@ -1,213 +0,0 @@ | |||
import getNumberName from '../../../index'; | |||
describe('Ordinals', () => { | |||
describe('0 in hundreds place', () => { | |||
describe('0 in tens place', () => { | |||
describe.each` | |||
ones | onesName | |||
${0} | ${'zeroth'} | |||
${1} | ${'first'} | |||
${2} | ${'second'} | |||
${3} | ${'third'} | |||
${4} | ${'fourth'} | |||
${5} | ${'fifth'} | |||
${6} | ${'sixth'} | |||
${7} | ${'seventh'} | |||
${8} | ${'eighth'} | |||
${9} | ${'ninth'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
it(`converts ${ones} to ${onesName}`, () => { | |||
expect(getNumberName(ones, {ordinal: true})).toBe(onesName); | |||
}); | |||
}); | |||
}); | |||
describe('1 in tens place', () => { | |||
describe.each` | |||
ones | onesName | |||
${0} | ${'tenth'} | |||
${1} | ${'eleventh'} | |||
${2} | ${'twelfth'} | |||
${3} | ${'thirteenth'} | |||
${4} | ${'fourteenth'} | |||
${5} | ${'fifteenth'} | |||
${6} | ${'sixteenth'} | |||
${7} | ${'seventeenth'} | |||
${8} | ${'eighteenth'} | |||
${9} | ${'nineteenth'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
it(`converts 1${ones} to ${onesName}`, () => { | |||
expect(getNumberName(10 + ones, {ordinal: true})).toBe(onesName); | |||
}); | |||
}); | |||
}); | |||
describe.each` | |||
tens | tensName | |||
${2} | ${'twenty'} | |||
${3} | ${'thirty'} | |||
${4} | ${'forty'} | |||
${5} | ${'fifty'} | |||
${6} | ${'sixty'} | |||
${7} | ${'seventy'} | |||
${8} | ${'eighty'} | |||
${9} | ${'ninety'} | |||
`('$tens in tens place', ({tens, tensName}) => { | |||
describe('0 in ones place', () => { | |||
const value = (tens * 10); | |||
const name = tensName.replace(/y$/, 'ieth'); | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {ordinal: true})).toBe(name); | |||
}); | |||
}); | |||
describe.each` | |||
ones | onesName | |||
${1} | ${'first'} | |||
${2} | ${'second'} | |||
${3} | ${'third'} | |||
${4} | ${'fourth'} | |||
${5} | ${'fifth'} | |||
${6} | ${'sixth'} | |||
${7} | ${'seventh'} | |||
${8} | ${'eighth'} | |||
${9} | ${'ninth'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
const value = (tens * 10) + ones; | |||
const name = [tensName, onesName].join(' ').trim(); | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {ordinal: true})).toBe(name); | |||
}); | |||
}); | |||
}); | |||
}); | |||
describe.each` | |||
hundreds | hundredsName | |||
${1} | ${'one hundred'} | |||
${2} | ${'two hundred'} | |||
${3} | ${'three hundred'} | |||
${4} | ${'four hundred'} | |||
${5} | ${'five hundred'} | |||
${6} | ${'six hundred'} | |||
${7} | ${'seven hundred'} | |||
${8} | ${'eight hundred'} | |||
${9} | ${'nine hundred'} | |||
`('$hundreds in hundreds place', ({ | |||
hundreds, | |||
hundredsName, | |||
}) => { | |||
describe('0 in tens place', () => { | |||
describe('0 in ones place', () => { | |||
const value = (hundreds * 100); | |||
const name = hundredsName + 'th'; | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {ordinal: true})).toBe(name); | |||
}); | |||
}); | |||
describe.each` | |||
ones | onesName | |||
${1} | ${'first'} | |||
${2} | ${'second'} | |||
${3} | ${'third'} | |||
${4} | ${'fourth'} | |||
${5} | ${'fifth'} | |||
${6} | ${'sixth'} | |||
${7} | ${'seventh'} | |||
${8} | ${'eighth'} | |||
${9} | ${'ninth'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
const value = (hundreds * 100) + ones; | |||
const name = [hundredsName, onesName].join(' ').trim(); | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {ordinal: true})).toBe(name); | |||
}); | |||
}); | |||
}); | |||
describe('1 in tens place', () => { | |||
describe.each` | |||
ones | onesName | |||
${0} | ${'tenth'} | |||
${1} | ${'eleventh'} | |||
${2} | ${'twelfth'} | |||
${3} | ${'thirteenth'} | |||
${4} | ${'fourteenth'} | |||
${5} | ${'fifteenth'} | |||
${6} | ${'sixteenth'} | |||
${7} | ${'seventeenth'} | |||
${8} | ${'eighteenth'} | |||
${9} | ${'nineteenth'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
const value = (hundreds * 100) + 10 + ones; | |||
const name = [hundredsName, onesName].join(' ').trim(); | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {ordinal: true})).toBe(name); | |||
}); | |||
}); | |||
}); | |||
describe.each` | |||
tens | tensName | |||
${2} | ${'twenty'} | |||
${3} | ${'thirty'} | |||
${4} | ${'forty'} | |||
${5} | ${'fifty'} | |||
${6} | ${'sixty'} | |||
${7} | ${'seventy'} | |||
${8} | ${'eighty'} | |||
${9} | ${'ninety'} | |||
`('$tens in tens place', ({tens, tensName}) => { | |||
describe('0 in ones place', () => { | |||
const value = (hundreds * 100) + (tens * 10); | |||
const name = [hundredsName, tensName.replace(/y$/, 'ieth')].join(' ').trim(); | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {ordinal: true})).toBe(name); | |||
}); | |||
}); | |||
describe.each` | |||
ones | onesName | |||
${1} | ${'first'} | |||
${2} | ${'second'} | |||
${3} | ${'third'} | |||
${4} | ${'fourth'} | |||
${5} | ${'fifth'} | |||
${6} | ${'sixth'} | |||
${7} | ${'seventh'} | |||
${8} | ${'eighth'} | |||
${9} | ${'ninth'} | |||
`('$ones in ones place', ({ | |||
ones, | |||
onesName, | |||
}) => { | |||
const value = (hundreds * 100) + (tens * 10) + ones; | |||
const name = [hundredsName, tensName, onesName].join(' ').trim(); | |||
it(`converts ${value} to ${name}`, () => { | |||
expect(getNumberName(value, {ordinal: true})).toBe(name); | |||
}); | |||
}); | |||
}); | |||
}); | |||
}); |
@@ -1,44 +0,0 @@ | |||
import getNumberName from '../../../index'; | |||
describe('Technical numbers', () => { | |||
describe('Number.MAX_SAFE_INTEGER', () => { | |||
it( | |||
'converts Number.MAX_SAFE_INTEGER to nine quadrillion seven trillion one hundred ninety nine billion two hundred fifty four million seven hundred forty thousand nine hundred ninety one', | |||
() => { | |||
expect(Number.MAX_SAFE_INTEGER).toBe(9_007_199_254_740_991); | |||
expect(getNumberName(Number.MAX_SAFE_INTEGER)) | |||
.toBe( | |||
'nine quadrillion seven trillion one hundred ninety nine billion two hundred fifty four million seven hundred forty thousand nine hundred ninety one'); | |||
}, | |||
); | |||
}); | |||
describe('Powers of 2', () => { | |||
it.each` | |||
value | name | |||
${2 ** 0} | ${'one'} | |||
${2 ** 1} | ${'two'} | |||
${2 ** 2} | ${'four'} | |||
${2 ** 3} | ${'eight'} | |||
${2 ** 4} | ${'sixteen'} | |||
${2 ** 5} | ${'thirty two'} | |||
${2 ** 6} | ${'sixty four'} | |||
${2 ** 7} | ${'one hundred twenty eight'} | |||
${2 ** 8} | ${'two hundred fifty six'} | |||
${2 ** 9} | ${'five hundred twelve'} | |||
${2 ** 10} | ${'one thousand twenty four'} | |||
${2 ** 11} | ${'two thousand forty eight'} | |||
${2 ** 12} | ${'four thousand ninety six'} | |||
${2 ** 13} | ${'eight thousand one hundred ninety two'} | |||
${2 ** 14} | ${'sixteen thousand three hundred eighty four'} | |||
${2 ** 15} | ${'thirty two thousand seven hundred sixty eight'} | |||
${2 ** 16} | ${'sixty five thousand five hundred thirty six'} | |||
${2 ** 17} | ${'one hundred thirty one thousand seventy two'} | |||
${2 ** 18} | ${'two hundred sixty two thousand one hundred forty four'} | |||
${2 ** 19} | ${'five hundred twenty four thousand two hundred eighty eight'} | |||
${2 ** 20} | ${'one million forty eight thousand five hundred seventy six'} | |||
`('converts $value to $name', ({value, name}) => { | |||
expect(getNumberName(value)).toBe(name); | |||
}); | |||
}); | |||
}); |
@@ -0,0 +1,553 @@ | |||
// noinspection SpellCheckingInspection | |||
import { Group } from '../common'; | |||
import { numberToExponential } from '../exponent'; | |||
const DECIMAL_POINT = '.'; | |||
const GROUPING_SYMBOL = ','; | |||
const NEGATIVE = 'negative'; | |||
const ONES = [ | |||
'zero', | |||
'one', | |||
'two', | |||
'three', | |||
'four', | |||
'five', | |||
'six', | |||
'seven', | |||
'eight', | |||
'nine', | |||
] as const; | |||
type OnesName = typeof ONES[number]; | |||
const TEN_PLUS_ONES = [ | |||
'ten', | |||
'eleven', | |||
'twelve', | |||
'thirteen', | |||
'fourteen', | |||
'fifteen', | |||
'sixteen', | |||
'seventeen', | |||
'eighteen', | |||
'nineteen', | |||
] as const; | |||
type TenPlusOnesName = typeof TEN_PLUS_ONES[number]; | |||
const TENS = [ | |||
'zero', | |||
TEN_PLUS_ONES[0], | |||
'twenty', | |||
'thirty', | |||
'forty', | |||
'fifty', | |||
'sixty', | |||
'seventy', | |||
'eighty', | |||
'ninety', | |||
] as const; | |||
type TensName = typeof TENS[number]; | |||
const HUNDRED = 'hundred' as const; | |||
const THOUSAND = 'thousand' as const; | |||
// const ILLION_ORDINAL_SUFFIX = 'illionth' as const; | |||
// const THOUSAND_ORDINAL = 'thousandth' as const; | |||
const MILLIONS_SPECIAL_PREFIXES = [ | |||
'', | |||
'm', | |||
'b', | |||
'tr', | |||
'quadr', | |||
'quint', | |||
'sext', | |||
'sept', | |||
'oct', | |||
'non', | |||
] as const; | |||
type MillionsSpecialPrefix = typeof MILLIONS_SPECIAL_PREFIXES[number]; | |||
const MILLIONS_PREFIXES = [ | |||
'', | |||
'un', | |||
'duo', | |||
'tre', | |||
'quattuor', | |||
'quin', | |||
'sex', | |||
'septen', | |||
'octo', | |||
'novem', | |||
] as const; | |||
type MillionsPrefix = typeof MILLIONS_PREFIXES[number]; | |||
const DECILLIONS_PREFIXES = [ | |||
'', | |||
'dec', | |||
'vigin', | |||
'trigin', | |||
'quadragin', | |||
'quinquagin', | |||
'sexagin', | |||
'septuagin', | |||
'octogin', | |||
'nonagin', | |||
] as const; | |||
type DecillionsPrefix = typeof DECILLIONS_PREFIXES[number]; | |||
const CENTILLIONS_PREFIXES = [ | |||
'', | |||
'cen', | |||
'duocen', | |||
'trecen', | |||
'quadringen', | |||
'quingen', | |||
'sescen', | |||
'septingen', | |||
'octingen', | |||
'nongen', | |||
] as const; | |||
type CentillionsPrefix = typeof CENTILLIONS_PREFIXES[number]; | |||
const MILLIA_PREFIX = 'millia' as const; | |||
const ILLION_SUFFIX = 'illion' as const; | |||
const makeTensName = (tens: number, ones: number) => { | |||
if (tens === 0) { | |||
return ONES[ones]; | |||
} | |||
if (tens === 1) { | |||
return TEN_PLUS_ONES[ones] as TenPlusOnesName; | |||
} | |||
if (ones === 0) { | |||
return TENS[tens]; | |||
} | |||
return `${TENS[tens] as Exclude<TensName, 'zero' | 'ten'>} ${ONES[ones] as Exclude<OnesName, 'zero'>}` as const; | |||
}; | |||
const makeHundredsName = (hundreds: number, tens: number, ones: number) => { | |||
if (hundreds === 0) { | |||
return makeTensName(tens, ones); | |||
} | |||
if (tens === 0 && ones === 0) { | |||
return `${ONES[hundreds]} ${HUNDRED}` as const; | |||
} | |||
return `${ONES[hundreds]} ${HUNDRED} ${makeTensName(tens, ones)}` as const; | |||
}; | |||
const makeMillionsPrefix = (millions: number, milliaCount: number) => { | |||
if (milliaCount > 0) { | |||
return MILLIONS_PREFIXES[millions] as Exclude<MillionsPrefix, ''>; | |||
} | |||
return MILLIONS_SPECIAL_PREFIXES[millions] as Exclude<MillionsSpecialPrefix, ''>; | |||
}; | |||
const makeDecillionsPrefix = (decillions: number, millions: number, milliaCount: number) => { | |||
if (decillions === 0) { | |||
return makeMillionsPrefix(millions, milliaCount); | |||
} | |||
const onesPrefix = MILLIONS_PREFIXES[millions] as Exclude<MillionsPrefix, ''>; | |||
const tensName = DECILLIONS_PREFIXES[decillions] as Exclude<DecillionsPrefix, ''>; | |||
return `${onesPrefix}${tensName}` as const; | |||
}; | |||
const makeCentillionsPrefix = (centillions: number, decillions: number, millions: number, milliaCount: number) => { | |||
if (centillions === 0) { | |||
return makeDecillionsPrefix(decillions, millions, milliaCount); | |||
} | |||
const onesPrefix = MILLIONS_PREFIXES[millions] as Exclude<MillionsPrefix, ''>; | |||
const tensName = DECILLIONS_PREFIXES[decillions] as Exclude<DecillionsPrefix, ''>; | |||
const hundredsName = CENTILLIONS_PREFIXES[centillions] as Exclude<CentillionsPrefix, ''>; | |||
return `${hundredsName}${onesPrefix}${decillions > 0 ? tensName : ''}` as const; | |||
}; | |||
const getGroupName = (place: number, shortenMillia: boolean) => { | |||
if (place === 0) { | |||
return '' as const; | |||
} | |||
if (place === 1) { | |||
return THOUSAND; | |||
} | |||
const bigGroupPlace = place - 1; | |||
const groupGroups = bigGroupPlace | |||
.toString() | |||
.split('') | |||
.reduceRight<Group[]>( | |||
(acc, c, i, cc) => { | |||
const firstGroup = acc.at(0); | |||
const currentPlace = Math.floor((cc.length - i - 1) / 3); | |||
if (typeof firstGroup === 'undefined') { | |||
return [[c, currentPlace]]; | |||
} | |||
if (firstGroup[0].length > 2) { | |||
return [[c, currentPlace], ...acc]; | |||
} | |||
return [ | |||
[c + firstGroup[0], currentPlace], | |||
...acc.slice(1) | |||
]; | |||
}, | |||
[] | |||
) | |||
.map(([group, groupPlace]) => [group.padStart(3, '0'), groupPlace] as const) | |||
.filter(([group]) => group !== '000') | |||
.map(([group, groupPlace]) => { | |||
const [hundreds, tens, ones] = group.split('').map(Number); | |||
if (groupPlace < 1) { | |||
return makeCentillionsPrefix(hundreds, tens, ones, groupPlace); | |||
} | |||
const milliaSuffix = ( | |||
shortenMillia && groupPlace > 1 | |||
? `${MILLIA_PREFIX}^${groupPlace}` | |||
: MILLIA_PREFIX.repeat(groupPlace) | |||
); | |||
if (group === '001') { | |||
return milliaSuffix; | |||
} | |||
return makeCentillionsPrefix(hundreds, tens, ones, groupPlace) + milliaSuffix; | |||
}) | |||
.join(''); | |||
if (groupGroups.endsWith(DECILLIONS_PREFIXES[1])) { | |||
return `${groupGroups}${ILLION_SUFFIX}` as const; | |||
} | |||
if (bigGroupPlace > 10) { | |||
return `${groupGroups}t${ILLION_SUFFIX}` as const; | |||
} | |||
return `${groupGroups}${ILLION_SUFFIX}` as const; | |||
}; | |||
export const makeGroup = (group: string, place: number, options?: Record<string, unknown>): string => { | |||
const makeHundredsArgs = group | |||
.padStart(3, '0') | |||
.split('') | |||
.map((s) => Number(s)) as [number, number, number]; | |||
const groupDigitsName = makeHundredsName(...makeHundredsArgs); | |||
const groupName = getGroupName(place, options?.shortenMillia as boolean ?? false); | |||
if (groupName.length > 0) { | |||
return `${groupDigitsName} ${groupName}` as const; | |||
} | |||
return groupDigitsName; | |||
}; | |||
/** | |||
* Group a number string into groups of three digits, starting from the decimal point. | |||
* @param value - The number string to group. | |||
*/ | |||
export const group = (value: string): Group[] => { | |||
const exponentDelimiter = 'e'; | |||
const [significand, exponentString] = numberToExponential( | |||
value, | |||
{ | |||
decimalPoint: DECIMAL_POINT, | |||
groupingSymbol: GROUPING_SYMBOL, | |||
exponentDelimiter, | |||
}) | |||
.split(exponentDelimiter); | |||
const exponent = Number(exponentString); | |||
const significantDigits = significand.replace(DECIMAL_POINT, ''); | |||
return significantDigits.split('').reduce<Group[]>( | |||
(acc, c, i) => { | |||
const currentPlace = Math.floor((exponent - i) / 3); | |||
const lastGroup = acc.at(-1) ?? ['000', currentPlace]; | |||
const currentPlaceInGroup = 2 - ((exponent - i) % 3); | |||
if (lastGroup[1] === currentPlace) { | |||
const lastGroupDigits = lastGroup[0].split(''); | |||
lastGroupDigits[currentPlaceInGroup] = c; | |||
return [...acc.slice(0, -1) ?? [], [ | |||
lastGroupDigits.join(''), | |||
currentPlace | |||
]]; | |||
} | |||
return [...acc, [c.padEnd(3, '0'), currentPlace]]; | |||
}, | |||
[], | |||
); | |||
}; | |||
/** | |||
* Formats the final tokenized string. | |||
* @param tokens - The tokens to finalize. | |||
*/ | |||
export const finalize = (tokens: string[]) => ( | |||
tokens | |||
.map((t) => t.trim()) | |||
.join(' ') | |||
.trim() | |||
); | |||
/** | |||
* Makes a negative string. | |||
* @param s - The string to make negative. | |||
*/ | |||
export const makeNegative = (s: string) => ( | |||
`${NEGATIVE} ${s}` | |||
); | |||
export const tokenize = (s: string) => ( | |||
s.split(' ').filter((s) => s.length > 0) | |||
); | |||
const FINAL_TOKEN = ''; | |||
const getGroupFromGroupName = (groupName: string) => { | |||
if (groupName === THOUSAND) { | |||
return 1; | |||
} | |||
const groupNameBase = groupName.replace(ILLION_SUFFIX, ''); | |||
const specialMillions = MILLIONS_SPECIAL_PREFIXES.findIndex((p) => { | |||
return groupNameBase === p; | |||
}); | |||
if (specialMillions > -1) { | |||
return 1 + specialMillions; | |||
} | |||
let groupNameCurrent = groupNameBase; | |||
let millias = [0]; | |||
let milliaIndex = 0; | |||
while (groupNameCurrent.length > 0) { | |||
if (groupNameCurrent === 't') { | |||
break; | |||
} | |||
const centillions = CENTILLIONS_PREFIXES.findIndex((p) => { | |||
return p.length > 0 && groupNameCurrent.startsWith(p); | |||
}); | |||
if (centillions > -1) { | |||
milliaIndex = 0; | |||
millias[milliaIndex] += (centillions * 100); | |||
groupNameCurrent = groupNameCurrent.slice(CENTILLIONS_PREFIXES[centillions].length); | |||
continue; | |||
} | |||
const decillions = DECILLIONS_PREFIXES.findIndex((p) => { | |||
return p.length > 0 && groupNameCurrent.startsWith(p); | |||
}); | |||
if (decillions > -1) { | |||
milliaIndex = 0; | |||
millias[milliaIndex] += decillions * 10; | |||
groupNameCurrent = groupNameCurrent.slice(DECILLIONS_PREFIXES[decillions].length); | |||
continue; | |||
} | |||
const millions = MILLIONS_PREFIXES.findIndex((p) => { | |||
return p.length > 0 && groupNameCurrent.startsWith(p); | |||
}); | |||
if (millions > -1) { | |||
milliaIndex = 0; | |||
millias[milliaIndex] += millions; | |||
groupNameCurrent = groupNameCurrent.slice(MILLIONS_PREFIXES[millions].length); | |||
continue; | |||
} | |||
if (groupNameCurrent.startsWith(`${MILLIA_PREFIX}^`)) { | |||
// short millia | |||
groupNameCurrent = groupNameCurrent.slice(MILLIA_PREFIX.length); | |||
const matchedMilliaArray = groupNameCurrent.match(/^\d+/); | |||
if (!matchedMilliaArray) { | |||
throw new Error(`Invalid groupName: ${groupName}`); | |||
} | |||
const matchedMillia = matchedMilliaArray[0]; | |||
millias[Number(matchedMillia)] = millias[milliaIndex] || 1; | |||
millias[milliaIndex] = 0; | |||
groupNameCurrent = groupNameCurrent.slice(matchedMillia.length); | |||
} | |||
if (groupNameCurrent.startsWith(MILLIA_PREFIX)) { | |||
millias[milliaIndex + 1] = millias[milliaIndex] || 1; | |||
millias[milliaIndex] = 0; | |||
milliaIndex += 1; | |||
groupNameCurrent = groupNameCurrent.slice(MILLIA_PREFIX.length); | |||
continue; | |||
} | |||
break; | |||
} | |||
const bigGroupPlace = Number( | |||
millias | |||
.map((s) => s.toString().padStart(3, '0')) | |||
.reverse() | |||
.join('') | |||
); | |||
return 1 + bigGroupPlace; | |||
}; | |||
enum ParseGroupsMode { | |||
INITIAL = 'unknown', | |||
ONES = 'ones', | |||
TENS = 'tens', | |||
TEN_PLUS_ONES = 'tenPlusOnes', | |||
HUNDRED = 'hundred', | |||
THOUSAND = 'thousand', | |||
DONE = 'done', | |||
} | |||
interface ParserState { | |||
lastToken?: string; | |||
groups: Group[]; | |||
mode: ParseGroupsMode; | |||
} | |||
export const parseGroups = (tokens: string[]) => { | |||
const { groups } = [...tokens, FINAL_TOKEN].reduce<ParserState>( | |||
(acc, token) => { | |||
const lastGroup = acc.groups.at(-1) ?? ['000', 0]; | |||
if (token === THOUSAND || token.endsWith(ILLION_SUFFIX)) { | |||
if (acc.mode === ParseGroupsMode.ONES) { | |||
const ones = ONES.findIndex((o) => o === acc.lastToken); | |||
lastGroup[0] = `${lastGroup[0].slice(0, 2)}${ones}`; | |||
} | |||
lastGroup[1] = getGroupFromGroupName(token); | |||
return { | |||
...acc, | |||
groups: [...acc.groups.slice(0, -1), lastGroup], | |||
lastToken: token, | |||
mode: ParseGroupsMode.THOUSAND, | |||
} | |||
} | |||
if (token === HUNDRED) { | |||
if (acc.mode === ParseGroupsMode.ONES) { | |||
const hundreds = ONES.findIndex((o) => o === acc.lastToken); | |||
lastGroup[0] = `${hundreds}${lastGroup[0].slice(1)}`; | |||
return { | |||
...acc, | |||
groups: [...acc.groups.slice(0, -1), lastGroup], | |||
mode: ParseGroupsMode.HUNDRED, | |||
}; | |||
} | |||
} | |||
if (token === FINAL_TOKEN) { | |||
if (acc.mode === ParseGroupsMode.ONES) { | |||
const ones = ONES.findIndex((o) => o === acc.lastToken); | |||
lastGroup[0] = `${lastGroup[0].slice(0, 2)}${ones}`; | |||
lastGroup[1] = 0; | |||
return { | |||
...acc, | |||
groups: [...acc.groups.slice(0, -1), lastGroup], | |||
mode: ParseGroupsMode.DONE, | |||
}; | |||
} | |||
} | |||
if (ONES.includes(token as OnesName)) { | |||
if (acc.mode === ParseGroupsMode.THOUSAND) { | |||
return { | |||
...acc, | |||
lastToken: token, | |||
mode: ParseGroupsMode.ONES, | |||
groups: [...acc.groups, ['000', 0]], | |||
} | |||
} | |||
return { | |||
...acc, | |||
lastToken: token, | |||
mode: ParseGroupsMode.ONES, | |||
} | |||
} | |||
const tenPlusOnes = TEN_PLUS_ONES.findIndex((t) => t === token); | |||
if (tenPlusOnes > -1) { | |||
lastGroup[0] = `${lastGroup[0].slice(0, 1)}1${tenPlusOnes}`; | |||
return { | |||
...acc, | |||
lastToken: token, | |||
mode: ParseGroupsMode.TEN_PLUS_ONES, | |||
groups: [...acc.groups.slice(0, -1), lastGroup] | |||
}; | |||
} | |||
const tens = TENS.findIndex((t) => t === token); | |||
if (tens > -1) { | |||
lastGroup[0] = `${lastGroup[0].slice(0, 1)}${tens}${lastGroup[0].slice(2)}`; | |||
return { | |||
...acc, | |||
lastToken: token, | |||
mode: ParseGroupsMode.TENS, | |||
groups: [...acc.groups.slice(0, -1), lastGroup], | |||
}; | |||
} | |||
return { | |||
...acc, | |||
lastToken: token, | |||
}; | |||
}, | |||
{ | |||
lastToken: undefined, | |||
groups: [], | |||
mode: ParseGroupsMode.INITIAL, | |||
}, | |||
); | |||
return groups; | |||
}; | |||
export const combineGroups = (groups: Group[]) => { | |||
const groupsSorted = groups.sort((a, b) => b[1] - a[1]); // sort by place | |||
const firstGroup = groupsSorted[0]; | |||
const firstGroupPlace = firstGroup[1]; | |||
const digits = groupsSorted.reduce( | |||
(s, group) => { | |||
const [groupDigits] = group; | |||
return `${s}${groupDigits}`; | |||
}, | |||
'' | |||
).replace(/^0+/, '') || '0'; | |||
const firstGroupDigits = firstGroup[0]; | |||
const firstGroupDigitsWithoutZeroes = firstGroupDigits.replace(/^0+/, ''); | |||
const exponentExtra = firstGroupDigits.length - firstGroupDigitsWithoutZeroes.length; | |||
const exponentValue = BigInt((firstGroupPlace * 3) + (2 - exponentExtra)); | |||
const exponent = exponentValue < 0 ? exponentValue.toString() : `+${exponentValue}`; | |||
const significandInteger = digits.slice(0, 1); | |||
const significandFraction = digits.slice(1); | |||
if (significandFraction.length > 0) { | |||
return `${significandInteger}${DECIMAL_POINT}${significandFraction}e${exponent}`; | |||
} | |||
return `${significandInteger}e${exponent}`; | |||
}; |
@@ -0,0 +1 @@ | |||
export * as enUS from './en-US'; |
@@ -1,112 +0,0 @@ | |||
import {BLANK_DIGIT, createBlankDigits, deconstructNumeric, Group, groupDigits, normalizeNumeric} from '../numeric'; | |||
import BigNumber from 'bignumber.js'; | |||
interface Config { | |||
hundredsLatinNames: string[], | |||
onesLatinNames: string[], | |||
onesSpecialLatinNames: string[], | |||
tensLatinNames: string[], | |||
illion: string, | |||
illionth: string, | |||
illiard?: string, | |||
illiardth?: string, | |||
millia: string, | |||
} | |||
const getLatinPowerGroupDigitsName = (latinRaw: string, special: boolean, config: Config) => { | |||
const digits = latinRaw.padStart(3, BLANK_DIGIT); | |||
const [hundreds, tens, ones] = digits.split('').map(s => Number(s)); | |||
const names = []; | |||
if (hundreds > 0) { | |||
names.push(config.hundredsLatinNames[hundreds]); | |||
names.push(config.onesLatinNames[ones]); | |||
names.push(config.tensLatinNames[tens]); | |||
} else { | |||
if (tens > 0) { | |||
names.push(config.onesLatinNames[ones]); | |||
names.push(config.tensLatinNames[tens]); | |||
} else { | |||
if (special) { | |||
names.push(config.onesSpecialLatinNames[ones]); | |||
} else if (ones > 1) { | |||
names.push(config.onesLatinNames[ones]); | |||
} | |||
} | |||
} | |||
return names.join(''); | |||
}; | |||
const getLatinPowerSuffix = (latinRaw: string, isOdd: boolean, special: boolean, ordinal: boolean, config: Config) => { | |||
const digits = latinRaw.padStart(3, BLANK_DIGIT); | |||
const [hundreds, tens, ones] = digits.split('').map(s => Number(s)); | |||
const suffix = isOdd ? (ordinal ? config.illiardth : config.illiard) : (ordinal ? config.illionth : config.illion); | |||
if (hundreds > 0) { | |||
if (tens !== 1) { | |||
return 't' + suffix; | |||
} | |||
return suffix; | |||
} | |||
if (tens > 0) { | |||
switch (tens) { | |||
case 1: | |||
return suffix; | |||
default: | |||
break; | |||
} | |||
return 't' + suffix; | |||
} | |||
switch (ones) { | |||
case 1: | |||
case 2: | |||
case 3: | |||
case 4: | |||
return special ? suffix : 't' + suffix; | |||
case 5: | |||
case 6: | |||
case 7: | |||
return 't' + suffix; | |||
case 8: | |||
case 9: | |||
return suffix; | |||
default: | |||
break; | |||
} | |||
return ''; | |||
}; | |||
const getLatinPowerGroupName = (g: Group, config: Config) => { | |||
const [digits, index] = g; | |||
if (index.lt(1)) { | |||
return getLatinPowerGroupDigitsName(digits, index.eq(0), config); | |||
} | |||
let milliaSuffix = ''; | |||
for (let i = new BigNumber(0); i.lt(index); i = i.plus(1)) { | |||
milliaSuffix += config.millia; | |||
} | |||
return [getLatinPowerGroupDigitsName(digits, index.eq(0), config), milliaSuffix].join(''); | |||
}; | |||
const getLatinPowerName = (latinRaw: BigNumber, isOdd: boolean, ordinal: boolean, config: Config) => { | |||
const x = normalizeNumeric(latinRaw); | |||
const {significandDigits, exponent} = deconstructNumeric(x); | |||
const blankDigits = createBlankDigits(3); | |||
const groups = groupDigits(significandDigits, exponent, 3); | |||
if (groups.length === 1) { | |||
return [ | |||
getLatinPowerGroupName(groups[0], config), | |||
getLatinPowerSuffix(groups[0][0], isOdd, true, ordinal, config), | |||
].join(''); | |||
} | |||
const visibleGroups = groups.filter(([digits]) => digits !== blankDigits); | |||
const [lastVisibleGroup] = visibleGroups.slice(-1); | |||
return [ | |||
...visibleGroups.map(g => getLatinPowerGroupName(g, config)), | |||
getLatinPowerSuffix(lastVisibleGroup[0], isOdd, lastVisibleGroup[1].eq(0), ordinal, config), | |||
].join(''); | |||
}; | |||
export default getLatinPowerName; |
@@ -1,76 +0,0 @@ | |||
import BigNumber from 'bignumber.js'; | |||
const EXPONENT_SEPARATOR = 'e'; | |||
const SIGNIFICAND_DECIMAL_POINT = '.'; | |||
export const NEGATIVE_SIGN = '-'; | |||
export const BLANK_DIGIT = '0'; // must be a value where Number(BLANK_DIGIT) === 0 | |||
export type Numeric = number | bigint | string | BigNumber | |||
export type Group = [string, BigNumber] | |||
export const normalizeNumeric = (x: Numeric): string => { | |||
try { | |||
switch (typeof x) { | |||
case 'number': | |||
return new BigNumber(x).toString(10); | |||
case 'bigint': | |||
return x.toString(10); | |||
case 'string': | |||
// TODO assume not all strings follow a correct format | |||
return x.trim(); | |||
// return new BigNumber(x).toString(10) | |||
case 'object': | |||
return x.toString(10); | |||
default: | |||
break; | |||
} | |||
} catch { | |||
throw new RangeError('Not a valid numeric value in the current locale.'); | |||
} | |||
throw new TypeError('Not a valid numeric value in any locale.'); | |||
}; | |||
export const deconstructNumeric = (x: string) => { | |||
const absolute = x.replaceAll(NEGATIVE_SIGN, ''); | |||
if (!absolute.includes(EXPONENT_SEPARATOR)) { | |||
return { | |||
exponent: new BigNumber(absolute.length - 1), | |||
significandDigits: absolute, | |||
}; | |||
} | |||
const [significandStrExp, exponentStr] = absolute.split(EXPONENT_SEPARATOR); | |||
const [integral] = significandStrExp.split(SIGNIFICAND_DECIMAL_POINT); | |||
return { | |||
exponent: new BigNumber(exponentStr).plus(integral.length - 1), | |||
significandDigits: significandStrExp.replaceAll(SIGNIFICAND_DECIMAL_POINT, ''), | |||
}; | |||
}; | |||
export const createBlankDigits = (grouping: number) => new Array<string>(grouping).fill(BLANK_DIGIT).join(''); | |||
export const groupDigits = (significandStr: string, exponent: BigNumber, grouping: number) => { | |||
const blankDigits = createBlankDigits(grouping); | |||
return significandStr | |||
.split('') | |||
.reduceRight( | |||
(theGroups, c, i): any => { | |||
const currentGroupIndex = exponent.minus(i).dividedToIntegerBy(grouping).minus(1); | |||
const [lastGroup = [blankDigits, currentGroupIndex.plus(1)] as Group] = theGroups; | |||
const [digits, groupIndex] = lastGroup; | |||
const currentPlaceValue = exponent.minus(i).mod(grouping); | |||
if (currentPlaceValue.eq(0)) { | |||
return [[`${blankDigits.slice(0, -c.length)}${c}`, currentGroupIndex.plus(1)], ...theGroups]; | |||
} | |||
const currentDigitStringIndex = new BigNumber(grouping).minus(1).minus(currentPlaceValue).toNumber(); | |||
const newDigits = digits.slice( | |||
0, | |||
currentDigitStringIndex, | |||
) + c + digits.slice(currentDigitStringIndex + c.length); | |||
return [[newDigits, groupIndex] as Group, ...theGroups.slice(1)]; | |||
}, | |||
[] as Group[], | |||
); | |||
}; |
@@ -0,0 +1,57 @@ | |||
import { describe, it, expect } from 'vitest'; | |||
import { numberToExponential } from '../src/exponent'; | |||
describe('numberToExponential', () => { | |||
it('converts 0 to 0e+0', () => { | |||
expect(numberToExponential(0)).toBe('0e+0'); | |||
}); | |||
it('converts "0" to 0e+0', () => { | |||
expect(numberToExponential('0')).toBe('0e+0'); | |||
}); | |||
it('converts "00000000000000000" to 0e+0', () => { | |||
expect(numberToExponential('00000000000000000')).toBe('0e+0'); | |||
}); | |||
it('converts 1 to 1e+0', () => { | |||
expect(numberToExponential(1)).toBe('1e+0'); | |||
}); | |||
it('converts "0.1" to 1e-1', () => { | |||
expect(numberToExponential('0.1')).toBe('1e-1'); | |||
}); | |||
it('converts "0.10" to 1e-1', () => { | |||
expect(numberToExponential('0.10')).toBe('1e-1'); | |||
}); | |||
it('converts "1" to 1e+0', () => { | |||
expect(numberToExponential('1')).toBe('1e+0'); | |||
}); | |||
it('converts "10" to 1e+1', () => { | |||
expect(numberToExponential('10')).toBe('1e+1'); | |||
}); | |||
it('converts "100" to 1e+2', () => { | |||
expect(numberToExponential('100')).toBe('1e+2'); | |||
}); | |||
it('converts "0100" to 1e+2', () => { | |||
expect(numberToExponential('0100')).toBe('1e+2'); | |||
}); | |||
it('converts "1234567890" to 1.23456789e+9', () => { | |||
expect(numberToExponential('1234567890')).toBe('1.23456789e+9'); | |||
}); | |||
it('converts "1234567890.1234567890" to 1.234567890123456789e+9', () => { | |||
expect(numberToExponential('1234567890.1234567890')).toBe('1.234567890123456789e+9'); | |||
}); | |||
it('converts "1e+100" to 1e+100', () => { | |||
expect(numberToExponential('1e+100')).toBe('1e+100'); | |||
}); | |||
}); |
@@ -0,0 +1,245 @@ | |||
import { describe, it, expect } from 'vitest'; | |||
import {parse, stringify, systems} from '../../src'; | |||
const options = { system: systems.enUS }; | |||
describe('numerica', () => { | |||
describe('group names', () => { | |||
describe('0-9', () => { | |||
it.each` | |||
ones | expected | |||
${0} | ${'zero'} | |||
${1} | ${'one'} | |||
${2} | ${'two'} | |||
${3} | ${'three'} | |||
${4} | ${'four'} | |||
${5} | ${'five'} | |||
${6} | ${'six'} | |||
${7} | ${'seven'} | |||
${8} | ${'eight'} | |||
${9} | ${'nine'} | |||
`('converts $ones to $expected', ({ ones, expected }) => { | |||
expect(stringify(ones, options)).toBe(expected) | |||
expect(parse(expected, { ...options, type: 'number' })).toBe(ones); | |||
}); | |||
}); | |||
describe('10-19', () => { | |||
it.each` | |||
tenPlusOnes | expected | |||
${10} | ${'ten'} | |||
${11} | ${'eleven'} | |||
${12} | ${'twelve'} | |||
${13} | ${'thirteen'} | |||
${14} | ${'fourteen'} | |||
${15} | ${'fifteen'} | |||
${16} | ${'sixteen'} | |||
${17} | ${'seventeen'} | |||
${18} | ${'eighteen'} | |||
${19} | ${'nineteen'} | |||
`('converts $tenPlusOnes to $expected', ({ tenPlusOnes, expected }) => { | |||
expect(stringify(tenPlusOnes, options)).toBe(expected) | |||
expect(parse(expected, { ...options, type: 'number' })).toBe(tenPlusOnes); | |||
}); | |||
}); | |||
describe.each` | |||
tensStart | tensEnd | tensBase | |||
${20} | ${29} | ${'twenty'} | |||
${30} | ${39} | ${'thirty'} | |||
${40} | ${49} | ${'forty'} | |||
${50} | ${59} | ${'fifty'} | |||
${60} | ${69} | ${'sixty'} | |||
${70} | ${79} | ${'seventy'} | |||
${80} | ${89} | ${'eighty'} | |||
${90} | ${99} | ${'ninety'} | |||
`('$tensStart-$tensEnd', ({ | |||
tensStart, tensBase, | |||
}) => { | |||
it.each` | |||
value | expected | |||
${tensStart + 0} | ${tensBase} | |||
${tensStart + 1} | ${`${tensBase} one`} | |||
${tensStart + 2} | ${`${tensBase} two`} | |||
${tensStart + 3} | ${`${tensBase} three`} | |||
${tensStart + 4} | ${`${tensBase} four`} | |||
${tensStart + 5} | ${`${tensBase} five`} | |||
${tensStart + 6} | ${`${tensBase} six`} | |||
${tensStart + 7} | ${`${tensBase} seven`} | |||
${tensStart + 8} | ${`${tensBase} eight`} | |||
${tensStart + 9} | ${`${tensBase} nine`} | |||
`('converts $value to $expected', ({ value, expected }) => { | |||
expect(stringify(value, options)).toBe(expected); | |||
expect(parse(expected, { ...options, type: 'number' })).toBe(value); | |||
}); | |||
}); | |||
describe.each` | |||
hundredsStart | hundredsEnd | hundredsBase | |||
${100} | ${199} | ${'one hundred'} | |||
${200} | ${299} | ${'two hundred'} | |||
${300} | ${399} | ${'three hundred'} | |||
${400} | ${499} | ${'four hundred'} | |||
${500} | ${599} | ${'five hundred'} | |||
${600} | ${699} | ${'six hundred'} | |||
${700} | ${799} | ${'seven hundred'} | |||
${800} | ${899} | ${'eight hundred'} | |||
${900} | ${999} | ${'nine hundred'} | |||
`('$hundredsStart-$hundredsEnd', ({ | |||
hundredsStart, hundredsBase, | |||
}) => { | |||
describe(`${hundredsStart}-${hundredsStart + 9}`, () => { | |||
it.each` | |||
value | expected | |||
${hundredsStart + 0} | ${hundredsBase} | |||
${hundredsStart + 1} | ${`${hundredsBase} one`} | |||
${hundredsStart + 2} | ${`${hundredsBase} two`} | |||
${hundredsStart + 3} | ${`${hundredsBase} three`} | |||
${hundredsStart + 4} | ${`${hundredsBase} four`} | |||
${hundredsStart + 5} | ${`${hundredsBase} five`} | |||
${hundredsStart + 6} | ${`${hundredsBase} six`} | |||
${hundredsStart + 7} | ${`${hundredsBase} seven`} | |||
${hundredsStart + 8} | ${`${hundredsBase} eight`} | |||
${hundredsStart + 9} | ${`${hundredsBase} nine`} | |||
`('converts $value to $expected', ({ value, expected }) => { | |||
expect(stringify(value, options)).toBe(expected) | |||
expect(parse(expected, { ...options, type: 'number' })).toBe(value) | |||
}); | |||
}); | |||
describe(`${hundredsStart + 10}-${hundredsStart + 19}`, () => { | |||
it.each` | |||
value | expected | |||
${hundredsStart + 10} | ${`${hundredsBase} ten`} | |||
${hundredsStart + 11} | ${`${hundredsBase} eleven`} | |||
${hundredsStart + 12} | ${`${hundredsBase} twelve`} | |||
${hundredsStart + 13} | ${`${hundredsBase} thirteen`} | |||
${hundredsStart + 14} | ${`${hundredsBase} fourteen`} | |||
${hundredsStart + 15} | ${`${hundredsBase} fifteen`} | |||
${hundredsStart + 16} | ${`${hundredsBase} sixteen`} | |||
${hundredsStart + 17} | ${`${hundredsBase} seventeen`} | |||
${hundredsStart + 18} | ${`${hundredsBase} eighteen`} | |||
${hundredsStart + 19} | ${`${hundredsBase} nineteen`} | |||
`('converts $value to $expected', ({ value, expected }) => { | |||
expect(stringify(value, options)).toBe(expected) | |||
expect(parse(expected, { ...options, type: 'number' })).toBe(value) | |||
}); | |||
}); | |||
describe.each` | |||
start | end | base | |||
${20} | ${29} | ${'twenty'} | |||
${30} | ${39} | ${'thirty'} | |||
${40} | ${49} | ${'forty'} | |||
${50} | ${59} | ${'fifty'} | |||
${60} | ${69} | ${'sixty'} | |||
${70} | ${79} | ${'seventy'} | |||
${80} | ${89} | ${'eighty'} | |||
${90} | ${99} | ${'ninety'} | |||
`('$start-$end', ({ | |||
start, base, | |||
}) => { | |||
it.each` | |||
value | expected | |||
${hundredsStart + start + 0} | ${`${hundredsBase} ${base}`} | |||
${hundredsStart + start + 1} | ${`${hundredsBase} ${base} one`} | |||
${hundredsStart + start + 2} | ${`${hundredsBase} ${base} two`} | |||
${hundredsStart + start + 3} | ${`${hundredsBase} ${base} three`} | |||
${hundredsStart + start + 4} | ${`${hundredsBase} ${base} four`} | |||
${hundredsStart + start + 5} | ${`${hundredsBase} ${base} five`} | |||
${hundredsStart + start + 6} | ${`${hundredsBase} ${base} six`} | |||
${hundredsStart + start + 7} | ${`${hundredsBase} ${base} seven`} | |||
${hundredsStart + start + 8} | ${`${hundredsBase} ${base} eight`} | |||
${hundredsStart + start + 9} | ${`${hundredsBase} ${base} nine`} | |||
`('converts $value to $expected', ({ value, expected }) => { | |||
expect(stringify(value, options)).toBe(expected); | |||
expect(parse(expected, { ...options, type: 'number' })).toBe(value); | |||
}); | |||
}); | |||
}); | |||
}); | |||
it('converts 1000 to one thousand', () => { | |||
expect(stringify(1000, options)).toBe('one thousand'); | |||
expect(parse('one thousand', { ...options, type: 'number' })).toBe(1000); | |||
}); | |||
it('converts 10000 to ten thousand', () => { | |||
expect(stringify(10000, options)).toBe('ten thousand'); | |||
expect(parse('ten thousand', { ...options, type: 'number' })).toBe(10000); | |||
}); | |||
it('converts 100000 to one hundred thousand', () => { | |||
expect(stringify(100000, options)).toBe('one hundred thousand'); | |||
expect(parse('one hundred thousand', { ...options, type: 'number' })).toBe(100000); | |||
}); | |||
it('converts 123456 to one hundred twenty three thousand four hundred fifty six', () => { | |||
expect(stringify(123456, options)).toBe('one hundred twenty three thousand four hundred fifty six'); | |||
expect(parse('one hundred twenty three thousand four hundred fifty six', { ...options, type: 'number' })).toBe(123456); | |||
}); | |||
it.each` | |||
value | expected | |||
${1e+6} | ${'one million'} | |||
${1e+9} | ${'one billion'} | |||
${1e+12} | ${'one trillion'} | |||
${1e+15} | ${'one quadrillion'} | |||
${1e+18} | ${'one quintillion'} | |||
${1e+21} | ${'one sextillion'} | |||
${1e+24} | ${'one septillion'} | |||
${1e+27} | ${'one octillion'} | |||
${1e+30} | ${'one nonillion'} | |||
`('converts $value to $expected', ({ value, expected }) => { | |||
expect(stringify(value, options)).toBe(expected); | |||
expect(parse(expected, { ...options, type: 'number' })).toBe(value); | |||
}); | |||
it.each` | |||
value | expected | |||
${'1e+33'} | ${'one decillion'} | |||
${'1e+36'} | ${'one undecillion'} | |||
${'1e+39'} | ${'one duodecillion'} | |||
${'1e+42'} | ${'one tredecillion'} | |||
${'1e+45'} | ${'one quattuordecillion'} | |||
${'1e+48'} | ${'one quindecillion'} | |||
${'1e+51'} | ${'one sexdecillion'} | |||
${'1e+54'} | ${'one septendecillion'} | |||
${'1e+57'} | ${'one octodecillion'} | |||
${'1e+60'} | ${'one novemdecillion'} | |||
`('converts $value to $expected', ({ value, expected }) => { | |||
expect(stringify(value, options)).toBe(expected); | |||
expect(parse(expected, options)).toBe(value); | |||
}); | |||
it.each` | |||
value | expected | |||
${'1e+63'} | ${'one vigintillion'} | |||
${'1e+66'} | ${'one unvigintillion'} | |||
${'1e+69'} | ${'one duovigintillion'} | |||
${'1e+72'} | ${'one trevigintillion'} | |||
${'1e+75'} | ${'one quattuorvigintillion'} | |||
${'1e+78'} | ${'one quinvigintillion'} | |||
${'1e+81'} | ${'one sexvigintillion'} | |||
${'1e+84'} | ${'one septenvigintillion'} | |||
${'1e+87'} | ${'one octovigintillion'} | |||
${'1e+90'} | ${'one novemvigintillion'} | |||
`('converts $value to $expected', ({ value, expected }) => { | |||
expect(stringify(value, options)).toBe(expected); | |||
expect(parse(expected, options)).toBe(value); | |||
}); | |||
it.each` | |||
value | expected | |||
${'1e+93'} | ${'one trigintillion'} | |||
${'1e+123'} | ${'one quadragintillion'} | |||
${'1e+153'} | ${'one quinquagintillion'} | |||
${'1e+183'} | ${'one sexagintillion'} | |||
${'1e+213'} | ${'one septuagintillion'} | |||
${'1e+243'} | ${'one octogintillion'} | |||
${'1e+273'} | ${'one nonagintillion'} | |||
`('converts $value to $expected', ({ value, expected }) => { | |||
expect(stringify(value, options)).toBe(expected); | |||
expect(parse(expected, options)).toBe(value); | |||
}); | |||
}); |
@@ -1,8 +1,10 @@ | |||
import getNumberName from '../../../index'; | |||
import { describe, it, expect } from 'vitest'; | |||
import { stringify, parse } from '../../../src'; | |||
import {numberToExponential} from '../../../src/exponent'; | |||
describe('Landon\'s original test cases', () => { | |||
describe('Basic conversions', () => { | |||
it.each` | |||
describe('Basic conversions', () => { | |||
it.each` | |||
value | americanName | |||
${1} | ${'one'} | |||
${1000} | ${'one thousand'} | |||
@@ -12,60 +14,61 @@ describe('Landon\'s original test cases', () => { | |||
${1000000000000000} | ${'one quadrillion'} | |||
${1000000000000000000} | ${'one quintillion'} | |||
`('converts $value to $americanName', ({value, americanName}) => { | |||
expect(getNumberName(value)).toBe(americanName); | |||
}); | |||
expect(stringify(value)).toBe(americanName); | |||
expect(parse(americanName, { type: 'number' })).toBe(value); | |||
}); | |||
it( | |||
'converts 987654321 to nine hundred eighty seven million six hundred fifty four thousand three hundred twenty one', | |||
() => { | |||
expect(getNumberName(987654321)) | |||
.toBe('nine hundred eighty seven million six hundred fifty four thousand three hundred twenty one'); | |||
}, | |||
); | |||
it( | |||
'converts 987654321 to nine hundred eighty seven million six hundred fifty four thousand three hundred twenty one', | |||
() => { | |||
expect(stringify(987654321)) | |||
.toBe('nine hundred eighty seven million six hundred fifty four thousand three hundred twenty one'); | |||
}, | |||
); | |||
it( | |||
'converts 123456789246801357 to one hundred twenty three quadrillion four hundred fifty six trillion seven hundred eighty nine billion two hundred forty six million eight hundred one thousand three hundred fifty seven', | |||
() => { | |||
expect(getNumberName('123456789246801357')) | |||
.toBe( | |||
'one hundred twenty three quadrillion four hundred fifty six trillion seven hundred eighty nine billion two hundred forty six million eight hundred one thousand three hundred fifty seven'); | |||
}, | |||
); | |||
it( | |||
'converts 123456789246801357 to one hundred twenty three quadrillion four hundred fifty six trillion seven hundred eighty nine billion two hundred forty six million eight hundred one thousand three hundred fifty seven', | |||
() => { | |||
expect(stringify('123456789246801357')) | |||
.toBe( | |||
'one hundred twenty three quadrillion four hundred fifty six trillion seven hundred eighty nine billion two hundred forty six million eight hundred one thousand three hundred fifty seven'); | |||
}, | |||
); | |||
it( | |||
'converts 123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 to one hundred twenty three duoseptuagintillion four hundred fifty six unseptuagintillion seven hundred eighty nine septuagintillion two hundred forty six novemsexagintillion eight hundred one octosexagintillion three hundred fifty seven septensexagintillion', | |||
() => { | |||
expect(getNumberName( | |||
'123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) | |||
.toBe( | |||
'one hundred twenty three duoseptuagintillion four hundred fifty six unseptuagintillion seven hundred eighty nine septuagintillion two hundred forty six novemsexagintillion eight hundred one octosexagintillion three hundred fifty seven septensexagintillion'); | |||
}, | |||
); | |||
it( | |||
'converts 123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 to one hundred twenty three duoseptuagintillion four hundred fifty six unseptuagintillion seven hundred eighty nine septuagintillion two hundred forty six novemsexagintillion eight hundred one octosexagintillion three hundred fifty seven septensexagintillion', | |||
() => { | |||
expect(stringify( | |||
'123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) | |||
.toBe( | |||
'one hundred twenty three duoseptuagintillion four hundred fifty six unseptuagintillion seven hundred eighty nine septuagintillion two hundred forty six novemsexagintillion eight hundred one octosexagintillion three hundred fifty seven septensexagintillion'); | |||
}, | |||
); | |||
it( | |||
'converts 123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 to one hundred twenty three cenduoseptuagintillion four hundred fifty six cenunseptuagintillion seven hundred eighty nine censeptuagintillion two hundred forty six cennovemsexagintillion eight hundred one cenoctosexagintillion three hundred fifty seven censeptensexagintillion', | |||
() => { | |||
expect(getNumberName( | |||
'123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) | |||
.toBe( | |||
'one hundred twenty three cenduoseptuagintillion four hundred fifty six cenunseptuagintillion seven hundred eighty nine censeptuagintillion two hundred forty six cennovemsexagintillion eight hundred one cenoctosexagintillion three hundred fifty seven censeptensexagintillion'); | |||
}, | |||
); | |||
it( | |||
'converts 123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 to one hundred twenty three cenduoseptuagintillion four hundred fifty six cenunseptuagintillion seven hundred eighty nine censeptuagintillion two hundred forty six cennovemsexagintillion eight hundred one cenoctosexagintillion three hundred fifty seven censeptensexagintillion', | |||
() => { | |||
expect(stringify( | |||
'123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) | |||
.toBe( | |||
'one hundred twenty three cenduoseptuagintillion four hundred fifty six cenunseptuagintillion seven hundred eighty nine censeptuagintillion two hundred forty six cennovemsexagintillion eight hundred one cenoctosexagintillion three hundred fifty seven censeptensexagintillion'); | |||
}, | |||
); | |||
it( | |||
'converts 123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 to one hundred twenty three trecenduoseptuagintillion four hundred fifty six trecenunseptuagintillion seven hundred eighty nine trecenseptuagintillion two hundred forty six trecennovemsexagintillion eight hundred one trecenoctosexagintillion three hundred fifty seven trecenseptensexagintillion', | |||
() => { | |||
expect(getNumberName( | |||
'123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) | |||
.toBe( | |||
'one hundred twenty three trecenduoseptuagintillion four hundred fifty six trecenunseptuagintillion seven hundred eighty nine trecenseptuagintillion two hundred forty six trecennovemsexagintillion eight hundred one trecenoctosexagintillion three hundred fifty seven trecenseptensexagintillion'); | |||
}, | |||
); | |||
}); | |||
it( | |||
'converts 123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 to one hundred twenty three trecenduoseptuagintillion four hundred fifty six trecenunseptuagintillion seven hundred eighty nine trecenseptuagintillion two hundred forty six trecennovemsexagintillion eight hundred one trecenoctosexagintillion three hundred fifty seven trecenseptensexagintillion', | |||
() => { | |||
expect(stringify( | |||
'123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) | |||
.toBe( | |||
'one hundred twenty three trecenduoseptuagintillion four hundred fifty six trecenunseptuagintillion seven hundred eighty nine trecenseptuagintillion two hundred forty six trecennovemsexagintillion eight hundred one trecenoctosexagintillion three hundred fifty seven trecenseptensexagintillion'); | |||
}, | |||
); | |||
}); | |||
describe('Medium size numbers (<= 1e+63)', () => { | |||
describe('Table 1', () => { | |||
it.each` | |||
describe('Medium size numbers (<= 1e+63)', () => { | |||
describe('Table 1', () => { | |||
it.each` | |||
value | americanName | |||
${'1e+9'} | ${'billion'} | |||
${'1e+12'} | ${'trillion'} | |||
@@ -87,13 +90,14 @@ describe('Landon\'s original test cases', () => { | |||
${'1e+60'} | ${'novemdecillion'} | |||
${'1e+63'} | ${'vigintillion'} | |||
`('converts $value to $americanName', ({value, americanName}) => { | |||
expect(getNumberName(value)).toBe(`one ${americanName}`); | |||
}); | |||
}); | |||
}); | |||
expect(stringify(value)).toBe(`one ${americanName}`); | |||
expect(parse(`one ${americanName}`)).toBe(value); | |||
}); | |||
}); | |||
}); | |||
describe('Large size numbers (< 1e+303)', () => { | |||
it.each` | |||
describe('Large size numbers (< 1e+303)', () => { | |||
it.each` | |||
value | americanName | |||
${'1e+66'} | ${'unvigintillion'} | |||
${'1e+69'} | ${'duovigintillion'} | |||
@@ -116,12 +120,13 @@ describe('Landon\'s original test cases', () => { | |||
${'1e+273'} | ${'nonagintillion'} | |||
${'1e+300'} | ${'novemnonagintillion'} | |||
`('converts $value to $americanName', ({value, americanName}) => { | |||
expect(getNumberName(value)).toBe(`one ${americanName}`); | |||
}); | |||
}); | |||
expect(stringify(value)).toBe(`one ${americanName}`); | |||
expect(parse(`one ${americanName}`)).toBe(value); | |||
}); | |||
}); | |||
describe('Gigantic size numbers (< 1e+3003)', () => { | |||
it.each` | |||
describe('Gigantic size numbers (< 1e+3003)', () => { | |||
it.each` | |||
value | americanName | |||
${'1e+303'} | ${'centillion'} | |||
${'1e+306'} | ${'cenuntillion'} | |||
@@ -142,22 +147,23 @@ describe('Landon\'s original test cases', () => { | |||
${'1e+2403'} | ${'octingentillion'} | |||
${'1e+2703'} | ${'nongentillion'} | |||
`('converts $value to $americanName', ({value, americanName}) => { | |||
expect(getNumberName(value)).toBe(`one ${americanName}`); | |||
}); | |||
}); | |||
expect(stringify(value)).toBe(`one ${americanName}`); | |||
expect(parse(`one ${americanName}`)).toBe(value); | |||
}); | |||
}); | |||
describe('Titanic size numbers (< 1e+3000003)', () => { | |||
it( | |||
'converts 123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 to one hundred twenty three milliaduoseptuagintillion four hundred fifty six milliaunseptuagintillion seven hundred eighty nine milliaseptuagintillion two hundred forty six millianovemsexagintillion eight hundred one milliaoctosexagintillion three hundred fifty seven milliaseptensexagintillion', | |||
() => { | |||
expect(getNumberName( | |||
'123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) | |||
.toBe( | |||
'one hundred twenty three milliaduoseptuagintillion four hundred fifty six milliaunseptuagintillion seven hundred eighty nine milliaseptuagintillion two hundred forty six millianovemsexagintillion eight hundred one milliaoctosexagintillion three hundred fifty seven milliaseptensexagintillion'); | |||
}, | |||
); | |||
describe('Titanic size numbers (< 1e+3000003)', () => { | |||
it( | |||
'converts 123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 to one hundred twenty three milliaduoseptuagintillion four hundred fifty six milliaunseptuagintillion seven hundred eighty nine milliaseptuagintillion two hundred forty six millianovemsexagintillion eight hundred one milliaoctosexagintillion three hundred fifty seven milliaseptensexagintillion', | |||
() => { | |||
expect(stringify( | |||
'123456789246801357000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')) | |||
.toBe( | |||
'one hundred twenty three milliaduoseptuagintillion four hundred fifty six milliaunseptuagintillion seven hundred eighty nine milliaseptuagintillion two hundred forty six millianovemsexagintillion eight hundred one milliaoctosexagintillion three hundred fifty seven milliaseptensexagintillion'); | |||
}, | |||
); | |||
it.each` | |||
it.each` | |||
value | americanName | |||
${'1.23456789246801357e+6221'} | ${'one hundred twenty three duomilliaduoseptuagintillion four hundred fifty six duomilliaunseptuagintillion seven hundred eighty nine duomilliaseptuagintillion two hundred forty six duomillianovemsexagintillion eight hundred one duomilliaoctosexagintillion three hundred fifty seven duomilliaseptensexagintillion'} | |||
${'1.23456789246801357e+2961221'} | ${'one hundred twenty three nongenseptenoctoginmilliaduoseptuagintillion four hundred fifty six nongenseptenoctoginmilliaunseptuagintillion seven hundred eighty nine nongenseptenoctoginmilliaseptuagintillion two hundred forty six nongenseptenoctoginmillianovemsexagintillion eight hundred one nongenseptenoctoginmilliaoctosexagintillion three hundred fifty seven nongenseptenoctoginmilliaseptensexagintillion'} | |||
@@ -171,7 +177,8 @@ describe('Landon\'s original test cases', () => { | |||
${'1e+3000000000003'} | ${'one milliamilliamilliamilliatillion'} | |||
${'1e+696276510359811'} | ${'one duocenduotriginmilliamilliamilliamilliaduononaginmilliamilliamilliacenseptuaginmilliamilliacennovemdecmillianongensextrigintillion'} | |||
`('converts $value to $americanName', ({value, americanName}) => { | |||
expect(getNumberName(value)).toBe(americanName); | |||
}); | |||
}); | |||
}); | |||
expect(stringify(value)).toBe(americanName); | |||
expect(parse(americanName)).toBe(numberToExponential(value)); | |||
}); | |||
}); | |||
}); |
@@ -16,6 +16,9 @@ | |||
"moduleResolution": "node", | |||
"jsx": "react", | |||
"esModuleInterop": true, | |||
"target": "ES2017" | |||
"target": "es2018", | |||
"paths": { | |||
"@/*": ["./src/*"], | |||
} | |||
} | |||
} |
@@ -16,6 +16,9 @@ | |||
"moduleResolution": "node", | |||
"jsx": "react", | |||
"esModuleInterop": true, | |||
"target": "ES2017" | |||
"target": "es2018", | |||
"paths": { | |||
"@/*": ["./src/*"], | |||
} | |||
} | |||
} |
@@ -1,5 +0,0 @@ | |||
node_modules | |||
.DS_Store | |||
dist | |||
dist-ssr | |||
*.local |
@@ -1,101 +0,0 @@ | |||
<!DOCTYPE html> | |||
<html lang="en-PH"> | |||
<head> | |||
<meta charset="UTF-8" /> | |||
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" /> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||
<title>Numerica</title> | |||
<style> | |||
:root { | |||
--color-bg: white; | |||
--color-fg: black; | |||
} | |||
:root { | |||
background-color: var(--color-bg); | |||
color: var(--color-fg); | |||
} | |||
body { | |||
margin: 0; | |||
font-size: 5vw; | |||
} | |||
form { | |||
display: block; | |||
width: 100vw; | |||
height: 100vh; | |||
text-align: center; | |||
} | |||
.locale { | |||
width: 100%; | |||
height: 5%; | |||
border: 0; | |||
padding: 0 2rem; | |||
margin: 0; | |||
text-align: inherit; | |||
font: inherit; | |||
font-size: 0.25em; | |||
display: grid; | |||
place-content: center; | |||
outline: 0; | |||
appearance: none; | |||
-webkit-appearance: none; | |||
-moz-appearance: none; | |||
background-color: var(--color-bg); | |||
color: var(--color-fg); | |||
border-radius: 0; | |||
box-sizing: border-box; | |||
} | |||
.input { | |||
width: 100%; | |||
height: 45%; | |||
border: 0; | |||
padding: 0 2rem; | |||
margin: 0; | |||
text-align: inherit; | |||
font: inherit; | |||
display: grid; | |||
place-content: center; | |||
outline: 0; | |||
background-color: inherit; | |||
color: inherit; | |||
border-radius: 0; | |||
box-sizing: border-box; | |||
resize: none; | |||
} | |||
.output { | |||
width: 100%; | |||
height: 50%; | |||
border: 0; | |||
padding: 0 2rem; | |||
margin: 0; | |||
text-align: inherit; | |||
font: inherit; | |||
display: grid; | |||
place-content: center; | |||
outline: 0; | |||
background-color: inherit; | |||
color: inherit; | |||
border-radius: 0; | |||
box-sizing: border-box; | |||
font-size: 0.5em; | |||
resize: none; | |||
} | |||
@media only screen and (prefers-color-scheme: dark) { | |||
:root { | |||
--color-bg: black; | |||
--color-fg: white; | |||
} | |||
} | |||
</style> | |||
</head> | |||
<body> | |||
<div id="root"></div> | |||
<script type="module" src="/src/main.tsx"></script> | |||
</body> | |||
</html> |
@@ -1,21 +0,0 @@ | |||
{ | |||
"name": "@theoryofnekomata/numerica-example", | |||
"version": "0.0.0", | |||
"scripts": { | |||
"dev": "vite", | |||
"build": "tsc && vite build", | |||
"serve": "vite preview" | |||
}, | |||
"dependencies": { | |||
"@theoryofnekomata/formxtra": "^0.2.3", | |||
"react": "^17.0.0", | |||
"react-dom": "^17.0.0" | |||
}, | |||
"devDependencies": { | |||
"@types/react": "^17.0.0", | |||
"@types/react-dom": "^17.0.0", | |||
"@vitejs/plugin-react-refresh": "^1.3.1", | |||
"typescript": "^4.3.2", | |||
"vite": "^2.4.3" | |||
} | |||
} |
@@ -1,95 +0,0 @@ | |||
import * as React from 'react' | |||
import getFormValues from '@theoryofnekomata/formxtra/dist/index'; | |||
import getNumberName from '../../core/src'; | |||
import enGB from '../../core/src/locales/variants/en-GB'; | |||
import enPH from '../../core/src/locales/variants/en-PH'; | |||
import deDE from '../../core/src/locales/variants/de-DE'; | |||
const App = () => { | |||
const [numberName, setNumberName] = React.useState('') | |||
const computeNumberName = (form: HTMLFormElement) => { | |||
const { number: x, locale: localeStr } = getFormValues(form) as { number: string, locale: string } | |||
const { [localeStr]: locale = enPH } = { | |||
'en-PH': enPH, | |||
'en-GB': enGB, | |||
'de-DE': deDE, | |||
} as Record<string, (...args: unknown[]) => string> | |||
let numberName: string | |||
try { | |||
numberName = getNumberName( | |||
x | |||
.replaceAll(' ', '') | |||
.replaceAll('_', '') | |||
.replaceAll(',', ''), | |||
{ | |||
locale, | |||
} | |||
) | |||
} catch { | |||
numberName = '' | |||
} | |||
setNumberName(numberName) | |||
} | |||
const handleSubmit: React.FormEventHandler = e => { | |||
computeNumberName(e.target as HTMLFormElement) | |||
} | |||
const handleChange: React.ChangeEventHandler<HTMLTextAreaElement | HTMLSelectElement> = e => { | |||
computeNumberName(e.target.form as HTMLFormElement) | |||
} | |||
return ( | |||
<form | |||
onSubmit={handleSubmit} | |||
> | |||
<select | |||
name="locale" | |||
className="locale" | |||
aria-label="Locale" | |||
onChange={handleChange} | |||
> | |||
<optgroup | |||
label="English (English)" | |||
> | |||
<option | |||
value="en-PH" | |||
> | |||
Philippines | |||
</option> | |||
<option | |||
value="en-GB" | |||
> | |||
United Kingdom | |||
</option> | |||
</optgroup> | |||
<optgroup | |||
label="Deutsch (German)" | |||
> | |||
<option | |||
value="de-DE" | |||
> | |||
Germany | |||
</option> | |||
</optgroup> | |||
</select> | |||
<textarea | |||
name="number" | |||
onChange={handleChange} | |||
placeholder="Enter a number, e.g. 1000, 1e+45" | |||
className="input" | |||
aria-label="Input" | |||
/> | |||
<textarea | |||
placeholder="Its name goes here" | |||
readOnly | |||
value={numberName} | |||
className="output" | |||
aria-label="Output" | |||
/> | |||
</form> | |||
) | |||
} | |||
export default App |
@@ -1,15 +0,0 @@ | |||
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/> | |||
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/> | |||
<defs> | |||
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse"> | |||
<stop stop-color="#41D1FF"/> | |||
<stop offset="1" stop-color="#BD34FE"/> | |||
</linearGradient> | |||
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse"> | |||
<stop stop-color="#FFEA83"/> | |||
<stop offset="0.0833333" stop-color="#FFDD35"/> | |||
<stop offset="1" stop-color="#FFA800"/> | |||
</linearGradient> | |||
</defs> | |||
</svg> |
@@ -1,7 +0,0 @@ | |||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"> | |||
<g fill="#61DAFB"> | |||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/> | |||
<circle cx="420.9" cy="296.5" r="45.7"/> | |||
<path d="M520.5 78.1z"/> | |||
</g> | |||
</svg> |
@@ -1,10 +0,0 @@ | |||
import React from 'react' | |||
import ReactDOM from 'react-dom' | |||
import App from './App' | |||
ReactDOM.render( | |||
<React.StrictMode> | |||
<App /> | |||
</React.StrictMode>, | |||
document.getElementById('root') | |||
) |
@@ -1 +0,0 @@ | |||
/// <reference types="vite/client" /> |
@@ -1,19 +0,0 @@ | |||
{ | |||
"compilerOptions": { | |||
"target": "ESNext", | |||
"lib": ["DOM", "DOM.Iterable", "ESNext"], | |||
"allowJs": false, | |||
"skipLibCheck": false, | |||
"esModuleInterop": false, | |||
"allowSyntheticDefaultImports": true, | |||
"strict": true, | |||
"forceConsistentCasingInFileNames": true, | |||
"module": "ESNext", | |||
"moduleResolution": "Node", | |||
"resolveJsonModule": true, | |||
"isolatedModules": true, | |||
"noEmit": true, | |||
"jsx": "react" | |||
}, | |||
"include": ["./src"] | |||
} |
@@ -1,7 +0,0 @@ | |||
import { defineConfig } from 'vite' | |||
import reactRefresh from '@vitejs/plugin-react-refresh' | |||
// https://vitejs.dev/config/ | |||
export default defineConfig({ | |||
plugins: [reactRefresh()] | |||
}) |
@@ -1,565 +0,0 @@ | |||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. | |||
# yarn lockfile v1 | |||
"@babel/code-frame@^7.14.5": | |||
version "7.14.5" | |||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" | |||
integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== | |||
dependencies: | |||
"@babel/highlight" "^7.14.5" | |||
"@babel/compat-data@^7.14.5": | |||
version "7.14.7" | |||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.7.tgz#7b047d7a3a89a67d2258dc61f604f098f1bc7e08" | |||
integrity sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw== | |||
"@babel/core@^7.14.6": | |||
version "7.14.8" | |||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.8.tgz#20cdf7c84b5d86d83fac8710a8bc605a7ba3f010" | |||
integrity sha512-/AtaeEhT6ErpDhInbXmjHcUQXH0L0TEgscfcxk1qbOvLuKCa5aZT0SOOtDKFY96/CLROwbLSKyFor6idgNaU4Q== | |||
dependencies: | |||
"@babel/code-frame" "^7.14.5" | |||
"@babel/generator" "^7.14.8" | |||
"@babel/helper-compilation-targets" "^7.14.5" | |||
"@babel/helper-module-transforms" "^7.14.8" | |||
"@babel/helpers" "^7.14.8" | |||
"@babel/parser" "^7.14.8" | |||
"@babel/template" "^7.14.5" | |||
"@babel/traverse" "^7.14.8" | |||
"@babel/types" "^7.14.8" | |||
convert-source-map "^1.7.0" | |||
debug "^4.1.0" | |||
gensync "^1.0.0-beta.2" | |||
json5 "^2.1.2" | |||
semver "^6.3.0" | |||
source-map "^0.5.0" | |||
"@babel/generator@^7.14.8": | |||
version "7.14.8" | |||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.8.tgz#bf86fd6af96cf3b74395a8ca409515f89423e070" | |||
integrity sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg== | |||
dependencies: | |||
"@babel/types" "^7.14.8" | |||
jsesc "^2.5.1" | |||
source-map "^0.5.0" | |||
"@babel/helper-compilation-targets@^7.14.5": | |||
version "7.14.5" | |||
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz#7a99c5d0967911e972fe2c3411f7d5b498498ecf" | |||
integrity sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw== | |||
dependencies: | |||
"@babel/compat-data" "^7.14.5" | |||
"@babel/helper-validator-option" "^7.14.5" | |||
browserslist "^4.16.6" | |||
semver "^6.3.0" | |||
"@babel/helper-function-name@^7.14.5": | |||
version "7.14.5" | |||
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz#89e2c474972f15d8e233b52ee8c480e2cfcd50c4" | |||
integrity sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ== | |||
dependencies: | |||
"@babel/helper-get-function-arity" "^7.14.5" | |||
"@babel/template" "^7.14.5" | |||
"@babel/types" "^7.14.5" | |||
"@babel/helper-get-function-arity@^7.14.5": | |||
version "7.14.5" | |||
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz#25fbfa579b0937eee1f3b805ece4ce398c431815" | |||
integrity sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg== | |||
dependencies: | |||
"@babel/types" "^7.14.5" | |||
"@babel/helper-hoist-variables@^7.14.5": | |||
version "7.14.5" | |||
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz#e0dd27c33a78e577d7c8884916a3e7ef1f7c7f8d" | |||
integrity sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ== | |||
dependencies: | |||
"@babel/types" "^7.14.5" | |||
"@babel/helper-member-expression-to-functions@^7.14.5": | |||
version "7.14.7" | |||
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz#97e56244beb94211fe277bd818e3a329c66f7970" | |||
integrity sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA== | |||
dependencies: | |||
"@babel/types" "^7.14.5" | |||
"@babel/helper-module-imports@^7.14.5": | |||
version "7.14.5" | |||
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz#6d1a44df6a38c957aa7c312da076429f11b422f3" | |||
integrity sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ== | |||
dependencies: | |||
"@babel/types" "^7.14.5" | |||
"@babel/helper-module-transforms@^7.14.8": | |||
version "7.14.8" | |||
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.14.8.tgz#d4279f7e3fd5f4d5d342d833af36d4dd87d7dc49" | |||
integrity sha512-RyE+NFOjXn5A9YU1dkpeBaduagTlZ0+fccnIcAGbv1KGUlReBj7utF7oEth8IdIBQPcux0DDgW5MFBH2xu9KcA== | |||
dependencies: | |||
"@babel/helper-module-imports" "^7.14.5" | |||
"@babel/helper-replace-supers" "^7.14.5" | |||
"@babel/helper-simple-access" "^7.14.8" | |||
"@babel/helper-split-export-declaration" "^7.14.5" | |||
"@babel/helper-validator-identifier" "^7.14.8" | |||
"@babel/template" "^7.14.5" | |||
"@babel/traverse" "^7.14.8" | |||
"@babel/types" "^7.14.8" | |||
"@babel/helper-optimise-call-expression@^7.14.5": | |||
version "7.14.5" | |||
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz#f27395a8619e0665b3f0364cddb41c25d71b499c" | |||
integrity sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA== | |||
dependencies: | |||
"@babel/types" "^7.14.5" | |||
"@babel/helper-plugin-utils@^7.14.5": | |||
version "7.14.5" | |||
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" | |||
integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== | |||
"@babel/helper-replace-supers@^7.14.5": | |||
version "7.14.5" | |||
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz#0ecc0b03c41cd567b4024ea016134c28414abb94" | |||
integrity sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow== | |||
dependencies: | |||
"@babel/helper-member-expression-to-functions" "^7.14.5" | |||
"@babel/helper-optimise-call-expression" "^7.14.5" | |||
"@babel/traverse" "^7.14.5" | |||
"@babel/types" "^7.14.5" | |||
"@babel/helper-simple-access@^7.14.8": | |||
version "7.14.8" | |||
resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz#82e1fec0644a7e775c74d305f212c39f8fe73924" | |||
integrity sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg== | |||
dependencies: | |||
"@babel/types" "^7.14.8" | |||
"@babel/helper-split-export-declaration@^7.14.5": | |||
version "7.14.5" | |||
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz#22b23a54ef51c2b7605d851930c1976dd0bc693a" | |||
integrity sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA== | |||
dependencies: | |||
"@babel/types" "^7.14.5" | |||
"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.8": | |||
version "7.14.8" | |||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.8.tgz#32be33a756f29e278a0d644fa08a2c9e0f88a34c" | |||
integrity sha512-ZGy6/XQjllhYQrNw/3zfWRwZCTVSiBLZ9DHVZxn9n2gip/7ab8mv2TWlKPIBk26RwedCBoWdjLmn+t9na2Gcow== | |||
"@babel/helper-validator-option@^7.14.5": | |||
version "7.14.5" | |||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" | |||
integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== | |||
"@babel/helpers@^7.14.8": | |||
version "7.14.8" | |||
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.14.8.tgz#839f88f463025886cff7f85a35297007e2da1b77" | |||
integrity sha512-ZRDmI56pnV+p1dH6d+UN6GINGz7Krps3+270qqI9UJ4wxYThfAIcI5i7j5vXC4FJ3Wap+S9qcebxeYiqn87DZw== | |||
dependencies: | |||
"@babel/template" "^7.14.5" | |||
"@babel/traverse" "^7.14.8" | |||
"@babel/types" "^7.14.8" | |||
"@babel/highlight@^7.14.5": | |||
version "7.14.5" | |||
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" | |||
integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== | |||
dependencies: | |||
"@babel/helper-validator-identifier" "^7.14.5" | |||
chalk "^2.0.0" | |||
js-tokens "^4.0.0" | |||
"@babel/parser@^7.14.5", "@babel/parser@^7.14.8": | |||
version "7.14.8" | |||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.8.tgz#66fd41666b2d7b840bd5ace7f7416d5ac60208d4" | |||
integrity sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA== | |||
"@babel/plugin-transform-react-jsx-self@^7.14.5": | |||
version "7.14.5" | |||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.14.5.tgz#703b5d1edccd342179c2a99ee8c7065c2b4403cc" | |||
integrity sha512-M/fmDX6n0cfHK/NLTcPmrfVAORKDhK8tyjDhyxlUjYyPYYO8FRWwuxBA3WBx8kWN/uBUuwGa3s/0+hQ9JIN3Tg== | |||
dependencies: | |||
"@babel/helper-plugin-utils" "^7.14.5" | |||
"@babel/plugin-transform-react-jsx-source@^7.14.5": | |||
version "7.14.5" | |||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.14.5.tgz#79f728e60e6dbd31a2b860b0bf6c9765918acf1d" | |||
integrity sha512-1TpSDnD9XR/rQ2tzunBVPThF5poaYT9GqP+of8fAtguYuI/dm2RkrMBDemsxtY0XBzvW7nXjYM0hRyKX9QYj7Q== | |||
dependencies: | |||
"@babel/helper-plugin-utils" "^7.14.5" | |||
"@babel/template@^7.14.5": | |||
version "7.14.5" | |||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4" | |||
integrity sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g== | |||
dependencies: | |||
"@babel/code-frame" "^7.14.5" | |||
"@babel/parser" "^7.14.5" | |||
"@babel/types" "^7.14.5" | |||
"@babel/traverse@^7.14.5", "@babel/traverse@^7.14.8": | |||
version "7.14.8" | |||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.8.tgz#c0253f02677c5de1a8ff9df6b0aacbec7da1a8ce" | |||
integrity sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg== | |||
dependencies: | |||
"@babel/code-frame" "^7.14.5" | |||
"@babel/generator" "^7.14.8" | |||
"@babel/helper-function-name" "^7.14.5" | |||
"@babel/helper-hoist-variables" "^7.14.5" | |||
"@babel/helper-split-export-declaration" "^7.14.5" | |||
"@babel/parser" "^7.14.8" | |||
"@babel/types" "^7.14.8" | |||
debug "^4.1.0" | |||
globals "^11.1.0" | |||
"@babel/types@^7.14.5", "@babel/types@^7.14.8": | |||
version "7.14.8" | |||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.8.tgz#38109de8fcadc06415fbd9b74df0065d4d41c728" | |||
integrity sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q== | |||
dependencies: | |||
"@babel/helper-validator-identifier" "^7.14.8" | |||
to-fast-properties "^2.0.0" | |||
"@rollup/pluginutils@^4.1.0": | |||
version "4.1.1" | |||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.1.tgz#1d4da86dd4eded15656a57d933fda2b9a08d47ec" | |||
integrity sha512-clDjivHqWGXi7u+0d2r2sBi4Ie6VLEAzWMIkvJLnDmxoOhBYOTfzGbOQBA32THHm11/LiJbd01tJUpJsbshSWQ== | |||
dependencies: | |||
estree-walker "^2.0.1" | |||
picomatch "^2.2.2" | |||
"@theoryofnekomata/formxtra@^0.2.3": | |||
version "0.2.3" | |||
resolved "https://js.pack.modal.sh/@theoryofnekomata%2fformxtra/-/formxtra-0.2.3.tgz#5ea5ddfc2ae7246ffeb4325030ead04b557d05d1" | |||
integrity sha512-TZWMG+fV3pbUz9wPgt29DlaIppa8mmGIBA8fH8Vk+idvcNVJLwJ42nQ/ZSMS/si+EZ49LaHLf0XHZS9qCHrwig== | |||
"@types/prop-types@*": | |||
version "15.7.4" | |||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" | |||
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== | |||
"@types/react-dom@^17.0.0": | |||
version "17.0.9" | |||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add" | |||
integrity sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg== | |||
dependencies: | |||
"@types/react" "*" | |||
"@types/react@*", "@types/react@^17.0.0": | |||
version "17.0.15" | |||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.15.tgz#c7533dc38025677e312606502df7656a6ea626d0" | |||
integrity sha512-uTKHDK9STXFHLaKv6IMnwp52fm0hwU+N89w/p9grdUqcFA6WuqDyPhaWopbNyE1k/VhgzmHl8pu1L4wITtmlLw== | |||
dependencies: | |||
"@types/prop-types" "*" | |||
"@types/scheduler" "*" | |||
csstype "^3.0.2" | |||
"@types/scheduler@*": | |||
version "0.16.2" | |||
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" | |||
integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== | |||
"@vitejs/plugin-react-refresh@^1.3.1": | |||
version "1.3.5" | |||
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react-refresh/-/plugin-react-refresh-1.3.5.tgz#be47e56d9965423968c8a6b2d62e5014e1e24478" | |||
integrity sha512-7c4ELQMygKw5YFCNMLhDHrt4BOgXmROP65gPax/W43mJPNQaYW8ny1kI/bvCDNuzMqZWSK8uf2tEjPVVBnZ5IQ== | |||
dependencies: | |||
"@babel/core" "^7.14.6" | |||
"@babel/plugin-transform-react-jsx-self" "^7.14.5" | |||
"@babel/plugin-transform-react-jsx-source" "^7.14.5" | |||
"@rollup/pluginutils" "^4.1.0" | |||
react-refresh "^0.10.0" | |||
ansi-styles@^3.2.1: | |||
version "3.2.1" | |||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" | |||
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== | |||
dependencies: | |||
color-convert "^1.9.0" | |||
browserslist@^4.16.6: | |||
version "4.16.6" | |||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" | |||
integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== | |||
dependencies: | |||
caniuse-lite "^1.0.30001219" | |||
colorette "^1.2.2" | |||
electron-to-chromium "^1.3.723" | |||
escalade "^3.1.1" | |||
node-releases "^1.1.71" | |||
caniuse-lite@^1.0.30001219: | |||
version "1.0.30001247" | |||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001247.tgz#105be7a8fb30cdd303275e769a9dfb87d4b3577a" | |||
integrity sha512-4rS7co+7+AoOSPRPOPUt5/GdaqZc0EsUpWk66ofE3HJTAajUK2Ss2VwoNzVN69ghg8lYYlh0an0Iy4LIHHo9UQ== | |||
chalk@^2.0.0: | |||
version "2.4.2" | |||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" | |||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== | |||
dependencies: | |||
ansi-styles "^3.2.1" | |||
escape-string-regexp "^1.0.5" | |||
supports-color "^5.3.0" | |||
color-convert@^1.9.0: | |||
version "1.9.3" | |||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" | |||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== | |||
dependencies: | |||
color-name "1.1.3" | |||
color-name@1.1.3: | |||
version "1.1.3" | |||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" | |||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= | |||
colorette@^1.2.2: | |||
version "1.2.2" | |||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" | |||
integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== | |||
convert-source-map@^1.7.0: | |||
version "1.8.0" | |||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" | |||
integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== | |||
dependencies: | |||
safe-buffer "~5.1.1" | |||
csstype@^3.0.2: | |||
version "3.0.8" | |||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" | |||
integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw== | |||
debug@^4.1.0: | |||
version "4.3.2" | |||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" | |||
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== | |||
dependencies: | |||
ms "2.1.2" | |||
electron-to-chromium@^1.3.723: | |||
version "1.3.788" | |||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.788.tgz#7a304c8ebb11d30916a1a1c1b4a9bad3983ef232" | |||
integrity sha512-dbMIpX4E4/Gk4gzOh1GYS7ls1vGsByWKpIqLviJi1mSmSt5BvrWLLtSqpFE5BaC7Ef4NnI0GMaiddNX2Brw6zA== | |||
esbuild@^0.12.8: | |||
version "0.12.16" | |||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.16.tgz#c397144ce13b445a6ead9c1f747da11f79ec5e67" | |||
integrity sha512-XqI9cXP2bmQ6MREIqrYBb13KfYFSERsV1+e5jSVWps8dNlLZK+hln7d0mznzDIpfISsg/AgQW0DW3kSInXWhrg== | |||
escalade@^3.1.1: | |||
version "3.1.1" | |||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" | |||
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== | |||
escape-string-regexp@^1.0.5: | |||
version "1.0.5" | |||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" | |||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= | |||
estree-walker@^2.0.1: | |||
version "2.0.2" | |||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" | |||
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== | |||
fsevents@~2.3.2: | |||
version "2.3.2" | |||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" | |||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== | |||
function-bind@^1.1.1: | |||
version "1.1.1" | |||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" | |||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== | |||
gensync@^1.0.0-beta.2: | |||
version "1.0.0-beta.2" | |||
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" | |||
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== | |||
globals@^11.1.0: | |||
version "11.12.0" | |||
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" | |||
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== | |||
has-flag@^3.0.0: | |||
version "3.0.0" | |||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" | |||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= | |||
has@^1.0.3: | |||
version "1.0.3" | |||
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" | |||
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== | |||
dependencies: | |||
function-bind "^1.1.1" | |||
is-core-module@^2.2.0: | |||
version "2.5.0" | |||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" | |||
integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== | |||
dependencies: | |||
has "^1.0.3" | |||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: | |||
version "4.0.0" | |||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" | |||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== | |||
jsesc@^2.5.1: | |||
version "2.5.2" | |||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" | |||
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== | |||
json5@^2.1.2: | |||
version "2.2.0" | |||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" | |||
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== | |||
dependencies: | |||
minimist "^1.2.5" | |||
loose-envify@^1.1.0: | |||
version "1.4.0" | |||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" | |||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== | |||
dependencies: | |||
js-tokens "^3.0.0 || ^4.0.0" | |||
minimist@^1.2.5: | |||
version "1.2.5" | |||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" | |||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== | |||
ms@2.1.2: | |||
version "2.1.2" | |||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" | |||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== | |||
nanoid@^3.1.23: | |||
version "3.1.23" | |||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" | |||
integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== | |||
node-releases@^1.1.71: | |||
version "1.1.73" | |||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20" | |||
integrity sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg== | |||
object-assign@^4.1.1: | |||
version "4.1.1" | |||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" | |||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= | |||
path-parse@^1.0.6: | |||
version "1.0.7" | |||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" | |||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== | |||
picomatch@^2.2.2: | |||
version "2.3.0" | |||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" | |||
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== | |||
postcss@^8.3.5: | |||
version "8.3.6" | |||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.6.tgz#2730dd76a97969f37f53b9a6096197be311cc4ea" | |||
integrity sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A== | |||
dependencies: | |||
colorette "^1.2.2" | |||
nanoid "^3.1.23" | |||
source-map-js "^0.6.2" | |||
react-dom@^17.0.0: | |||
version "17.0.2" | |||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" | |||
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== | |||
dependencies: | |||
loose-envify "^1.1.0" | |||
object-assign "^4.1.1" | |||
scheduler "^0.20.2" | |||
react-refresh@^0.10.0: | |||
version "0.10.0" | |||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3" | |||
integrity sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ== | |||
react@^17.0.0: | |||
version "17.0.2" | |||
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" | |||
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== | |||
dependencies: | |||
loose-envify "^1.1.0" | |||
object-assign "^4.1.1" | |||
resolve@^1.20.0: | |||
version "1.20.0" | |||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" | |||
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== | |||
dependencies: | |||
is-core-module "^2.2.0" | |||
path-parse "^1.0.6" | |||
rollup@^2.38.5: | |||
version "2.54.0" | |||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.54.0.tgz#99ea816e8e9b1c6af3ab957a4e7a8f78dbd87773" | |||
integrity sha512-RHzvstAVwm9A751NxWIbGPFXs3zL4qe/eYg+N7WwGtIXVLy1cK64MiU37+hXeFm1jqipK6DGgMi6Z2hhPuCC3A== | |||
optionalDependencies: | |||
fsevents "~2.3.2" | |||
safe-buffer@~5.1.1: | |||
version "5.1.2" | |||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" | |||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== | |||
scheduler@^0.20.2: | |||
version "0.20.2" | |||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" | |||
integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== | |||
dependencies: | |||
loose-envify "^1.1.0" | |||
object-assign "^4.1.1" | |||
semver@^6.3.0: | |||
version "6.3.0" | |||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" | |||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== | |||
source-map-js@^0.6.2: | |||
version "0.6.2" | |||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" | |||
integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== | |||
source-map@^0.5.0: | |||
version "0.5.7" | |||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" | |||
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= | |||
supports-color@^5.3.0: | |||
version "5.5.0" | |||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" | |||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== | |||
dependencies: | |||
has-flag "^3.0.0" | |||
to-fast-properties@^2.0.0: | |||
version "2.0.0" | |||
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" | |||
integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= | |||
typescript@^4.3.2: | |||
version "4.3.5" | |||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" | |||
integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== | |||
vite@^2.4.3: | |||
version "2.4.3" | |||
resolved "https://registry.yarnpkg.com/vite/-/vite-2.4.3.tgz#fe4aa78e9dd7d36bcb12eccbd52313b26cfadf77" | |||
integrity sha512-iT6NPeiUUZ2FkzC3eazytOEMRaM4J+xgRQcNcpRcbmfYjakCFP4WKPJpeEz1U5JEKHAtwv3ZBQketQUFhFU3ng== | |||
dependencies: | |||
esbuild "^0.12.8" | |||
postcss "^8.3.5" | |||
resolve "^1.20.0" | |||
rollup "^2.38.5" | |||
optionalDependencies: | |||
fsevents "~2.3.2" |
@@ -0,0 +1,9 @@ | |||
{ | |||
"root": true, | |||
"extends": [ | |||
"lxsmnsyc/typescript" | |||
], | |||
"parserOptions": { | |||
"project": "./tsconfig.eslint.json" | |||
} | |||
} |
@@ -0,0 +1,107 @@ | |||
# Logs | |||
logs | |||
*.log | |||
npm-debug.log* | |||
yarn-debug.log* | |||
yarn-error.log* | |||
lerna-debug.log* | |||
# Diagnostic reports (https://nodejs.org/api/report.html) | |||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json | |||
# Runtime data | |||
pids | |||
*.pid | |||
*.seed | |||
*.pid.lock | |||
# Directory for instrumented libs generated by jscoverage/JSCover | |||
lib-cov | |||
# Coverage directory used by tools like istanbul | |||
coverage | |||
*.lcov | |||
# nyc test coverage | |||
.nyc_output | |||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) | |||
.grunt | |||
# Bower dependency directory (https://bower.io/) | |||
bower_components | |||
# node-waf configuration | |||
.lock-wscript | |||
# Compiled binary addons (https://nodejs.org/api/addons.html) | |||
build/Release | |||
# Dependency directories | |||
node_modules/ | |||
jspm_packages/ | |||
# TypeScript v1 declaration files | |||
typings/ | |||
# TypeScript cache | |||
*.tsbuildinfo | |||
# Optional npm cache directory | |||
.npm | |||
# Optional eslint cache | |||
.eslintcache | |||
# Microbundle cache | |||
.rpt2_cache/ | |||
.rts2_cache_cjs/ | |||
.rts2_cache_es/ | |||
.rts2_cache_umd/ | |||
# Optional REPL history | |||
.node_repl_history | |||
# Output of 'npm pack' | |||
*.tgz | |||
# Yarn Integrity file | |||
.yarn-integrity | |||
# dotenv environment variables file | |||
.env | |||
.env.production | |||
.env.development | |||
# parcel-bundler cache (https://parceljs.org/) | |||
.cache | |||
# Next.js build output | |||
.next | |||
# Nuxt.js build / generate output | |||
.nuxt | |||
dist | |||
# Gatsby files | |||
.cache/ | |||
# Comment in the public line in if your project uses Gatsby and *not* Next.js | |||
# https://nextjs.org/blog/next-9-1#public-directory-support | |||
# public | |||
# vuepress build output | |||
.vuepress/dist | |||
# Serverless directories | |||
.serverless/ | |||
# FuseBox cache | |||
.fusebox/ | |||
# DynamoDB Local files | |||
.dynamodb/ | |||
# TernJS port file | |||
.tern-port | |||
.npmrc |
@@ -0,0 +1,71 @@ | |||
{ | |||
"name": "@modal-sh/numerica-web-api", | |||
"version": "0.0.0", | |||
"files": [ | |||
"dist", | |||
"src" | |||
], | |||
"engines": { | |||
"node": ">=12" | |||
}, | |||
"keywords": [ | |||
"pridepack" | |||
], | |||
"devDependencies": { | |||
"@types/node": "^18.14.1", | |||
"eslint": "^8.35.0", | |||
"eslint-config-lxsmnsyc": "^0.5.0", | |||
"pridepack": "2.4.4", | |||
"tslib": "^2.5.0", | |||
"typescript": "^4.9.5", | |||
"vite": "^4.3.9", | |||
"vite-tsconfig-paths": "^4.2.0", | |||
"vitest": "^0.28.1" | |||
}, | |||
"dependencies": { | |||
"@modal-sh/core": "workspace:*", | |||
"fastify": "^4.12.0" | |||
}, | |||
"scripts": { | |||
"prepublishOnly": "pridepack clean && pridepack build", | |||
"build": "pridepack build", | |||
"type-check": "pridepack check", | |||
"lint": "pridepack lint", | |||
"clean": "pridepack clean", | |||
"watch": "pridepack watch", | |||
"start": "pridepack start", | |||
"dev": "pridepack dev", | |||
"test": "vitest" | |||
}, | |||
"private": true, | |||
"description": "Web API.", | |||
"repository": { | |||
"url": "", | |||
"type": "git" | |||
}, | |||
"homepage": "", | |||
"bugs": { | |||
"url": "" | |||
}, | |||
"author": "TheoryOfNekomata <allan.crisostomo@outlook.com>", | |||
"publishConfig": { | |||
"access": "restricted" | |||
}, | |||
"types": "./dist/types/index.d.ts", | |||
"main": "./dist/cjs/production/index.js", | |||
"module": "./dist/esm/production/index.js", | |||
"exports": { | |||
".": { | |||
"development": { | |||
"require": "./dist/cjs/development/index.js", | |||
"import": "./dist/esm/development/index.js" | |||
}, | |||
"require": "./dist/cjs/production/index.js", | |||
"import": "./dist/esm/production/index.js", | |||
"types": "./dist/types/index.d.ts" | |||
} | |||
}, | |||
"typesVersions": { | |||
"*": {} | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
{ | |||
"target": "es2018" | |||
} |
@@ -0,0 +1,7 @@ | |||
export namespace meta { | |||
export const host = process.env.HOST ?? '0.0.0.0'; | |||
export const port = Number(process.env.PORT ?? 8080); | |||
export const env = process.env.NODE_ENV ?? 'development'; | |||
} |
@@ -0,0 +1,20 @@ | |||
import { createServer } from '@/server'; | |||
import { addRoutes } from '@/routes'; | |||
import * as config from '@/config'; | |||
const server = createServer({ | |||
logger: config.meta.env !== 'test', | |||
}); | |||
addRoutes(server); | |||
server.listen( | |||
{ port: config.meta.port, host: config.meta.host }, | |||
(err, address) => { | |||
if (err) { | |||
server.log.error(err.message); | |||
process.exit(1); | |||
} | |||
server.log.info(`server listening on ${address}`); | |||
}, | |||
); |
@@ -0,0 +1,60 @@ | |||
import { RouteHandlerMethod } from 'fastify'; | |||
import { | |||
AdderService, | |||
AdderServiceImpl, | |||
ArgumentOutOfRangeError, | |||
InvalidArgumentTypeError, | |||
} from '@/modules/adder/adder.service'; | |||
import { constants } from 'http2'; | |||
export interface AdderController { | |||
addNumbers: RouteHandlerMethod; | |||
subtractNumbers: RouteHandlerMethod; | |||
} | |||
export class AdderControllerImpl implements AdderController { | |||
constructor( | |||
private readonly adderService: AdderService = new AdderServiceImpl(), | |||
) { | |||
// noop | |||
} | |||
readonly addNumbers: RouteHandlerMethod = async (request, reply) => { | |||
const { a, b } = request.body as { a: number; b: number }; | |||
try { | |||
const response = this.adderService.addNumbers({ a, b }); | |||
reply.send(response); | |||
} catch (errorRaw) { | |||
if (errorRaw instanceof InvalidArgumentTypeError) { | |||
request.log.info(errorRaw); | |||
reply.status(constants.HTTP_STATUS_BAD_REQUEST).send(errorRaw.message); | |||
return; | |||
} | |||
if (errorRaw instanceof ArgumentOutOfRangeError) { | |||
reply.status(constants.HTTP_STATUS_BAD_REQUEST).send(errorRaw.message); | |||
return; | |||
} | |||
const error = errorRaw as Error; | |||
reply.status(constants.HTTP_STATUS_INTERNAL_SERVER_ERROR).send(error.message); | |||
} | |||
} | |||
readonly subtractNumbers: RouteHandlerMethod = async (request, reply) => { | |||
const { a, b } = request.body as { a: number; b: number }; | |||
try { | |||
const response = this.adderService.addNumbers({ a, b: -b }); | |||
reply.send(response); | |||
} catch (errorRaw) { | |||
if (errorRaw instanceof InvalidArgumentTypeError) { | |||
reply.status(constants.HTTP_STATUS_BAD_REQUEST).send(errorRaw.message); | |||
return; | |||
} | |||
if (errorRaw instanceof ArgumentOutOfRangeError) { | |||
reply.status(constants.HTTP_STATUS_BAD_REQUEST).send(errorRaw.message); | |||
return; | |||
} | |||
const error = errorRaw as Error; | |||
reply.status(constants.HTTP_STATUS_INTERNAL_SERVER_ERROR).send(error.message); | |||
} | |||
} | |||
} |
@@ -0,0 +1,21 @@ | |||
import { | |||
add, | |||
AddFunctionOptions, | |||
ArgumentOutOfRangeError, | |||
InvalidArgumentTypeError, | |||
} from '@modal-sh/core'; | |||
export interface AdderService { | |||
addNumbers(options: AddFunctionOptions): number; | |||
} | |||
export class AdderServiceImpl implements AdderService { | |||
addNumbers(options: AddFunctionOptions) { | |||
return add(options); | |||
} | |||
} | |||
export { | |||
ArgumentOutOfRangeError, | |||
InvalidArgumentTypeError, | |||
}; |
@@ -0,0 +1,2 @@ | |||
export * from './adder.controller'; | |||
export * from './adder.service'; |
@@ -0,0 +1,36 @@ | |||
import { FastifyInstance } from 'fastify'; | |||
import { AdderController, AdderControllerImpl } from '@/modules/adder'; | |||
export const addRoutes = (server: FastifyInstance) => { | |||
const adderController: AdderController = new AdderControllerImpl(); | |||
return server | |||
.route({ | |||
method: 'POST', | |||
url: '/add', | |||
schema: { | |||
body: { | |||
type: 'object', | |||
properties: { | |||
a: { type: 'number' }, | |||
b: { type: 'number' }, | |||
} | |||
} | |||
}, | |||
handler: adderController.addNumbers, | |||
}) | |||
.route({ | |||
method: 'POST', | |||
url: '/subtract', | |||
schema: { | |||
body: { | |||
type: 'object', | |||
properties: { | |||
a: { type: 'number' }, | |||
b: { type: 'number' }, | |||
} | |||
} | |||
}, | |||
handler: adderController.subtractNumbers, | |||
}); | |||
}; |
@@ -0,0 +1,11 @@ | |||
import fastify, { FastifyServerOptions } from 'fastify'; | |||
export interface CreateServerOptions extends FastifyServerOptions {} | |||
export const createServer = (options = {} as CreateServerOptions) => { | |||
const server = fastify(options); | |||
// TODO set up server stuff here | |||
return server; | |||
}; |
@@ -0,0 +1,87 @@ | |||
import { FastifyInstance } from 'fastify'; | |||
import { | |||
describe, | |||
it, | |||
expect, | |||
beforeAll, | |||
afterAll, | |||
vi, | |||
} from 'vitest'; | |||
import { constants } from 'http2'; | |||
import { createServer } from '../src/server'; | |||
import { addRoutes } from '../src/routes'; | |||
import { | |||
AdderServiceImpl, | |||
ArgumentOutOfRangeError, | |||
InvalidArgumentTypeError, | |||
} from '../src/modules/adder'; | |||
describe('Example', () => { | |||
let server: FastifyInstance; | |||
const body = { a: 1, b: 2 }; | |||
beforeAll(() => { | |||
server = createServer(); | |||
addRoutes(server); | |||
}); | |||
afterAll(async () => { | |||
await server.close(); | |||
}); | |||
it('returns result when successful', async () => { | |||
const response = await server | |||
.inject() | |||
.post('/') | |||
.body(body) | |||
.headers({ | |||
'Accept': 'application/json', | |||
}); | |||
expect(response.statusCode).toBe(constants.HTTP_STATUS_OK); | |||
}); | |||
it('returns error when given invalid inputs', async () => { | |||
vi.spyOn(AdderServiceImpl.prototype, 'addNumbers').mockImplementationOnce(() => { | |||
throw new InvalidArgumentTypeError('Invalid input'); | |||
}); | |||
const response = await server | |||
.inject() | |||
.post('/') | |||
.body(body) | |||
.headers({ | |||
'Accept': 'application/json', | |||
}); | |||
expect(response.statusCode).toBe(constants.HTTP_STATUS_BAD_REQUEST); | |||
}); | |||
it('returns error when given out-of-range inputs', async () => { | |||
vi.spyOn(AdderServiceImpl.prototype, 'addNumbers').mockImplementationOnce(() => { | |||
throw new ArgumentOutOfRangeError('Out of range'); | |||
}); | |||
const response = await server | |||
.inject() | |||
.post('/') | |||
.body(body) | |||
.headers({ | |||
'Accept': 'application/json', | |||
}); | |||
expect(response.statusCode).toBe(constants.HTTP_STATUS_BAD_REQUEST); | |||
}); | |||
it('returns error when an unexpected error occurs', async () => { | |||
vi.spyOn(AdderServiceImpl.prototype, 'addNumbers').mockImplementationOnce(() => { | |||
throw new Error('Unexpected error'); | |||
}); | |||
const response = await server | |||
.inject() | |||
.post('/') | |||
.body({ a: 1, b: 2 }) | |||
.headers({ | |||
'Accept': 'application/json', | |||
}); | |||
expect(response.statusCode).toBe(constants.HTTP_STATUS_INTERNAL_SERVER_ERROR); | |||
}); | |||
}); |
@@ -0,0 +1,24 @@ | |||
{ | |||
"exclude": ["node_modules"], | |||
"include": ["src", "types", "test"], | |||
"compilerOptions": { | |||
"module": "ESNext", | |||
"lib": ["DOM", "ESNext"], | |||
"importHelpers": true, | |||
"declaration": true, | |||
"sourceMap": true, | |||
"rootDir": "./", | |||
"strict": true, | |||
"noUnusedLocals": true, | |||
"noUnusedParameters": true, | |||
"noImplicitReturns": true, | |||
"noFallthroughCasesInSwitch": true, | |||
"moduleResolution": "node", | |||
"jsx": "react", | |||
"esModuleInterop": true, | |||
"target": "es2018", | |||
"paths": { | |||
"@/*": ["./src/*"], | |||
} | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
{ | |||
"exclude": ["node_modules"], | |||
"include": ["src", "types"], | |||
"compilerOptions": { | |||
"module": "ESNext", | |||
"lib": ["ESNext"], | |||
"importHelpers": true, | |||
"declaration": true, | |||
"sourceMap": true, | |||
"rootDir": "./src", | |||
"strict": true, | |||
"noUnusedLocals": true, | |||
"noUnusedParameters": true, | |||
"noImplicitReturns": true, | |||
"noFallthroughCasesInSwitch": true, | |||
"moduleResolution": "node", | |||
"jsx": "react", | |||
"esModuleInterop": true, | |||
"target": "es2018", | |||
"paths": { | |||
"@/*": ["./src/*"], | |||
} | |||
} | |||
} |
@@ -0,0 +1,6 @@ | |||
import { defineConfig } from 'vite'; | |||
import tsconfigPaths from 'vite-tsconfig-paths'; | |||
export default defineConfig({ | |||
plugins: [tsconfigPaths()], | |||
}); |
@@ -0,0 +1,2 @@ | |||
packages: | |||
- 'packages/*' |
@@ -1,17 +0,0 @@ | |||
const { default: getNumberName } = require('../packages/core') | |||
const fs = require('fs') | |||
const path = require('path') | |||
const main = async () => { | |||
const ws = fs.createWriteStream(path.resolve(__dirname, 'count-to-one-milliatillion.out.csv')) | |||
ws.write('number,name\n') | |||
for (let i = 0; i <= 3000003; i++) { | |||
if (i % 1000 === 0) { | |||
console.log(i) | |||
} | |||
ws.write(`1e+${i}` + ',' + getNumberName(`1e+${i}`) + '\n') | |||
} | |||
ws.close() | |||
} | |||
void main() |
@@ -1,17 +0,0 @@ | |||
const { default: getNumberName } = require('../packages/core') | |||
const fs = require('fs') | |||
const path = require('path') | |||
const main = async () => { | |||
const ws = fs.createWriteStream(path.resolve(__dirname, 'count-to-one-million.out.csv')) | |||
ws.write('number,name\n') | |||
for (let i = 0; i <= 1000000; i++) { | |||
if (i % 1000 === 0) { | |||
console.log(i) | |||
} | |||
ws.write(i + ',' + getNumberName(i) + '\n') | |||
} | |||
ws.close() | |||
} | |||
void main() |