namefully 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import { Config } from './config.js';
2
2
  import { Validators } from './validator.js';
3
+ import { ZERO_WIDTH_SPACE } from './constants.js';
3
4
  import { Namon, Title } from './types.js';
4
5
  import { NameError, UnknownError } from './error.js';
5
6
  import { FirstName, LastName, Name } from './name.js';
@@ -31,14 +32,18 @@ export class FullName {
31
32
  get suffix() {
32
33
  return this.#suffix;
33
34
  }
35
+ get isMono() {
36
+ return this instanceof Mononym;
37
+ }
34
38
  static parse(json, config) {
35
39
  try {
40
+ const { prefix, firstName: fn, middleName: mn, lastName: ln, suffix } = json;
36
41
  return new FullName(config)
37
- .setPrefix(json.prefix)
38
- .setFirstName(json.firstName)
39
- .setMiddleName(json.middleName ?? [])
40
- .setLastName(json.lastName)
41
- .setSuffix(json.suffix);
42
+ .setPrefix(prefix)
43
+ .setFirstName(typeof fn === 'string' ? fn : new FirstName(fn.value, ...(fn.more ?? [])))
44
+ .setMiddleName(typeof mn === 'string' ? [mn] : (mn ?? []))
45
+ .setLastName(typeof ln === 'string' ? ln : new LastName(ln.father, ln.mother))
46
+ .setSuffix(suffix);
42
47
  }
43
48
  catch (error) {
44
49
  if (error instanceof NameError)
@@ -56,7 +61,7 @@ export class FullName {
56
61
  if (!this.#config.bypass)
57
62
  Validators.prefix.validate(name);
58
63
  const prefix = name instanceof Name ? name.value : name;
59
- this.#prefix = Name.prefix(this.#config.title === Title.US ? `${prefix}.` : prefix);
64
+ this.#prefix = Name.prefix(this.#config.title === Title.US && !prefix.endsWith('.') ? `${prefix}.` : prefix);
60
65
  return this;
61
66
  }
62
67
  setFirstName(name) {
@@ -97,6 +102,11 @@ export class FullName {
97
102
  return !!this.#suffix;
98
103
  return namon.equal(Namon.MIDDLE_NAME) ? this.#middleName.length > 0 : true;
99
104
  }
105
+ toString() {
106
+ if (this.isMono)
107
+ return this.value;
108
+ return Array.from(this.toIterable(true)).join(' ');
109
+ }
100
110
  *toIterable(flat = false) {
101
111
  if (this.#prefix)
102
112
  yield this.#prefix;
@@ -117,3 +127,37 @@ export class FullName {
117
127
  yield* this.toIterable(true);
118
128
  }
119
129
  }
130
+ export class Mononym extends FullName {
131
+ #namon;
132
+ #type;
133
+ constructor(name, options) {
134
+ super(options ?? { name: 'mononym', mono: true });
135
+ this.#namon = name.toString();
136
+ this.type = name instanceof Name ? name.type : Namon.FIRST_NAME;
137
+ }
138
+ set type(type) {
139
+ this.#type = typeof type === 'string' ? (Namon.cast(type) ?? Namon.FIRST_NAME) : type;
140
+ this.#build(this.#namon);
141
+ }
142
+ get type() {
143
+ return this.#type;
144
+ }
145
+ get value() {
146
+ return this.#namon;
147
+ }
148
+ #build(name) {
149
+ this.setFirstName(ZERO_WIDTH_SPACE).setLastName(ZERO_WIDTH_SPACE).setMiddleName([]).setPrefix(null).setSuffix(null);
150
+ if (this.#type.equal(Namon.FIRST_NAME))
151
+ this.setFirstName(name);
152
+ else if (this.#type.equal(Namon.LAST_NAME))
153
+ this.setLastName(name);
154
+ else if (this.#type.equal(Namon.MIDDLE_NAME))
155
+ this.setMiddleName([name]);
156
+ else if (this.#type.equal(Namon.PREFIX))
157
+ this.setPrefix(name);
158
+ else if (this.#type.equal(Namon.SUFFIX))
159
+ this.setSuffix(name);
160
+ else
161
+ throw new NameError(name, 'invalid mononym type');
162
+ }
163
+ }
@@ -112,8 +112,14 @@ export declare function isNameArray(value?: unknown): value is Name[];
112
112
  /** JSON signature for `FullName` data. */
113
113
  export interface JsonName {
114
114
  prefix?: string;
115
- firstName: string;
116
- middleName?: string[];
117
- lastName: string;
115
+ firstName: string | {
116
+ value: string;
117
+ more?: string[];
118
+ };
119
+ middleName?: string | string[];
120
+ lastName: string | {
121
+ father: string;
122
+ mother?: string;
123
+ };
118
124
  suffix?: string;
119
125
  }
@@ -32,8 +32,8 @@ import { Parser } from './parser.js';
32
32
  * this: `John Smith`, where `John` is the first name piece and `Smith`, the last
33
33
  * name piece.
34
34
  *
35
- * @see {@link https://www.fbiic.gov/public/2008/nov/Naming_practice_guide_UK_2006.pdf}
36
- * for more info on name standards.
35
+ * @see {@link https://www.fbiic.gov/public/2008/nov/Naming_practice_guide_UK_2006.pdf} for
36
+ * more info on name standards.
37
37
  *
38
38
  * **IMPORTANT**: Keep in mind that the order of appearance (or name order) matters
39
39
  * and may be altered through configurable parameters, which will be seen later.
@@ -86,6 +86,8 @@ export declare class Namefully {
86
86
  static parse(text: string, index?: NameIndex): Promise<Namefully>;
87
87
  /** The configuration dictating this name's behavior. */
88
88
  get config(): Config;
89
+ /** Whether the name is a single word name. */
90
+ get isMono(): boolean;
89
91
  /** The number of characters of the `birthName`, including spaces. */
90
92
  get length(): number;
91
93
  /** The prefix part of the name set. */
@@ -354,4 +356,5 @@ export type NameOptions = Partial<{
354
356
  ending: boolean;
355
357
  bypass: boolean;
356
358
  surname: Surname | 'father' | 'mother' | 'hyphenated' | 'all';
359
+ mono: boolean | Namon;
357
360
  }>;
@@ -23,7 +23,12 @@ export class Namefully {
23
23
  get config() {
24
24
  return this.#fullName.config;
25
25
  }
26
+ get isMono() {
27
+ return this.#fullName.isMono;
28
+ }
26
29
  get length() {
30
+ if (this.isMono)
31
+ return this.#fullName.toString().length;
27
32
  return this.birth.length;
28
33
  }
29
34
  get prefix() {
@@ -113,6 +118,8 @@ export class Namefully {
113
118
  return this.#fullName.has(namon);
114
119
  }
115
120
  fullName(orderedBy) {
121
+ if (this.isMono)
122
+ return this.#fullName.toString();
116
123
  const sep = this.config.ending ? ',' : '';
117
124
  const names = [];
118
125
  if (this.prefix)
@@ -121,13 +128,21 @@ export class Namefully {
121
128
  names.push(this.first, ...this.middleName(), this.last + sep);
122
129
  }
123
130
  else {
124
- names.push(this.last, this.first, this.middleName().join(' ') + sep);
131
+ names.push(this.last);
132
+ if (this.hasMiddle) {
133
+ names.push(this.first, this.middleName().join(' ') + sep);
134
+ }
135
+ else {
136
+ names.push(this.first + sep);
137
+ }
125
138
  }
126
139
  if (this.suffix)
127
140
  names.push(this.suffix);
128
141
  return names.join(' ').trim();
129
142
  }
130
143
  birthName(orderedBy) {
144
+ if (this.isMono)
145
+ return this.#fullName.toString();
131
146
  orderedBy ??= this.config.orderedBy;
132
147
  return orderedBy === NameOrder.FIRST_NAME
133
148
  ? [this.first, ...this.middleName(), this.last].join(' ')
@@ -143,6 +158,8 @@ export class Namefully {
143
158
  return this.#fullName.lastName.toString(format);
144
159
  }
145
160
  initials(options) {
161
+ if (this.isMono)
162
+ return [this.#fullName.toString()[0]];
146
163
  const { orderedBy = this.config.orderedBy, only = NameType.BIRTH_NAME, asJson } = options ?? {};
147
164
  const firstInits = this.#fullName.firstName.initials();
148
165
  const midInits = this.#fullName.middleName.map((n) => n.value[0]);
@@ -160,6 +177,8 @@ export class Namefully {
160
177
  }
161
178
  }
162
179
  shorten(orderedBy) {
180
+ if (this.isMono)
181
+ return this.#fullName.toString();
163
182
  orderedBy ??= this.config.orderedBy;
164
183
  const { firstName, lastName } = this.#fullName;
165
184
  return orderedBy === NameOrder.FIRST_NAME
@@ -170,6 +189,8 @@ export class Namefully {
170
189
  const { by = Flat.MIDDLE_NAME, limit = 20, recursive = false, withMore = false, withPeriod = true, surname, } = options;
171
190
  if (this.length <= limit)
172
191
  return this.full;
192
+ if (this.isMono)
193
+ return `${this.initials()}${withPeriod ? '.' : ''}`;
173
194
  const { firstName, lastName, middleName } = this.#fullName;
174
195
  const sep = withPeriod ? '.' : '';
175
196
  const hasMid = this.hasMiddle;
@@ -317,6 +338,28 @@ export class Namefully {
317
338
  toToggleCase() {
318
339
  return toggleCase(this.birth);
319
340
  }
341
+ serialize() {
342
+ const { config, firstName: fn, lastName: ln } = this.#fullName;
343
+ return {
344
+ names: {
345
+ prefix: this.prefix,
346
+ firstName: fn.hasMore ? { value: fn.value, more: fn.more } : fn.value,
347
+ middleName: this.hasMiddle ? this.middleName() : undefined,
348
+ lastName: ln.hasMother ? { father: ln.father, mother: ln.mother } : ln.value,
349
+ suffix: this.suffix,
350
+ },
351
+ config: {
352
+ name: config.name,
353
+ orderedBy: config.orderedBy,
354
+ separator: config.separator.token,
355
+ title: config.title,
356
+ ending: config.ending,
357
+ bypass: config.bypass,
358
+ surname: config.surname,
359
+ mono: config.mono instanceof Namon ? config.mono.key : config.mono,
360
+ },
361
+ };
362
+ }
320
363
  #toParser(raw) {
321
364
  if (raw instanceof Parser)
322
365
  return raw;
@@ -388,27 +431,6 @@ export class Namefully {
388
431
  return ALLOWED_FORMAT_TOKENS.includes(char) ? char : undefined;
389
432
  }
390
433
  }
391
- serialize() {
392
- const { config, firstName: fn, lastName: ln } = this.#fullName;
393
- return {
394
- names: {
395
- prefix: this.prefix,
396
- firstName: fn.hasMore ? { value: fn.value, more: fn.more } : fn.value,
397
- middleName: this.hasMiddle ? this.middleName() : undefined,
398
- lastName: ln.hasMother ? { father: ln.father, mother: ln.mother } : ln.value,
399
- suffix: this.suffix,
400
- },
401
- config: {
402
- name: config.name,
403
- orderedBy: config.orderedBy,
404
- separator: config.separator.token,
405
- title: config.title,
406
- ending: config.ending,
407
- bypass: config.bypass,
408
- surname: config.surname,
409
- },
410
- };
411
- }
412
434
  }
413
435
  export default (names, options) => {
414
436
  return new Namefully(names, options);
@@ -1,6 +1,6 @@
1
1
  import { Config } from './config.js';
2
2
  import { NameIndex } from './utils.js';
3
- import { FullName } from './fullname.js';
3
+ import { FullName, Mononym } from './fullname.js';
4
4
  import { Name, JsonName } from './name.js';
5
5
  /**
6
6
  * A parser signature that helps to organize the names accordingly.
@@ -12,6 +12,11 @@ export declare abstract class Parser<T = unknown> {
12
12
  * @param raw data to be parsed
13
13
  */
14
14
  constructor(raw: T);
15
+ /**
16
+ * Parses raw data into a `FullName` while applying some options.
17
+ * @param options for additional configuration to apply.
18
+ */
19
+ abstract parse(options?: Partial<Config>): FullName;
15
20
  /**
16
21
  * Builds a dynamic `Parser` on the fly and throws a `NameError` when unable
17
22
  * to do so. The built parser only knows how to operate birth names.
@@ -19,11 +24,6 @@ export declare abstract class Parser<T = unknown> {
19
24
  static build(text: string, index?: NameIndex): Parser;
20
25
  /** Builds asynchronously a dynamic `Parser`. */
21
26
  static buildAsync(text: string, index?: NameIndex): Promise<Parser>;
22
- /**
23
- * Parses the raw data into a `FullName` while considering some options.
24
- * @param options for additional configuration to apply.
25
- */
26
- abstract parse(options?: Partial<Config>): FullName;
27
27
  }
28
28
  export declare class StringParser extends Parser<string> {
29
29
  parse(options: Partial<Config>): FullName;
@@ -37,3 +37,6 @@ export declare class NamaParser extends Parser<JsonName> {
37
37
  export declare class ArrayNameParser extends Parser<Name[]> {
38
38
  parse(options: Partial<Config>): FullName;
39
39
  }
40
+ export declare class MonoParser extends Parser<string | Name> {
41
+ parse(options: Partial<Config>): Mononym;
42
+ }
@@ -1,10 +1,10 @@
1
1
  import { Config } from './config.js';
2
2
  import { NameIndex } from './utils.js';
3
3
  import { InputError } from './error.js';
4
- import { FullName } from './fullname.js';
4
+ import { FullName, Mononym } from './fullname.js';
5
5
  import { Namon, Separator } from './types.js';
6
6
  import { FirstName, LastName, Name } from './name.js';
7
- import { ArrayStringValidator, ArrayNameValidator, NamaValidator } from './validator.js';
7
+ import { ArrayStringValidator, ArrayNameValidator, Validators } from './validator.js';
8
8
  export class Parser {
9
9
  raw;
10
10
  constructor(raw) {
@@ -20,10 +20,7 @@ export class Parser {
20
20
  return new ArrayNameParser(names);
21
21
  }
22
22
  if (length < 2) {
23
- throw new InputError({
24
- source: text,
25
- message: 'cannot build from invalid input',
26
- });
23
+ throw new InputError({ source: text, message: 'expecting at least 2 name parts' });
27
24
  }
28
25
  else if (length === 2 || length === 3) {
29
26
  return new StringParser(text);
@@ -47,13 +44,15 @@ export class StringParser extends Parser {
47
44
  parse(options) {
48
45
  const config = Config.merge(options);
49
46
  const names = this.raw.split(config.separator.token);
50
- return new ArrayStringParser(names).parse(options);
47
+ return new ArrayStringParser(names).parse(config);
51
48
  }
52
49
  }
53
50
  export class ArrayStringParser extends Parser {
54
51
  parse(options) {
55
52
  const config = Config.merge(options);
56
- const fullName = new FullName(config);
53
+ if (this.raw.length === 1 && config.mono) {
54
+ return new MonoParser(this.raw[0]).parse(config);
55
+ }
57
56
  const raw = this.raw.map((n) => n.trim());
58
57
  const index = NameIndex.when(config.orderedBy, raw.length);
59
58
  const validator = new ArrayStringValidator(index);
@@ -64,8 +63,9 @@ export class ArrayStringParser extends Parser {
64
63
  validator.validate(raw);
65
64
  }
66
65
  const { firstName, lastName, middleName, prefix, suffix } = index;
67
- fullName.setFirstName(new FirstName(raw[firstName]));
68
- fullName.setLastName(new LastName(raw[lastName]));
66
+ const fullName = new FullName(config)
67
+ .setFirstName(new FirstName(raw[firstName]))
68
+ .setLastName(new LastName(raw[lastName]));
69
69
  if (raw.length >= 3)
70
70
  fullName.setMiddleName(raw[middleName].split(config.separator.token));
71
71
  if (raw.length >= 4)
@@ -89,10 +89,10 @@ export class NamaParser extends Parser {
89
89
  return [namon, value];
90
90
  }));
91
91
  if (config.bypass) {
92
- NamaValidator.create().validateKeys(names);
92
+ Validators.nama.validateKeys(names);
93
93
  }
94
94
  else {
95
- NamaValidator.create().validate(names);
95
+ Validators.nama.validate(names);
96
96
  }
97
97
  return FullName.parse(this.raw, config);
98
98
  }
@@ -100,8 +100,13 @@ export class NamaParser extends Parser {
100
100
  export class ArrayNameParser extends Parser {
101
101
  parse(options) {
102
102
  const config = Config.merge(options);
103
+ if (this.raw.length === 1 && config.mono) {
104
+ return new MonoParser(this.raw[0]).parse(options);
105
+ }
106
+ else {
107
+ ArrayNameValidator.create().validate(this.raw);
108
+ }
103
109
  const fullName = new FullName(config);
104
- ArrayNameValidator.create().validate(this.raw);
105
110
  for (const name of this.raw) {
106
111
  if (name.isPrefix) {
107
112
  fullName.setPrefix(name);
@@ -123,3 +128,13 @@ export class ArrayNameParser extends Parser {
123
128
  return fullName;
124
129
  }
125
130
  }
131
+ export class MonoParser extends Parser {
132
+ parse(options) {
133
+ const config = Config.merge(options);
134
+ if (config.bypass)
135
+ Validators.namon.validate(this.raw);
136
+ const type = config.mono instanceof Namon ? config.mono : Namon.FIRST_NAME;
137
+ const name = this.raw instanceof Name ? this.raw : new Name(this.raw.trim(), type);
138
+ return new Mononym(name, config);
139
+ }
140
+ }