Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 33 additions & 4 deletions src/scriptures/books.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ export interface ScriptureBook {
* (e.g. `"chapter-"` turns chapter `1` into the path segment `chapter-1`).
*/
chapterPrefix?: string;
/**
* Optional map from a normalized section key to the URL path segment
* appended after `urlBase`. When present, the URL builder resolves the
* chapter via this map instead of the default `chapterPrefix + chapter`
* construction. Keys use the canonical forms returned by
* `normalizePmgSection` (e.g. `"message"`, `"intro"`, `"1"`, `"3.1"`,
* `"3.2"`, …).
*/
chapterMap?: Record<string, string>;
}

export const SCRIPTURE_BOOKS: ScriptureBook[] = [
Expand Down Expand Up @@ -136,9 +145,29 @@ export const SCRIPTURE_BOOKS: ScriptureBook[] = [
{
names: ['Preach My Gospel', 'PMG'],
volume: 'manual',
slug: 'preach-my-gospel-a-guide-to-sharing-the-gospel-of-jesus-christ',
urlBase:
'https://www.churchofjesuschrist.org/study/manual/preach-my-gospel-a-guide-to-sharing-the-gospel-of-jesus-christ',
chapterPrefix: 'chapter-',
slug: 'preach-my-gospel-2023',
urlBase: 'https://www.churchofjesuschrist.org/study/manual/preach-my-gospel-2023',
chapterMap: {
'message': '01-first-presidency-message',
'intro': '02-intro',
'1': '03-chapter-1',
'2': '04-chapter-2',
'3.1': '04-chapter-3/06-chapter-3-intro',
'3.2': '04-chapter-3/07-chapter-3-invite',
'3.3': '04-chapter-3/08-chapter-3-lesson-1',
'3.4': '04-chapter-3/09-chapter-3-lesson-2',
'3.5': '04-chapter-3/10-chapter-3-lesson-3',
'3.6': '04-chapter-3/11-chapter-3-lesson-4',
'4': '12-chapter-4',
'5': '13-chapter-5',
'6': '14-chapter-6',
'7': '15-chapter-7',
'8': '16-chapter-8',
'9': '17-chapter-9',
'10': '18-chapter-10',
'11': '19-chapter-11',
'12': '20-chapter-12',
'13': '21-chapter-13',
},
},
];
14 changes: 9 additions & 5 deletions src/scriptures/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,17 @@ export const SCRIPTURE_REGEX: RegExp = (() => {
// hyphen-separated additional digit groups (e.g. "1", "2-4", "9,11").
const versePart = String.raw`\d+(?:[-,]\d+)*`;

// After the book name, optionally allow "page N", "pg. N", or "#N"
// in addition to the bare "N" used by standard scripture references.
// This supports formats like "PMG page 18", "PMG pg. 18", "PMG #18".
const numberPrefix = String.raw`(?:(?:page|pg\.)\s+|#\s*)?`;
// Chapter / section identifier, tried in specificity order so that longer
// PMG-specific tokens are matched before the generic digit fallback:
// • PMG named sections: Message, Intro
// • PMG lesson shorthand: L1–L4
// • PMG chapter-3 letter: 3B (= section 3.2, the "invite" page)
// • PMG chapter-3 decimal: 3.1–3.6
// • Any book: optional "chapter"/"ch."/"#" prefix followed by digits
const chapterPart = String.raw`Message|Intro|L[1-4]|3B|3\.[1-6]|(?:(?:chapter|ch\.)\s+|#\s*)?\d+`;

return new RegExp(
String.raw`\b(${bookAlt})\s+${numberPrefix}(\d+)(?::(${versePart}))?`,
String.raw`\b(${bookAlt})\s+(${chapterPart})(?::(${versePart}))?`,
'gi',
);
})();
Expand Down
69 changes: 68 additions & 1 deletion src/scriptures/urlBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,57 @@ function firstVerseAnchor(verseStr: string): string {
return m ? `p${m[0]}` : 'p1';
}

/**
* Normalise a raw PMG section string (as captured by the regex) into the
* canonical key used in `ScriptureBook.chapterMap`.
*
* Supported inputs (case-insensitive):
* "Message" → "message"
* "Intro" → "intro"
* "L1"–"L4" → "3.3"–"3.6"
* "3B" → "3.2"
* "3.1"–"3.6" → "3.1"–"3.6"
* "3" / "chapter 3" / "ch. 3" / "#3" → "3.1" (chapter-3 intro)
* "1" / "chapter 1" / "ch. 1" / "#1" → "1"
*/
export function normalizePmgSection(raw: string): string | undefined {
const s = raw.toLowerCase().trim();

if (s === 'message') return 'message';
if (s === 'intro') return 'intro';

// Lesson shorthand: L1–L4 map to chapter-3 sections 3.3–3.6.
// Offset 2 because: L1 → 1+2=3 → "3.3", L2 → 2+2=4 → "3.4", etc.
const lessonMatch = s.match(/^l([1-4])$/);
if (lessonMatch) return `3.${2 + parseInt(lessonMatch[1]!, 10)}`;

// Letter shorthand: 3B = section 3.2 (the "invite" page).
if (s === '3b') return '3.2';

// Decimal notation 3.1–3.6: pass through as-is.
const decimalMatch = s.match(/^3\.([1-6])$/);
if (decimalMatch) return `3.${decimalMatch[1]}`;

// Generic chapter reference (optional "chapter"/"ch."/"#" prefix + digits).
const chapterMatch = s.match(/^(?:(?:chapter|ch\.)\s+|#\s*)?(\d+)$/);
if (chapterMatch) {
const n = chapterMatch[1];
// Bare "3" (or "chapter 3" etc.) means the chapter-3 intro page.
return n === '3' ? '3.1' : n;
}

return undefined;
}

/**
* Build a ChurchOfJesusChrist.org study URL for the given reference.
*
* Examples:
* When `book.chapterMap` is present (e.g. Preach My Gospel), the chapter
* string is normalised via `normalizePmgSection` and looked up in the map to
* produce the correct path segment. Verse anchors are not applied for
* chapterMap books because PMG pages do not use paragraph-level anchors.
*
* Examples for standard scripture books:
* book=gen, chapter="9", verses=undefined
* → https://…/ot/gen/9?lang=eng
* book=gen, chapter="9", verses="1"
Expand All @@ -35,13 +82,33 @@ function firstVerseAnchor(verseStr: string): string {
* → https://…/ot/gen/9?lang=eng&id=p2-p4#p2
* book=matt, chapter="5", verses="9,11"
* → https://…/nt/matt/5?lang=eng&id=p9,p11#p9
*
* Examples for Preach My Gospel:
* book=pmg, chapter="Message"
* → https://…/preach-my-gospel-2023/01-first-presidency-message?lang=eng
* book=pmg, chapter="3" (or "3.1")
* → https://…/preach-my-gospel-2023/04-chapter-3/06-chapter-3-intro?lang=eng
* book=pmg, chapter="L1" (or "3.3")
* → https://…/preach-my-gospel-2023/04-chapter-3/08-chapter-3-lesson-1?lang=eng
*/
export function buildUrl(
book: ScriptureBook,
chapter: string,
verses: string | undefined,
): string {
const base = book.urlBase ?? `${BASE_URL}/${book.volume}/${book.slug}`;

// Books with a chapterMap (e.g. PMG) use a lookup table for URL paths.
if (book.chapterMap) {
const key = normalizePmgSection(chapter);
const path = key !== undefined ? book.chapterMap[key] : undefined;
if (path) {
return `${base}/${path}?lang=eng`;
}
// Unrecognised section: fall back to a best-effort URL.
return `${base}/${chapter}?lang=eng`;
}

const chapterSegment = `${book.chapterPrefix ?? ''}${chapter}`;
const chapterUrl = `${base}/${chapterSegment}?lang=eng`;

Expand Down
Loading