Browse Source

Rewrite package

Include parse and stringify functions for American short-count locale.
master
TheoryOfNekomata 1 year ago
parent
commit
4022962dc5
77 changed files with 6798 additions and 7721 deletions
  1. +11
    -0
      .editorconfig
  2. +2
    -1
      .gitignore
  3. +0
    -24
      TODO.md
  4. +9
    -0
      packages/cli/.eslintrc
  5. +107
    -0
      packages/cli/.gitignore
  6. +73
    -0
      packages/cli/package.json
  7. +3
    -0
      packages/cli/pridepack.json
  8. +9
    -0
      packages/cli/src/cli.ts
  9. +22
    -0
      packages/cli/src/commands.ts
  10. +11
    -0
      packages/cli/src/index.ts
  11. +82
    -0
      packages/cli/src/modules/adder/adder.controller.ts
  12. +21
    -0
      packages/cli/src/modules/adder/adder.service.ts
  13. +2
    -0
      packages/cli/src/modules/adder/index.ts
  14. +337
    -0
      packages/cli/src/packages/cli-wrapper.ts
  15. +19
    -0
      packages/cli/src/packages/write-stream.ts
  16. +68
    -0
      packages/cli/test/index.test.ts
  17. +24
    -0
      packages/cli/tsconfig.eslint.json
  18. +24
    -0
      packages/cli/tsconfig.json
  19. +51
    -28
      packages/core/package.json
  20. +3
    -0
      packages/core/pridepack.json
  21. +15
    -0
      packages/core/src/common.ts
  22. +59
    -0
      packages/core/src/converter.ts
  23. +123
    -0
      packages/core/src/exponent.ts
  24. +3
    -19
      packages/core/src/index.ts
  25. +0
    -93
      packages/core/src/locales/common/de/index.ts
  26. +0
    -77
      packages/core/src/locales/common/en/index.ts
  27. +0
    -48
      packages/core/src/locales/variants/de-DE/chongo.test.ts
  28. +0
    -204
      packages/core/src/locales/variants/de-DE/groups.test.ts
  29. +0
    -113
      packages/core/src/locales/variants/de-DE/index.ts
  30. +0
    -62
      packages/core/src/locales/variants/de-DE/plurals.test.ts
  31. +0
    -48
      packages/core/src/locales/variants/en-GB/chongo.test.ts
  32. +0
    -104
      packages/core/src/locales/variants/en-GB/index.ts
  33. +0
    -34
      packages/core/src/locales/variants/en-PH/custom.test.ts
  34. +0
    -189
      packages/core/src/locales/variants/en-PH/groups.test.ts
  35. +0
    -99
      packages/core/src/locales/variants/en-PH/index.ts
  36. +0
    -213
      packages/core/src/locales/variants/en-PH/ordinals.test.ts
  37. +0
    -44
      packages/core/src/locales/variants/en-PH/technical.test.ts
  38. +553
    -0
      packages/core/src/systems/en-US.ts
  39. +1
    -0
      packages/core/src/systems/index.ts
  40. +0
    -112
      packages/core/src/utils/common/latinPowers.ts
  41. +0
    -76
      packages/core/src/utils/numeric.ts
  42. +57
    -0
      packages/core/test/exponent.test.ts
  43. +245
    -0
      packages/core/test/systems/en-US.test.ts
  44. +87
    -80
      packages/core/test/systems/en-US/chongo.test.ts
  45. +4
    -1
      packages/core/tsconfig.eslint.json
  46. +4
    -1
      packages/core/tsconfig.json
  47. +0
    -5171
      packages/core/yarn.lock
  48. +0
    -5
      packages/example/.gitignore
  49. +0
    -101
      packages/example/index.html
  50. +0
    -21
      packages/example/package.json
  51. +0
    -95
      packages/example/src/App.tsx
  52. +0
    -15
      packages/example/src/favicon.svg
  53. +0
    -7
      packages/example/src/logo.svg
  54. +0
    -10
      packages/example/src/main.tsx
  55. +0
    -1
      packages/example/src/vite-env.d.ts
  56. +0
    -19
      packages/example/tsconfig.json
  57. +0
    -7
      packages/example/vite.config.ts
  58. +0
    -565
      packages/example/yarn.lock
  59. +9
    -0
      packages/web-api/.eslintrc
  60. +107
    -0
      packages/web-api/.gitignore
  61. +71
    -0
      packages/web-api/package.json
  62. +3
    -0
      packages/web-api/pridepack.json
  63. +7
    -0
      packages/web-api/src/config.ts
  64. +20
    -0
      packages/web-api/src/index.ts
  65. +60
    -0
      packages/web-api/src/modules/adder/adder.controller.ts
  66. +21
    -0
      packages/web-api/src/modules/adder/adder.service.ts
  67. +2
    -0
      packages/web-api/src/modules/adder/index.ts
  68. +36
    -0
      packages/web-api/src/routes.ts
  69. +11
    -0
      packages/web-api/src/server.ts
  70. +87
    -0
      packages/web-api/test/index.test.ts
  71. +24
    -0
      packages/web-api/tsconfig.eslint.json
  72. +24
    -0
      packages/web-api/tsconfig.json
  73. +6
    -0
      packages/web-api/vite.config.ts
  74. +4279
    -0
      pnpm-lock.yaml
  75. +2
    -0
      pnpm-workspace.yaml
  76. +0
    -17
      scripts/count-to-one-milliamilliatillion.js
  77. +0
    -17
      scripts/count-to-one-million.js

+ 11
- 0
.editorconfig View File

@@ -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

+ 2
- 1
.gitignore View File

@@ -1,2 +1,3 @@
node_modules
.DS_Store
.idea/
scripts/*.out.*

+ 0
- 24
TODO.md View File

@@ -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`?

+ 9
- 0
packages/cli/.eslintrc View File

@@ -0,0 +1,9 @@
{
"root": true,
"extends": [
"lxsmnsyc/typescript"
],
"parserOptions": {
"project": "./tsconfig.eslint.json"
}
}

+ 107
- 0
packages/cli/.gitignore View File

@@ -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

+ 73
- 0
packages/cli/package.json View File

@@ -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"
}
}

+ 3
- 0
packages/cli/pridepack.json View File

@@ -0,0 +1,3 @@
{
"target": "es2018"
}

+ 9
- 0
packages/cli/src/cli.ts View File

@@ -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;
};

+ 22
- 0
packages/cli/src/commands.ts View File

@@ -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,
});
};

+ 11
- 0
packages/cli/src/index.ts View File

@@ -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));

+ 82
- 0
packages/cli/src/modules/adder/adder.controller.ts View File

@@ -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;
}
}

+ 21
- 0
packages/cli/src/modules/adder/adder.service.ts View File

@@ -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,
};

+ 2
- 0
packages/cli/src/modules/adder/index.ts View File

@@ -0,0 +1,2 @@
export * from './adder.controller';
export * from './adder.service';

+ 337
- 0
packages/cli/src/packages/cli-wrapper.ts View File

@@ -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;
},
};
}
}

+ 19
- 0
packages/cli/src/packages/write-stream.ts View File

@@ -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;
}
}

+ 68
- 0
packages/cli/test/index.test.ts View File

@@ -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);
});
});
});

+ 24
- 0
packages/cli/tsconfig.eslint.json View File

@@ -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/*"],
}
}
}

+ 24
- 0
packages/cli/tsconfig.json View File

@@ -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/*"],
}
}
}

+ 51
- 28
packages/core/package.json View File

@@ -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": {
"*": {}
}
}

+ 3
- 0
packages/core/pridepack.json View File

@@ -0,0 +1,3 @@
{
"target": "es2018"
}

+ 15
- 0
packages/core/src/common.ts View File

@@ -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;
}

+ 59
- 0
packages/core/src/converter.ts View File

@@ -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;
};

+ 123
- 0
packages/core/src/exponent.ts View File

@@ -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}`;
};

+ 3
- 19
packages/core/src/index.ts View File

@@ -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';

+ 0
- 93
packages/core/src/locales/common/de/index.ts View File

@@ -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('');
};

+ 0
- 77
packages/core/src/locales/common/en/index.ts View File

@@ -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(' ');
};

+ 0
- 48
packages/core/src/locales/variants/de-DE/chongo.test.ts View File

@@ -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}`);
});
});
});
});

+ 0
- 204
packages/core/src/locales/variants/de-DE/groups.test.ts View File

@@ -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);
});
});
});
});
});

+ 0
- 113
packages/core/src/locales/variants/de-DE/index.ts View File

@@ -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;

+ 0
- 62
packages/core/src/locales/variants/de-DE/plurals.test.ts View File

@@ -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);
});
});
});

+ 0
- 48
packages/core/src/locales/variants/en-GB/chongo.test.ts View File

@@ -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}`);
});
});
});
});

+ 0
- 104
packages/core/src/locales/variants/en-GB/index.ts View File

@@ -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;

+ 0
- 34
packages/core/src/locales/variants/en-PH/custom.test.ts View File

@@ -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');
},
);
});

+ 0
- 189
packages/core/src/locales/variants/en-PH/groups.test.ts View File

@@ -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);
});
});
});
});
});

+ 0
- 99
packages/core/src/locales/variants/en-PH/index.ts View File

@@ -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;

+ 0
- 213
packages/core/src/locales/variants/en-PH/ordinals.test.ts View File

@@ -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);
});
});
});
});
});

+ 0
- 44
packages/core/src/locales/variants/en-PH/technical.test.ts View File

@@ -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);
});
});
});

+ 553
- 0
packages/core/src/systems/en-US.ts View File

@@ -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}`;
};

+ 1
- 0
packages/core/src/systems/index.ts View File

@@ -0,0 +1 @@
export * as enUS from './en-US';

+ 0
- 112
packages/core/src/utils/common/latinPowers.ts View File

@@ -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;

+ 0
- 76
packages/core/src/utils/numeric.ts View File

@@ -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[],
);
};

+ 57
- 0
packages/core/test/exponent.test.ts View File

@@ -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');
});
});

+ 245
- 0
packages/core/test/systems/en-US.test.ts View File

@@ -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);
});
});

packages/core/src/locales/variants/en-PH/chongo.test.ts → packages/core/test/systems/en-US/chongo.test.ts View File

@@ -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));
});
});
});

+ 4
- 1
packages/core/tsconfig.eslint.json View File

@@ -16,6 +16,9 @@
"moduleResolution": "node",
"jsx": "react",
"esModuleInterop": true,
"target": "ES2017"
"target": "es2018",
"paths": {
"@/*": ["./src/*"],
}
}
}

+ 4
- 1
packages/core/tsconfig.json View File

@@ -16,6 +16,9 @@
"moduleResolution": "node",
"jsx": "react",
"esModuleInterop": true,
"target": "ES2017"
"target": "es2018",
"paths": {
"@/*": ["./src/*"],
}
}
}

+ 0
- 5171
packages/core/yarn.lock
File diff suppressed because it is too large
View File


+ 0
- 5
packages/example/.gitignore View File

@@ -1,5 +0,0 @@
node_modules
.DS_Store
dist
dist-ssr
*.local

+ 0
- 101
packages/example/index.html View File

@@ -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>

+ 0
- 21
packages/example/package.json View File

@@ -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"
}
}

+ 0
- 95
packages/example/src/App.tsx View File

@@ -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

+ 0
- 15
packages/example/src/favicon.svg View File

@@ -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>

+ 0
- 7
packages/example/src/logo.svg View File

@@ -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>

+ 0
- 10
packages/example/src/main.tsx View File

@@ -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')
)

+ 0
- 1
packages/example/src/vite-env.d.ts View File

@@ -1 +0,0 @@
/// <reference types="vite/client" />

+ 0
- 19
packages/example/tsconfig.json View File

@@ -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"]
}

+ 0
- 7
packages/example/vite.config.ts View File

@@ -1,7 +0,0 @@
import { defineConfig } from 'vite'
import reactRefresh from '@vitejs/plugin-react-refresh'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [reactRefresh()]
})

+ 0
- 565
packages/example/yarn.lock View File

@@ -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"

+ 9
- 0
packages/web-api/.eslintrc View File

@@ -0,0 +1,9 @@
{
"root": true,
"extends": [
"lxsmnsyc/typescript"
],
"parserOptions": {
"project": "./tsconfig.eslint.json"
}
}

+ 107
- 0
packages/web-api/.gitignore View File

@@ -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

+ 71
- 0
packages/web-api/package.json View File

@@ -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": {
"*": {}
}
}

+ 3
- 0
packages/web-api/pridepack.json View File

@@ -0,0 +1,3 @@
{
"target": "es2018"
}

+ 7
- 0
packages/web-api/src/config.ts View File

@@ -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';
}

+ 20
- 0
packages/web-api/src/index.ts View File

@@ -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}`);
},
);

+ 60
- 0
packages/web-api/src/modules/adder/adder.controller.ts View File

@@ -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);
}
}
}

+ 21
- 0
packages/web-api/src/modules/adder/adder.service.ts View File

@@ -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,
};

+ 2
- 0
packages/web-api/src/modules/adder/index.ts View File

@@ -0,0 +1,2 @@
export * from './adder.controller';
export * from './adder.service';

+ 36
- 0
packages/web-api/src/routes.ts View File

@@ -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,
});
};

+ 11
- 0
packages/web-api/src/server.ts View File

@@ -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;
};

+ 87
- 0
packages/web-api/test/index.test.ts View File

@@ -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);
});
});

+ 24
- 0
packages/web-api/tsconfig.eslint.json View File

@@ -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/*"],
}
}
}

+ 24
- 0
packages/web-api/tsconfig.json View File

@@ -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/*"],
}
}
}

+ 6
- 0
packages/web-api/vite.config.ts View File

@@ -0,0 +1,6 @@
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';

export default defineConfig({
plugins: [tsconfigPaths()],
});

+ 4279
- 0
pnpm-lock.yaml
File diff suppressed because it is too large
View File


+ 2
- 0
pnpm-workspace.yaml View File

@@ -0,0 +1,2 @@
packages:
- 'packages/*'

+ 0
- 17
scripts/count-to-one-milliamilliatillion.js View File

@@ -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()

+ 0
- 17
scripts/count-to-one-million.js View File

@@ -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()

Loading…
Cancel
Save