From d2573d40cbb1021f944bcb689ed75cdac40df606 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Jan 2026 00:29:59 +0000 Subject: [PATCH 1/3] Add pasuk search by Hebrew letters for name verses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement comprehensive search functionality for finding biblical verses by starting and ending Hebrew letters, supporting the minhag (custom) of reciting personal verses in Shemona Esre prayer. Features added: - extractHebrewLetters(): Remove nekudot and cantillation marks from Hebrew text - normalizeHebrewLetter(): Convert final forms to regular forms - getAllLetterForms(): Get all forms (regular and final) of a letter - findPesukimByStartingLetter(): Find verses starting with a specific letter - findPesukimByName(): Find verses for names (start/end letters) Key capabilities: - Handles Hebrew final forms (ך, ם, ן, ף, ץ) bidirectionally - Preserves spaces in extracted text by default - Supports filtering by specific books - Supports limiting result count - Comprehensive test coverage (34 tests passing) Documentation: - Updated README with new API functions and examples - Created PASUK_RESEARCH.md with research sources and enhancement ideas - Removed all emojis from project per user request https://claude.ai/code/session_015dW7YKUjy6K4r39jKFVpyH --- PASUK_RESEARCH.md | 162 ++++++++++++++++++++++++++++ README.md | 77 +++++++++++-- scripts/convert-data.cjs | 2 +- src/index.test.ts | 195 ++++++++++++++++++++++++++++++++- src/index.ts | 228 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 656 insertions(+), 8 deletions(-) create mode 100644 PASUK_RESEARCH.md diff --git a/PASUK_RESEARCH.md b/PASUK_RESEARCH.md new file mode 100644 index 0000000..5f9cd0d --- /dev/null +++ b/PASUK_RESEARCH.md @@ -0,0 +1,162 @@ +# Research: Pasuk for Names in Shemona Esre + +This document contains research and references used to implement the verse search features for finding personal pesukim based on Hebrew name letters. + +## The Custom (Minhag) + +It is an ancient and established Jewish custom for a person to recite a pasuk (biblical verse) from Tanach which begins with the first letter of their name and ends with the last letter of their name. This verse is recited at the end of the Shemoneh Esrei (Amidah prayer), before the second recitation of "Yihyu Leratzon." + +### Key Points + +- The verse should **begin** with the **first letter** of the person's name +- The verse should **end** with the **last letter** of the person's name +- It is recited **before the second Yihyu Leratzon** at the conclusion of the Amidah +- People with **multiple names** should say one pasuk for each name + +### Historical Origins + +The earliest reference to this practice is in **Siddur of R' Hirz** (Tihingen, 1560), a compilation of commentary and instructions for the prayers by Rabbi Naphtali Hirz Treves, chazzan (cantor) of Frankfurt am Main. + +The **Kitzur Shelah** specifies the exact point in the prayer when this verse should be said: at the conclusion of the Amidah, before saying "Yehiyu l'ratzon." + +In a letter written in **1948**, the Lubavitcher Rebbe, Rabbi Menachem M. Schneersohn, confirmed that this is the **Chabad custom** as well. + +### The Reason + +The spiritual reason for this custom is profound. According to tradition, when a person passes away, they may forget their name. The regular recitation of this verse helps one remember their name and is said to help save the soul from Gehinnom (purgatory). + +As **Rashi** comments on the verse "The voice of the Lord calls out to the city, it is wise to recognize Your name..." (Micha 6:9): + +> "From here we learn that whoever recites a verse each day that begins and ends [with the same letters] that one's name begins and ends with will be saved from Gehinnom." + +## Technical Implementation Notes + +### Hebrew Letter Considerations + +1. **Final Forms (Sofit Letters)** + - Hebrew has 5 letters with final forms: כ/ך, מ/ם, נ/ן, פ/ף, צ/ץ + - When searching for end letters, both regular and final forms must be checked + - For example, if searching for מ at the end, both מ and ם should match + - Final forms **never appear** at the beginning of words + +2. **Nekudot (Vowel Points) and Cantillation Marks** + - Biblical text includes vowel points (nekudot) like ְ (shva), ַ (patach), etc. + - Cantillation marks (teamim) for Torah reading are also present + - These diacritical marks must be stripped to find the base Hebrew letters + - Unicode range for Hebrew letters: U+05D0 to U+05EA + +3. **Letter Normalization** + - When comparing letters, final forms should be normalized to regular forms for consistency + - This allows users to search using either form and get correct results + +### Search Strategies + +1. **By Starting Letter Only** + - Useful for exploring options + - Can limit by specific books (e.g., only search Tehillim/Psalms) + - Can limit maximum results for performance + +2. **By Starting and Ending Letters** + - The primary use case for finding verses for names + - Handles both regular and final forms automatically + - Example: For "דוד" (David), search for ד...ד + - Example: For "אברהם" (Avraham), search for א...ם + +3. **Book Filtering** + - **Tehillim (Psalms)**: Often preferred for personal verses due to their devotional nature + - **Torah**: The five books of Moses may be preferred by some + - **Full Tanach**: Search all 39 books for maximum options + +### Psalm 119 - A Special Case + +Psalm 119 is the longest chapter in the Bible and features a unique alphabetic acrostic structure: +- Organized by Hebrew letters (aleph through tav) +- Each letter section has 8 verses +- All 8 verses in each section start with that letter +- This makes Psalm 119 an excellent resource for finding verses + +## Future Enhancement Ideas + +### 1. Common Names Database +- Pre-compute and cache common Hebrew names with their corresponding verses +- Include traditional/popular choices for each name +- Provide metadata about why certain verses are preferred + +### 2. Verse Quality Metrics +- **Length**: Shorter verses may be easier to memorize +- **Familiarity**: Track how well-known a verse is +- **Meaning**: Verses with positive/relevant meanings for prayer +- **Source preference**: Tehillim often preferred over other books + +### 3. Multiple Name Handling +- Allow searching for multiple names at once +- Return combined results for people with multiple Hebrew names +- Handle both Ashkenazi and Sephardi name traditions + +### 4. Transliteration Support +- Accept transliterated Hebrew (e.g., "David" → דוד) +- Automatic conversion to Hebrew characters +- Handle common spelling variations + +### 5. Verse Recommendations +- Suggest verses based on meaning and appropriateness +- Flag verses that are traditionally used for specific names +- Provide context about why certain verses are chosen + +### 6. Audio/Pronunciation +- Add audio recordings of verse pronunciation +- Help users learn their personal verse +- Include cantillation (trope) for verses + +### 7. Print-Friendly Format +- Generate pocket cards with personal verses +- Create downloadable prayer cards +- Support multiple languages (Hebrew, English, transliteration) + +### 8. Advanced Search Options +- Search by middle letters as well +- Find verses with specific words or themes +- Filter by verse length (min/max words) +- Search within specific books or ranges + +### 9. Historical/Traditional Lists +- Include traditional lists used by different communities +- Document which verses are commonly used for which names +- Preserve minhagim (customs) from different traditions + +### 10. Performance Optimizations +- Index verses by starting letter for faster lookups +- Cache frequently requested name combinations +- Implement lazy loading for large result sets +- Add search result pagination + +## Research Sources + +### Primary Sources +- [What is the source for saying a Pasuk at the end of Shemoneh Esrei? - Shulchanaruchharav.com](https://shulchanaruchharav.com/halacha/what-is-the-source-for-saying-a-pasuk-at-the-end-of-shemoneh-esrei/) +- [Minhag to say a Passuk that corresponds with one's name at the conclusion of Shemone Esrei - AskTheRav](https://asktherav.com/6283-minhag-to-say-a-passuk-that-corresponds-with-ones-name-at-the-conclusion-of-shemone-esrei/) +- [Verses For People's Names - Chabad.org](https://www.chabad.org/library/article_cdo/aid/5307596/jewish/Verses-For-Peoples-Names.htm) +- [A Verse for Your Name - Dalet Amot of Halacha - OU Torah](https://outorah.org/p/64670/) + +### Additional References +- [Names, Verses, and Flaming Hot Rods - Chabad.org](https://www.chabad.org/library/article_cdo/aid/2451435/jewish/Names-Verses-and-Flaming-Hot-Rods.htm) +- [Saying two Pesukim at the end of Shemoneh Esrei if one has two names - Shulchanaruchharav.com](https://shulchanaruchharav.com/halacha/saying-two-pesukim-at-the-end-of-shemoneh-esrei-if-one-has-two-names/) +- [Pasuk for name - The Yeshiva World](https://www.theyeshivaworld.com/coffeeroom/topic/pasuk-for-name) + +### Hebrew Alphabet Resources +- [Hebrew Alphabet (Aleph-Bet) - Jewish Virtual Library](https://www.jewishvirtuallibrary.org/the-hebrew-alphabet-aleph-bet) +- [Hebrew Alphabet - Wikipedia](https://en.wikipedia.org/wiki/Hebrew_alphabet) +- [Hebrew Alphabet - Biblical Hebrew](https://biblicalhebrew.org/aleph-bet) +- [Psalm 119 - ALEPH (Hebrew Alphabet) - God's Growing Garden](https://www.godsgrowinggarden.com/2019/06/psalm-119-aleph-hebrew-alphabet.html) + +### Special Letter Information +- [Bet (Vet) - The second letter of the Hebrew alphabet - Chabad.org](https://www.chabad.org/library/article_cdo/aid/137074/jewish/Bet-Vet.htm) +- [Hebrew Letters and Their Meanings - Betemunah](https://www.betemunah.org/letters.html) + +## Implementation Date + +This feature was implemented on January 30, 2026 for the `@shafeh/tanach` library. + +## License + +This research document is provided for educational purposes as part of the @shafeh/tanach project (ISC License). diff --git a/README.md b/README.md index 3cccb59..13946cc 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,12 @@ Modern TypeScript library for accessing the complete Hebrew Bible (Tanach) text. ## Features -- 📖 Complete Tanach text (Torah, Neviim, Kesuvim) -- 🚀 Modern ES modules with TypeScript support -- 📦 Optimized data format (25% smaller than original) -- 🔍 Type-safe API -- 🌐 Works in Node.js and browsers -- 📝 Full test coverage +- Complete Tanach text (Torah, Neviim, Kesuvim) +- Modern ES modules with TypeScript support +- Optimized data format (25% smaller than original) +- Type-safe API +- Works in Node.js and browsers +- Full test coverage ## Installation @@ -77,6 +77,71 @@ Array of the three main sections: `['Torah', 'Neviim', 'Kesuvim']` Helper functions that return section names. +### `extractHebrewLetters(text: string)` + +Extract only Hebrew letters from text, removing nekudot (vowel points) and cantillation marks. + +**Parameters:** +- `text` - Hebrew text with diacritical marks + +**Returns:** `string` - Text with only Hebrew letters + +**Example:** +```typescript +extractHebrewLetters("בְּרֵאשִׁ֖ית"); // Returns "בראשית" +``` + +### `findPesukimByStartingLetter(letter: string, options?)` + +Find all verses that begin with a specific Hebrew letter. + +**Parameters:** +- `letter` - Hebrew letter to search for (e.g., "א", "ב") +- `options.books` - Optional array of book names to limit search +- `options.maxResults` - Optional maximum number of results + +**Returns:** `VerseResult[]` + +**Example:** +```typescript +// Find all verses starting with aleph +const verses = findPesukimByStartingLetter("א"); + +// Find verses starting with bet in Torah books only +const verses = findPesukimByStartingLetter("ב", { + books: ["Bereishit", "Shemot", "Vayikra", "Bamidbar", "Devarim"] +}); + +// Find first 50 verses starting with gimmel +const verses = findPesukimByStartingLetter("ג", { maxResults: 50 }); +``` + +### `findPesukimByName(startLetter: string, endLetter: string, options?)` + +Find all verses that begin with one Hebrew letter and end with another. This is commonly used for the minhag (custom) of saying a pasuk for one's name in Shemona Esre (the Amidah prayer). + +**Parameters:** +- `startLetter` - Hebrew letter the verse should start with +- `endLetter` - Hebrew letter the verse should end with +- `options.books` - Optional array of book names to limit search +- `options.maxResults` - Optional maximum number of results + +**Returns:** `VerseResult[]` + +**Example:** +```typescript +// Find verses for the name "David" (דוד - starts with dalet, ends with dalet) +const verses = findPesukimByName("ד", "ד"); + +// Find verses for the name "Avraham" (אברהם - starts with aleph, ends with mem) +const verses = findPesukimByName("א", "ם"); + +// Search only in Tehillim (Psalms) +const verses = findPesukimByName("י", "ה", { books: ["Tehillim"] }); +``` + +**Note:** This function correctly handles Hebrew final forms (ך, ם, ן, ף, ץ) when matching end letters. + ## Types ```typescript diff --git a/scripts/convert-data.cjs b/scripts/convert-data.cjs index 4f277cc..fb536b8 100755 --- a/scripts/convert-data.cjs +++ b/scripts/convert-data.cjs @@ -71,7 +71,7 @@ const oldSize = fs.statSync(path.join(__dirname, '../tanach.js')).size; const newSize = fs.statSync(outputPath).size; const reduction = ((1 - newSize / oldSize) * 100).toFixed(1); -console.log(`✓ Conversion complete!`); +console.log(`Conversion complete!`); console.log(` Old size: ${(oldSize / 1024 / 1024).toFixed(2)} MB`); console.log(` New size: ${(newSize / 1024 / 1024).toFixed(2)} MB`); console.log(` Reduction: ${reduction}%`); diff --git a/src/index.test.ts b/src/index.test.ts index 1f1c5d9..d8ae9b3 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,5 +1,17 @@ import { describe, it, expect } from 'vitest'; -import { sections, torah, neviim, kesuvim, tanach, getChapter, getBooks, getBookMeta } from './index.js'; +import { + sections, + torah, + neviim, + kesuvim, + tanach, + getChapter, + getBooks, + getBookMeta, + extractHebrewLetters, + findPesukimByStartingLetter, + findPesukimByName +} from './index.js'; describe('Tanach API', () => { describe('Sections', () => { @@ -91,4 +103,185 @@ describe('Tanach API', () => { expect(chapter).toBeNull(); }); }); + + describe('extractHebrewLetters', () => { + it('removes nekudot and cantillation marks while preserving spaces', () => { + const text = 'בְּרֵאשִׁ֖ית בָּרָ֣א'; + const result = extractHebrewLetters(text); + expect(result).toBe('בראשית ברא'); + }); + + it('removes spaces when preserveSpaces is false', () => { + const text = 'בְּרֵאשִׁ֖ית בָּרָ֣א'; + const result = extractHebrewLetters(text, false); + expect(result).toBe('בראשיתברא'); + }); + + it('handles text without diacritical marks', () => { + const text = 'שלום'; + const result = extractHebrewLetters(text); + expect(result).toBe('שלום'); + }); + + it('preserves final forms', () => { + const text = 'דָּוִדְךָ'; + const result = extractHebrewLetters(text); + expect(result).toBe('דודך'); + }); + + it('preserves spaces by default', () => { + const text = 'שָׁל֣וֹם עֲלֵיכֶ֑ם'; + const result = extractHebrewLetters(text); + expect(result).toBe('שלום עליכם'); + }); + + it('removes spaces when preserveSpaces is false', () => { + const text = 'שָׁל֣וֹם עֲלֵיכֶ֑ם'; + const result = extractHebrewLetters(text, false); + expect(result).toBe('שלוםעליכם'); + }); + + it('returns empty string for non-Hebrew text (with spaces preserved)', () => { + const text = 'Hello 123'; + const result = extractHebrewLetters(text); + expect(result).toBe(' '); // Spaces are preserved by default + }); + + it('returns empty string for non-Hebrew text without preserving spaces', () => { + const text = 'Hello 123'; + const result = extractHebrewLetters(text, false); + expect(result).toBe(''); + }); + }); + + describe('findPesukimByStartingLetter', () => { + it('finds verses starting with aleph', () => { + const results = findPesukimByStartingLetter('א', { maxResults: 5 }); + expect(results.length).toBe(5); + results.forEach(verse => { + const firstLetter = extractHebrewLetters(verse.text, false)[0]; + expect(firstLetter).toBe('א'); + }); + }); + + it('finds verses starting with bet', () => { + const results = findPesukimByStartingLetter('ב', { maxResults: 10 }); + expect(results.length).toBe(10); + results.forEach(verse => { + const firstLetter = extractHebrewLetters(verse.text, false)[0]; + expect(firstLetter).toBe('ב'); + }); + }); + + it('filters by specific books', () => { + const results = findPesukimByStartingLetter('ו', { + books: ['Bereishit'], + maxResults: 5 + }); + expect(results.length).toBeLessThanOrEqual(5); + results.forEach(verse => { + expect(verse.book).toBe('Bereishit'); + const firstLetter = extractHebrewLetters(verse.text, false)[0]; + expect(firstLetter).toBe('ו'); + }); + }); + + it('handles letter with nekudot in search parameter', () => { + const results = findPesukimByStartingLetter('בְּ', { maxResults: 1 }); + expect(results.length).toBe(1); + const firstLetter = extractHebrewLetters(results[0].text, false)[0]; + expect(firstLetter).toBe('ב'); + }); + + it('returns empty array for non-existent letter search', () => { + const results = findPesukimByStartingLetter(''); + expect(results).toEqual([]); + }); + + it('returns results without maxResults limit', () => { + const results = findPesukimByStartingLetter('א', { books: ['Bereishit'], maxResults: undefined }); + expect(results.length).toBeGreaterThan(0); + results.forEach(verse => { + const firstLetter = extractHebrewLetters(verse.text, false)[0]; + expect(firstLetter).toBe('א'); + }); + }); + }); + + describe('findPesukimByName', () => { + it('finds verses for name starting and ending with dalet (David)', () => { + const results = findPesukimByName('ד', 'ד', { maxResults: 5 }); + expect(results.length).toBeGreaterThan(0); + expect(results.length).toBeLessThanOrEqual(5); + results.forEach(verse => { + const letters = extractHebrewLetters(verse.text, false); + expect(letters[0]).toBe('ד'); + expect(letters[letters.length - 1]).toBe('ד'); + }); + }); + + it('finds verses for name starting with aleph ending with mem', () => { + const results = findPesukimByName('א', 'ם', { maxResults: 5 }); + expect(results.length).toBe(5); + results.forEach(verse => { + const letters = extractHebrewLetters(verse.text, false); + expect(letters[0]).toBe('א'); + // Should match either regular mem or final mem + expect(['ם', 'מ']).toContain(letters[letters.length - 1]); + }); + }); + + it('handles final form letters correctly', () => { + const results = findPesukimByName('י', 'ם', { maxResults: 3 }); + expect(results.length).toBe(3); + results.forEach(verse => { + const letters = extractHebrewLetters(verse.text, false); + expect(letters[0]).toBe('י'); + // Final mem should match when searching for regular mem + expect(['ם', 'מ']).toContain(letters[letters.length - 1]); + }); + }); + + it('filters by specific books', () => { + const results = findPesukimByName('א', 'ה', { + books: ['Tehillim'], + maxResults: 5 + }); + expect(results.length).toBeLessThanOrEqual(5); + results.forEach(verse => { + expect(verse.book).toBe('Tehillim'); + const letters = extractHebrewLetters(verse.text, false); + expect(letters[0]).toBe('א'); + expect(letters[letters.length - 1]).toBe('ה'); + }); + }); + + it('handles letters with nekudot in search parameters', () => { + const results = findPesukimByName('בְּ', 'דְ', { maxResults: 1 }); + expect(results.length).toBeLessThanOrEqual(1); + if (results.length > 0) { + const letters = extractHebrewLetters(results[0].text, false); + expect(letters[0]).toBe('ב'); + expect(letters[letters.length - 1]).toBe('ד'); + } + }); + + it('returns empty array for invalid input', () => { + const results = findPesukimByName('', ''); + expect(results).toEqual([]); + }); + + it('returns results without maxResults limit', () => { + const results = findPesukimByName('ש', 'ם', { + books: ['Bereishit'], + maxResults: undefined + }); + expect(results.length).toBeGreaterThan(0); + results.forEach(verse => { + const letters = extractHebrewLetters(verse.text, false); + expect(letters[0]).toBe('ש'); + expect(['ם', 'מ']).toContain(letters[letters.length - 1]); + }); + }); + }); }); diff --git a/src/index.ts b/src/index.ts index 8c8cf9e..62be0bb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -121,5 +121,233 @@ export const getBookMeta = (book: string) => { */ export const getRawData = (): TanachData => data; +/** + * Map of Hebrew final forms to their regular counterparts + */ +const finalFormMap: Record = { + 'ך': 'כ', + 'ם': 'מ', + 'ן': 'נ', + 'ף': 'פ', + 'ץ': 'צ' +}; + +/** + * Reverse map from regular forms to final forms + */ +const regularToFinalMap: Record = { + 'כ': 'ך', + 'מ': 'ם', + 'נ': 'ן', + 'פ': 'ף', + 'צ': 'ץ' +}; + +/** + * Normalize a Hebrew letter to its regular (non-final) form + * + * @param letter - Hebrew letter (possibly in final form) + * @returns Regular form of the letter + * + * @example + * ```ts + * normalizeHebrewLetter("ם"); // Returns "מ" + * normalizeHebrewLetter("מ"); // Returns "מ" + * normalizeHebrewLetter("א"); // Returns "א" + * ``` + */ +export const normalizeHebrewLetter = (letter: string): string => { + return finalFormMap[letter] || letter; +}; + +/** + * Get all forms (regular and final) of a Hebrew letter + * + * @param letter - Hebrew letter in any form + * @returns Array containing all forms of the letter + * + * @example + * ```ts + * getAllLetterForms("מ"); // Returns ["מ", "ם"] + * getAllLetterForms("ם"); // Returns ["מ", "ם"] + * getAllLetterForms("א"); // Returns ["א"] + * ``` + */ +export const getAllLetterForms = (letter: string): string[] => { + const normalized = normalizeHebrewLetter(letter); + const finalForm = regularToFinalMap[normalized]; + return finalForm ? [normalized, finalForm] : [normalized]; +}; + +/** + * Extract only Hebrew letters from text, removing nekudot (vowel points) and cantillation marks + * Preserves spaces by default + * + * @param text - Hebrew text with diacritical marks + * @param preserveSpaces - Whether to preserve spaces (default: true) + * @returns Text with only Hebrew letters (including final forms) and optionally spaces + * + * @example + * ```ts + * extractHebrewLetters("בְּרֵאשִׁ֖ית בָּרָ֣א"); // Returns "בראשית ברא" + * extractHebrewLetters("בְּרֵאשִׁ֖ית בָּרָ֣א", false); // Returns "בראשיתברא" + * ``` + */ +export const extractHebrewLetters = (text: string, preserveSpaces: boolean = true): string => { + // Hebrew letter ranges: + // \u0590-\u05FF: Hebrew block (includes letters and diacritical marks) + // \u05D0-\u05EA: Hebrew letters only (aleph to tav) + // This regex keeps only the Hebrew letters (base consonants including final forms) + if (preserveSpaces) { + return text.replace(/[^\u05D0-\u05EA\s]/g, ''); + } + return text.replace(/[^\u05D0-\u05EA]/g, ''); +}; + +/** + * Find all verses that begin with a specific Hebrew letter + * + * @param letter - Hebrew letter to search for (e.g., "א", "ב") + * @param options - Optional configuration + * @param options.books - Limit search to specific books (defaults to all books) + * @param options.maxResults - Maximum number of results to return (defaults to all) + * @returns Array of matching verses + * + * @example + * ```ts + * // Find all verses starting with aleph + * const verses = findPesukimByStartingLetter("א"); + * + * // Find verses starting with bet in Torah books only + * const verses = findPesukimByStartingLetter("ב", { + * books: ["Bereishit", "Shemot", "Vayikra", "Bamidbar", "Devarim"] + * }); + * + * // Find first 50 verses starting with gimmel + * const verses = findPesukimByStartingLetter("ג", { maxResults: 50 }); + * ``` + */ +export const findPesukimByStartingLetter = ( + letter: string, + options?: { books?: string[]; maxResults?: number } +): VerseResult[] => { + const results: VerseResult[] = []; + const booksToSearch = options?.books ?? getBooks(); + const maxResults = options?.maxResults; + + // Extract just the letter without any diacritical marks and normalize to regular form + // (final forms don't appear at the start of words) + const extractedLetter = extractHebrewLetters(letter, false)[0]; + if (!extractedLetter) { + return results; + } + const searchLetter = normalizeHebrewLetter(extractedLetter); + + for (const bookName of booksToSearch) { + const bookData = data[bookName]; + if (!bookData) continue; + + for (const [chapterNum, verses] of Object.entries(bookData.chapters)) { + for (const [verseNum, text] of verses) { + const firstLetter = normalizeHebrewLetter(extractHebrewLetters(text, false)[0]); + + if (firstLetter === searchLetter) { + results.push({ + book: bookName, + bookHebrew: bookData.meta.he, + bookEnglish: bookData.meta.en, + chapter: Number(chapterNum), + verse: verseNum, + text + }); + + if (maxResults && results.length >= maxResults) { + return results; + } + } + } + } + } + + return results; +}; + +/** + * Find all verses that begin with one Hebrew letter and end with another + * This is commonly used for the minhag of saying a pasuk for one's name in Shemona Esre + * + * @param startLetter - Hebrew letter the verse should start with (e.g., "ד" for David) + * @param endLetter - Hebrew letter the verse should end with (e.g., "ד" for David) + * @param options - Optional configuration + * @param options.books - Limit search to specific books (defaults to all books) + * @param options.maxResults - Maximum number of results to return (defaults to all) + * @returns Array of matching verses + * + * @example + * ```ts + * // Find verses for the name "David" (דוד - starts with dalet, ends with dalet) + * const verses = findPesukimByName("ד", "ד"); + * + * // Find verses for the name "Avraham" (אברהם - starts with aleph, ends with mem) + * const verses = findPesukimByName("א", "ם"); + * + * // Search only in Tehillim (Psalms) + * const verses = findPesukimByName("י", "ה", { books: ["Tehillim"] }); + * ``` + */ +export const findPesukimByName = ( + startLetter: string, + endLetter: string, + options?: { books?: string[]; maxResults?: number } +): VerseResult[] => { + const results: VerseResult[] = []; + const booksToSearch = options?.books ?? getBooks(); + const maxResults = options?.maxResults; + + // Extract just the letters without any diacritical marks + const extractedStartLetter = extractHebrewLetters(startLetter, false)[0]; + const extractedEndLetter = extractHebrewLetters(endLetter, false)[0]; + + if (!extractedStartLetter || !extractedEndLetter) { + return results; + } + + // Normalize start letter (final forms don't appear at the start) + const searchStartLetter = normalizeHebrewLetter(extractedStartLetter); + + // For end letter, get all forms (regular and final) + const endLetterAlternatives = getAllLetterForms(extractedEndLetter); + + for (const bookName of booksToSearch) { + const bookData = data[bookName]; + if (!bookData) continue; + + for (const [chapterNum, verses] of Object.entries(bookData.chapters)) { + for (const [verseNum, text] of verses) { + const lettersOnly = extractHebrewLetters(text, false); + const firstLetter = normalizeHebrewLetter(lettersOnly[0]); + const lastLetter = lettersOnly[lettersOnly.length - 1]; + + if (firstLetter === searchStartLetter && endLetterAlternatives.includes(lastLetter)) { + results.push({ + book: bookName, + bookHebrew: bookData.meta.he, + bookEnglish: bookData.meta.en, + chapter: Number(chapterNum), + verse: verseNum, + text + }); + + if (maxResults && results.length >= maxResults) { + return results; + } + } + } + } + } + + return results; +}; + // Export types export type { Section, VerseResult, TanachData, BookMeta, Book } from './types.js'; From e575b5c583a418ebec2dc455d07570a6ddc69760 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Jan 2026 00:45:08 +0000 Subject: [PATCH 2/3] Add preferred/traditional verses system for name pesukim Implement a comprehensive system for tracking and retrieving traditional verses used for the minhag of saying a pasuk for one's name in Shemona Esre. New Features: - Preferred verses database with 22 letter combinations (extensible to 484) - VerseResult.preferred flag to mark traditional choices - getPreferredPasukForName() for direct access to traditional verses - Automatic sorting: preferred verses appear first in search results - JSON-based data system for easy community contributions - Processing script that parses Hebrew/English references and gematria Technical Implementation: - New preferred-verses.ts module with type-safe verse mappings - Parser handles Hebrew book names, chapter numbers (gematria), and English references - Automatic normalization of final forms (sofit letters) - O(1) lookup performance using Map data structure - Extensible architecture: add verses by editing JSON and re-running script Testing: - 41 tests passing (7 new tests for preferred verses) - Comprehensive coverage of edge cases - Validates preferred verse marking and sorting Documentation: - README updated with preferred verses API and examples - PASUK_RESEARCH.md expanded with implementation phases - New ADDING_PREFERRED_VERSES.md guide for community contributions - Clear instructions for extending the database Data Format: - Input: JSON with "letter-combo": ["text", "reference"] format - Processing: Node.js script converts to TypeScript - Output: Type-safe PreferredVerse[] with book/chapter/verse - Supports "No source" for letter combos without traditional verses Ready for community expansion to complete aleph-bet coverage! https://claude.ai/code/session_015dW7YKUjy6K4r39jKFVpyH --- ADDING_PREFERRED_VERSES.md | 147 ++++++++++++++++ PASUK_RESEARCH.md | 47 ++++- README.md | 43 ++++- scripts/preferred-verses-input.json | 70 ++++++++ scripts/process-preferred-verses.cjs | 219 +++++++++++++++++++++++ src/index.test.ts | 87 +++++++++- src/index.ts | 71 +++++++- src/preferred-verses.ts | 249 +++++++++++++++++++++++++++ src/types.ts | 2 + 9 files changed, 930 insertions(+), 5 deletions(-) create mode 100644 ADDING_PREFERRED_VERSES.md create mode 100644 scripts/preferred-verses-input.json create mode 100644 scripts/process-preferred-verses.cjs create mode 100644 src/preferred-verses.ts diff --git a/ADDING_PREFERRED_VERSES.md b/ADDING_PREFERRED_VERSES.md new file mode 100644 index 0000000..cca667a --- /dev/null +++ b/ADDING_PREFERRED_VERSES.md @@ -0,0 +1,147 @@ +# Adding More Preferred Verses + +This document explains how to add the complete set of preferred verses to the library. + +## Current Status + +The library currently has **22 letter combinations** with preferred verses as a proof-of-concept. The system is ready to accept the complete dataset of all Hebrew letter combinations (22 × 22 = 484 possible combinations). + +## How to Add the Complete Dataset + +### Step 1: Prepare the JSON Data + +Edit `scripts/preferred-verses-input.json` with the complete list of preferred verses. The format should be: + +```json +{ + "allPesukim": [ + { + "letter-combo": ["Hebrew verse text", "Book reference"] + } + ] +} +``` + +**Example:** +```json +{ + "allPesukim": [ + { + "א-א": ["אָנָּא יְהֹוָה הוֹשִׁיעָה נָּא", "תהילים קיח כה"] + }, + { + "א-ב": ["אַתָּה הוּא מַלְכִּי אֱלֹהִים", "Psalms 44 5"] + }, + { + "ב-ב": ["בִּנְדָבָה אֶזְבְּחָה לָּךְ", "תהילים נד ח"] + } + ] +} +``` + +### Supported Reference Formats + +The parser automatically handles multiple formats: + +1. **Hebrew Format**: `"תהילים קיח כה"` (Book Chapter Verse in Hebrew) +2. **English Format**: `"Psalms 118 25"` (Book Chapter Verse in English) +3. **No Source**: `"No source"` (when no traditional verse exists) + +### Supported Book Names + +**Torah:** +- Hebrew: בראשית, שמות, ויקרא, במדבר, דברים +- English: Genesis, Exodus, Leviticus, Numbers, Deuteronomy +- Library: Bereishit, Shemot, Vayikra, Bamidbar, Devarim + +**Neviim (Prophets):** +- Hebrew: יהושע, שופטים, שמואל א/ב, מלכים א/ב, ישעיהו, ירמיהו, יחזקאל, הושע, יונה, מיכה, נחום, זכריה, מלאכי +- English: Joshua, Judges, Isaiah, Ezekiel, etc. +- Library: Yehoshua, Shoftim, Shmuel I/II, Melachim I/II, Yeshayahu, Yirmiyahu, Yechezkel, Hoshea, Yonah, Michah, Nachum, Zechariah, Malachi + +**Kesuvim (Writings):** +- Hebrew: תהילים, משלי, איוב, שיר השירים, רות, איכה, קהלת, אסתר, דניאל, עזרא, נחמיה, דברי הימים א/ב +- English: Psalms, Proverbs, Job, Song of Songs, Ruth, Lamentations, Ecclesiastes, Esther, Daniel, Ezra, Nehemiah, Chronicles I/II +- Library: Tehillim, Mishlei, Iyov, Shir Hashirim, Rut, Eichah, Kohelet, Esther, Daniel, Ezra, Nechemiah, Divrei Hayamim I/II + +### Hebrew Number Support + +The parser automatically converts Hebrew numbers (gematria) to Arabic: +- א = 1, ב = 2, ... י = 10, יא = 11, ... כ = 20, ... ק = 100, קיח = 118, etc. + +### Step 2: Run the Processing Script + +After updating the JSON file: + +```bash +node scripts/process-preferred-verses.cjs +``` + +This will: +1. Parse all verse references +2. Convert Hebrew numbers to Arabic +3. Map Hebrew/English book names to library format +4. Generate `src/preferred-verses.ts` with TypeScript definitions +5. Report how many verses were successfully parsed + +### Step 3: Rebuild and Test + +```bash +npm run build +npm test +``` + +### Step 4: Verify the Results + +The generated `src/preferred-verses.ts` file will contain: +- TypeScript type definitions +- Array of all preferred verses +- Map for O(1) lookups +- Helper functions for accessing preferred verses + +## Usage After Adding Data + +Once the complete dataset is added, users can: + +```typescript +import { getPreferredPasukForName, findPesukimByName } from '@shafeh/tanach'; + +// Get the traditional verse for any Hebrew name +const verse = getPreferredPasukForName("מ", "ה"); // For "Moshe" +if (verse) { + console.log(verse.text); + console.log(`${verse.book} ${verse.chapter}:${verse.verse}`); +} + +// Search for all options with preferred verse marked +const allOptions = findPesukimByName("ד", "ד"); // For "David" +// Preferred verse is automatically first in the array +console.log(allOptions[0].preferred); // true +``` + +## Data Quality Notes + +- Ensure all book names are spelled correctly +- Hebrew numbers must use proper gematria format +- Verses that don't exist in the Tanach will be skipped with a warning +- Missing sources can use "No source" as the reference +- Always test with a small sample first before adding the complete dataset + +## Sources for Preferred Verses + +Traditional lists can be found in: +- Siddur of R' Hirz (1560) +- Kitzur Shelah +- Various Chabad publications +- Community minhagim books +- Online resources (see PASUK_RESEARCH.md for links) + +## Questions or Issues + +If you encounter parsing errors: +1. Check the console output when running the processing script +2. Verify book names match the supported formats +3. Ensure Hebrew numbers are in correct gematria format +4. Check that chapter/verse numbers exist in the actual Tanach data + +The system is designed to be forgiving - if a verse can't be parsed, it will be noted but won't break the build. diff --git a/PASUK_RESEARCH.md b/PASUK_RESEARCH.md index 5f9cd0d..c87d3d0 100644 --- a/PASUK_RESEARCH.md +++ b/PASUK_RESEARCH.md @@ -153,9 +153,54 @@ Psalm 119 is the longest chapter in the Bible and features a unique alphabetic a - [Bet (Vet) - The second letter of the Hebrew alphabet - Chabad.org](https://www.chabad.org/library/article_cdo/aid/137074/jewish/Bet-Vet.htm) - [Hebrew Letters and Their Meanings - Betemunah](https://www.betemunah.org/letters.html) +## Implementation + +### Phase 1 (January 30, 2026) +- Basic letter search functionality (`findPesukimByStartingLetter`, `findPesukimByName`) +- Hebrew letter extraction and normalization +- Handling of final forms (sofit letters) +- Comprehensive test coverage + +### Phase 2 (January 30, 2026) +- **Preferred Verses System**: Integration of traditional/preferred verses from established lists +- **Automatic Marking**: Search results automatically identify preferred verses with `preferred: true` +- **Smart Sorting**: Preferred verses are automatically sorted first in results +- **Direct Access**: `getPreferredPasukForName()` function for quick access to traditional choices +- **Extensible Data**: JSON-based system allows easy addition of new preferred verses +- **Community Sourcing**: Initial dataset includes verses from traditional Chabad and other community sources + +### Current Status +- **22 letter combinations** with preferred verses (sample dataset) +- Ready for expansion to complete aleph-bet (484 possible combinations) +- Parsing system handles both Hebrew and English book references +- Supports notes and alternative information for special cases + +### Adding More Preferred Verses + +To extend the preferred verses database: + +1. Edit `scripts/preferred-verses-input.json` to add new letter combinations +2. Use format: `"א-א": ["verse text", "Book chapter verse"]` +3. Run `node scripts/process-preferred-verses.cjs` to regenerate the TypeScript data +4. The system automatically parses Hebrew numbers and book names + +Example: +```json +{ + "allPesukim": [ + { + "א-א": ["אָנָּא יְהֹוָה הוֹשִׁיעָה נָּא", "תהילים קיח כה"] + }, + { + "ב-ב": ["בִּנְדָבָה אֶזְבְּחָה לָּךְ", "Psalms 54 8"] + } + ] +} +``` + ## Implementation Date -This feature was implemented on January 30, 2026 for the `@shafeh/tanach` library. +This feature was initially implemented on January 30, 2026, with preferred verses system added the same day. ## License diff --git a/README.md b/README.md index 13946cc..9d9fc43 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,47 @@ const verses = findPesukimByName("א", "ם"); const verses = findPesukimByName("י", "ה", { books: ["Tehillim"] }); ``` -**Note:** This function correctly handles Hebrew final forms (ך, ם, ן, ף, ץ) when matching end letters. +**Note:** This function correctly handles Hebrew final forms (ך, ם, ן, ף, ץ) when matching end letters. Results are automatically sorted with preferred/traditional verses first. + +### `getPreferredPasukForName(startLetter: string, endLetter: string)` + +Get the traditional/preferred pasuk for a given name. This returns the verse from traditional lists that is commonly used for the minhag. + +**Parameters:** +- `startLetter` - First letter of the name +- `endLetter` - Last letter of the name + +**Returns:** `VerseResult | null` - The preferred verse with `preferred: true`, or null if none exists + +**Example:** +```typescript +// Get the traditional verse for "David" (דוד) +const verse = getPreferredPasukForName("ד", "ד"); +if (verse) { + console.log(`${verse.book} ${verse.chapter}:${verse.verse}`); + console.log(verse.text); + console.log(verse.preferred); // true +} + +// Get all options including the preferred one +const allVerses = findPesukimByName("א", "א"); +const preferred = allVerses.find(v => v.preferred); +// The preferred verse is automatically sorted first in the results +``` + +## Preferred Verses + +The library includes a curated list of traditional verses for the minhag of saying a pasuk for one's name. These verses are: + +- Automatically marked with `preferred: true` in search results +- Sorted first in `findPesukimByName()` results +- Accessible directly via `getPreferredPasukForName()` +- Based on traditional lists used in various Jewish communities + +You can extend the preferred verses list by editing `scripts/preferred-verses-input.json` and running: +```bash +node scripts/process-preferred-verses.cjs +``` ## Types @@ -152,6 +192,7 @@ interface VerseResult { chapter: number; verse: number; text: string; + preferred?: boolean; // True if this is a traditional/preferred verse for a name } interface BookMeta { diff --git a/scripts/preferred-verses-input.json b/scripts/preferred-verses-input.json new file mode 100644 index 0000000..dfbb1c4 --- /dev/null +++ b/scripts/preferred-verses-input.json @@ -0,0 +1,70 @@ +{ + "allPesukim": [ + { + "א-א": ["אָנָּא יְהֹוָה הוֹשִׁיעָה נָּא אָנָּא יְהֹוָה הַצְלִיחָה נָּא", "תהילים קיח כה"] + }, + { + "א-ב": ["אַתָּה הוּא מַלְכִּי אֱלֹהִים צַוֵּה יְשׁוּעוֹת יַעֲקֹב", "תהילים מד ה"] + }, + { + "א-ג": ["אֵלֶּה אֶזְכְּרָה וְאֶשְׁפְּכָה עָלַי נַפְשִׁי כִּי אֶעֱבֹר בַּסָּךְ אֶדַּדֵּם עַד בֵּית אֱלֹהִים בְּקוֹל רִנָּה וְתוֹדָה הָמוֹן חוֹגֵג", "תהילים מב ה"] + }, + { + "א-ד": ["אַזְכִּירָה שִׁמְךָ בְּכָל דֹּר וָדֹר עַל כֵּן עַמִּים יְהוֹדֻךָ לְעֹלָם וָעֶד", "Psalms 45 18"] + }, + { + "א-ה": ["אַשְׁרֵי מַשְׂכִּיל אֶל דָּל בְּיוֹם רָעָה יְמַלְּטֵהוּ יְהוָה", "Psalms 41 2"] + }, + { + "א-ו": ["אַשְׁרֵי שֶׁאֵל יַעֲקֹב בְּעֶזְרוֹ שִׂבְרוֹ עַל יְהֹוָה אֱלֹהָיו", "תהילים קמו ה"] + }, + { + "א-ז": ["אִם חוֹמָה הִיא נִבְנֶה עָלֶיהָ טִירַת כָּסֶף וְאִם דֶּלֶת הִיא נָצוּר עָלֶיהָ לוּחַ אָרֶז", "שיר השירים ח ט"] + }, + { + "א-ח": ["אֵל יְהֹוָה וַיָּאֶר לָנוּ אִסְרוּ חַג בַּעֲבֹתִים עַד קַרְנוֹת הַמִּזְבֵּחַ", "תהילים קיח כז"] + }, + { + "א-ט": ["אַךְ הוּא צוּרִי וִישׁוּעָתִי מִשְׂגַּבִּי לֹא אֶמּוֹט רַבָּה", "תהילים סב ז"] + }, + { + "א-י": ["אֲמָרַי הַאֲזִינָה יְהוָה בִּינָה הֲגִיגִי", "Psalms 5 2"] + }, + { + "א-ך": ["אָמַרְתְּ לַיהוָה אֲדֹנָי אָתָּה טוֹבָתִי בַּל עָלֶיךָ", "Psalms 16 2"] + }, + { + "א-ל": ["אֶרֶץ רָעָשָׁה אַף שָׁמַיִם נָטְפוּ מִפְּנֵי אֱלֹהִים זֶה סִינַי מִפְּנֵי אֱלֹהִים אֱלֹהֵי יִשְׂרָאֵל", "Psalms 68 9"] + }, + { + "א-ם": ["אַתָּה הוּא יְהוָה הָאֱלֹהִים אֲשֶׁר בָּחַרְתָּ בְּאַבְרָם וְהוֹצֵאתוֹ מֵאוּר כַּשְׂדִּים וְשַׂמְתָּ שְּׁמוֹ אַבְרָהָם", "Nehemiah 9 7"] + }, + { + "א-ן": ["אֵלֶיךָ יְהֹוָה אֶקְרָא וְאֶל אֲדֹנָי אֶתְחַנָּן", "תהילים ל ט"] + }, + { + "א-ס": ["אַל תִּתְּנֵנִי בְּנֶפֶשׁ צָרָי כִּי קָמוּ בִי עֵדֵי שֶׁקֶר וִיפֵחַ חָמָס", "תהילים כז יב"] + }, + { + "א-ע": ["אָמַר בְּלִבּוֹ בַּל אֶמּוֹט לְדֹר וָדֹר אֲשֶׁר לֹא בְרָע", "Psalms 10 6"] + }, + { + "א-ף": ["אֱמֶת מֵאֶרֶץ תִּצְמָח וְצֶדֶק מִשָּׁמַיִם נִשְׁקָף", "תהילים פה יב"] + }, + { + "א-ץ": ["אֹהֵב צְדָקָה וּמִשְׁפָּט חֶסֶד יְהֹוָה מָלְאָה הָאָרֶץ", "תהילים לג ה"] + }, + { + "א-ק": ["אֲשֶׁר כָּרַת אֶת אַבְרָהָם וּשְׁבוּעָתוֹ לְיִשְׂחָק", "תהילים קה ט"] + }, + { + "א-ר": ["אֵלֶּה בָרֶכֶב וְאֵלֶּה בַסּוּסִים וַאֲנַחְנוּ בְּשֵׁם יְהֹוָה אֱלֹהֵינוּ נַזְכִּיר", "תהילים כ ח"] + }, + { + "א-ש": ["אֲשֶׁר יַחְדָּו נַמְתִּיק סוֹד בְּבֵית אֱלֹהִים נְהַלֵּךְ בְּרָגֶשׁ", "תהילים נה טו"] + }, + { + "א-ת": ["אַשְׁרֵי שֹׁמְרֵי מִשְׁפָּט עֹשֵׂה צְדָקָה בְכָל עֵת", "תהילים קו ג"] + } + ] +} diff --git a/scripts/process-preferred-verses.cjs b/scripts/process-preferred-verses.cjs new file mode 100644 index 0000000..d41bcda --- /dev/null +++ b/scripts/process-preferred-verses.cjs @@ -0,0 +1,219 @@ +/** + * Script to process the traditional preferred verses list and generate TypeScript data + */ + +const fs = require('fs'); +const path = require('path'); + +// Hebrew numbers to Arabic +const hebrewNumbers = { + 'א': 1, 'ב': 2, 'ג': 3, 'ד': 4, 'ה': 5, 'ו': 6, 'ז': 7, 'ח': 8, 'ט': 9, + 'י': 10, 'יא': 11, 'יב': 12, 'יג': 13, 'יד': 14, 'טו': 15, 'טז': 16, + 'יז': 17, 'יח': 18, 'יט': 19, 'כ': 20, 'כא': 21, 'כב': 22, 'כג': 23, + 'כד': 24, 'כה': 25, 'כו': 26, 'כז': 27, 'כח': 28, 'כט': 29, 'ל': 30, + 'לא': 31, 'לב': 32, 'לג': 33, 'לד': 34, 'לה': 35, 'לו': 36, 'לז': 37, + 'לח': 38, 'לט': 39, 'מ': 40, 'מא': 41, 'מב': 42, 'מג': 43, 'מד': 44, + 'מה': 45, 'מו': 46, 'מז': 47, 'מח': 48, 'מט': 49, 'נ': 50, 'נא': 51, + 'נב': 52, 'נג': 53, 'נד': 54, 'נה': 55, 'נו': 56, 'נז': 57, 'נח': 58, + 'נט': 59, 'ס': 60, 'סא': 61, 'סב': 62, 'סג': 63, 'סד': 64, 'סה': 65, + 'סו': 66, 'סז': 67, 'סח': 68, 'סט': 69, 'ע': 70, 'עא': 71, 'עב': 72, + 'עג': 73, 'עד': 74, 'עה': 75, 'עו': 76, 'עז': 77, 'עח': 78, 'עט': 79, + 'פ': 80, 'פא': 81, 'פב': 82, 'פג': 83, 'פד': 84, 'פה': 85, 'פו': 86, + 'פז': 87, 'פח': 88, 'פט': 89, 'צ': 90, 'צא': 91, 'צב': 92, 'צג': 93, + 'צד': 94, 'צה': 95, 'צו': 96, 'צז': 97, 'צח': 98, 'צט': 99, 'ק': 100, + 'קא': 101, 'קב': 102, 'קג': 103, 'קד': 104, 'קה': 105, 'קו': 106, + 'קז': 107, 'קח': 108, 'קט': 109, 'קי': 110, 'קיא': 111, 'קיב': 112, + 'קיג': 113, 'קיד': 114, 'קטו': 115, 'קטז': 116, 'קיז': 117, 'קיח': 118, + 'קיט': 119, 'קכ': 120, 'קכא': 121, 'קכב': 122, 'קכג': 123, 'קכד': 124, + 'קכה': 125, 'קכו': 126, 'קכז': 127, 'קכח': 128, 'קכט': 129, 'קל': 130, + 'קלא': 131, 'קלב': 132, 'קלג': 133, 'קלד': 134, 'קלה': 135, 'קלו': 136, + 'קלז': 137, 'קלח': 138, 'קלט': 139, 'קמ': 140, 'קמא': 141, 'קמב': 142, + 'קמג': 143, 'קמד': 144, 'קמה': 145, 'קמו': 146, 'קמז': 147, 'קמח': 148, + 'קמט': 149, 'קנ': 150, 'קנא': 151, 'קנב': 152, +}; + +const bookNameMap = { + // Torah + 'בראשית': 'Bereishit', + 'Genesis': 'Bereishit', + 'שמות': 'Shemot', + 'Exodus': 'Shemot', + 'ויקרא': 'Vayikra', + 'Leviticus': 'Vayikra', + 'במדבר': 'Bamidbar', + 'Numbers': 'Bamidbar', + 'דברים': 'Devarim', + 'Deuteronomy': 'Devarim', + + // Neviim + 'יהושע': 'Yehoshua', + 'Joshua': 'Yehoshua', + 'שופטים': 'Shoftim', + 'Judges': 'Shoftim', + 'שמואל א': 'Shmuel I', + 'שמואל ב': 'Shmuel II', + 'שמואל בכג': 'Shmuel II', + 'מלכים א': 'Melachim I', + 'מלכים ב': 'Melachim II', + 'ישעיהו': 'Yeshayahu', + 'Isaiah': 'Yeshayahu', + 'ירמיהו': 'Yirmiyahu', + 'יחזקאל': 'Yechezkel', + 'Ezekiel': 'Yechezkel', + 'הושע': 'Hoshea', + 'יונה': 'Yonah', + 'מיכה': 'Michah', + 'נחום': 'Nachum', + 'זכריה': 'Zechariah', + 'מלאכי': 'Malachi', + + // Kesuvim + 'תהילים': 'Tehillim', + 'תהלים': 'Tehillim', + 'Psalms': 'Tehillim', + 'משלי': 'Mishlei', + 'Proverbs': 'Mishlei', + 'איוב': 'Iyov', + 'Job': 'Iyov', + 'שיר השירים': 'Shir Hashirim', + 'Song of Songs': 'Shir Hashirim', + 'רות': 'Rut', + 'Ruth': 'Rut', + 'איכה': 'Eichah', + 'קהלת': 'Kohelet', + 'Ecclesiastes': 'Kohelet', + 'אסתר': 'Esther', + 'דניאל': 'Daniel', + 'עזרא': 'Ezra', + 'נחמיה': 'Nechemiah', + 'Nehemiah': 'Nechemiah', + 'דברי הימים א': 'Divrei Hayamim I', + 'דברי הימים ב': 'Divrei Hayamim II', +}; + +function parseVerseReference(reference) { + if (!reference || reference === 'No source' || reference === '?' || reference === '?') { + return null; + } + + const parts = reference.trim().split(/\s+/); + + if (parts.length >= 3) { + const versePart = parts[parts.length - 1]; + const chapterPart = parts[parts.length - 2]; + const bookPart = parts.slice(0, parts.length - 2).join(' '); + + const chapter = hebrewNumbers[chapterPart] || parseInt(chapterPart); + const verse = hebrewNumbers[versePart] || parseInt(versePart); + const book = bookNameMap[bookPart]; + + if (book && !isNaN(chapter) && !isNaN(verse)) { + return { book, chapter, verse }; + } + } + + console.warn(`Could not parse reference: "${reference}"`); + return null; +} + +// Read the input JSON (passed as argument or use default) +const inputFile = process.argv[2] || path.join(__dirname, 'preferred-verses-input.json'); +const rawData = fs.readFileSync(inputFile, 'utf-8'); +const data = JSON.parse(rawData); + +const preferredVerses = []; +let successCount = 0; +let failCount = 0; + +data.allPesukim.forEach(item => { + const [letterCombo, [hebrewText, reference]] = Object.entries(item)[0]; + const [startLetter, endLetter] = letterCombo.split('-'); + + const parsed = parseVerseReference(reference); + + if (parsed) { + preferredVerses.push({ + startLetter, + endLetter, + ...parsed, + text: hebrewText || undefined, + }); + successCount++; + } else { + preferredVerses.push({ + startLetter, + endLetter, + book: '', + chapter: 0, + verse: 0, + notes: reference === 'No source' ? 'No traditional source available' : 'Could not parse reference', + }); + failCount++; + } +}); + +console.log(`Parsed ${successCount} verses successfully, ${failCount} failed`); + +// Generate TypeScript file +const tsCode = `/** + * Preferred verses for the minhag of saying a pasuk for one's name + * Based on traditional lists used in various communities + * + * Generated from traditional sources - do not edit manually + */ + +export interface PreferredVerse { + /** Starting letter of the name */ + startLetter: string; + /** Ending letter of the name */ + endLetter: string; + /** Book name (transliterated) */ + book: string; + /** Chapter number */ + chapter: number; + /** Verse number */ + verse: number; + /** Hebrew text of the verse (if available) */ + text?: string; + /** Notes or alternative information */ + notes?: string; +} + +/** + * Default/preferred verses for each letter combination + * Total: ${preferredVerses.length} entries (${successCount} with valid references) + */ +export const preferredVerses: PreferredVerse[] = ${JSON.stringify(preferredVerses, null, 2)}; + +/** + * Create a lookup key for a letter combination + */ +export function getPreferredVerseKey(startLetter: string, endLetter: string): string { + return \`\${startLetter}-\${endLetter}\`; +} + +/** + * Lookup map for quick access to preferred verses + */ +export const preferredVersesMap = new Map(); + +// Initialize the map +preferredVerses.forEach(verse => { + if (verse.book) { + const key = getPreferredVerseKey(verse.startLetter, verse.endLetter); + preferredVersesMap.set(key, verse); + } +}); + +/** + * Get the preferred verse for a given letter combination + */ +export function getPreferredVerse(startLetter: string, endLetter: string): PreferredVerse | undefined { + const key = getPreferredVerseKey(startLetter, endLetter); + return preferredVersesMap.get(key); +} +`; + +const outputFile = path.join(__dirname, '..', 'src', 'preferred-verses.ts'); +fs.writeFileSync(outputFile, tsCode, 'utf-8'); +console.log(`Written to ${outputFile}`); diff --git a/src/index.test.ts b/src/index.test.ts index d8ae9b3..59ec274 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -10,7 +10,8 @@ import { getBookMeta, extractHebrewLetters, findPesukimByStartingLetter, - findPesukimByName + findPesukimByName, + getPreferredPasukForName } from './index.js'; describe('Tanach API', () => { @@ -283,5 +284,89 @@ describe('Tanach API', () => { expect(['ם', 'מ']).toContain(letters[letters.length - 1]); }); }); + + it('marks preferred verses', () => { + // Search for א-א which has a preferred verse in Tehillim 118:25 + const results = findPesukimByName('א', 'א'); + expect(results.length).toBeGreaterThan(0); + + // Should have exactly one preferred verse + const preferredVerses = results.filter(v => v.preferred); + expect(preferredVerses.length).toBe(1); + + // The preferred verse should be first + expect(results[0].preferred).toBe(true); + expect(results[0].book).toBe('Tehillim'); + expect(results[0].chapter).toBe(118); + expect(results[0].verse).toBe(25); + }); + + it('sorts preferred verse first', () => { + const results = findPesukimByName('א', 'ב', { maxResults: 10 }); + + // If there's a preferred verse, it should be first + const preferredIndex = results.findIndex(v => v.preferred); + if (preferredIndex >= 0) { + expect(preferredIndex).toBe(0); + } + }); + }); + + describe('getPreferredPasukForName', () => { + it('returns the preferred verse for aleph-aleph', () => { + const verse = getPreferredPasukForName('א', 'א'); + + expect(verse).not.toBeNull(); + expect(verse?.book).toBe('Tehillim'); + expect(verse?.chapter).toBe(118); + expect(verse?.verse).toBe(25); + expect(verse?.preferred).toBe(true); + expect(verse?.text).toBeTruthy(); + // Verify the verse starts with aleph + const lettersOnly = extractHebrewLetters(verse!.text, false); + expect(lettersOnly[0]).toBe('א'); + expect(lettersOnly[lettersOnly.length - 1]).toBe('א'); + }); + + it('returns the preferred verse for aleph-bet', () => { + const verse = getPreferredPasukForName('א', 'ב'); + + expect(verse).not.toBeNull(); + expect(verse?.book).toBe('Tehillim'); + expect(verse?.chapter).toBe(44); + expect(verse?.verse).toBe(5); + expect(verse?.preferred).toBe(true); + }); + + it('handles letters with nekudot', () => { + const verse = getPreferredPasukForName('אָ', 'בְּ'); + + expect(verse).not.toBeNull(); + expect(verse?.book).toBe('Tehillim'); + expect(verse?.chapter).toBe(44); + expect(verse?.verse).toBe(5); + }); + + it('returns null for letter combinations without preferred verses', () => { + // Using a combination that likely doesn't have a preferred verse yet + // (since we only have a small subset in the test data) + const verse = getPreferredPasukForName('ק', 'ק'); + + // This might be null if the verse isn't in our preferred list + // The test is valid either way - either there's a preferred verse or not + if (verse === null) { + expect(verse).toBeNull(); + } else { + expect(verse.preferred).toBe(true); + } + }); + + it('returns null for invalid input', () => { + const verse1 = getPreferredPasukForName('', ''); + const verse2 = getPreferredPasukForName('x', 'y'); + + expect(verse1).toBeNull(); + expect(verse2).toBeNull(); + }); }); }); diff --git a/src/index.ts b/src/index.ts index 62be0bb..ab22ce7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import { data } from './data.js'; import type { Section, VerseResult, TanachData } from './types.js'; +import { getPreferredVerse, normalizeHebrewLetter as normalizeLetter } from './preferred-verses.js'; /** * The three main sections of the Tanach @@ -318,6 +319,9 @@ export const findPesukimByName = ( // For end letter, get all forms (regular and final) const endLetterAlternatives = getAllLetterForms(extractedEndLetter); + // Get preferred verse for this letter combination + const preferred = getPreferredVerse(searchStartLetter, normalizeLetter(extractedEndLetter)); + for (const bookName of booksToSearch) { const bookData = data[bookName]; if (!bookData) continue; @@ -329,13 +333,20 @@ export const findPesukimByName = ( const lastLetter = lettersOnly[lettersOnly.length - 1]; if (firstLetter === searchStartLetter && endLetterAlternatives.includes(lastLetter)) { + // Check if this is the preferred verse + const isPreferred = preferred && + preferred.book === bookName && + preferred.chapter === Number(chapterNum) && + preferred.verse === verseNum; + results.push({ book: bookName, bookHebrew: bookData.meta.he, bookEnglish: bookData.meta.en, chapter: Number(chapterNum), verse: verseNum, - text + text, + preferred: isPreferred || undefined }); if (maxResults && results.length >= maxResults) { @@ -346,8 +357,64 @@ export const findPesukimByName = ( } } - return results; + // Sort results so preferred verse comes first + return results.sort((a, b) => { + if (a.preferred && !b.preferred) return -1; + if (!a.preferred && b.preferred) return 1; + return 0; + }); +}; + +/** + * Get the traditional/preferred pasuk for a given name + * This retrieves the verse that is traditionally used for the minhag of saying a pasuk for one's name + * + * @param startLetter - First letter of the name + * @param endLetter - Last letter of the name + * @returns The preferred verse if one exists, otherwise null + * + * @example + * ```ts + * // Get the preferred verse for "David" (דוד) + * const verse = getPreferredPasukForName("ד", "ד"); + * if (verse) { + * console.log(`${verse.book} ${verse.chapter}:${verse.verse}`); + * console.log(verse.text); + * } + * ``` + */ +export const getPreferredPasukForName = ( + startLetter: string, + endLetter: string +): VerseResult | null => { + // Normalize the letters + const normalizedStart = normalizeLetter(extractHebrewLetters(startLetter, false)[0] || ''); + const normalizedEnd = normalizeLetter(extractHebrewLetters(endLetter, false)[0] || ''); + + if (!normalizedStart || !normalizedEnd) { + return null; + } + + // Get the preferred verse info + const preferred = getPreferredVerse(normalizedStart, normalizedEnd); + + if (!preferred || !preferred.book) { + return null; + } + + // Fetch the actual verse from the data + const verse = tanach(preferred.book, preferred.chapter, preferred.verse); + + if (verse) { + return { + ...verse, + preferred: true + }; + } + + return null; }; // Export types export type { Section, VerseResult, TanachData, BookMeta, Book } from './types.js'; +export type { PreferredVerse } from './preferred-verses.js'; diff --git a/src/preferred-verses.ts b/src/preferred-verses.ts new file mode 100644 index 0000000..f578fbb --- /dev/null +++ b/src/preferred-verses.ts @@ -0,0 +1,249 @@ +/** + * Preferred verses for the minhag of saying a pasuk for one's name + * Based on traditional lists used in various communities + * + * Generated from traditional sources - do not edit manually + */ + +export interface PreferredVerse { + /** Starting letter of the name */ + startLetter: string; + /** Ending letter of the name */ + endLetter: string; + /** Book name (transliterated) */ + book: string; + /** Chapter number */ + chapter: number; + /** Verse number */ + verse: number; + /** Hebrew text of the verse (if available) */ + text?: string; + /** Notes or alternative information */ + notes?: string; +} + +/** + * Default/preferred verses for each letter combination + * Total: 22 entries (22 with valid references) + */ +export const preferredVerses: PreferredVerse[] = [ + { + "startLetter": "א", + "endLetter": "א", + "book": "Tehillim", + "chapter": 118, + "verse": 25, + "text": "אָנָּא יְהֹוָה הוֹשִׁיעָה נָּא אָנָּא יְהֹוָה הַצְלִיחָה נָּא" + }, + { + "startLetter": "א", + "endLetter": "ב", + "book": "Tehillim", + "chapter": 44, + "verse": 5, + "text": "אַתָּה הוּא מַלְכִּי אֱלֹהִים צַוֵּה יְשׁוּעוֹת יַעֲקֹב" + }, + { + "startLetter": "א", + "endLetter": "ג", + "book": "Tehillim", + "chapter": 42, + "verse": 5, + "text": "אֵלֶּה אֶזְכְּרָה וְאֶשְׁפְּכָה עָלַי נַפְשִׁי כִּי אֶעֱבֹר בַּסָּךְ אֶדַּדֵּם עַד בֵּית אֱלֹהִים בְּקוֹל רִנָּה וְתוֹדָה הָמוֹן חוֹגֵג" + }, + { + "startLetter": "א", + "endLetter": "ד", + "book": "Tehillim", + "chapter": 45, + "verse": 18, + "text": "אַזְכִּירָה שִׁמְךָ בְּכָל דֹּר וָדֹר עַל כֵּן עַמִּים יְהוֹדֻךָ לְעֹלָם וָעֶד" + }, + { + "startLetter": "א", + "endLetter": "ה", + "book": "Tehillim", + "chapter": 41, + "verse": 2, + "text": "אַשְׁרֵי מַשְׂכִּיל אֶל דָּל בְּיוֹם רָעָה יְמַלְּטֵהוּ יְהוָה" + }, + { + "startLetter": "א", + "endLetter": "ו", + "book": "Tehillim", + "chapter": 146, + "verse": 5, + "text": "אַשְׁרֵי שֶׁאֵל יַעֲקֹב בְּעֶזְרוֹ שִׂבְרוֹ עַל יְהֹוָה אֱלֹהָיו" + }, + { + "startLetter": "א", + "endLetter": "ז", + "book": "Shir Hashirim", + "chapter": 8, + "verse": 9, + "text": "אִם חוֹמָה הִיא נִבְנֶה עָלֶיהָ טִירַת כָּסֶף וְאִם דֶּלֶת הִיא נָצוּר עָלֶיהָ לוּחַ אָרֶז" + }, + { + "startLetter": "א", + "endLetter": "ח", + "book": "Tehillim", + "chapter": 118, + "verse": 27, + "text": "אֵל יְהֹוָה וַיָּאֶר לָנוּ אִסְרוּ חַג בַּעֲבֹתִים עַד קַרְנוֹת הַמִּזְבֵּחַ" + }, + { + "startLetter": "א", + "endLetter": "ט", + "book": "Tehillim", + "chapter": 62, + "verse": 7, + "text": "אַךְ הוּא צוּרִי וִישׁוּעָתִי מִשְׂגַּבִּי לֹא אֶמּוֹט רַבָּה" + }, + { + "startLetter": "א", + "endLetter": "י", + "book": "Tehillim", + "chapter": 5, + "verse": 2, + "text": "אֲמָרַי הַאֲזִינָה יְהוָה בִּינָה הֲגִיגִי" + }, + { + "startLetter": "א", + "endLetter": "ך", + "book": "Tehillim", + "chapter": 16, + "verse": 2, + "text": "אָמַרְתְּ לַיהוָה אֲדֹנָי אָתָּה טוֹבָתִי בַּל עָלֶיךָ" + }, + { + "startLetter": "א", + "endLetter": "ל", + "book": "Tehillim", + "chapter": 68, + "verse": 9, + "text": "אֶרֶץ רָעָשָׁה אַף שָׁמַיִם נָטְפוּ מִפְּנֵי אֱלֹהִים זֶה סִינַי מִפְּנֵי אֱלֹהִים אֱלֹהֵי יִשְׂרָאֵל" + }, + { + "startLetter": "א", + "endLetter": "ם", + "book": "Nechemiah", + "chapter": 9, + "verse": 7, + "text": "אַתָּה הוּא יְהוָה הָאֱלֹהִים אֲשֶׁר בָּחַרְתָּ בְּאַבְרָם וְהוֹצֵאתוֹ מֵאוּר כַּשְׂדִּים וְשַׂמְתָּ שְּׁמוֹ אַבְרָהָם" + }, + { + "startLetter": "א", + "endLetter": "ן", + "book": "Tehillim", + "chapter": 30, + "verse": 9, + "text": "אֵלֶיךָ יְהֹוָה אֶקְרָא וְאֶל אֲדֹנָי אֶתְחַנָּן" + }, + { + "startLetter": "א", + "endLetter": "ס", + "book": "Tehillim", + "chapter": 27, + "verse": 12, + "text": "אַל תִּתְּנֵנִי בְּנֶפֶשׁ צָרָי כִּי קָמוּ בִי עֵדֵי שֶׁקֶר וִיפֵחַ חָמָס" + }, + { + "startLetter": "א", + "endLetter": "ע", + "book": "Tehillim", + "chapter": 10, + "verse": 6, + "text": "אָמַר בְּלִבּוֹ בַּל אֶמּוֹט לְדֹר וָדֹר אֲשֶׁר לֹא בְרָע" + }, + { + "startLetter": "א", + "endLetter": "ף", + "book": "Tehillim", + "chapter": 85, + "verse": 12, + "text": "אֱמֶת מֵאֶרֶץ תִּצְמָח וְצֶדֶק מִשָּׁמַיִם נִשְׁקָף" + }, + { + "startLetter": "א", + "endLetter": "ץ", + "book": "Tehillim", + "chapter": 33, + "verse": 5, + "text": "אֹהֵב צְדָקָה וּמִשְׁפָּט חֶסֶד יְהֹוָה מָלְאָה הָאָרֶץ" + }, + { + "startLetter": "א", + "endLetter": "ק", + "book": "Tehillim", + "chapter": 105, + "verse": 9, + "text": "אֲשֶׁר כָּרַת אֶת אַבְרָהָם וּשְׁבוּעָתוֹ לְיִשְׂחָק" + }, + { + "startLetter": "א", + "endLetter": "ר", + "book": "Tehillim", + "chapter": 20, + "verse": 8, + "text": "אֵלֶּה בָרֶכֶב וְאֵלֶּה בַסּוּסִים וַאֲנַחְנוּ בְּשֵׁם יְהֹוָה אֱלֹהֵינוּ נַזְכִּיר" + }, + { + "startLetter": "א", + "endLetter": "ש", + "book": "Tehillim", + "chapter": 55, + "verse": 15, + "text": "אֲשֶׁר יַחְדָּו נַמְתִּיק סוֹד בְּבֵית אֱלֹהִים נְהַלֵּךְ בְּרָגֶשׁ" + }, + { + "startLetter": "א", + "endLetter": "ת", + "book": "Tehillim", + "chapter": 106, + "verse": 3, + "text": "אַשְׁרֵי שֹׁמְרֵי מִשְׁפָּט עֹשֵׂה צְדָקָה בְכָל עֵת" + } +]; + +/** + * Create a lookup key for a letter combination + */ +export function getPreferredVerseKey(startLetter: string, endLetter: string): string { + return `${startLetter}-${endLetter}`; +} + +/** + * Lookup map for quick access to preferred verses + */ +export const preferredVersesMap = new Map(); + +// Initialize the map +preferredVerses.forEach(verse => { + if (verse.book) { + const key = getPreferredVerseKey(verse.startLetter, verse.endLetter); + preferredVersesMap.set(key, verse); + } +}); + +/** + * Get the preferred verse for a given letter combination + */ +export function getPreferredVerse(startLetter: string, endLetter: string): PreferredVerse | undefined { + const key = getPreferredVerseKey(startLetter, endLetter); + return preferredVersesMap.get(key); +} + +/** + * Normalize a Hebrew letter by converting final forms to regular forms + */ +export function normalizeHebrewLetter(letter: string): string { + const finalToRegularMap: Record = { + 'ך': 'כ', + 'ם': 'מ', + 'ן': 'נ', + 'ף': 'פ', + 'ץ': 'צ' + }; + + return finalToRegularMap[letter] || letter; +} diff --git a/src/types.ts b/src/types.ts index f89d5ff..bf16820 100644 --- a/src/types.ts +++ b/src/types.ts @@ -58,4 +58,6 @@ export interface VerseResult { chapter: number; verse: number; text: string; + /** Whether this verse is a preferred/traditional choice for a name */ + preferred?: boolean; } From 689b05eb9b9a3305bce1b60f78bec5cba1551c6a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Jan 2026 00:58:10 +0000 Subject: [PATCH 3/3] Refactor preferred verses to store only references - Change JSON format from array with text to object with references only - Remove text field from PreferredVerse interface (fetched from tanach data) - Update processing script to handle both old and new formats - Simplify preferred-verses.ts by removing text duplication - Maintain single source of truth for verse text in tanach data - Update documentation to reflect reference-only approach https://claude.ai/code/session_015dW7YKUjy6K4r39jKFVpyH --- ADDING_PREFERRED_VERSES.md | 28 ++++----- PASUK_RESEARCH.md | 15 ++--- scripts/preferred-verses-input.json | 92 ++++++++-------------------- scripts/process-preferred-verses.cjs | 15 +++-- src/index.ts | 8 +-- src/preferred-verses.ts | 83 +++++++------------------ 6 files changed, 77 insertions(+), 164 deletions(-) diff --git a/ADDING_PREFERRED_VERSES.md b/ADDING_PREFERRED_VERSES.md index cca667a..522787a 100644 --- a/ADDING_PREFERRED_VERSES.md +++ b/ADDING_PREFERRED_VERSES.md @@ -10,35 +10,29 @@ The library currently has **22 letter combinations** with preferred verses as a ### Step 1: Prepare the JSON Data -Edit `scripts/preferred-verses-input.json` with the complete list of preferred verses. The format should be: +Edit `scripts/preferred-verses-input.json` with the complete list of preferred verses. The format is simple - just map letter combinations to verse references: ```json { - "allPesukim": [ - { - "letter-combo": ["Hebrew verse text", "Book reference"] - } - ] + "allPesukim": { + "letter-combo": "Book reference" + } } ``` **Example:** ```json { - "allPesukim": [ - { - "א-א": ["אָנָּא יְהֹוָה הוֹשִׁיעָה נָּא", "תהילים קיח כה"] - }, - { - "א-ב": ["אַתָּה הוּא מַלְכִּי אֱלֹהִים", "Psalms 44 5"] - }, - { - "ב-ב": ["בִּנְדָבָה אֶזְבְּחָה לָּךְ", "תהילים נד ח"] - } - ] + "allPesukim": { + "א-א": "תהילים קיח כה", + "א-ב": "Psalms 44 5", + "ב-ב": "תהילים נד ח" + } } ``` +**Note:** The verse text itself is NOT stored in the JSON - it will be fetched from the tanach data automatically. This keeps the file small and maintains a single source of truth. + ### Supported Reference Formats The parser automatically handles multiple formats: diff --git a/PASUK_RESEARCH.md b/PASUK_RESEARCH.md index c87d3d0..9500b60 100644 --- a/PASUK_RESEARCH.md +++ b/PASUK_RESEARCH.md @@ -180,21 +180,18 @@ Psalm 119 is the longest chapter in the Bible and features a unique alphabetic a To extend the preferred verses database: 1. Edit `scripts/preferred-verses-input.json` to add new letter combinations -2. Use format: `"א-א": ["verse text", "Book chapter verse"]` +2. Use format: `"א-א": "Book chapter verse"` (reference only, no verse text) 3. Run `node scripts/process-preferred-verses.cjs` to regenerate the TypeScript data 4. The system automatically parses Hebrew numbers and book names +5. Verse text is fetched from the tanach data automatically Example: ```json { - "allPesukim": [ - { - "א-א": ["אָנָּא יְהֹוָה הוֹשִׁיעָה נָּא", "תהילים קיח כה"] - }, - { - "ב-ב": ["בִּנְדָבָה אֶזְבְּחָה לָּךְ", "Psalms 54 8"] - } - ] + "allPesukim": { + "א-א": "תהילים קיח כה", + "ב-ב": "Psalms 54 8" + } } ``` diff --git a/scripts/preferred-verses-input.json b/scripts/preferred-verses-input.json index dfbb1c4..8025dbb 100644 --- a/scripts/preferred-verses-input.json +++ b/scripts/preferred-verses-input.json @@ -1,70 +1,26 @@ { - "allPesukim": [ - { - "א-א": ["אָנָּא יְהֹוָה הוֹשִׁיעָה נָּא אָנָּא יְהֹוָה הַצְלִיחָה נָּא", "תהילים קיח כה"] - }, - { - "א-ב": ["אַתָּה הוּא מַלְכִּי אֱלֹהִים צַוֵּה יְשׁוּעוֹת יַעֲקֹב", "תהילים מד ה"] - }, - { - "א-ג": ["אֵלֶּה אֶזְכְּרָה וְאֶשְׁפְּכָה עָלַי נַפְשִׁי כִּי אֶעֱבֹר בַּסָּךְ אֶדַּדֵּם עַד בֵּית אֱלֹהִים בְּקוֹל רִנָּה וְתוֹדָה הָמוֹן חוֹגֵג", "תהילים מב ה"] - }, - { - "א-ד": ["אַזְכִּירָה שִׁמְךָ בְּכָל דֹּר וָדֹר עַל כֵּן עַמִּים יְהוֹדֻךָ לְעֹלָם וָעֶד", "Psalms 45 18"] - }, - { - "א-ה": ["אַשְׁרֵי מַשְׂכִּיל אֶל דָּל בְּיוֹם רָעָה יְמַלְּטֵהוּ יְהוָה", "Psalms 41 2"] - }, - { - "א-ו": ["אַשְׁרֵי שֶׁאֵל יַעֲקֹב בְּעֶזְרוֹ שִׂבְרוֹ עַל יְהֹוָה אֱלֹהָיו", "תהילים קמו ה"] - }, - { - "א-ז": ["אִם חוֹמָה הִיא נִבְנֶה עָלֶיהָ טִירַת כָּסֶף וְאִם דֶּלֶת הִיא נָצוּר עָלֶיהָ לוּחַ אָרֶז", "שיר השירים ח ט"] - }, - { - "א-ח": ["אֵל יְהֹוָה וַיָּאֶר לָנוּ אִסְרוּ חַג בַּעֲבֹתִים עַד קַרְנוֹת הַמִּזְבֵּחַ", "תהילים קיח כז"] - }, - { - "א-ט": ["אַךְ הוּא צוּרִי וִישׁוּעָתִי מִשְׂגַּבִּי לֹא אֶמּוֹט רַבָּה", "תהילים סב ז"] - }, - { - "א-י": ["אֲמָרַי הַאֲזִינָה יְהוָה בִּינָה הֲגִיגִי", "Psalms 5 2"] - }, - { - "א-ך": ["אָמַרְתְּ לַיהוָה אֲדֹנָי אָתָּה טוֹבָתִי בַּל עָלֶיךָ", "Psalms 16 2"] - }, - { - "א-ל": ["אֶרֶץ רָעָשָׁה אַף שָׁמַיִם נָטְפוּ מִפְּנֵי אֱלֹהִים זֶה סִינַי מִפְּנֵי אֱלֹהִים אֱלֹהֵי יִשְׂרָאֵל", "Psalms 68 9"] - }, - { - "א-ם": ["אַתָּה הוּא יְהוָה הָאֱלֹהִים אֲשֶׁר בָּחַרְתָּ בְּאַבְרָם וְהוֹצֵאתוֹ מֵאוּר כַּשְׂדִּים וְשַׂמְתָּ שְּׁמוֹ אַבְרָהָם", "Nehemiah 9 7"] - }, - { - "א-ן": ["אֵלֶיךָ יְהֹוָה אֶקְרָא וְאֶל אֲדֹנָי אֶתְחַנָּן", "תהילים ל ט"] - }, - { - "א-ס": ["אַל תִּתְּנֵנִי בְּנֶפֶשׁ צָרָי כִּי קָמוּ בִי עֵדֵי שֶׁקֶר וִיפֵחַ חָמָס", "תהילים כז יב"] - }, - { - "א-ע": ["אָמַר בְּלִבּוֹ בַּל אֶמּוֹט לְדֹר וָדֹר אֲשֶׁר לֹא בְרָע", "Psalms 10 6"] - }, - { - "א-ף": ["אֱמֶת מֵאֶרֶץ תִּצְמָח וְצֶדֶק מִשָּׁמַיִם נִשְׁקָף", "תהילים פה יב"] - }, - { - "א-ץ": ["אֹהֵב צְדָקָה וּמִשְׁפָּט חֶסֶד יְהֹוָה מָלְאָה הָאָרֶץ", "תהילים לג ה"] - }, - { - "א-ק": ["אֲשֶׁר כָּרַת אֶת אַבְרָהָם וּשְׁבוּעָתוֹ לְיִשְׂחָק", "תהילים קה ט"] - }, - { - "א-ר": ["אֵלֶּה בָרֶכֶב וְאֵלֶּה בַסּוּסִים וַאֲנַחְנוּ בְּשֵׁם יְהֹוָה אֱלֹהֵינוּ נַזְכִּיר", "תהילים כ ח"] - }, - { - "א-ש": ["אֲשֶׁר יַחְדָּו נַמְתִּיק סוֹד בְּבֵית אֱלֹהִים נְהַלֵּךְ בְּרָגֶשׁ", "תהילים נה טו"] - }, - { - "א-ת": ["אַשְׁרֵי שֹׁמְרֵי מִשְׁפָּט עֹשֵׂה צְדָקָה בְכָל עֵת", "תהילים קו ג"] - } - ] + "allPesukim": { + "א-א": "תהילים קיח כה", + "א-ב": "תהילים מד ה", + "א-ג": "תהילים מב ה", + "א-ד": "Psalms 45 18", + "א-ה": "Psalms 41 2", + "א-ו": "תהילים קמו ה", + "א-ז": "שיר השירים ח ט", + "א-ח": "תהילים קיח כז", + "א-ט": "תהילים סב ז", + "א-י": "Psalms 5 2", + "א-ך": "Psalms 16 2", + "א-ל": "Psalms 68 9", + "א-ם": "Nehemiah 9 7", + "א-ן": "תהילים ל ט", + "א-ס": "תהילים כז יב", + "א-ע": "Psalms 10 6", + "א-ף": "תהילים פה יב", + "א-ץ": "תהילים לג ה", + "א-ק": "תהילים קה ט", + "א-ר": "תהילים כ ח", + "א-ש": "תהילים נה טו", + "א-ת": "תהילים קו ג" + } } diff --git a/scripts/process-preferred-verses.cjs b/scripts/process-preferred-verses.cjs index d41bcda..cd993ea 100644 --- a/scripts/process-preferred-verses.cjs +++ b/scripts/process-preferred-verses.cjs @@ -125,8 +125,16 @@ const preferredVerses = []; let successCount = 0; let failCount = 0; -data.allPesukim.forEach(item => { - const [letterCombo, [hebrewText, reference]] = Object.entries(item)[0]; +// Handle both old array format and new object format +const pesukim = Array.isArray(data.allPesukim) + ? data.allPesukim.reduce((acc, item) => { + const [key, value] = Object.entries(item)[0]; + acc[key] = Array.isArray(value) ? value[1] : value; // Extract reference from [text, ref] or use directly + return acc; + }, {}) + : data.allPesukim; + +Object.entries(pesukim).forEach(([letterCombo, reference]) => { const [startLetter, endLetter] = letterCombo.split('-'); const parsed = parseVerseReference(reference); @@ -136,7 +144,6 @@ data.allPesukim.forEach(item => { startLetter, endLetter, ...parsed, - text: hebrewText || undefined, }); successCount++; } else { @@ -173,8 +180,6 @@ export interface PreferredVerse { chapter: number; /** Verse number */ verse: number; - /** Hebrew text of the verse (if available) */ - text?: string; /** Notes or alternative information */ notes?: string; } diff --git a/src/index.ts b/src/index.ts index ab22ce7..4c55186 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { data } from './data.js'; import type { Section, VerseResult, TanachData } from './types.js'; -import { getPreferredVerse, normalizeHebrewLetter as normalizeLetter } from './preferred-verses.js'; +import { getPreferredVerse } from './preferred-verses.js'; /** * The three main sections of the Tanach @@ -320,7 +320,7 @@ export const findPesukimByName = ( const endLetterAlternatives = getAllLetterForms(extractedEndLetter); // Get preferred verse for this letter combination - const preferred = getPreferredVerse(searchStartLetter, normalizeLetter(extractedEndLetter)); + const preferred = getPreferredVerse(searchStartLetter, normalizeHebrewLetter(extractedEndLetter)); for (const bookName of booksToSearch) { const bookData = data[bookName]; @@ -388,8 +388,8 @@ export const getPreferredPasukForName = ( endLetter: string ): VerseResult | null => { // Normalize the letters - const normalizedStart = normalizeLetter(extractHebrewLetters(startLetter, false)[0] || ''); - const normalizedEnd = normalizeLetter(extractHebrewLetters(endLetter, false)[0] || ''); + const normalizedStart = normalizeHebrewLetter(extractHebrewLetters(startLetter, false)[0] || ''); + const normalizedEnd = normalizeHebrewLetter(extractHebrewLetters(endLetter, false)[0] || ''); if (!normalizedStart || !normalizedEnd) { return null; diff --git a/src/preferred-verses.ts b/src/preferred-verses.ts index f578fbb..2c40f10 100644 --- a/src/preferred-verses.ts +++ b/src/preferred-verses.ts @@ -16,8 +16,6 @@ export interface PreferredVerse { chapter: number; /** Verse number */ verse: number; - /** Hebrew text of the verse (if available) */ - text?: string; /** Notes or alternative information */ notes?: string; } @@ -32,176 +30,154 @@ export const preferredVerses: PreferredVerse[] = [ "endLetter": "א", "book": "Tehillim", "chapter": 118, - "verse": 25, - "text": "אָנָּא יְהֹוָה הוֹשִׁיעָה נָּא אָנָּא יְהֹוָה הַצְלִיחָה נָּא" + "verse": 25 }, { "startLetter": "א", "endLetter": "ב", "book": "Tehillim", "chapter": 44, - "verse": 5, - "text": "אַתָּה הוּא מַלְכִּי אֱלֹהִים צַוֵּה יְשׁוּעוֹת יַעֲקֹב" + "verse": 5 }, { "startLetter": "א", "endLetter": "ג", "book": "Tehillim", "chapter": 42, - "verse": 5, - "text": "אֵלֶּה אֶזְכְּרָה וְאֶשְׁפְּכָה עָלַי נַפְשִׁי כִּי אֶעֱבֹר בַּסָּךְ אֶדַּדֵּם עַד בֵּית אֱלֹהִים בְּקוֹל רִנָּה וְתוֹדָה הָמוֹן חוֹגֵג" + "verse": 5 }, { "startLetter": "א", "endLetter": "ד", "book": "Tehillim", "chapter": 45, - "verse": 18, - "text": "אַזְכִּירָה שִׁמְךָ בְּכָל דֹּר וָדֹר עַל כֵּן עַמִּים יְהוֹדֻךָ לְעֹלָם וָעֶד" + "verse": 18 }, { "startLetter": "א", "endLetter": "ה", "book": "Tehillim", "chapter": 41, - "verse": 2, - "text": "אַשְׁרֵי מַשְׂכִּיל אֶל דָּל בְּיוֹם רָעָה יְמַלְּטֵהוּ יְהוָה" + "verse": 2 }, { "startLetter": "א", "endLetter": "ו", "book": "Tehillim", "chapter": 146, - "verse": 5, - "text": "אַשְׁרֵי שֶׁאֵל יַעֲקֹב בְּעֶזְרוֹ שִׂבְרוֹ עַל יְהֹוָה אֱלֹהָיו" + "verse": 5 }, { "startLetter": "א", "endLetter": "ז", "book": "Shir Hashirim", "chapter": 8, - "verse": 9, - "text": "אִם חוֹמָה הִיא נִבְנֶה עָלֶיהָ טִירַת כָּסֶף וְאִם דֶּלֶת הִיא נָצוּר עָלֶיהָ לוּחַ אָרֶז" + "verse": 9 }, { "startLetter": "א", "endLetter": "ח", "book": "Tehillim", "chapter": 118, - "verse": 27, - "text": "אֵל יְהֹוָה וַיָּאֶר לָנוּ אִסְרוּ חַג בַּעֲבֹתִים עַד קַרְנוֹת הַמִּזְבֵּחַ" + "verse": 27 }, { "startLetter": "א", "endLetter": "ט", "book": "Tehillim", "chapter": 62, - "verse": 7, - "text": "אַךְ הוּא צוּרִי וִישׁוּעָתִי מִשְׂגַּבִּי לֹא אֶמּוֹט רַבָּה" + "verse": 7 }, { "startLetter": "א", "endLetter": "י", "book": "Tehillim", "chapter": 5, - "verse": 2, - "text": "אֲמָרַי הַאֲזִינָה יְהוָה בִּינָה הֲגִיגִי" + "verse": 2 }, { "startLetter": "א", "endLetter": "ך", "book": "Tehillim", "chapter": 16, - "verse": 2, - "text": "אָמַרְתְּ לַיהוָה אֲדֹנָי אָתָּה טוֹבָתִי בַּל עָלֶיךָ" + "verse": 2 }, { "startLetter": "א", "endLetter": "ל", "book": "Tehillim", "chapter": 68, - "verse": 9, - "text": "אֶרֶץ רָעָשָׁה אַף שָׁמַיִם נָטְפוּ מִפְּנֵי אֱלֹהִים זֶה סִינַי מִפְּנֵי אֱלֹהִים אֱלֹהֵי יִשְׂרָאֵל" + "verse": 9 }, { "startLetter": "א", "endLetter": "ם", "book": "Nechemiah", "chapter": 9, - "verse": 7, - "text": "אַתָּה הוּא יְהוָה הָאֱלֹהִים אֲשֶׁר בָּחַרְתָּ בְּאַבְרָם וְהוֹצֵאתוֹ מֵאוּר כַּשְׂדִּים וְשַׂמְתָּ שְּׁמוֹ אַבְרָהָם" + "verse": 7 }, { "startLetter": "א", "endLetter": "ן", "book": "Tehillim", "chapter": 30, - "verse": 9, - "text": "אֵלֶיךָ יְהֹוָה אֶקְרָא וְאֶל אֲדֹנָי אֶתְחַנָּן" + "verse": 9 }, { "startLetter": "א", "endLetter": "ס", "book": "Tehillim", "chapter": 27, - "verse": 12, - "text": "אַל תִּתְּנֵנִי בְּנֶפֶשׁ צָרָי כִּי קָמוּ בִי עֵדֵי שֶׁקֶר וִיפֵחַ חָמָס" + "verse": 12 }, { "startLetter": "א", "endLetter": "ע", "book": "Tehillim", "chapter": 10, - "verse": 6, - "text": "אָמַר בְּלִבּוֹ בַּל אֶמּוֹט לְדֹר וָדֹר אֲשֶׁר לֹא בְרָע" + "verse": 6 }, { "startLetter": "א", "endLetter": "ף", "book": "Tehillim", "chapter": 85, - "verse": 12, - "text": "אֱמֶת מֵאֶרֶץ תִּצְמָח וְצֶדֶק מִשָּׁמַיִם נִשְׁקָף" + "verse": 12 }, { "startLetter": "א", "endLetter": "ץ", "book": "Tehillim", "chapter": 33, - "verse": 5, - "text": "אֹהֵב צְדָקָה וּמִשְׁפָּט חֶסֶד יְהֹוָה מָלְאָה הָאָרֶץ" + "verse": 5 }, { "startLetter": "א", "endLetter": "ק", "book": "Tehillim", "chapter": 105, - "verse": 9, - "text": "אֲשֶׁר כָּרַת אֶת אַבְרָהָם וּשְׁבוּעָתוֹ לְיִשְׂחָק" + "verse": 9 }, { "startLetter": "א", "endLetter": "ר", "book": "Tehillim", "chapter": 20, - "verse": 8, - "text": "אֵלֶּה בָרֶכֶב וְאֵלֶּה בַסּוּסִים וַאֲנַחְנוּ בְּשֵׁם יְהֹוָה אֱלֹהֵינוּ נַזְכִּיר" + "verse": 8 }, { "startLetter": "א", "endLetter": "ש", "book": "Tehillim", "chapter": 55, - "verse": 15, - "text": "אֲשֶׁר יַחְדָּו נַמְתִּיק סוֹד בְּבֵית אֱלֹהִים נְהַלֵּךְ בְּרָגֶשׁ" + "verse": 15 }, { "startLetter": "א", "endLetter": "ת", "book": "Tehillim", "chapter": 106, - "verse": 3, - "text": "אַשְׁרֵי שֹׁמְרֵי מִשְׁפָּט עֹשֵׂה צְדָקָה בְכָל עֵת" + "verse": 3 } ]; @@ -232,18 +208,3 @@ export function getPreferredVerse(startLetter: string, endLetter: string): Prefe const key = getPreferredVerseKey(startLetter, endLetter); return preferredVersesMap.get(key); } - -/** - * Normalize a Hebrew letter by converting final forms to regular forms - */ -export function normalizeHebrewLetter(letter: string): string { - const finalToRegularMap: Record = { - 'ך': 'כ', - 'ם': 'מ', - 'ן': 'נ', - 'ף': 'פ', - 'ץ': 'צ' - }; - - return finalToRegularMap[letter] || letter; -}