diff --git a/changelog.md b/changelog.md
index 90524b6..dce6c5e 100644
--- a/changelog.md
+++ b/changelog.md
@@ -4,6 +4,21 @@ This file contains the documentation on the notable changes and bug fixes, and
is formatted following this [standard](https://keepachangelog.com/en/1.0.0/).
This project also adheres to [Semantic Versioning](https://semver.org/).
+## [2.2.0] - 2026-02-22
+
+**Added**:
+
+- Added support for mononyms (enabled via `Config.mono` flag); only available only for
+ `Namefully(string | string[] | Names[])` or `NameBuilder`.
+- Updated FullName parsing to handle new JsonName structure (now hierarchical)
+- Added more practical examples
+
+**Fixed**:
+
+- Refactored deserialization logic to accommodate flexible name formats
+- Adjusted error handling for invalid name inputs
+- Fixed deserialization issue when casting separators
+
## [2.1.0] - 2025-12-30
**Added**:
@@ -204,6 +219,8 @@ This project also adheres to [Semantic Versioning](https://semver.org/).
Initial version
+[2.2.0]: https://github.com/ralflorent/namefully/compare/v2.1.0...v2.2.0
+[2.1.0]: https://github.com/ralflorent/namefully/compare/v2.0.2...v2.1.0
[2.0.2]: https://github.com/ralflorent/namefully/compare/v2.0.1...v2.0.2
[2.0.1]: https://github.com/ralflorent/namefully/compare/v2.0.0...v2.0.1
[2.0.0]: https://github.com/ralflorent/namefully/compare/v1.3.0...v2.0.0
diff --git a/example/advanced.ts b/example/advanced.ts
index ed0ba41..49a5fe4 100644
--- a/example/advanced.ts
+++ b/example/advanced.ts
@@ -1,4 +1,4 @@
-import { Name, NameBuilder, Title } from '../src/index';
+import { Name, NameBuilder } from 'namefully';
function main() {
const builder = NameBuilder.of(Name.first('Nikola'), Name.last('Tesla'));
@@ -11,7 +11,7 @@ function main() {
builder.add(Name.prefix('Mr'));
// Build the name with options if needed
- name = builder.build({ title: Title.US });
+ name = builder.build({ title: 'US' });
console.log(name.full); // Mr. Nikola Tesla
}
diff --git a/example/index.html b/example/index.html
new file mode 100644
index 0000000..5846f97
--- /dev/null
+++ b/example/index.html
@@ -0,0 +1,82 @@
+
+
+
+
+ Namefully | Demo
+
+
+
+
+
+
+
How to use namefully
+
Simple case
+
Given the name John Smith, use only the first name:
+
+
+
+
It can also do that
+
+ Let us say you want to distinguish the name parts of a full name like this
+ Mr,Smith,John,Ben,Ph.D (comma-separated values), you can achieve the following:
+
+
+
+
+
+
diff --git a/example/main.ts b/example/main.ts
index 267f3bf..93ea8a7 100644
--- a/example/main.ts
+++ b/example/main.ts
@@ -1,4 +1,4 @@
-import namefully from '../src/index';
+import namefully from 'namefully';
function main() {
// Gives a simple name some super power.
diff --git a/example/mononym.ts b/example/mononym.ts
new file mode 100644
index 0000000..f7dbf02
--- /dev/null
+++ b/example/mononym.ts
@@ -0,0 +1,17 @@
+import namefully from 'namefully';
+
+function main() {
+ const name = namefully('Plato', { mono: true });
+ console.log(name.length); // 5
+ console.log(name.first); // Plato
+ console.log(name.middle); // undefined
+ console.log(name.last); //
+ console.log(name.public); // Plato
+ console.log(name.initials()); // ['P']
+ console.log(name.format('L, f m')); // , Plato
+ console.log(name.shorten()); // Plato
+ console.log(name.zip()); // P.
+ console.log(name.toUpperCase()); // PLATO
+}
+
+main();
diff --git a/example/parser.ts b/example/parser.ts
new file mode 100644
index 0000000..869a6fd
--- /dev/null
+++ b/example/parser.ts
@@ -0,0 +1,28 @@
+import namefully, { Config, FullName, Parser } from 'namefully';
+
+class CustomParser extends Parser {
+ constructor(
+ raw: string,
+ public separator = '|',
+ ) {
+ super(raw);
+ }
+
+ parse(options: Partial): FullName {
+ const [fn, ln] = this.raw.split(this.separator, 2);
+ return new FullName(options).setFirstName(fn.trim()).setLastName(ln.trim());
+ }
+}
+
+function main() {
+ const name = namefully(new CustomParser('Juan | García | Jr.'));
+ console.log(name.full); // Juan García
+ console.log(name.first); // Juan
+ console.log(name.initials()); // ['J', 'G']
+ console.log(name.format('L, f m')); // García, Juan
+ console.log(name.size); // 2
+ console.log(name.zip()); // Juan G.
+ console.log(name.toUpperCase()); // JUAN GARCÍA
+}
+
+main();
diff --git a/jsr.json b/jsr.json
index 41949a1..6f7dab4 100644
--- a/jsr.json
+++ b/jsr.json
@@ -1,6 +1,6 @@
{
"name": "@ralflorent/namefully",
- "version": "2.1.0",
+ "version": "2.2.0",
"description": "Handle personal names in a particular order, way, or shape.",
"keywords": ["format", "parse", "human", "personal", "family", "name"],
"exports": "./src/index.ts",
diff --git a/package.json b/package.json
index 9d2ad3d..97c31f6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "namefully",
- "version": "2.1.0",
+ "version": "2.2.0",
"description": "Handle personal names in a particular order, way, or shape.",
"author": "Ralph Florent",
"license": "MIT",
@@ -30,7 +30,7 @@
"format": "prettier --write src example",
"lint": "eslint src",
"test": "jest",
- "test:cov": "jest --collectCoverage",
+ "test:cov": "jest --coverage",
"prebuild": "shx rm -rf dist",
"build:esm": "tsc -p tsconfig.json",
"build:cjs": "tsc -p tsconfig.cjs.json",
diff --git a/readme.md b/readme.md
index 03b1d5f..96321e5 100644
--- a/readme.md
+++ b/readme.md
@@ -4,6 +4,7 @@
[![JSR Version][jsr-version]][jsr-url]
[![CI build][ci-img]][ci-url]
[![MIT License][license-img]][license-url]
+[![DeepWiki][deepwiki-img]][deepwiki-url]
Human name handling made easy.
[Try it live](https://stackblitz.com/edit/namefully).
@@ -204,6 +205,24 @@ const name = new Namefully(
);
```
+### mono
+
+`boolean | Namon` - default: `false`
+
+Enables support for mononyms (i.e., single word names). You may also use `Namon`
+to assign which name type is being used to represent the mononym.
+
+> You should know that this goes against the original design philosophy of the library,
+> which is intentionally opinionated around shaping and organizing multiple name components.
+> Treating a single token as a "full" name makes a lot of the existing API semantics
+> somewhat meaningless.
+
+```ts
+const name = new Namefully('Plato', { mono: true }); // throws an exception without this flag.
+console.log(name.full); // Plato
+console.log(name.initials()); // ['P']
+```
+
To sum it all up, the default values are:
```ts
@@ -213,7 +232,8 @@ To sum it all up, the default values are:
title: Title.UK,
ending: false,
bypass: true,
- surname: Surname.FATHER
+ surname: Surname.FATHER,
+ mono: false
}
```
@@ -227,8 +247,10 @@ import { Config, FullName, Namefully, Parser } from 'namefully';
// Suppose you want to cover this '#' separator
class SimpleParser extends Parser {
parse(options: Partial): FullName {
- const [firstName, lastName] = this.raw.split('#');
- return FullName.parse({ firstName, lastName }, Config.merge(options));
+ const [fn, ln] = this.raw.split('#', 2);
+ return new FullName(options)
+ .setFirstName(fn.trim())
+ .setLastName(ln.trim());
}
}
@@ -303,7 +325,7 @@ So, this utility understands the name parts as follows:
`namefully` does not support certain use cases:
-- mononame: `Plato` - a workaround is to set the mononame as both first and last name;
+- mononame: `Plato` - enable the mononym flag `Config.mono` to support this.
- nickname: `Dwayne "The Rock" Johnson` - use custom parser instead.
- multiple prefixes or suffixes: `Prof. Dr. Einstein`.
@@ -326,6 +348,8 @@ The underlying content of this utility is licensed under [MIT License][license-u
[ci-url]: https://github.com/ralflorent/namefully/actions/workflows/ci.yml
[license-img]: https://img.shields.io/npm/l/namefully
[license-url]: https://opensource.org/licenses/MIT
+[deepwiki-img]: https://deepwiki.com/badge.svg
+[deepwiki-url]: https://deepwiki.com/ralflorent/namefully
[contributing-url]: https://github.com/ralflorent/namefully/blob/main/CONTRIBUTING.md
[examples]: https://github.com/ralflorent/namefully/tree/main/example
diff --git a/src/builder.ts b/src/builder.ts
index 66f5750..8fae278 100644
--- a/src/builder.ts
+++ b/src/builder.ts
@@ -145,9 +145,10 @@ export class NameBuilder extends Builder {
this.prebuild?.();
const names = [...this.queue];
- ArrayNameValidator.create().validate(names);
+ const config = Config.merge(options as Partial);
+ if (!config.mono) ArrayNameValidator.create().validate(names);
- this.instance = new Namefully(names, options);
+ this.instance = new Namefully(names, config);
this.postbuild?.(this.instance);
return this.instance;
diff --git a/src/config.ts b/src/config.ts
index e882ea8..c3e04c0 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -1,4 +1,4 @@
-import { NameOrder, Separator, Title, Surname } from './types.js';
+import { NameOrder, Namon, Separator, Title, Surname } from './types.js';
const defaultName = 'default';
const copyAlias = '_copy';
@@ -39,6 +39,7 @@ export class Config {
#ending: boolean;
#bypass: boolean;
#surname: Surname;
+ #mono: boolean | Namon;
/** Cache for multiple instances. */
private static cache = new Map();
@@ -91,6 +92,11 @@ export class Config {
return this.#surname;
}
+ /** Whether to parse a single word name as a mononym. */
+ get mono(): boolean | Namon {
+ return this.#mono;
+ }
+
/** The name of the cached configuration. */
get name(): string {
return this.#name;
@@ -104,6 +110,7 @@ export class Config {
ending = false,
bypass = true,
surname = Surname.FATHER,
+ mono: boolean | Namon = false,
) {
this.#name = name;
this.#orderedBy = orderedBy;
@@ -112,6 +119,7 @@ export class Config {
this.#ending = ending;
this.#bypass = bypass;
this.#surname = surname;
+ this.#mono = mono;
}
/**
@@ -139,6 +147,7 @@ export class Config {
config.#ending = other.ending ?? config.ending;
config.#bypass = other.bypass ?? config.bypass;
config.#surname = other.surname ?? config.surname;
+ config.#mono = other.mono ?? config.mono;
return config;
}
}
@@ -153,7 +162,7 @@ export class Config {
* be named `default_copy`.
*/
copyWith(options: Partial = {}): Config {
- const { name, orderedBy, separator, title, ending, bypass, surname } = options;
+ const { name, orderedBy, separator, title, ending, bypass, surname, mono } = options;
const config = Config.create(this.#genNewName(name ?? this.name + copyAlias));
config.#orderedBy = orderedBy ?? this.orderedBy;
config.#separator = separator ?? this.separator;
@@ -161,6 +170,7 @@ export class Config {
config.#ending = ending ?? this.ending;
config.#bypass = bypass ?? this.bypass;
config.#surname = surname ?? this.surname;
+ config.#mono = mono ?? this.mono;
return config;
}
@@ -177,21 +187,11 @@ export class Config {
this.#ending = false;
this.#bypass = true;
this.#surname = Surname.FATHER;
+ this.#mono = false;
Config.cache.set(this.name, this);
}
- /**
- * Alters the name order between the first and last name, and rearrange the
- * order of appearance of a name set.
- * @deprecated use `update()` method instead.
- */
- updateOrder(orderedBy: NameOrder): void {
- this.update({ orderedBy });
- }
-
- /**
- * Allows the possibility to alter some options after creating a name set.
- */
+ /** Allows the possibility to alter behavior-related options after creating a name set. */
update({ orderedBy, title, ending }: Partial>): void {
const config = Config.cache.get(this.name);
if (!config) return;
diff --git a/src/constants.ts b/src/constants.ts
index fe7d988..705e08c 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -1,4 +1,5 @@
-export const VERSION = '2.1.0';
+export const VERSION = '2.2.0';
export const MIN_NUMBER_OF_NAME_PARTS = 2;
export const MAX_NUMBER_OF_NAME_PARTS = 5;
export const ALLOWED_FORMAT_TOKENS = ` .,_-()[]<>'"bBfFlLmMnNoOpPsS$`;
+export const ZERO_WIDTH_SPACE = String.fromCharCode(8203);
diff --git a/src/data.spec.ts b/src/data.spec.ts
index 400bec6..50dc25f 100644
--- a/src/data.spec.ts
+++ b/src/data.spec.ts
@@ -2,6 +2,7 @@ import { Namefully } from './namefully.js';
import { InputError, UnknownError } from './error.js';
import { Name, FirstName, LastName } from './name.js';
import { deserialize, SerializedName } from './data.js';
+import { Separator, Title, Surname, NameOrder } from './types.js';
describe('JSON serialization', () => {
describe('serialize', () => {
@@ -19,6 +20,7 @@ describe('JSON serialization', () => {
ending: expect.any(Boolean),
bypass: expect.any(Boolean),
surname: expect.any(String),
+ mono: expect.any(Boolean),
}),
});
});
@@ -82,27 +84,41 @@ describe('JSON serialization', () => {
});
describe('deserialize', () => {
- test('should deserialize a simple name from object', () => {
- const data: SerializedName = {
- names: {
- firstName: 'John',
- lastName: 'Smith',
- },
- config: {
- name: 'fullName',
- orderedBy: 'firstName',
- separator: ' ',
- title: 'US',
- ending: false,
- bypass: false,
- surname: 'father',
- },
+ let defaultConfig: SerializedName['config'];
+
+ beforeEach(() => {
+ defaultConfig = {
+ name: 'defaultConfig',
+ orderedBy: 'firstName',
+ separator: ' ',
+ title: 'US',
+ ending: false,
+ bypass: true,
+ surname: 'father',
+ mono: false,
};
+ });
- const name = deserialize(data);
- expect(name.firstName()).toBe('John');
- expect(name.lastName()).toBe('Smith');
+ test('should deserialize a simple name from object', () => {
+ const name = deserialize({
+ names: { firstName: 'John', lastName: 'Smith' },
+ config: { ...defaultConfig, name: 'fullName', bypass: false, surname: 'all' },
+ });
+ expect(name.first).toBe('John');
+ expect(name.last).toBe('Smith');
expect(name.full).toBe('John Smith');
+ expect(name.middle).toBeUndefined();
+ expect(name.prefix).toBeUndefined();
+ expect(name.suffix).toBeUndefined();
+
+ expect(name.config.name).toBe('fullName');
+ expect(name.config.orderedBy).toBe('firstName');
+ expect(name.config.separator).toBe(Separator.SPACE);
+ expect(name.config.title).toBe(Title.US);
+ expect(name.config.ending).toBe(false);
+ expect(name.config.bypass).toBe(false);
+ expect(name.config.surname).toBe(Surname.ALL);
+ expect(name.config.mono).toBe(false);
});
test('should deserialize a name from JSON string', () => {
@@ -110,80 +126,67 @@ describe('JSON serialization', () => {
names: {
firstName: 'Jane',
lastName: 'Doe',
+ suffix: 'Ph.D',
},
config: {
- name: 'fullName',
- orderedBy: 'firstName',
- separator: ' ',
- title: 'US',
- ending: false,
- bypass: false,
+ name: 'byLastName',
+ orderedBy: 'lastName',
+ separator: ',',
+ title: 'UK',
+ ending: true,
+ bypass: true,
surname: 'father',
},
});
const name = deserialize(jsonString);
- expect(name.firstName()).toBe('Jane');
- expect(name.lastName()).toBe('Doe');
+ expect(name.first).toBe('Jane');
+ expect(name.last).toBe('Doe');
+ expect(name.full).toBe('Doe Jane, Ph.D');
+ expect(name.middle).toBeUndefined();
+ expect(name.prefix).toBeUndefined();
+ expect(name.suffix).toBe('Ph.D');
+
+ expect(name.config.name).toBe('byLastName');
+ expect(name.config.orderedBy).toBe(NameOrder.LAST_NAME);
+ expect(name.config.separator).toBe(Separator.COMMA);
+ expect(name.config.title).toBe(Title.UK);
+ expect(name.config.ending).toBe(true);
+ expect(name.config.bypass).toBe(true);
+ expect(name.config.surname).toBe(Surname.FATHER);
});
test('should deserialize a name with prefix and suffix', () => {
- const original = new Namefully([Name.prefix('Mr'), Name.first('John'), Name.last('Smith'), Name.suffix('Jr')]);
- const serialized = original.serialize();
+ const name = deserialize({
+ names: { prefix: 'Mr', firstName: 'John', lastName: 'Smith', suffix: 'Jr' },
+ config: defaultConfig,
+ });
- const name = deserialize(serialized);
- expect(name.prefix).toBe(original.prefix);
- expect(name.suffix).toBe(original.suffix);
+ expect(name.prefix).toBe('Mr.');
+ expect(name.first).toBe('John');
+ expect(name.last).toBe('Smith');
+ expect(name.suffix).toBe('Jr');
});
test('should deserialize a name with middle names', () => {
- const data: SerializedName = {
- names: {
- firstName: 'John',
- middleName: ['Michael', 'David'],
- lastName: 'Smith',
- },
- config: {
- name: 'fullName',
- orderedBy: 'firstName',
- separator: ' ',
- title: 'US',
- ending: false,
- bypass: false,
- surname: 'father',
- },
- };
-
- const name = deserialize(data);
+ const name = deserialize({
+ names: { firstName: 'John', middleName: ['Michael', 'David'], lastName: 'Smith' },
+ config: defaultConfig,
+ });
expect(name.middleName()).toEqual(['Michael', 'David']);
});
test('should deserialize a name with multiple first names', () => {
- const data: SerializedName = {
- names: {
- firstName: {
- value: 'John',
- more: ['Michael'],
- },
- lastName: 'Smith',
- },
- config: {
- name: 'fullName',
- orderedBy: 'firstName',
- separator: ' ',
- title: 'US',
- ending: false,
- bypass: false,
- surname: 'father',
- },
- };
-
- const name = deserialize(data);
+ const name = deserialize({
+ names: { firstName: { value: 'John', more: ['Michael'] }, lastName: 'Smith' },
+ config: defaultConfig,
+ });
expect(name.firstName(false)).toBe('John');
+ expect(name.firstName(true)).toEqual('John Michael');
});
test('should deserialize a name with hyphenated last name', () => {
- const data: SerializedName = {
+ const name = deserialize({
names: {
firstName: 'John',
lastName: {
@@ -191,19 +194,11 @@ describe('JSON serialization', () => {
mother: 'Jones',
},
},
- config: {
- name: 'fullName',
- orderedBy: 'firstName',
- separator: ' ',
- title: 'US',
- ending: false,
- bypass: false,
- surname: 'father',
- },
- };
-
- const name = deserialize(data);
- expect(name.lastName()).toBe('Smith');
+ config: { ...defaultConfig, surname: 'hyphenated' },
+ });
+ expect(name.first).toBe('John');
+ expect(name.last).toBe('Smith-Jones');
+ expect(name.full).toBe('John Smith-Jones');
});
test('should throw NameError for invalid data', () => {
@@ -254,5 +249,14 @@ describe('JSON serialization', () => {
expect(deserialized.full).toBe(original.full);
});
+
+ test('should serialize and deserialize mononyms', () => {
+ const original = new Namefully('Plato', { mono: true });
+ const serialized = original.serialize();
+ const deserialized = deserialize(serialized);
+
+ expect(deserialized.first).toBe(original.first);
+ expect(deserialized.last).toBe(original.last);
+ });
});
});
diff --git a/src/data.ts b/src/data.ts
index 4b04f5b..d055395 100644
--- a/src/data.ts
+++ b/src/data.ts
@@ -1,18 +1,13 @@
import { NameBuilder } from './builder.js';
-import { Name, FirstName, LastName } from './name.js';
+import { Namon, Separator } from './types.js';
+import { Name, FirstName, LastName, JsonName } from './name.js';
import { type Namefully, NameOptions } from './namefully.js';
import { InputError, NameError, UnknownError } from './error.js';
/** Serialized representation of a Namefully instance. */
export interface SerializedName {
/** The name data (with its hierarchy intact). */
- names: {
- prefix?: string;
- firstName: string | { value: string; more?: string[] };
- middleName?: string[];
- lastName: string | { father: string; mother?: string };
- suffix?: string;
- };
+ names: JsonName;
/** The configuration data. */
config: {
name: string;
@@ -22,6 +17,7 @@ export interface SerializedName {
ending: boolean;
bypass: boolean;
surname: string;
+ mono: boolean | string;
};
}
@@ -53,12 +49,14 @@ export function deserialize(data: SerializedName | string): Namefully {
if (px) builder.add(Name.prefix(px));
if (sx) builder.add(Name.suffix(sx));
- if (mn) builder.add(...mn.map((n) => Name.middle(n)));
+ if (mn) builder.add(...(typeof mn === 'string' ? [Name.middle(mn)] : mn.map((n) => Name.middle(n))));
builder.add(typeof fn === 'string' ? Name.first(fn) : new FirstName(fn.value, ...(fn.more ?? [])));
builder.add(typeof ln === 'string' ? Name.last(ln) : new LastName(ln.father, ln.mother));
- return builder.build(config as unknown as NameOptions);
+ const separator = Separator.cast(config.separator);
+ const mono = typeof config.mono === 'string' ? (Namon.cast(config.mono) ?? false) : config.mono;
+ return builder.build({ ...config, separator, mono } as NameOptions);
} catch (error) {
if (error instanceof NameError) throw error;
diff --git a/src/error.spec.ts b/src/error.spec.ts
index ef72376..204a7a2 100644
--- a/src/error.spec.ts
+++ b/src/error.spec.ts
@@ -111,6 +111,10 @@ describe('InputError', () => {
test('is thrown if the wrong argument is provided for a last name', () => {
expect(() => Validators.lastName.validate({} as LastName));
});
+
+ test('is thrown for unknown input types', () => {
+ expect(() => new Namefully(class {} as never)).toThrow(Errors.InputError);
+ });
});
describe('NotAllowedError', () => {
diff --git a/src/fullname.spec.ts b/src/fullname.spec.ts
index d0259dc..d47b802 100644
--- a/src/fullname.spec.ts
+++ b/src/fullname.spec.ts
@@ -1,8 +1,8 @@
import { FirstName, LastName, Name } from './name.js';
+import { NameError, ValidationError } from './error.js';
import { FullName } from './fullname.js';
import { Config } from './config.js';
import { Namon } from './types.js';
-import { NameError } from './error.js';
describe('FullName', () => {
let prefix: Name;
@@ -31,6 +31,22 @@ describe('FullName', () => {
});
runExpectations(fullName);
+
+ fullName = FullName.parse({
+ prefix: 'Mr',
+ firstName: { value: 'John', more: ['David'] },
+ middleName: 'Michael',
+ lastName: { father: 'Smith', mother: 'Jones' },
+ suffix: 'Ph.D',
+ });
+
+ expect(fullName.prefix?.toString()).toBe('Mr');
+ expect(fullName.firstName.toString()).toBe('John');
+ expect(fullName.firstName.toString(true)).toBe('John David');
+ expect(fullName.middleName.map((n) => n.toString()).join(' ')).toBe('Michael');
+ expect(fullName.lastName.toString()).toBe('Smith');
+ expect(fullName.lastName.toString('all')).toBe('Smith Jones');
+ expect(fullName.suffix?.toString()).toBe('Ph.D');
});
test('builds a full name as it goes', () => {
@@ -44,17 +60,19 @@ describe('FullName', () => {
runExpectations(fullName);
});
- test('builds a full name with no validation rules', () => {
- fullName = new FullName(Config.merge({ name: 'withBypass', bypass: true }))
- .setFirstName(new FirstName('2Pac'))
- .setLastName(new LastName('Shakur'));
-
- expect(fullName.firstName).toBeInstanceOf(FirstName);
- expect(fullName.lastName).toBeInstanceOf(LastName);
- expect(fullName.firstName.toString()).toBe('2Pac');
- expect(fullName.lastName.toString()).toBe('Shakur');
- expect(fullName.config).toBeDefined();
- expect(fullName.config.name).toBe('withBypass');
+ test('enforces validation rules if needed when building a full name', () => {
+ try {
+ fullName = new FullName(Config.merge({ name: 'noBypass', bypass: false }))
+ .setFirstName(new FirstName('2Pac')) // name with digits is invalid
+ .setLastName(new LastName('Shakur'));
+
+ fail('Expected NameError to be thrown');
+ } catch (error: unknown) {
+ expect(error).toBeInstanceOf(NameError);
+ expect((error as NameError).name).toContain('ValidationError');
+ expect((error as NameError).source).toContain('2Pac');
+ expect((error as ValidationError).nameType).toBe('firstName');
+ }
});
test('creates a full name as it goes from raw strings', () => {
diff --git a/src/fullname.ts b/src/fullname.ts
index 0290452..6f6d118 100644
--- a/src/fullname.ts
+++ b/src/fullname.ts
@@ -1,5 +1,6 @@
import { Config } from './config.js';
import { Validators } from './validator.js';
+import { ZERO_WIDTH_SPACE } from './constants.js';
import { Nullable, Namon, Title } from './types.js';
import { NameError, UnknownError } from './error.js';
import { FirstName, LastName, Name, JsonName } from './name.js';
@@ -66,6 +67,11 @@ export class FullName {
return this.#suffix;
}
+ /** Whether the full name is a single word name. */
+ get isMono(): boolean {
+ return this instanceof Mononym;
+ }
+
/**
* Parses a JSON name into a full name.
* @param {JsonName} json parsable name element
@@ -73,12 +79,13 @@ export class FullName {
*/
static parse(json: JsonName, config?: Config): FullName {
try {
+ const { prefix, firstName: fn, middleName: mn, lastName: ln, suffix } = json;
return new FullName(config)
- .setPrefix(json.prefix)
- .setFirstName(json.firstName)
- .setMiddleName(json.middleName ?? [])
- .setLastName(json.lastName)
- .setSuffix(json.suffix);
+ .setPrefix(prefix)
+ .setFirstName(typeof fn === 'string' ? fn : new FirstName(fn.value, ...(fn.more ?? [])))
+ .setMiddleName(typeof mn === 'string' ? [mn] : (mn ?? []))
+ .setLastName(typeof ln === 'string' ? ln : new LastName(ln.father, ln.mother))
+ .setSuffix(suffix);
} catch (error) {
if (error instanceof NameError) throw error;
@@ -94,7 +101,7 @@ export class FullName {
if (!name) return this;
if (!this.#config.bypass) Validators.prefix.validate(name);
const prefix = name instanceof Name ? name.value : name;
- this.#prefix = Name.prefix(this.#config.title === Title.US ? `${prefix}.` : prefix);
+ this.#prefix = Name.prefix(this.#config.title === Title.US && !prefix.endsWith('.') ? `${prefix}.` : prefix);
return this;
}
@@ -133,6 +140,11 @@ export class FullName {
return namon.equal(Namon.MIDDLE_NAME) ? this.#middleName.length > 0 : true;
}
+ toString(): string {
+ if (this.isMono) return (this as unknown as Mononym).value;
+ return Array.from(this.toIterable(true)).join(' ');
+ }
+
/** Returns an `Iterable` of existing `Name`s. */
*toIterable(flat: boolean = false): Iterable {
if (this.#prefix) yield this.#prefix;
@@ -153,3 +165,61 @@ export class FullName {
yield* this.toIterable(true);
}
}
+
+/**
+ * A single word name or mononym.
+ *
+ * This is a special case of `FullName` that is used to represent mononyms. This contradicts
+ * the original purpose of this library such as shaping and organizing name pieces accordingly.
+ *
+ * When enabled via `Config.mono`, this becomes the full name of a human. And as a single name,
+ * most of the `Namefully` methods become irrelevant.
+ */
+export class Mononym extends FullName {
+ readonly #namon!: string;
+ #type!: Namon;
+
+ /**
+ * Constructs a mononym from a piece of string.
+ * @param {string | Name} name to be used to construct the mononym.
+ */
+ constructor(name: string | Name, options?: Partial) {
+ super(options ?? { name: 'mononym', mono: true });
+ this.#namon = name.toString();
+ this.type = name instanceof Name ? name.type : Namon.FIRST_NAME;
+ }
+
+ /**
+ * Re-assigns which name type is being used to represent the mononym.
+ *
+ * Ideally, this doesn't really matter as the mononym is always a single piece of name.
+ * When used as `string`, it must be a valid `Namon` type or else it will default to
+ * `Namon.FIRST_NAME`.
+ * @param {string | Namon} type of name to use.
+ */
+ set type(type: string | Namon) {
+ this.#type = typeof type === 'string' ? (Namon.cast(type) ?? Namon.FIRST_NAME) : type;
+ this.#build(this.#namon);
+ }
+
+ /** The type of name being used to represent the mononym. */
+ get type(): Namon {
+ return this.#type;
+ }
+
+ /** The piece of string treated as a name. */
+ get value(): string {
+ return this.#namon;
+ }
+
+ #build(name: string): void {
+ this.setFirstName(ZERO_WIDTH_SPACE).setLastName(ZERO_WIDTH_SPACE).setMiddleName([]).setPrefix(null).setSuffix(null);
+
+ if (this.#type.equal(Namon.FIRST_NAME)) this.setFirstName(name);
+ else if (this.#type.equal(Namon.LAST_NAME)) this.setLastName(name);
+ else if (this.#type.equal(Namon.MIDDLE_NAME)) this.setMiddleName([name]);
+ else if (this.#type.equal(Namon.PREFIX)) this.setPrefix(name);
+ else if (this.#type.equal(Namon.SUFFIX)) this.setSuffix(name);
+ else throw new NameError(name, 'invalid mononym type');
+ }
+}
diff --git a/src/name.ts b/src/name.ts
index abc9a62..0d5c9d7 100644
--- a/src/name.ts
+++ b/src/name.ts
@@ -296,8 +296,8 @@ export function isNameArray(value?: unknown): value is Name[] {
/** JSON signature for `FullName` data. */
export interface JsonName {
prefix?: string;
- firstName: string;
- middleName?: string[];
- lastName: string;
+ firstName: string | { value: string; more?: string[] };
+ middleName?: string | string[];
+ lastName: string | { father: string; mother?: string };
suffix?: string;
}
diff --git a/src/namefully.spec.ts b/src/namefully.spec.ts
index 74d9e65..ff637e3 100644
--- a/src/namefully.spec.ts
+++ b/src/namefully.spec.ts
@@ -1,8 +1,11 @@
import { Config } from './config.js';
import { NameError } from './error.js';
import { NameIndex } from './utils.js';
+import { deserialize } from './data.js';
import { Namefully } from './namefully.js';
+import namefully from './namefully.js';
import { NameBuilder } from './builder.js';
+import { ZERO_WIDTH_SPACE } from './constants.js';
import { FirstName, LastName, Name } from './name.js';
import { SimpleParser, findNameCase } from './fixtures/helpers.js';
import { Flat, NameOrder, NameType, Namon, Separator, Surname, Title } from './types.js';
@@ -16,7 +19,7 @@ describe('Namefully', () => {
});
test('.parts returns the name components as a sequence', () => {
- expect(Array.from(name.parts).length).toBe(5);
+ expect(name.size).toBe(5);
for (const part of name.parts) expect(part).toBeInstanceOf(Name);
});
@@ -57,8 +60,11 @@ describe('Namefully', () => {
test('.deepEqual() checks whether two names are equal from a component perspective', () => {
const name1 = new Namefully('John Ben Smith');
const name2 = new Namefully([new FirstName('John', 'Ben'), new LastName('Smith')]);
+ const name3 = new Namefully([Name.first('John'), Name.middle('Ben'), Name.last('Smith')]);
expect(name1.equal(name2)).toBe(true);
expect(name1.deepEqual(name2)).toBe(false);
+ expect(name1.equal(name3)).toBe(true);
+ expect(name1.deepEqual(name3)).toBe(true);
});
test('.get() gets the raw form of a name', () => {
@@ -75,6 +81,7 @@ describe('Namefully', () => {
expect(name.get('firstName')).toBeInstanceOf(FirstName);
expect(name.get('lastName')).toBeInstanceOf(LastName);
expect(name.get('suffix')).toBeInstanceOf(Name);
+ expect(name.get('middle')).toBeUndefined();
});
test('.json() returns a json version of the full name', () => {
@@ -113,6 +120,7 @@ describe('Namefully', () => {
expect(name.format('f $l.')).toBe('John S.');
expect(name.format('f $m. l')).toBe('John B. Smith');
expect(name.format('$F.$M.$L')).toBe('J.B.S');
+ expect(name.format('$f.$m.$l')).toBe('J.B.S');
expect(name.format('$p')).toBe('');
expect(new Namefully('John Smith').format('o')).toBe('SMITH, John');
@@ -277,11 +285,13 @@ describe('Namefully', () => {
test('string', () => {
expect(new Namefully('John Smith').toString()).toBe('John Smith');
expect(new Namefully('Jane D Smith').toString()).toBe('Jane D Smith');
+ expect(new Namefully('Madonna', { mono: true }).toString()).toBe('Madonna');
});
test('string[]', () => {
expect(new Namefully(['John', 'Smith']).toString()).toBe('John Smith');
expect(new Namefully(['Jane', 'D', 'Smith']).toString()).toBe('Jane D Smith');
+ expect(new Namefully(['Madonna'], { mono: true }).toString()).toBe('Madonna');
});
test('json', () => {
@@ -292,16 +302,41 @@ describe('Namefully', () => {
const names = [new FirstName('John'), new LastName('Smith')];
expect(new Namefully(names).toString()).toBe('John Smith');
expect(new Namefully([Name.first('John'), Name.last('Smith'), Name.suffix('Ph.D')]).birth).toBe('John Smith');
+ expect(new Namefully([Name.last('Madonna')], { mono: true }).toString()).toBe('Madonna');
});
test('NameBuilder', () => {
const builder = NameBuilder.create();
builder.add(Name.first('John'), Name.last('Smith'), Name.suffix('Ph.D'));
expect(builder.build().birth).toBe('John Smith');
+
+ builder.clear();
+ builder.add(Name.middle('Madonna'));
+ expect(builder.size).toBe(1);
+ expect(builder.build({ mono: Namon.MIDDLE_NAME }).toString()).toBe('Madonna');
});
test('Parser (Custom Parser)', () => {
- expect(new Namefully(new SimpleParser('John#Smith')).toString()).toBe('John Smith');
+ const parser = new SimpleParser('John#Smith');
+ expect(new Namefully(parser).toString()).toBe('John Smith');
+ });
+
+ test('deserialize()', () => {
+ const name = deserialize({
+ names: { firstName: 'John', lastName: 'Smith' },
+ config: {
+ name: 'deserialize',
+ orderedBy: 'firstName',
+ separator: ' ',
+ title: 'US',
+ ending: false,
+ bypass: true,
+ surname: 'father',
+ mono: false,
+ },
+ });
+ expect(name).toBeInstanceOf(Namefully);
+ expect(name.full).toBe('John Smith');
});
test('tryParse()', () => {
@@ -364,6 +399,13 @@ describe('Namefully', () => {
await expect(Namefully.parse('John')).rejects.toThrow(NameError);
});
+
+ test('default namefully function import', () => {
+ expect(namefully('John Smith').full).toBe('John Smith');
+ expect(namefully(['John', 'Smith']).full).toBe('John Smith');
+ expect(namefully([Name.first('John'), Name.last('Smith')]).full).toBe('John Smith');
+ expect(namefully(new SimpleParser('John#Smith')).full).toBe('John Smith');
+ });
});
describe('can be built with a name', () => {
@@ -420,9 +462,23 @@ describe('Namefully', () => {
expect(() => new Namefully('Mr John Joe Sm1th', Config.create('noBypass'))).toThrow(NameError);
expect(() => new Namefully('Mr John Joe Smith Ph+', Config.create('noBypass'))).toThrow(NameError);
});
+
+ test('of mononyms', () => {
+ const name = new Namefully('Madonna', { mono: true });
+ expect(name.toString()).toBe('Madonna');
+ expect(name.first).toBe('Madonna');
+ expect(name.last).toBe(ZERO_WIDTH_SPACE);
+ expect(name.middle).toBeUndefined();
+ expect(name.initials()).toStrictEqual(['M']);
+ expect(name.format('L, f m')).toBe(ZERO_WIDTH_SPACE + ', Madonna');
+ expect(name.shorten()).toBe('Madonna');
+ expect(name.zip()).toBe('M.');
+ });
});
describe('Config', () => {
+ beforeEach(() => (Config as any).cache.clear());
+
test('creates a default configuration', () => {
expect(Config.create()).toEqual(
expect.objectContaining({
@@ -433,6 +489,7 @@ describe('Namefully', () => {
bypass: true,
ending: false,
surname: Surname.FATHER,
+ mono: false,
}),
);
});
@@ -445,6 +502,7 @@ describe('Namefully', () => {
title: Title.US,
surname: Surname.HYPHENATED,
ending: true,
+ mono: true,
}),
).toEqual(
expect.objectContaining({
@@ -455,21 +513,24 @@ describe('Namefully', () => {
bypass: true,
ending: true,
surname: Surname.HYPHENATED,
+ mono: true,
}),
);
- const { config } = new Namefully('f l', {
+ const config = Config.merge({
name: 'partial',
orderedBy: 'lastName',
title: 'US',
surname: 'all',
ending: true,
- });
+ mono: Namon.LAST_NAME,
+ } as Partial);
expect(config.name).toBe('partial');
expect(config.orderedBy).toBe(NameOrder.LAST_NAME);
expect(config.title).toBe(Title.US);
expect(config.ending).toBe(true);
expect(config.surname).toBe(Surname.ALL);
+ expect(config.mono).toBe(Namon.LAST_NAME);
});
test('can create more than 1 configuration when necessary', () => {
@@ -491,6 +552,7 @@ describe('Namefully', () => {
bypass: true,
ending: false,
surname: Surname.FATHER,
+ mono: false,
}),
);
@@ -504,6 +566,7 @@ describe('Namefully', () => {
bypass: false,
ending: false,
surname: Surname.MOTHER,
+ mono: false,
}),
);
});
@@ -528,6 +591,7 @@ describe('Namefully', () => {
bypass: false,
ending: false,
surname: Surname.MOTHER,
+ mono: false,
}),
);
@@ -541,6 +605,7 @@ describe('Namefully', () => {
bypass: false,
ending: false,
surname: Surname.MOTHER,
+ mono: false,
}),
);
@@ -554,6 +619,7 @@ describe('Namefully', () => {
bypass: true,
ending: false,
surname: Surname.FATHER,
+ mono: false,
}),
);
@@ -568,6 +634,7 @@ describe('Namefully', () => {
bypass: true,
ending: false,
surname: Surname.FATHER,
+ mono: false,
}),
);
});
diff --git a/src/namefully.ts b/src/namefully.ts
index eca7e2e..042a269 100644
--- a/src/namefully.ts
+++ b/src/namefully.ts
@@ -36,8 +36,8 @@ import { ArrayNameParser, ArrayStringParser, NamaParser, Parser, StringParser }
* this: `John Smith`, where `John` is the first name piece and `Smith`, the last
* name piece.
*
- * @see {@link https://www.fbiic.gov/public/2008/nov/Naming_practice_guide_UK_2006.pdf}
- * for more info on name standards.
+ * @see {@link https://www.fbiic.gov/public/2008/nov/Naming_practice_guide_UK_2006.pdf} for
+ * more info on name standards.
*
* **IMPORTANT**: Keep in mind that the order of appearance (or name order) matters
* and may be altered through configurable parameters, which will be seen later.
@@ -108,8 +108,14 @@ export class Namefully {
return this.#fullName.config;
}
+ /** Whether the name is a single word name. */
+ get isMono(): boolean {
+ return this.#fullName.isMono;
+ }
+
/** The number of characters of the `birthName`, including spaces. */
get length(): number {
+ if (this.isMono) return this.#fullName.toString().length;
return this.birth.length;
}
@@ -265,6 +271,8 @@ export class Namefully {
* ```
*/
fullName(orderedBy?: NameOptions['orderedBy']): string {
+ if (this.isMono) return this.#fullName.toString();
+
const sep: string = this.config.ending ? ',' : '';
const names: string[] = [];
@@ -272,7 +280,12 @@ export class Namefully {
if ((orderedBy ?? this.config.orderedBy) === NameOrder.FIRST_NAME) {
names.push(this.first, ...this.middleName(), this.last + sep);
} else {
- names.push(this.last, this.first, this.middleName().join(' ') + sep);
+ names.push(this.last);
+ if (this.hasMiddle) {
+ names.push(this.first, this.middleName().join(' ') + sep);
+ } else {
+ names.push(this.first + sep);
+ }
}
if (this.suffix) names.push(this.suffix);
@@ -286,6 +299,7 @@ export class Namefully {
* preset configuration.
*/
birthName(orderedBy?: NameOptions['orderedBy']): string {
+ if (this.isMono) return this.#fullName.toString();
orderedBy ??= this.config.orderedBy;
return orderedBy === NameOrder.FIRST_NAME
? [this.first, ...this.middleName(), this.last].join(' ')
@@ -333,6 +347,8 @@ export class Namefully {
only?: NameType | 'firstName' | 'lastName' | 'middleName' | 'birthName';
asJson?: boolean;
}): string[] | Record {
+ if (this.isMono) return [this.#fullName.toString()[0]];
+
const { orderedBy = this.config.orderedBy, only = NameType.BIRTH_NAME, asJson } = options ?? {};
const firstInits = this.#fullName.firstName.initials();
@@ -340,7 +356,6 @@ export class Namefully {
const lastInits = this.#fullName.lastName.initials();
if (asJson) return { firstName: firstInits, middleName: midInits, lastName: lastInits };
-
if (only !== NameType.BIRTH_NAME) {
return only === NameType.FIRST_NAME ? firstInits : only === NameType.MIDDLE_NAME ? midInits : lastInits;
} else if (orderedBy === NameOrder.FIRST_NAME) {
@@ -368,6 +383,8 @@ export class Namefully {
* the surname is set as `mother` is equivalent to making it: `FirstName MotherName`.
*/
shorten(orderedBy?: NameOptions['orderedBy']): string {
+ if (this.isMono) return this.#fullName.toString();
+
orderedBy ??= this.config.orderedBy;
const { firstName, lastName } = this.#fullName;
return orderedBy === NameOrder.FIRST_NAME
@@ -424,6 +441,7 @@ export class Namefully {
} = options;
if (this.length <= limit) return this.full;
+ if (this.isMono) return `${this.initials()}${withPeriod ? '.' : ''}`;
const { firstName, lastName, middleName } = this.#fullName;
const sep = withPeriod ? '.' : '';
@@ -645,6 +663,38 @@ export class Namefully {
return toggleCase(this.birth);
}
+ /**
+ * Serializes this Namefully instance to a JSON object.
+ *
+ * This includes both the name data (with full hierarchy for FirstName and LastName)
+ * and the configuration, allowing for complete reconstruction of the Namefully instance.
+ *
+ * @returns a JSON-serializable object containing name data and config.
+ */
+ serialize(): SerializedName {
+ const { config, firstName: fn, lastName: ln } = this.#fullName;
+
+ return {
+ names: {
+ prefix: this.prefix,
+ firstName: fn.hasMore ? { value: fn.value, more: fn.more } : fn.value,
+ middleName: this.hasMiddle ? this.middleName() : undefined,
+ lastName: ln.hasMother ? { father: ln.father, mother: ln.mother } : ln.value,
+ suffix: this.suffix,
+ },
+ config: {
+ name: config.name,
+ orderedBy: config.orderedBy,
+ separator: config.separator.token,
+ title: config.title,
+ ending: config.ending,
+ bypass: config.bypass,
+ surname: config.surname,
+ mono: config.mono instanceof Namon ? config.mono.key : config.mono,
+ },
+ };
+ }
+
#toParser(raw: string | string[] | Name[] | JsonName | Parser): Parser {
if (raw instanceof Parser) return raw;
if (typeof raw === 'string') return new StringParser(raw);
@@ -712,37 +762,6 @@ export class Namefully {
return ALLOWED_FORMAT_TOKENS.includes(char) ? char : undefined;
}
}
-
- /**
- * Serializes this Namefully instance to a JSON object.
- *
- * This includes both the name data (with full hierarchy for FirstName and LastName)
- * and the configuration, allowing for complete reconstruction of the Namefully instance.
- *
- * @returns a JSON-serializable object containing name data and config.
- */
- serialize(): SerializedName {
- const { config, firstName: fn, lastName: ln } = this.#fullName;
-
- return {
- names: {
- prefix: this.prefix,
- firstName: fn.hasMore ? { value: fn.value, more: fn.more } : fn.value,
- middleName: this.hasMiddle ? this.middleName() : undefined,
- lastName: ln.hasMother ? { father: ln.father, mother: ln.mother } : ln.value,
- suffix: this.suffix,
- },
- config: {
- name: config.name,
- orderedBy: config.orderedBy,
- separator: config.separator.token,
- title: config.title,
- ending: config.ending,
- bypass: config.bypass,
- surname: config.surname,
- },
- };
- }
}
/**
@@ -763,4 +782,5 @@ export type NameOptions = Partial<{
ending: boolean;
bypass: boolean;
surname: Surname | 'father' | 'mother' | 'hyphenated' | 'all';
+ mono: boolean | Namon;
}>;
diff --git a/src/parser.ts b/src/parser.ts
index 382e445..aee346a 100644
--- a/src/parser.ts
+++ b/src/parser.ts
@@ -1,10 +1,10 @@
import { Config } from './config.js';
import { NameIndex } from './utils.js';
import { InputError } from './error.js';
-import { FullName } from './fullname.js';
+import { FullName, Mononym } from './fullname.js';
import { Namon, Nullable, Separator } from './types.js';
import { FirstName, LastName, Name, JsonName } from './name.js';
-import { ArrayStringValidator, ArrayNameValidator, NamaValidator } from './validator.js';
+import { ArrayStringValidator, ArrayNameValidator, Validators } from './validator.js';
/**
* A parser signature that helps to organize the names accordingly.
@@ -16,6 +16,12 @@ export abstract class Parser {
*/
constructor(public raw: T) {}
+ /**
+ * Parses raw data into a `FullName` while applying some options.
+ * @param options for additional configuration to apply.
+ */
+ abstract parse(options?: Partial): FullName;
+
/**
* Builds a dynamic `Parser` on the fly and throws a `NameError` when unable
* to do so. The built parser only knows how to operate birth names.
@@ -32,10 +38,7 @@ export abstract class Parser {
}
if (length < 2) {
- throw new InputError({
- source: text,
- message: 'cannot build from invalid input',
- });
+ throw new InputError({ source: text, message: 'expecting at least 2 name parts' });
} else if (length === 2 || length === 3) {
return new StringParser(text);
} else {
@@ -53,26 +56,23 @@ export abstract class Parser {
return Promise.reject(error);
}
}
-
- /**
- * Parses the raw data into a `FullName` while considering some options.
- * @param options for additional configuration to apply.
- */
- abstract parse(options?: Partial): FullName;
}
export class StringParser extends Parser {
parse(options: Partial): FullName {
const config = Config.merge(options);
const names = this.raw.split(config.separator.token);
- return new ArrayStringParser(names).parse(options);
+ return new ArrayStringParser(names).parse(config);
}
}
export class ArrayStringParser extends Parser {
parse(options: Partial): FullName {
const config = Config.merge(options);
- const fullName = new FullName(config);
+
+ if (this.raw.length === 1 && config.mono) {
+ return new MonoParser(this.raw[0]).parse(config);
+ }
const raw = this.raw.map((n) => n.trim());
const index = NameIndex.when(config.orderedBy, raw.length);
@@ -85,8 +85,9 @@ export class ArrayStringParser extends Parser {
}
const { firstName, lastName, middleName, prefix, suffix } = index;
- fullName.setFirstName(new FirstName(raw[firstName]));
- fullName.setLastName(new LastName(raw[lastName]));
+ const fullName = new FullName(config)
+ .setFirstName(new FirstName(raw[firstName]))
+ .setLastName(new LastName(raw[lastName]));
if (raw.length >= 3) fullName.setMiddleName(raw[middleName].split(config.separator.token));
if (raw.length >= 4) fullName.setPrefix(Name.prefix(raw[prefix]));
@@ -113,9 +114,9 @@ export class NamaParser extends Parser {
);
if (config.bypass) {
- NamaValidator.create().validateKeys(names);
+ Validators.nama.validateKeys(names);
} else {
- NamaValidator.create().validate(names);
+ Validators.nama.validate(names);
}
return FullName.parse(this.raw, config);
@@ -125,10 +126,14 @@ export class NamaParser extends Parser {
export class ArrayNameParser extends Parser {
parse(options: Partial): FullName {
const config = Config.merge(options);
- const fullName = new FullName(config);
- ArrayNameValidator.create().validate(this.raw);
+ if (this.raw.length === 1 && config.mono) {
+ return new MonoParser(this.raw[0]).parse(options);
+ } else {
+ ArrayNameValidator.create().validate(this.raw);
+ }
+ const fullName = new FullName(config);
for (const name of this.raw) {
if (name.isPrefix) {
fullName.setPrefix(name);
@@ -146,3 +151,15 @@ export class ArrayNameParser extends Parser {
return fullName;
}
}
+
+export class MonoParser extends Parser {
+ parse(options: Partial): Mononym {
+ const config = Config.merge(options);
+
+ if (config.bypass) Validators.namon.validate(this.raw);
+
+ const type = config.mono instanceof Namon ? config.mono : Namon.FIRST_NAME;
+ const name = this.raw instanceof Name ? this.raw : new Name(this.raw.trim(), type);
+ return new Mononym(name, config);
+ }
+}