diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 99090ba..4f386fd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,10 +1,8 @@ -# This is a basic workflow to help you get started with Actions - name: Build docs on version update # Controls when the action will run. on: - # Triggers the workflow on push or pull request events but only for the master branch + # Triggers the workflow on push or pull request events but only for the main branch push: tags: - "*" @@ -15,7 +13,6 @@ on: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: build-and-deploy: - # The type of runner that the job will run on runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job steps: @@ -24,11 +21,30 @@ jobs: uses: actions/checkout@v2 - name: Install and Build - # use force because typedoc doesn't support current version of TS, but it still build run: | npm install npm run docs + - name: Check for changes + id: git-check + run: | + git diff --exit-code || echo "changes=true" >> $GITHUB_OUTPUT + + - name: Commit changes + if: steps.git-check.outputs.changes == 'true' + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add -A + git commit -m "Auto-update documentation" + + - name: Push changes + if: steps.git-check.outputs.changes == 'true' + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: main + - name: Deploy uses: JamesIves/github-pages-deploy-action@4.1.4 with: diff --git a/src/char.ts b/src/char.ts index 2fada4c..49d2ec9 100644 --- a/src/char.ts +++ b/src/char.ts @@ -10,73 +10,72 @@ export class Char { #text: string; #cluster: Cluster | null = null; #sequencePosition: number; + #isCharKeyOfCharToNameMap = isHebrewCharacter; constructor(char: string) { this.#text = char; - this.#sequencePosition = this.findPos(); + this.#sequencePosition = this.#findPos(); } - private findPos(): number { + #findPos() { const char = this.text; - if (Char.consonants.test(char)) { + if (Char.#consonants.test(char)) { return 0; } - if (Char.ligatures.test(char)) { + if (Char.#ligatures.test(char)) { return 1; } - if (Char.dagesh.test(char)) { + if (Char.#dagesh.test(char)) { return 2; } - if (Char.rafe.test(char)) { + if (Char.#rafe.test(char)) { return 2; } - if (Char.vowels.test(char)) { + if (Char.#vowels.test(char)) { return 3; } - if (Char.sheva.test(char)) { + if (Char.#sheva.test(char)) { return 3; } - if (Char.taamim.test(char)) { + if (Char.#taamim.test(char)) { return 4; } - if (Char.meteg.test(char)) { + if (Char.#meteg.test(char)) { return 4; } // i.e. any non-hebrew char return 10; } - private isCharKeyOfCharToNameMap = isHebrewCharacter; - - private static get consonants() { + static get #consonants() { return consonants; } - private static get dagesh() { + static get #dagesh() { return dagesh; } - private static get ligatures() { + static get #ligatures() { return ligatures; } - private static get meteg() { + static get #meteg() { return meteg; } - private static get rafe() { + static get #rafe() { return rafe; } - private static get sheva() { + static get #sheva() { return sheva; } - private static get taamim() { + static get #taamim() { return taamim; } - private static get vowels() { + static get #vowels() { return vowels; } @@ -93,7 +92,7 @@ export class Char { * // "דָּ" * ``` */ - get cluster(): Cluster | null { + get cluster() { return this.#cluster; } @@ -106,7 +105,7 @@ export class Char { * * @param name a character name */ - isCharacterName(name: keyof NameToCharMap): boolean { + isCharacterName(name: keyof NameToCharMap) { if (!nameToCharMap[name]) { throw new Error(`${name} is not a valid value`); } @@ -126,8 +125,8 @@ export class Char { * // true * ``` */ - get isConsonant(): boolean { - return Char.consonants.test(this.#text); + get isConsonant() { + return Char.#consonants.test(this.#text); } /** @@ -140,8 +139,8 @@ export class Char { * // true * ``` */ - get isLigature(): boolean { - return Char.ligatures.test(this.#text); + get isLigature() { + return Char.#ligatures.test(this.#text); } /** @@ -154,8 +153,8 @@ export class Char { * // true * ``` */ - get isDagesh(): boolean { - return Char.dagesh.test(this.#text); + get isDagesh() { + return Char.#dagesh.test(this.#text); } /** @@ -168,8 +167,8 @@ export class Char { * // true * ``` */ - get isRafe(): boolean { - return Char.rafe.test(this.#text); + get isRafe() { + return Char.#rafe.test(this.#text); } /** @@ -182,8 +181,8 @@ export class Char { * // true * ``` */ - get isSheva(): boolean { - return Char.sheva.test(this.#text); + get isSheva() { + return Char.#sheva.test(this.#text); } /** @@ -196,8 +195,8 @@ export class Char { * // true * ``` */ - get isVowel(): boolean { - return Char.vowels.test(this.#text); + get isVowel() { + return Char.#vowels.test(this.#text); } /** @@ -210,8 +209,8 @@ export class Char { * // true * ``` */ - get isTaamim(): boolean { - return Char.taamim.test(this.#text); + get isTaamim() { + return Char.#taamim.test(this.#text); } /** @@ -224,7 +223,7 @@ export class Char { * // true * ``` */ - get isNotHebrew(): boolean { + get isNotHebrew() { return this.sequencePosition === 10; } @@ -240,7 +239,7 @@ export class Char { */ get characterName(): CharToNameMap[keyof CharToNameMap] | null { const text = this.#text; - if (this.isCharKeyOfCharToNameMap(text)) { + if (this.#isCharKeyOfCharToNameMap(text)) { return charToNameMap[text]; } return null; @@ -265,7 +264,7 @@ export class Char { * // 3 * ``` */ - get sequencePosition(): number { + get sequencePosition() { return this.#sequencePosition; } @@ -281,7 +280,7 @@ export class Char { * // "א" * ``` */ - get text(): string { + get text() { return this.#text; } } diff --git a/src/cluster.ts b/src/cluster.ts index 7db786a..cdef1a1 100644 --- a/src/cluster.ts +++ b/src/cluster.ts @@ -36,6 +36,9 @@ export class Cluster extends Node { #vowelsCache: Vowel[] | null = null; #vowelNamesCache: VowelName[] | null = null; #taamimNamesCache: TaamimName[] | null = null; + #isCharConsonant = isCharConsonant; + #isCharTaam = isCharTaam; + #isCharVowel = isCharVowel; /** * Creates a new cluster @@ -55,26 +58,20 @@ export class Cluster extends Node { super(); this.value = this; this.#original = cluster; - this.#sequenced = this.sequence(noSequence); + this.#sequenced = this.#sequence(noSequence); this.#sequenced.forEach((char) => (char.cluster = this)); } - private sequence(noSequence: boolean = false): Char[] { + #sequence(noSequence: boolean = false) { const chars = [...this.original].map((char) => new Char(char)); return noSequence ? chars : chars.sort((a, b) => a.sequencePosition - b.sequencePosition); } - private isCharConsonant = isCharConsonant; - - private isCharTaam = isCharTaam; - - private isCharVowel = isCharVowel; - - private get hasMetegCharacter(): boolean { - return Cluster.meteg.test(this.text); + get #hasMetegCharacter() { + return Cluster.#meteg.test(this.text); } - private static get meteg() { + static get #meteg() { return meteg; } @@ -93,7 +90,7 @@ export class Cluster extends Node { * // ] * ``` */ - get chars(): Char[] { + get chars() { return this.#sequenced; } @@ -113,14 +110,14 @@ export class Cluster extends Node { * This can only every return one consonant, as a `Cluster` is defined by having only one consonant. * Though it is impossible to have two consonants in a cluster, this api is meant for consistency with `vowels` and `taamim` */ - get consonants(): Consonant[] { + get consonants() { if (this.#consonantsCache) { return this.#consonantsCache; } const consonants = this.chars.reduce((a, char) => { const text = char.text; - if (char.isConsonant && this.isCharConsonant(text)) { + if (char.isConsonant && this.#isCharConsonant(text)) { a.push(text); } return a; @@ -145,14 +142,14 @@ export class Cluster extends Node { * This can only every return one consonant, as a `Cluster` is defined by having only one consonant. * Though it is impossible to have two consonants in a cluster, this api is meant for consistency with `vowelNames` and `taamimNames` */ - get consonantNames(): ConsonantName[] { + get consonantNames() { if (this.#consonantNameCache) { return this.#consonantNameCache; } const consonantNames = this.chars.reduce((a, char) => { const text = char.text; - if (char.isConsonant && this.isCharConsonant(text)) { + if (char.isConsonant && this.#isCharConsonant(text)) { a.push(charToNameMap[text]); } return a; @@ -175,7 +172,7 @@ export class Cluster extends Node { * // false * ``` */ - hasConsonantName(name: ConsonantName): boolean { + hasConsonantName(name: ConsonantName) { if (!consonantNameToCharMap[name]) { throw new Error(`${name} is not a valid value`); } @@ -203,7 +200,7 @@ export class Cluster extends Node { * - \u{05B2} HATAF PATAH * - \u{05B3} HATAF QAMATS */ - get hasHalfVowel(): boolean { + get hasHalfVowel() { return /[\u{05B1}-\u{05B3}]/u.test(this.text); } @@ -228,7 +225,7 @@ export class Cluster extends Node { * - \u{05B9} HOLAM * - \u{05BA} HOLAM HASER FOR VAV */ - get hasLongVowel(): boolean { + get hasLongVowel() { return /[\u{05B5}\u{05B8}\u{05B9}\u{05BA}]/u.test(this.text); } @@ -250,7 +247,7 @@ export class Cluster extends Node { * Checks if the following character is present and a _sof pasuq_ does not follow it: * - \u{05BD} METEG */ - get hasMetheg(): boolean { + get hasMetheg() { return this.hasMeteg; } @@ -269,8 +266,8 @@ export class Cluster extends Node { * Checks if the following character is present and a _sof pasuq_ does not follow it: * - \u{05BD} METEG */ - get hasMeteg(): boolean { - if (!this.hasMetegCharacter) { + get hasMeteg() { + if (!this.#hasMetegCharacter) { return false; } let next = this.next; @@ -278,7 +275,7 @@ export class Cluster extends Node { if (next instanceof Cluster) { const nextText = next.text; const sofPassuq = /\u{05C3}/u; - if (Cluster.meteg.test(nextText)) { + if (Cluster.#meteg.test(nextText)) { return true; } if (sofPassuq.test(nextText)) { @@ -308,7 +305,7 @@ export class Cluster extends Node { * Checks if the following character is present: * - \u{05B0} SHEVA */ - get hasSheva(): boolean { + get hasSheva() { return /\u{05B0}/u.test(this.text); } @@ -332,7 +329,7 @@ export class Cluster extends Node { * Checks if the following character is present: * - \u{05B0} SHEVA */ - get hasShewa(): boolean { + get hasShewa() { return this.hasSheva; } @@ -358,7 +355,7 @@ export class Cluster extends Node { * - \u{05BB} QUBUTS * - \u{05C7} QAMATS QATAN */ - get hasShortVowel(): boolean { + get hasShortVowel() { return /[\u{05B4}\u{05B6}\u{05B7}\u{05BB}\u{05C7}]/u.test(this.text); } @@ -378,8 +375,8 @@ export class Cluster extends Node { * Checks if the following character is present and a _sof pasuq_ follows it: * - \u{05BD} METEG */ - get hasSilluq(): boolean { - if (this.hasMetegCharacter && !this.hasMeteg) { + get hasSilluq() { + if (this.#hasMetegCharacter && !this.hasMeteg) { // if it has a meteg character, but the character is not a meteg // then infer it is silluq return true; @@ -403,7 +400,7 @@ export class Cluster extends Node { * Note: it only checks according to the character name, not its semantic meaning. * E.g. "כֵֽן׃" would be `true` when checking for `"METEG"`, not silluq */ - hasTaamName(name: TaamimName): boolean { + hasTaamName(name: TaamimName) { if (!taamimNameToCharMap[name]) { throw new Error(`${name} is not a valid value`); } @@ -428,7 +425,7 @@ export class Cluster extends Node { * The following characters are considered taamim: * - \u{0591}-\u{05AF}\u{05BF}\u{05C0}\u{05C3}-\u{05C6}\u{05F3}\u{05F4} */ - get hasTaamim(): boolean { + get hasTaamim() { return taamim.test(this.text); } @@ -450,7 +447,7 @@ export class Cluster extends Node { * According to [Syllabification](/guides/syllabification), a sheva is a vowel and serves as the nucleus of a syllable. * Because `Cluster` is concerned with orthography, a sheva is **not** a vowel character. */ - get hasVowel(): boolean { + get hasVowel() { return this.hasLongVowel || this.hasShortVowel || this.hasHalfVowel; } @@ -472,7 +469,7 @@ export class Cluster extends Node { * According to [Syllabification](/guides/syllabification), a sheva is a vowel and serves as the nucleus of a syllable. * Because `Cluster` is concerned with orthography, a sheva is **not** a vowel character. */ - hasVowelName(name: VowelName): boolean { + hasVowelName(name: VowelName) { if (!vowelNameToCharMap[name]) { throw new Error(`${name} is not a valid value`); } @@ -504,7 +501,7 @@ export class Cluster extends Node { * There are potentially other instances when a consonant may be a _mater_ (e.g. a silent aleph), but these are the most common. * Though a shureq is a _mater_ letter, it is also a vowel itself, and thus separate from `isMater`. */ - get isMater(): boolean { + get isMater() { const nxtIsShureq = this.next instanceof Cluster ? this.next.isShureq : false; if (!this.hasVowel && !this.isShureq && !this.hasSheva && !nxtIsShureq) { const text = this.text; @@ -538,7 +535,7 @@ export class Cluster extends Node { * // true * ``` */ - get isNotHebrew(): boolean { + get isNotHebrew() { return !hebChars.test(this.text); } @@ -561,7 +558,7 @@ export class Cluster extends Node { * - \u{05C3} HEBREW PUNCTUATION SOF PASUQ ׃ * - \u{05C6} HEBREW PUNCTUATION NUN HAFUKHA ׆ */ - get isPunctuation(): boolean { + get isPunctuation() { const punctuationOnly = new RegExp(`^${punctuation.source}+$`, "u"); return punctuationOnly.test(this.text); } @@ -585,7 +582,7 @@ export class Cluster extends Node { * A shureq is a vowel itself, but contains no vowel characters (hence why `hasVowel` cannot be `true`). * This allows for easier syllabification. */ - get isShureq(): boolean { + get isShureq() { const shureq = /\u{05D5}\u{05BC}/u; const prvHasVowel = this.prev?.value?.hasVowel ?? false; return !this.hasVowel && !this.hasSheva && !prvHasVowel ? shureq.test(this.text) : false; @@ -612,7 +609,7 @@ export class Cluster extends Node { * - \u{05C6} HEBREW PUNCTUATION NUN HAFUKHA ׆ * */ - get isTaam(): boolean { + get isTaam() { return this.isPunctuation; } @@ -624,7 +621,7 @@ export class Cluster extends Node { * @description * The original string passed to the constructor that has not been normalized or sequenced. See {@link text} */ - get original(): string { + get original() { return this.#original; } @@ -643,7 +640,7 @@ export class Cluster extends Node { * @description * If created via the `Text` class, there should always be a syllable. */ - get syllable(): Syllable | null { + get syllable() { return this.#syllable; } @@ -666,13 +663,13 @@ export class Cluster extends Node { * // ["֑", "֔"] * ``` */ - get taamim(): Taam[] { + get taamim() { if (this.#taamimCache) { return this.#taamimCache; } const taamimChars = this.chars.reduce((a, char) => { - if (char.isTaamim && this.isCharTaam(char.text)) { + if (char.isTaamim && this.#isCharTaam(char.text)) { a.push(char.text); } @@ -693,14 +690,14 @@ export class Cluster extends Node { * // ['ETNAHTA', 'ZAQEF_QATAN' ] * ``` */ - get taamimNames(): TaamimName[] { + get taamimNames() { if (this.#taamimNamesCache) { return this.#taamimNamesCache; } const taaminNames = this.chars.reduce((a, char) => { const text = char.text; - if (char.isTaamim && this.isCharTaam(text)) { + if (char.isTaamim && this.#isCharTaam(text)) { a.push(charToNameMap[text]); } @@ -731,7 +728,7 @@ export class Cluster extends Node { * @description * The text has been normalized and sequenced — see {@link original} for text passed in the constructor. */ - get text(): string { + get text() { return this.chars.reduce((init, char) => init + char.text, ""); } @@ -752,13 +749,13 @@ export class Cluster extends Node { * According to [Syllabification](/guides/syllabification), a sheva is a vowel and serves as the nucleus of a syllable. * Because `Cluster` is concerned with orthography, a sheva is **not** a vowel character */ - get vowelNames(): VowelName[] { + get vowelNames() { if (this.#vowelNamesCache) { return this.#vowelNamesCache; } const vowelNames = this.chars.reduce((a, char) => { - if (char.isVowel && this.isCharVowel(char.text)) { + if (char.isVowel && this.#isCharVowel(char.text)) { a.push(charToNameMap[char.text]); } @@ -787,14 +784,14 @@ export class Cluster extends Node { * According to [Syllabification](/guides/syllabification), a sheva is a vowel and serves as the nucleus of a syllable. * Because `Cluster` is concerned with orthography, a sheva is **not** a vowel character */ - get vowels(): Vowel[] { + get vowels() { if (this.#vowelsCache) { return this.#vowelsCache; } const vowels = this.chars.reduce((a, char) => { const text = char.text; - if (char.isVowel && this.isCharVowel(text)) { + if (char.isVowel && this.#isCharVowel(text)) { a.push(text); } return a; diff --git a/src/syllable.ts b/src/syllable.ts index 8ce2f4f..7dd5713 100644 --- a/src/syllable.ts +++ b/src/syllable.ts @@ -1,4 +1,3 @@ -import { Char } from "./char"; import { Cluster } from "./cluster"; import { Node } from "./node"; import type { ConsonantName, Flip, TaamimName } from "./utils/charMap"; @@ -70,7 +69,7 @@ export class Syllable extends Node { this.#isFinal = isFinal; } - private isCharKeyOfSyllableVowelCharToNameMap(char: string): char is keyof SyllableVowelCharToNameMap { + #isCharKeyOfSyllableVowelCharToNameMap(char: string): char is keyof SyllableVowelCharToNameMap { return char in sylVowelCharToNameMap; } @@ -91,7 +90,7 @@ export class Syllable extends Node { * // ] * ``` */ - get chars(): Char[] { + get chars() { return this.clusters.map((cluster) => cluster.chars).flat(); } @@ -110,7 +109,7 @@ export class Syllable extends Node { * // ] * ``` */ - get clusters(): Cluster[] { + get clusters() { return this.#clusters; } @@ -126,7 +125,7 @@ export class Syllable extends Node { * // "ם" * ``` */ - get coda(): string { + get coda() { return this.structure()[2]; } @@ -144,7 +143,7 @@ export class Syllable extends Node { * // "" * ``` */ - get codaWithGemination(): string { + get codaWithGemination() { return this.structure(true)[2]; } @@ -207,7 +206,7 @@ export class Syllable extends Node { * @remarks * This checks if the syllable contains the given consonant name, even if the character is not a phonemic consonant. */ - hasConsonantName(name: ConsonantName): boolean { + hasConsonantName(name: ConsonantName) { if (!consonantNameToCharMap[name]) { throw new Error(`${name} is not a valid value`); } @@ -242,7 +241,7 @@ export class Syllable extends Node { * Unlike `Cluster`, a `Syllable` is concerned with linguistics, so a sheva **is** a vowel character. * It returns `true` for "SHEVA" only when the sheva is the vowel (i.e. a vocal sheva or sheva na'). */ - hasVowelName(name: SyllableVowelName): boolean { + hasVowelName(name: SyllableVowelName) { if (!sylVowelNameToCharMap[name]) { throw new Error(`${name} is not a valid value`); } @@ -266,7 +265,7 @@ export class Syllable extends Node { * Note: it only checks according to the character name, not its semantic meaning. * E.g. "כֵֽן׃" would be `true` when checking for `"METEG"`, not silluq */ - hasTaamName(name: TaamimName): boolean { + hasTaamName(name: TaamimName) { if (!taamimNameToCharMap[name]) { throw new Error(`${name} is not a valid value`); } @@ -290,7 +289,7 @@ export class Syllable extends Node { * @remarks * An accented syllable receives stress, and is typically indicated by the presence of a taam character */ - get isAccented(): boolean { + get isAccented() { return this.#isAccented; } @@ -321,7 +320,7 @@ export class Syllable extends Node { * @remarks * A closed syllable in Hebrew is a CVC or CVCC type, a mater letter does not close a syllable */ - get isClosed(): boolean { + get isClosed() { return this.#isClosed; } @@ -349,7 +348,7 @@ export class Syllable extends Node { * // true * ``` */ - get isFinal(): boolean { + get isFinal() { return this.#isFinal; } @@ -376,7 +375,7 @@ export class Syllable extends Node { * @remarks * The nucleus is the vowel of the syllable - present in every syllable and containing its {@link vowel} (with any materes lecticonis) or a shureq. */ - get nucleus(): string { + get nucleus() { return this.structure()[1]; } @@ -394,7 +393,7 @@ export class Syllable extends Node { * @remarks * The onset is any initial consonant of the syllable - present in every syllable except those containing a except word-initial shureq or a furtive patah. */ - get onset(): string { + get onset() { return this.structure()[0]; } @@ -581,7 +580,7 @@ export class Syllable extends Node { * @remarks * This returns a string that has been built up from the .text of its constituent Clusters. */ - get text(): string { + get text() { return this.clusters.map((c) => c.text).join(""); } @@ -604,7 +603,7 @@ export class Syllable extends Node { * According to [Syllabification](/guides/syllabification), a sheva is a vowel and serves as the nucleus of a syllable. * Unlike `Cluster`, a `Syllable` is concerned with linguistics, so a sheva **is** a vowel character. */ - get vowelNames(): SyllableVowelName[] { + get vowelNames() { if (this.#vowelNamesCache) { return this.#vowelNamesCache; } @@ -640,7 +639,7 @@ export class Syllable extends Node { * According to [Syllabification](/guides/syllabification), a sheva is a vowel and serves as the nucleus of a syllable. * Unlike `Cluster`, a `Syllable` is concerned with linguistics, so a sheva **is** a vowel character */ - get vowels(): SyllableVowel[] { + get vowels() { if (this.#vowelsCache) { return this.#vowelsCache; } @@ -653,7 +652,7 @@ export class Syllable extends Node { .replace(shureq, shureqPresentation) .split("") .reduce((a, v) => { - if (this.isCharKeyOfSyllableVowelCharToNameMap(v)) { + if (this.#isCharKeyOfSyllableVowelCharToNameMap(v)) { a.push(v); } if (v === shureqPresentation) { @@ -679,7 +678,7 @@ export class Syllable extends Node { * // } * ``` */ - get word(): Word | null { + get word() { return this.#word; } diff --git a/src/text.ts b/src/text.ts index e473f27..ab6d470 100644 --- a/src/text.ts +++ b/src/text.ts @@ -1,6 +1,3 @@ -import { Char } from "./char"; -import { Cluster } from "./cluster"; -import { Syllable } from "./syllable"; import { holemWaw } from "./utils/holemWaw"; import { convertsQametsQatan } from "./utils/qametsQatan"; import { splitGroup, taamim, taamimCaptureGroup } from "./utils/regularExpressions"; @@ -346,7 +343,7 @@ export interface SylOpts { */ export class Text { #original: string; - private options: SylOpts; + #options: SylOpts; /** * Cache for {@link SylOpts.ketivQeres} * @@ -363,7 +360,7 @@ export class Text { * * The cache will miss because `הִוא֙` and `הִֽוא׃` are not exact matches, even though `ignoreTaamim` is `true`. */ - private ketivQereCache: { [k: string]: string } = {}; + #ketivQereCache: { [k: string]: string } = {}; /** * `Text` requires an input string, @@ -374,11 +371,11 @@ export class Text { * @param options syllabification options */ constructor(text: string, options: SylOpts = {}) { - this.options = this.setOptions(options); - this.#original = this.options.allowNoNiqqud ? text : this.validateInput(text); + this.#options = this.#setOptions(options); + this.#original = this.#options.allowNoNiqqud ? text : this.#validateInput(text); } - private applyKetivQere = (text: string, kq: KetivQere) => { + #applyKetivQere(text: string, kq: KetivQere) { if (kq.input instanceof RegExp) { const match = text.match(kq.input); if (match) { @@ -391,45 +388,45 @@ export class Text { } return null; - }; + } - private captureTaamim = (text: string) => { - return text.matchAll(Text.taamimCaptureGroup); - }; + #captureTaamim(text: string): IterableIterator { + return text.matchAll(Text.#taamimCaptureGroup); + } - private processKetivQeres = (text: string) => { - if (this.ketivQereCache[text]) { - return this.ketivQereCache[text]; + #processKetivQeres(text: string) { + if (this.#ketivQereCache[text]) { + return this.#ketivQereCache[text]; } - const ketivQeres = this.options.ketivQeres; + const ketivQeres = this.#options.ketivQeres; if (!ketivQeres?.length) { return text; } for (const ketivQere of ketivQeres) { - const textWithoutTaamim = ketivQere.ignoreTaamim ? this.removeTaamim(text) : text; + const textWithoutTaamim = ketivQere.ignoreTaamim ? this.#removeTaamim(text) : text; - const appliedKetivQere = this.applyKetivQere(textWithoutTaamim, ketivQere); + const appliedKetivQere = this.#applyKetivQere(textWithoutTaamim, ketivQere); if (!appliedKetivQere) { return text; } - const taamimChars = ketivQere.captureTaamim ? this.captureTaamim(text) : null; + const taamimChars = ketivQere.captureTaamim ? this.#captureTaamim(text) : null; - const newText = taamimChars ? this.setTaamim(appliedKetivQere, taamimChars) : appliedKetivQere; + const newText = taamimChars ? this.#setTaamim(appliedKetivQere, taamimChars) : appliedKetivQere; - this.ketivQereCache[text] = newText; + this.#ketivQereCache[text] = newText; return newText; } return text; - }; + } - private validateInput(text: string): string { + #validateInput(text: string) { const niqqud = /[\u{05B0}-\u{05BC}\u{05C7}]/u; if (!niqqud.test(text)) { throw new Error("Text must contain niqqud"); @@ -437,7 +434,7 @@ export class Text { return text; } - private validateKetivQeres(ketivQeres: SylOpts["ketivQeres"]) { + #validateKetivQeres(ketivQeres: SylOpts["ketivQeres"]) { // if it's undefined, it's fine if (!ketivQeres) { return true; @@ -480,7 +477,7 @@ export class Text { return true; } - private validateOptions(options: SylOpts): SylOpts { + #validateOptions(options: SylOpts) { const validOpts = [ "allowNoNiqqud", "article", @@ -499,7 +496,7 @@ export class Text { throw new Error(`${k} is not a valid option`); } if (k === "ketivQeres") { - this.validateKetivQeres(v as SylOpts["ketivQeres"]); + this.#validateKetivQeres(v as SylOpts["ketivQeres"]); continue; } if (k === "holemHaser" && !["update", "preserve", "remove"].includes(String(v))) { @@ -512,12 +509,12 @@ export class Text { return options; } - private removeTaamim = (text: string) => { + #removeTaamim(text: string) { return text.replace(taamim, ""); - }; + } - private setOptions(options: SylOpts): SylOpts { - const validOpts = this.validateOptions(options); + #setOptions(options: SylOpts) { + const validOpts = this.#validateOptions(options); return { allowNoNiqqud: validOpts.allowNoNiqqud ?? false, article: validOpts.article ?? true, @@ -538,28 +535,28 @@ export class Text { }; } - private setTaamim(newText: string, taamimCapture: ReturnType) { + #setTaamim(newText: string, taamimCapture: IterableIterator) { return [...taamimCapture].reduce((text, group) => { return text.slice(0, group.index) + group[1] + text.slice(group.index); }, newText); } - private static get taamimCaptureGroup() { + static get #taamimCaptureGroup() { return taamimCaptureGroup; } - private get normalized(): string { + get #normalized() { return this.original.normalize("NFKD"); } - private get sanitized(): string { - const text = this.normalized.trim(); + get #sanitized() { + const text = this.#normalized.trim(); const sequencedChar = sequence(text).flat(); const sequencedText = sequencedChar.reduce((a, c) => a + c.text, ""); // split text at spaces and maqqef, spaces are added to the array as separate entries const textArr = sequencedText.split(splitGroup).filter((group) => group); - const mapQQatan = this.options.qametsQatan ? textArr.map(convertsQametsQatan) : textArr; - const mapHolemWaw = mapQQatan.map((w) => holemWaw(w, this.options)); + const mapQQatan = this.#options.qametsQatan ? textArr.map(convertsQametsQatan) : textArr; + const mapHolemWaw = mapQQatan.map((w) => holemWaw(w, this.#options)); return mapHolemWaw.join(""); } @@ -578,7 +575,7 @@ export class Text { * // ] * ``` */ - get chars(): Char[] { + get chars() { return this.clusters.map((cluster) => cluster.chars).flat(); } @@ -596,7 +593,7 @@ export class Text { * // ] * ``` */ - get clusters(): Cluster[] { + get clusters() { return this.syllables.map((syllable) => syllable.clusters).flat(); } @@ -608,7 +605,7 @@ export class Text { * @remarks * The original string passed to the constructor that has not been normalized or sequenced. See {@link text} */ - get original(): string { + get original() { return this.#original; } @@ -627,7 +624,7 @@ export class Text { * // ] * ``` */ - get syllables(): Syllable[] { + get syllables() { return this.words.map((word) => word.syllables).flat(); } @@ -644,7 +641,7 @@ export class Text { * // וַתָּשׇׁב * ``` */ - get text(): string { + get text() { return this.words.reduce((a, c) => `${a}${c.text}${c.whiteSpaceAfter ?? ""}`, ""); } @@ -660,12 +657,12 @@ export class Text { * // [ Word { original: "הֲבָרֹות" } ] * ``` */ - get words(): Word[] { - const split = this.sanitized.split(splitGroup); + get words() { + const split = this.#sanitized.split(splitGroup); const groups = split.filter((group) => group); const words = groups.map((original) => { - const word = this.processKetivQeres(original); - return new Word(word, this.options, word !== original ? original : undefined); + const word = this.#processKetivQeres(original); + return new Word(word, this.#options, word !== original ? original : undefined); }); const [first, ...rest] = words; first.siblings = rest; diff --git a/src/utils/divineName.ts b/src/utils/divineName.ts deleted file mode 100644 index 92644ec..0000000 --- a/src/utils/divineName.ts +++ /dev/null @@ -1,9 +0,0 @@ -const nonChars = /[^\u{05D0}-\u{05F4}]/gu; - -export const isDivineName = (text: string): boolean => { - return text.replace(nonChars, "") === "יהוה"; -}; - -export const hasDivineName = (text: string): boolean => { - return /יהוה/.test(text.replace(nonChars, "")); -}; diff --git a/src/word.ts b/src/word.ts index ed799c8..99e7296 100644 --- a/src/word.ts +++ b/src/word.ts @@ -1,11 +1,9 @@ -import { Char } from "./char"; import { Cluster } from "./cluster"; import { Node } from "./node"; import type { SyllableVowelName } from "./syllable"; import { Syllable } from "./syllable"; import { SylOpts } from "./text"; import type { ConsonantName, TaamimName } from "./utils/charMap"; -import { hasDivineName, isDivineName } from "./utils/divineName"; import { clusterSplitGroup, jerusalemTest } from "./utils/regularExpressions"; import { syllabify } from "./utils/syllabifier"; @@ -13,6 +11,8 @@ import { syllabify } from "./utils/syllabifier"; * A subunit of a {@link Text} consisting of words, which are strings are text separated by spaces or maqqefs. */ export class Word extends Node { + /** A regex for removing anything that is not a character */ + #nonCharacters = /[^\u{05D0}-\u{05F4}]/gu; #text: string; #original: string; /** @@ -75,7 +75,19 @@ export class Word extends Node { * ``` */ whiteSpaceAfter: string | null; - private sylOpts: SylOpts; + #sylOpts: SylOpts; + + constructor(text: string, sylOpts: SylOpts, original?: string) { + super(); + this.value = this; + this.#text = text; + this.#original = original ?? text; + const startMatch = text.match(/^\s*/g); + const endMatch = text.match(/\s*$/g); + this.whiteSpaceBefore = startMatch ? startMatch[0] : null; + this.whiteSpaceAfter = endMatch ? endMatch[0] : null; + this.#sylOpts = sylOpts; + } /** * @@ -84,7 +96,7 @@ export class Word extends Node { * @remarks * Splits a word at each consonant or the punctuation character, Sof Pasuq and Nun Hafukha */ - private makeClusters = (word: string): Cluster[] => { + #makeClusters(word: string) { const match = word.match(jerusalemTest); /** * The Masoretic spelling of Jerusalem contains some idiosyncrasies, @@ -103,18 +115,6 @@ export class Word extends Node { }); } return word.split(clusterSplitGroup).map((group) => new Cluster(group)); - }; - - constructor(text: string, sylOpts: SylOpts, original?: string) { - super(); - this.value = this; - this.#text = text; - this.#original = original ?? text; - const startMatch = text.match(/^\s*/g); - const endMatch = text.match(/\s*$/g); - this.whiteSpaceBefore = startMatch ? startMatch[0] : null; - this.whiteSpaceAfter = endMatch ? endMatch[0] : null; - this.sylOpts = sylOpts; } /** @@ -136,7 +136,7 @@ export class Word extends Node { * // ] * ``` */ - get chars(): Char[] { + get chars() { return this.clusters.map((cluster) => cluster.chars).flat(); } @@ -157,8 +157,8 @@ export class Word extends Node { * // ] * ``` */ - get clusters(): Cluster[] { - const clusters = this.makeClusters(this.text); + get clusters() { + const clusters = this.#makeClusters(this.text); const firstCluster = clusters[0]; const remainder = clusters.slice(1); firstCluster.siblings = remainder; @@ -230,8 +230,8 @@ export class Word extends Node { * // true * ``` */ - get hasDivineName(): boolean { - return hasDivineName(this.text); + get hasDivineName() { + return /יהוה/.test(this.text.replace(this.#nonCharacters, "")); } /** @@ -250,7 +250,7 @@ export class Word extends Node { * Note: it only checks according to the character name, not its semantic meaning. * E.g. "כֵֽן׃" would be `true` when checking for `"METEG"`, not silluq */ - hasTaamName(name: TaamimName): boolean { + hasTaamName(name: TaamimName) { return this.syllables.some((syllable) => syllable.hasTaamName(name)); } @@ -280,7 +280,7 @@ export class Word extends Node { * According to [Syllabification](/guides/syllabification), a sheva is a vowel and serves as the nucleus of a syllable. * It returns `true` for "SHEVA" only when the sheva is the vowel (i.e. a vocal sheva or sheva na'). */ - hasVowelName(name: SyllableVowelName): boolean { + hasVowelName(name: SyllableVowelName) { return this.syllables.some((syllable) => syllable.hasVowelName(name)); } @@ -296,8 +296,8 @@ export class Word extends Node { * // true * ``` */ - get isDivineName(): boolean { - return isDivineName(this.text); + get isDivineName() { + return this.text.replace(this.#nonCharacters, "") === "יהוה"; } /** @@ -315,7 +315,7 @@ export class Word extends Node { * @remarks * If the word contains non-Hebrew characters, it is not considered Hebrew because syllabification is likely not correct. */ - get isNotHebrew(): boolean { + get isNotHebrew() { return !this.clusters.map((c) => c.isNotHebrew).includes(false); } @@ -334,7 +334,7 @@ export class Word extends Node { * @remarks * The construct state is indicated by the presence of a maqqef (U+05BE) character */ - get isInConstruct(): boolean { + get isInConstruct() { // if word has a maqqef, it is in construct return this.text.includes("\u05BE"); } @@ -369,14 +369,14 @@ export class Word extends Node { * // ] * ``` */ - get syllables(): Syllable[] { + get syllables() { if (/\w/.test(this.text) || this.isDivineName || this.isNotHebrew) { const syl = new Syllable(this.clusters); syl.word = this; return [syl]; } - const syllables = syllabify(this.clusters, this.sylOpts, this.isInConstruct); + const syllables = syllabify(this.clusters, this.#sylOpts, this.isInConstruct); syllables.forEach((syl) => (syl.word = this)); return syllables; @@ -431,7 +431,7 @@ export class Word extends Node { * // ] * ``` */ - get text(): string { + get text() { return this.#text.trim(); }