mirror of
https://github.com/gslogimaker/my-bible-obsidian-plugin.git
synced 2026-07-22 09:20:28 +00:00
Add myBibleAPI for use in myBible codeblocks
This commit is contained in:
parent
3c237fef55
commit
95d2ddd863
4 changed files with 546 additions and 839 deletions
252
api.ts
Normal file
252
api.ts
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
|
||||
import MyBible, { DEFAULT_NAME_MAP, getPlugin, is_alpha, is_alphanumeric, is_numeric } from "main";
|
||||
|
||||
export class PluginAccess {
|
||||
static plugin:MyBible
|
||||
}
|
||||
|
||||
export module myBibleAPI {
|
||||
export module types {
|
||||
export type BookID = number
|
||||
export type ChapterID = number
|
||||
export type VerseID = number
|
||||
export type TranslationID = string
|
||||
|
||||
class ReferenceError extends Error {
|
||||
constructor(message?:string) {
|
||||
super(message)
|
||||
this.name = "Scripture Reference Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A reference to a scripture verse or verses
|
||||
export class Reference {
|
||||
book: types.BookID
|
||||
chapter: types.ChapterID
|
||||
verseStart?: types.VerseID
|
||||
verseEnd?: types.VerseID
|
||||
translation?: types.TranslationID
|
||||
|
||||
constructor(text:string) {
|
||||
const ref = text.trim().replace(/[:-]/g, " ").split(" ")
|
||||
|
||||
let book:string|undefined = undefined
|
||||
let book_id = undefined
|
||||
let chapter = undefined
|
||||
let verse = undefined
|
||||
let verse_end = undefined
|
||||
let maybe_translation:string|undefined = undefined
|
||||
let i = 0
|
||||
|
||||
while (i != ref.length) {
|
||||
// Compose book name
|
||||
if (i == 0) {
|
||||
// Always add first item, no matter what it is
|
||||
book = (book || "") + ref[i];
|
||||
} else {
|
||||
// Only add items that are words, and not numbers
|
||||
if (!is_alpha(ref[i])) {
|
||||
break;
|
||||
}
|
||||
book += " " + ref[i]
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
if (is_numeric(book || "")) {
|
||||
// Compose book_id
|
||||
book_id = Number(book)
|
||||
}
|
||||
|
||||
if (i != ref.length && is_numeric(ref[i])) {
|
||||
// Compose chapter
|
||||
chapter = Number(ref[i]);
|
||||
i += 1;
|
||||
} else {
|
||||
chapter = 0
|
||||
}
|
||||
|
||||
if (i != ref.length && is_numeric(ref[i])) {
|
||||
// Compose verse
|
||||
verse = Number(ref[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (i != ref.length && is_numeric(ref[i])) {
|
||||
// Compose range
|
||||
verse_end = Number(ref[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (i != ref.length && is_alphanumeric(ref[i])) {
|
||||
// Compose translation
|
||||
maybe_translation = ref[i];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (book_id === undefined && book !== undefined) {
|
||||
book_id = DEFAULT_NAME_MAP[book]
|
||||
?? getPlugin()
|
||||
.bible_api
|
||||
.book_id(book, maybe_translation ?? "YLT")
|
||||
}
|
||||
verse_end = verse_end ?? verse
|
||||
|
||||
// Errors
|
||||
if ((verse_end ?? 0) < (verse ?? 0)) {
|
||||
throw new ReferenceError(
|
||||
"End of verse range must be after beginning"
|
||||
)
|
||||
} else if (book_id === undefined) {
|
||||
throw new ReferenceError(
|
||||
"Invalid book name `{0}`".format(book??"")
|
||||
)
|
||||
}
|
||||
|
||||
this.book = book_id
|
||||
this.chapter = chapter
|
||||
this.verseStart = verse
|
||||
this.verseEnd = verse_end
|
||||
this.translation = maybe_translation
|
||||
}
|
||||
|
||||
get_translation():string {
|
||||
return this.translation ?? getPlugin().settings.translation
|
||||
}
|
||||
|
||||
async body(
|
||||
withVerseNumbers?:boolean,
|
||||
separator?:string,
|
||||
):Promise<string> {
|
||||
return await scripture(
|
||||
this,
|
||||
withVerseNumbers,
|
||||
separator,
|
||||
)
|
||||
}
|
||||
|
||||
async toString():Promise<string> {
|
||||
let bookData = await getPlugin()
|
||||
.bible_api
|
||||
.get_book_data(this.get_translation(), this.book)
|
||||
let text = bookData.name + " " + this.chapter
|
||||
if (this.verseStart !== undefined) {
|
||||
text += ":" + String(this.verseStart)
|
||||
}
|
||||
if (this.verseEnd !== undefined && this.verseEnd != this.verseStart) {
|
||||
text += "-" + String(this.verseEnd)
|
||||
}
|
||||
if (this.translation !== undefined) {
|
||||
text += " " + String(this.translation)
|
||||
}
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
export async function randRef(seed?:string):Promise<Reference> {
|
||||
let verse = await getPlugin()
|
||||
.bible_api
|
||||
.pick_random_verse(seed)
|
||||
return new Reference(verse)
|
||||
}
|
||||
|
||||
export function ref(reference:string):Reference {
|
||||
return new Reference(reference)
|
||||
}
|
||||
|
||||
/// Returns the text of a bible verse
|
||||
export async function scripture(
|
||||
ref:Reference|string,
|
||||
withVerseNumbers?:boolean,
|
||||
separator?:string,
|
||||
):Promise<string> {
|
||||
withVerseNumbers = withVerseNumbers ?? false
|
||||
separator = separator ?? " "
|
||||
if (!(ref instanceof Reference)) {
|
||||
ref = new Reference(ref)
|
||||
}
|
||||
let version = ref.translation ?? getPlugin().settings.translation
|
||||
|
||||
let translation = ref.translation
|
||||
?? getPlugin().settings.reading_translation
|
||||
|
||||
let bookName = (
|
||||
await getPlugin().bible_api.get_book_data(translation, ref.book)
|
||||
).name
|
||||
|
||||
let text = "";
|
||||
if (bookName.length === 0) {
|
||||
text = "\n> [!ERROR] Book and chapter must be provided\n"
|
||||
} else if (ref.verseStart === undefined || ref.verseStart <= 0) {
|
||||
// Whole chapter
|
||||
let verses = await getPlugin().bible_api.get_chapter(
|
||||
translation,
|
||||
ref.book,
|
||||
ref.chapter,
|
||||
);
|
||||
for (const verse_i_ of Object.keys(verses)) {
|
||||
const verse_i = Number(verse_i_)
|
||||
let verse = verses[verse_i];
|
||||
text += "<sup>" + (verse_i) + "</sup> " + verse;
|
||||
if (verse_i != Object.keys(verses).length+1) {
|
||||
text += " ";
|
||||
// text += "<br>";
|
||||
}
|
||||
}
|
||||
if (text.length === 0) {
|
||||
text = "\n> [!WARNING] No text found for {0} in translation {2}\n"
|
||||
.format(String(ref), translation)
|
||||
}
|
||||
} else if (ref.verseEnd === undefined || ref.verseEnd < ref.verseStart) {
|
||||
// Single verse
|
||||
text = await getPlugin().bible_api.get_verse(
|
||||
translation,
|
||||
ref.book,
|
||||
ref.chapter,
|
||||
ref.verseStart,
|
||||
)
|
||||
if (text.length === 0) {
|
||||
text = "\n> [!WARNING] No text found for {1} {0}:{2} in translation {3}\n"
|
||||
.format(
|
||||
String(ref.chapter),
|
||||
bookName,
|
||||
String(ref.verseStart),
|
||||
translation,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Verse range
|
||||
let verses = await getPlugin().bible_api.get_chapter(
|
||||
translation,
|
||||
ref.book,
|
||||
ref.chapter,
|
||||
);
|
||||
let j = ref.verseStart;
|
||||
while (j < ref.verseEnd + 1 && j < Object.keys(verses).length) {
|
||||
if (withVerseNumbers) {
|
||||
text += "<sup>" + j + "</sup> " + verses[j];
|
||||
if (j != ref.verseEnd) {
|
||||
text += separator
|
||||
}
|
||||
} else {
|
||||
text += verses[j] + separator
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
if (text.length === 0) {
|
||||
text = "\n> [!WARNING] No text found for {1} {0}:{2}-{3} in translation {4}\n"
|
||||
.format(
|
||||
String(ref.chapter),
|
||||
bookName,
|
||||
String(ref.verseStart),
|
||||
String(ref.verseEnd),
|
||||
translation,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
}
|
||||
859
main.ts
859
main.ts
|
|
@ -12,11 +12,13 @@ import {
|
|||
requestUrl,
|
||||
MarkdownPostProcessorContext,
|
||||
MarkdownRenderer,
|
||||
} from 'obsidian';
|
||||
} from 'obsidian'
|
||||
|
||||
import { E_CANCELED, Mutex } from 'async-mutex';
|
||||
import { match } from 'assert';
|
||||
import { randomBytes, randomInt } from 'crypto';
|
||||
import { E_CANCELED, Mutex } from 'async-mutex'
|
||||
import { match } from 'assert'
|
||||
import { randomBytes, randomInt } from 'crypto'
|
||||
import { BBCodeTag, legacy, parse_mybible } from 'mybible_parser'
|
||||
import { myBibleAPI, PluginAccess } from 'api'
|
||||
|
||||
const BUILD_END_TOAST = "Bible build finished!";
|
||||
const SELECTED_TRANSLATION_OPTION = "<Selected reading translation, {0}>"
|
||||
|
|
@ -132,7 +134,11 @@ const DEFAULT_SETTINGS: MyBibleSettings = {
|
|||
}
|
||||
}
|
||||
|
||||
function httpGet(theUrl: string): Promise<string> {
|
||||
export function getPlugin():MyBible {
|
||||
return MyBible.plugin
|
||||
}
|
||||
|
||||
export function httpGet(theUrl: string): Promise<string> {
|
||||
try {
|
||||
return new Promise(async (ok, err) => {
|
||||
ok(await requestUrl(theUrl).text);
|
||||
|
|
@ -145,7 +151,7 @@ function httpGet(theUrl: string): Promise<string> {
|
|||
}
|
||||
}
|
||||
|
||||
function is_alpha(string: string): boolean {
|
||||
export function is_alpha(string: string): boolean {
|
||||
for (let char_str of string) {
|
||||
let char = char_str.charCodeAt(0);
|
||||
if ((char > 64 && char < 91) || (char > 96 && char < 123) || (char > 39 && char < 42)) {
|
||||
|
|
@ -159,7 +165,7 @@ function is_alpha(string: string): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
function is_alphanumeric(string: string): boolean {
|
||||
export function is_alphanumeric(string: string): boolean {
|
||||
for (let char_str of string) {
|
||||
let char = char_str.charCodeAt(0);
|
||||
if ((char > 64 && char < 91) || (char > 96 && char < 123) || (char > 47 && char < 58)) {
|
||||
|
|
@ -173,7 +179,7 @@ function is_alphanumeric(string: string): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
function is_numeric(string: string): boolean {
|
||||
export function is_numeric(string: string): boolean {
|
||||
for (let char_str of string) {
|
||||
let char = char_str.charCodeAt(0);
|
||||
if (char > 47 && char < 58) {
|
||||
|
|
@ -187,8 +193,7 @@ function is_numeric(string: string): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
async function save_file(path: string, content: string) {
|
||||
export async function save_file(path: string, content: string) {
|
||||
let file_path = normalizePath(path);
|
||||
let file = this.app.vault.getAbstractFileByPath(file_path)
|
||||
if (file instanceof TFile) {
|
||||
|
|
@ -201,13 +206,13 @@ async function save_file(path: string, content: string) {
|
|||
}
|
||||
}
|
||||
|
||||
function translation_to_display_name(translation:Translation):string {
|
||||
export function translation_to_display_name(translation:Translation):string {
|
||||
return "{0} - {1} - {2}"
|
||||
.format(translation.language, translation.abbreviated_name, translation.display_name)
|
||||
;
|
||||
}
|
||||
|
||||
function cyrb128(str:string): number {
|
||||
export function cyrb128(str:string): number {
|
||||
let h1 = 1779033703, h2 = 3144134277,
|
||||
h3 = 1013904242, h4 = 2773480762;
|
||||
for (let i = 0, k; i < str.length; i++) {
|
||||
|
|
@ -225,23 +230,21 @@ function cyrb128(str:string): number {
|
|||
return h1>>>0
|
||||
}
|
||||
|
||||
|
||||
export default class MyBible extends Plugin {
|
||||
bible_api: BibleAPI
|
||||
settings: MyBibleSettings
|
||||
progress_notice: Notice | null
|
||||
parser: MBInterpreter
|
||||
legacyParser: legacy.VerseParser
|
||||
|
||||
static plugin:MyBible
|
||||
|
||||
async onload() {
|
||||
this.parser = new MBInterpreter(this)
|
||||
this.parser.add_command("random")
|
||||
.arg("seed", "string", "")
|
||||
.callback(async (args, el, mb) => {
|
||||
let verse = await this.bible_api.pick_random_verse(args["seed"])
|
||||
el.createEl("h3", {"text": verse})
|
||||
await mb.parser.render_verse(verse, el, false)
|
||||
})
|
||||
;
|
||||
MyBible.plugin = this
|
||||
|
||||
// @ts-ignore
|
||||
globalThis["mb"] = myBibleAPI
|
||||
|
||||
this.legacyParser = new legacy.VerseParser()
|
||||
|
||||
await this.loadSettings();
|
||||
|
||||
|
|
@ -297,13 +300,12 @@ export default class MyBible extends Plugin {
|
|||
});
|
||||
|
||||
this.registerMarkdownCodeBlockProcessor("verse", async (source, el, ctx) =>
|
||||
await this.parser.parse(source, el)
|
||||
await this.legacyParser.parse(source, el)
|
||||
);
|
||||
|
||||
this.registerMarkdownCodeBlockProcessor("mybible", async (source, el, ctx) => {
|
||||
let code_context = {}
|
||||
let parsed = parse_mybible(source)
|
||||
console.log(parsed)
|
||||
if (parsed instanceof Error) {
|
||||
MarkdownRenderer.render(
|
||||
this.app,
|
||||
|
|
@ -323,13 +325,12 @@ export default class MyBible extends Plugin {
|
|||
let text = ""
|
||||
for (const X of parsed) {
|
||||
if (X instanceof BBCodeTag) {
|
||||
text += await X.toText(this.parser)
|
||||
text += await X.toText(code_context)
|
||||
} else {
|
||||
text += X
|
||||
}
|
||||
}
|
||||
MarkdownRenderer.render(this.app, text, el, "", this)
|
||||
// let display = await this.parser.process_bbcode_tag(source, code_context)
|
||||
});
|
||||
|
||||
this.addSettingTab(new SettingsTab(this.app, this));
|
||||
|
|
@ -544,767 +545,11 @@ export default class MyBible extends Plugin {
|
|||
await this.saveData(saving)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface MBParseResult {
|
||||
tag: string
|
||||
args: string
|
||||
content: string
|
||||
remainder: string
|
||||
range_start: number
|
||||
range_end: number
|
||||
}
|
||||
|
||||
export class MBInterpreter {
|
||||
plugin: MyBible
|
||||
commands: MBCommand[]
|
||||
context: any
|
||||
|
||||
constructor(plugin:MyBible) {
|
||||
this.plugin = plugin
|
||||
this.commands = []
|
||||
this.context = {}
|
||||
}
|
||||
|
||||
run_js(code:string, context:any) {
|
||||
let call_result:any = ""
|
||||
let run_code = new Function(
|
||||
"__code__",
|
||||
"return eval(__code__)"
|
||||
)
|
||||
try {
|
||||
call_result = run_code.call(context, code)
|
||||
if (String(call_result) == "[object Object]") {
|
||||
call_result = JSON.stringify(call_result)
|
||||
}
|
||||
} catch(e) {
|
||||
call_result = "\n> [!ERROR] " + String(e) + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
parse_bbcode_tag(code:string): MBParseResult|undefined {
|
||||
// tag, args, content, remainder
|
||||
let unproccessed_code = code
|
||||
let opened = []
|
||||
while (true) {
|
||||
let offset = code.length - unproccessed_code.length
|
||||
let match = unproccessed_code.match(/(?:\\)?(\[\s*([^=\s\]\[]*)\s*(=?[^\]]*)\])([\s\S]*)/)
|
||||
if (match === null) {
|
||||
break
|
||||
}
|
||||
if (match.input === undefined) {
|
||||
throw ""
|
||||
}
|
||||
let full_match_txt = match[0]
|
||||
let tag = match[1]
|
||||
let tag_name = match[2]
|
||||
.toLocaleLowerCase()
|
||||
.replace(/[_\-]/g, "")
|
||||
let args = match[3]
|
||||
let remaining = match[4]
|
||||
if (tag_name === undefined) {
|
||||
throw new Error("Expected tag to have a name")
|
||||
}
|
||||
|
||||
if (tag_name.startsWith("/")) {
|
||||
// Closing tag
|
||||
tag_name = tag_name.slice(1)
|
||||
let latest_open_i = opened.findLastIndex((x) =>
|
||||
x[2].toLocaleLowerCase().replace(/[_\-]/g, "") === tag_name
|
||||
)
|
||||
if (latest_open_i === -1) {
|
||||
// TODO: Handle closing tags with no opening
|
||||
throw new Error("Encountered closing tag without opening tag")
|
||||
}
|
||||
if (opened.length === 1) {
|
||||
let open_match = opened[0]
|
||||
let openTag = open_match[1]
|
||||
let open_args = open_match[3]
|
||||
|
||||
let range_start = open_match.index ?? 0
|
||||
let range_end = offset + (match.index ?? 0) + tag.length
|
||||
let content_start = range_start + openTag.length
|
||||
let content_end = range_end - tag.length
|
||||
return {
|
||||
tag: tag_name,
|
||||
args: open_args,
|
||||
content: code.slice(content_start, content_end),
|
||||
remainder: remaining,
|
||||
range_start: range_start,
|
||||
range_end: range_end,
|
||||
}
|
||||
}
|
||||
opened.splice(latest_open_i, 1)
|
||||
} else {
|
||||
// Opening tag
|
||||
opened.push(match)
|
||||
}
|
||||
if (remaining === undefined) {
|
||||
return
|
||||
}
|
||||
unproccessed_code = remaining ?? unproccessed_code
|
||||
}
|
||||
|
||||
if (opened.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let open_match = opened[0]
|
||||
let openTag = open_match[1]
|
||||
let openTag_name = open_match[2]
|
||||
.toLocaleLowerCase()
|
||||
.replace(/[_\-]/g, "")
|
||||
let open_args = open_match[3]
|
||||
|
||||
let range_start = open_match.index ?? 0
|
||||
let range_end = range_start + open_match[0].length
|
||||
let content_start = range_start + openTag.length
|
||||
let content_end = range_end
|
||||
return {
|
||||
tag: openTag_name,
|
||||
args: open_args,
|
||||
content: code.slice(content_start, content_end),
|
||||
remainder: "",
|
||||
range_start: range_start,
|
||||
range_end: range_end,
|
||||
}
|
||||
}
|
||||
|
||||
async process_bbcode_tag(
|
||||
code:string,
|
||||
context:any,
|
||||
): Promise<string|Error|undefined> {
|
||||
try {
|
||||
let result = this.parse_bbcode_tag(code)
|
||||
if (result === undefined) {
|
||||
// No tags found, return as is
|
||||
return code
|
||||
}
|
||||
let pre_tag = code.slice(0, result.range_start)
|
||||
let post_tag = code.slice(result.range_end)
|
||||
|
||||
if (result.tag === "verse") {
|
||||
let args = result.args
|
||||
if (result.args.startsWith("=")) {
|
||||
args = result.args.slice(1)
|
||||
}
|
||||
return pre_tag
|
||||
+ await this.process_verse_reference(args)
|
||||
+ await this.process_bbcode_tag(result.content + post_tag, context)
|
||||
|
||||
} else if (result.tag === "randomverse") {
|
||||
let args = this.parse_bbcode_args(result.args) ?? {}
|
||||
if (args instanceof Error) {
|
||||
return String(args)
|
||||
}
|
||||
|
||||
let random_verse = await this.plugin.bible_api
|
||||
.pick_random_verse(args["seed"])
|
||||
let with_verse_numbers = args["withnumbers"] ?? false
|
||||
let separator = (args["separator"] ?? " ")
|
||||
.replace(/\\n/, "\n")
|
||||
let translation = args["translation"]
|
||||
|
||||
return pre_tag
|
||||
+ await this.process_verse_reference(
|
||||
random_verse,
|
||||
with_verse_numbers,
|
||||
separator,
|
||||
translation,
|
||||
) + await this.process_bbcode_tag(
|
||||
result.content + post_tag,
|
||||
context,
|
||||
)
|
||||
|
||||
} else if (result.tag === "js") {
|
||||
let call_result:any = ""
|
||||
let run_code = new Function(
|
||||
"__code__",
|
||||
"return eval(__code__)"
|
||||
)
|
||||
try {
|
||||
call_result = run_code.call(context, result?.content)
|
||||
if (String(call_result) == "[object Object]") {
|
||||
call_result = JSON.stringify(call_result)
|
||||
}
|
||||
} catch(e) {
|
||||
call_result = "\n> [!ERROR] " + String(e) + "\n"
|
||||
}
|
||||
return pre_tag
|
||||
+ String(call_result)
|
||||
+ await this.process_bbcode_tag(post_tag, context)
|
||||
}
|
||||
} catch (e) {
|
||||
if (!(e instanceof MBGeneralError)) {
|
||||
let ge = new MBGeneralError(e.message)
|
||||
ge.name = e.name
|
||||
ge.stack = e.stack
|
||||
return ge
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
return code
|
||||
}
|
||||
|
||||
parse_bbcode_args(
|
||||
source:string,
|
||||
): Record<string, any>|MBArgValueParseError|undefined {
|
||||
source = source.trim()
|
||||
let parsed:any = {}
|
||||
if (source.startsWith("=")) {
|
||||
source = source.slice(1)
|
||||
|
||||
let value_result = this.parse_value(source)
|
||||
if (value_result instanceof MBValueParseError) {
|
||||
return new MBArgValueParseError("", value_result)
|
||||
}
|
||||
|
||||
parsed[""] = value_result[0]
|
||||
source = value_result[1]
|
||||
}
|
||||
let arg_match:RegExpMatchArray | null = null
|
||||
while ((arg_match = source.match(/([\w\-]*)\s*=([\s\S]*)/)) !== null) {
|
||||
let arg_name = (arg_match[1] ?? "")
|
||||
.toLocaleLowerCase()
|
||||
.replace(/[_\-]/g, "")
|
||||
let remainder = arg_match[2] ?? ""
|
||||
|
||||
let value_result = this.parse_value(remainder)
|
||||
if (value_result instanceof MBValueParseError) {
|
||||
return new MBArgValueParseError(arg_name, value_result)
|
||||
}
|
||||
|
||||
parsed[arg_name] = value_result[0]
|
||||
remainder = value_result[1]
|
||||
source = remainder
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
parse_value(source:string):MBValueParseError|any[] {
|
||||
let trimmed_source = source.trim()
|
||||
let match:RegExpMatchArray|null = null
|
||||
if (
|
||||
(match = trimmed_source.match(/""|".*?[^\\]"|''|'.*?[^\\]'/))
|
||||
!== null
|
||||
&& match.index === 0
|
||||
) {
|
||||
// Match string
|
||||
let remaining = match.input?.slice(match[0].length)
|
||||
return [match[0].slice(1, -1), remaining]
|
||||
|
||||
} else if (
|
||||
(match = trimmed_source.match(/true|false/))
|
||||
!== null
|
||||
&& match.index === 0
|
||||
) {
|
||||
// Match boolean
|
||||
let remaining = match.input?.slice(match[0].length)
|
||||
return [Boolean(match[0][0] === "t"), remaining]
|
||||
} else if (
|
||||
(match = trimmed_source.match(/([\-\+]*)?([\d_]+)?(?:(\.)([\d_]*))?/))
|
||||
!== null
|
||||
&& match.index === 0
|
||||
&& match[0].length !== 0
|
||||
) {
|
||||
// Match number
|
||||
let remaining = match.input?.slice(match[0].length)
|
||||
return [Number(match[0].replace("_", "")), remaining]
|
||||
}
|
||||
|
||||
return new MBValueParseError(source)
|
||||
}
|
||||
|
||||
add_command(
|
||||
keyword:string,
|
||||
): MBCommand {
|
||||
let cmd = new MBCommand
|
||||
this.commands.push(cmd)
|
||||
cmd.keyword = keyword
|
||||
return cmd
|
||||
}
|
||||
|
||||
parse(source:string, el:HTMLElement) {
|
||||
for (const LINE of source.split("\n")) {
|
||||
this.render_line(LINE, el)
|
||||
}
|
||||
}
|
||||
|
||||
parse_cmd_args(
|
||||
cmd:MBCommand,
|
||||
args:string,
|
||||
): Record<string, any> | MBParseError {
|
||||
let result:Record<string, any> = {}
|
||||
let remaining_args = args
|
||||
let args_offset = Number(args.match(/\s*/)?.[0].length)
|
||||
for (let i=0; args.slice(args_offset).length !== 0; i++) {
|
||||
let potential_offset = 0
|
||||
|
||||
// Match arg name
|
||||
let arg_key = ""
|
||||
let arg_name_match = args.slice(args_offset).match(/(\w*)\s*=/)
|
||||
if (arg_name_match !== null) {
|
||||
arg_key = String(arg_name_match?.[1])
|
||||
potential_offset += arg_name_match?.[0].length
|
||||
potential_offset += Number(args.slice(args_offset).match(/\s*/)?.[0].length)
|
||||
} else {
|
||||
arg_key = cmd.arguments[i].key
|
||||
}
|
||||
let has_key = false
|
||||
for (const CMD of cmd.arguments) {
|
||||
if (arg_key === CMD.key) {
|
||||
has_key = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!has_key) {
|
||||
return new MBParseError(
|
||||
"No argument by name \"{0}\"".format(arg_key),
|
||||
args,
|
||||
args_offset,
|
||||
)
|
||||
}
|
||||
|
||||
// Match string
|
||||
let str_match = args.slice(args_offset)
|
||||
.match(/""|".*?[^\\]"|''|'.*?[^\\]'/)
|
||||
if (str_match !== null) {
|
||||
result[arg_key] = str_match[0].slice(1, -1)
|
||||
potential_offset += str_match[0].length
|
||||
}
|
||||
|
||||
if (Object.keys(result).length !== i+1) {
|
||||
return new MBParseError(
|
||||
"Failed to parse argument",
|
||||
args,
|
||||
args_offset,
|
||||
)
|
||||
}
|
||||
if (Object.keys(result).length > cmd.arguments.length) {
|
||||
return new MBParseError(
|
||||
"Unexpected argument",
|
||||
args,
|
||||
args_offset,
|
||||
)
|
||||
}
|
||||
if (typeof(result[arg_key]) !== cmd.arguments[i].type) {
|
||||
return new MBParseError(
|
||||
"Invalid argument",
|
||||
args,
|
||||
args_offset,
|
||||
)
|
||||
}
|
||||
if (result[arg_key] === undefined) {
|
||||
return new MBParseError(
|
||||
"Absent required argument",
|
||||
args,
|
||||
args_offset,
|
||||
)
|
||||
}
|
||||
|
||||
args_offset += potential_offset
|
||||
args_offset += Number(args.slice(args_offset).match(/\s*/)?.[0].length)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async render_mb_code(source:string, el:HTMLElement) {
|
||||
let promises = []
|
||||
for (const LINE of source.split("\n")) {
|
||||
promises.push(this.render_line(LINE, el.createDiv()))
|
||||
}
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
async render_line(line:string, el: HTMLElement) {
|
||||
let cmd_txt = line
|
||||
let args_txt = ""
|
||||
if (line.contains(" ")) {
|
||||
cmd_txt = line.slice(0, line.indexOf(" "))
|
||||
args_txt = line.slice(line.indexOf(" "))
|
||||
}
|
||||
let cmd = cmd_txt.toLowerCase().replace(/[_-]/g, "")
|
||||
if (cmd.startsWith("/")) {
|
||||
cmd = cmd.slice(1)
|
||||
for (const CMD of this.commands) {
|
||||
if (cmd === CMD.keyword) {
|
||||
let args = this.parse_cmd_args(CMD, args_txt)
|
||||
if (args instanceof MBParseError) {
|
||||
args.render("/"+cmd, el)
|
||||
return
|
||||
}
|
||||
await CMD.call(args, el, this.plugin)
|
||||
return
|
||||
}
|
||||
}
|
||||
new MBParseError(
|
||||
"No command exists with this name".format(cmd),
|
||||
line,
|
||||
0
|
||||
).render(undefined, el)
|
||||
} else {
|
||||
await this.render_verse(line, el)
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes a verse reference to verse text
|
||||
async process_verse_reference(
|
||||
raw_ref:string,
|
||||
with_verse_numbers:boolean=true,
|
||||
separator:string=" ",
|
||||
version:string|undefined = undefined,
|
||||
): Promise<string> {
|
||||
const ref = raw_ref.trim().replace(/[:-]/g, " ").split(" ");
|
||||
|
||||
let book:string|null = null;
|
||||
let book_id = -1;
|
||||
let chapter = -1;
|
||||
let verse = -1;
|
||||
let verse_end = -1;
|
||||
let maybe_translation:string|undefined = undefined;
|
||||
let i = 0;
|
||||
|
||||
while (i != ref.length) {
|
||||
// Compose book name
|
||||
if (i == 0) {
|
||||
// Always add first item, no matter what it is
|
||||
book = (book || "") + ref[i];
|
||||
} else {
|
||||
// Only add items that are words, and not numbers
|
||||
if (!is_alpha(ref[i])) {
|
||||
break;
|
||||
}
|
||||
book += " " + ref[i]
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
if (is_numeric(book || "")) {
|
||||
// Compose book_id
|
||||
book_id = Number(book);
|
||||
book = null;
|
||||
} else {
|
||||
book_id = 0
|
||||
}
|
||||
|
||||
if (i != ref.length && is_numeric(ref[i])) {
|
||||
// Compose chapter
|
||||
chapter = Number(ref[i]);
|
||||
i += 1;
|
||||
} else {
|
||||
chapter = 0
|
||||
}
|
||||
|
||||
if (i != ref.length && is_numeric(ref[i])) {
|
||||
// Compose verse
|
||||
verse = Number(ref[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (i != ref.length && is_numeric(ref[i])) {
|
||||
// Compose range
|
||||
verse_end = Number(ref[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (i != ref.length && is_alphanumeric(ref[i])) {
|
||||
// Compose translation
|
||||
maybe_translation = ref[i];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
let translation = version
|
||||
?? maybe_translation
|
||||
?? this.plugin.settings.reading_translation;
|
||||
if (book !== null) {
|
||||
book_id = await this.plugin.bible_api
|
||||
.book_id(this.plugin.settings._built_translation||translation, book);
|
||||
}
|
||||
|
||||
if (book_id === 0) {
|
||||
return "\n> [!ERROR] Book and chapter must be provided\n"
|
||||
}
|
||||
if (chapter === 0) {
|
||||
return "\n> [!ERROR] Chapter must be provided\n"
|
||||
}
|
||||
|
||||
book = (await this.plugin.bible_api.get_book_data(translation, book_id)).name;
|
||||
|
||||
let text = "";
|
||||
if (book.length === 0) {
|
||||
text = "\n> [!ERROR] Book and chapter must be provided\n"
|
||||
} else if (chapter === -1) {
|
||||
text = "\n> [!ERROR] Chapter must be provided\n"
|
||||
} else if (verse === -1) {
|
||||
// Whole chapter
|
||||
let verses = await this.plugin.bible_api.get_chapter(
|
||||
translation,
|
||||
book_id,
|
||||
chapter,
|
||||
);
|
||||
for (const verse_i_ of Object.keys(verses)) {
|
||||
const verse_i = Number(verse_i_)
|
||||
let verse = verses[verse_i];
|
||||
text += "<sup>" + (verse_i) + "</sup> " + verse;
|
||||
if (verse_i != Object.keys(verses).length+1) {
|
||||
text += " ";
|
||||
// text += "<br>";
|
||||
}
|
||||
}
|
||||
if (text.length === 0) {
|
||||
text = "\n> [!WARNING] No text found for {1} {0} in translation {2}\n"
|
||||
.format(String(chapter), book, translation)
|
||||
}
|
||||
} else if (verse_end < verse) {
|
||||
// Single verse
|
||||
text = await this.plugin.bible_api.get_verse(
|
||||
translation,
|
||||
book_id,
|
||||
chapter,
|
||||
verse,
|
||||
)
|
||||
if (text.length === 0) {
|
||||
text = "\n> [!WARNING] No text found for {1} {0}:{2} in translation {3}\n"
|
||||
.format(String(chapter), book, String(verse), translation)
|
||||
}
|
||||
} else {
|
||||
// Verse range
|
||||
let verses = await this.plugin.bible_api.get_chapter(
|
||||
translation,
|
||||
book_id,
|
||||
chapter,
|
||||
);
|
||||
let j = verse;
|
||||
while (j < verse_end + 1 && j < Object.keys(verses).length) {
|
||||
if (with_verse_numbers) {
|
||||
text += "<sup>" + j + "</sup> " + verses[j];
|
||||
if (j != verse_end) {
|
||||
text += separator
|
||||
}
|
||||
} else {
|
||||
text += verses[j] + separator
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
if (text.length === 0) {
|
||||
text = "\n> [!WARNING] No text found for {1} {0}:{2}-{3} in translation {4}\n"
|
||||
.format(String(chapter), book, String(verse), String(verse_end), translation)
|
||||
}
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
async render_verse(line:string, el:HTMLElement, with_verse_numbers:boolean=true) {
|
||||
let div = el.createDiv()
|
||||
const ref = line.trim().replace(/[:-]/g, " ").split(" ");
|
||||
|
||||
let book:string|null = null;
|
||||
let book_id = -1;
|
||||
let chapter = -1;
|
||||
let verse = -1;
|
||||
let verse_end = -1;
|
||||
let maybe_translation: string | null = null;
|
||||
let i = 0;
|
||||
|
||||
while (i != ref.length) {
|
||||
// Compose book name
|
||||
if (i == 0) {
|
||||
// Always add first item, no matter what it is
|
||||
book = (book || "") + ref[i];
|
||||
} else {
|
||||
// Only add items that are words, and not numbers
|
||||
if (!is_alpha(ref[i])) {
|
||||
break;
|
||||
}
|
||||
book += " " + ref[i]
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
if (is_numeric(book || "")) {
|
||||
// Compose book_id
|
||||
book_id = Number(book);
|
||||
book = null;
|
||||
}
|
||||
|
||||
if (i != ref.length && is_numeric(ref[i])) {
|
||||
// Compose chapter
|
||||
chapter = Number(ref[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (i != ref.length && is_numeric(ref[i])) {
|
||||
// Compose verse
|
||||
verse = Number(ref[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (i != ref.length && is_numeric(ref[i])) {
|
||||
// Compose range
|
||||
verse_end = Number(ref[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (i != ref.length && is_alphanumeric(ref[i])) {
|
||||
// Compose translation
|
||||
maybe_translation = ref[i];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
let translation = maybe_translation || this.plugin.settings.reading_translation;
|
||||
if (book !== null) {
|
||||
book_id = await this.plugin.bible_api
|
||||
.book_id(this.plugin.settings._built_translation||translation, book);
|
||||
}
|
||||
book = (await this.plugin.bible_api.get_book_data(translation, book_id)).name;
|
||||
|
||||
let text = "";
|
||||
if (book.length === 0) {
|
||||
text = "[Book and chapter must be provided]";
|
||||
} else if (chapter === -1) {
|
||||
text = "[Chapter must be provided]";
|
||||
} else if (verse === -1) {
|
||||
// Whole chapter
|
||||
let verses = await this.plugin.bible_api.get_chapter(
|
||||
translation,
|
||||
book_id,
|
||||
chapter,
|
||||
);
|
||||
for (const verse_i_ of Object.keys(verses)) {
|
||||
const verse_i = Number(verse_i_)
|
||||
let verse = verses[verse_i];
|
||||
text += "<sup>" + (verse_i) + "</sup> " + verse;
|
||||
if (verse_i != Object.keys(verses).length+1) {
|
||||
text += " ";
|
||||
// text += "<br>";
|
||||
}
|
||||
}
|
||||
if (text.length === 0) {
|
||||
text = "<No text found for {1} {0} in translation {2}>"
|
||||
.format(String(chapter), book, translation)
|
||||
}
|
||||
} else if (verse_end < verse) {
|
||||
// Single verse
|
||||
text = await this.plugin.bible_api.get_verse(
|
||||
translation,
|
||||
book_id,
|
||||
chapter,
|
||||
verse,
|
||||
)
|
||||
if (text.length === 0) {
|
||||
text = "<No text found for {1} {0}:{2} in translation {3}>"
|
||||
.format(String(chapter), book, String(verse), translation)
|
||||
}
|
||||
} else {
|
||||
// Verse range
|
||||
let verses = await this.plugin.bible_api.get_chapter(
|
||||
translation,
|
||||
book_id,
|
||||
chapter,
|
||||
);
|
||||
let j = verse;
|
||||
while (j < verse_end + 1 && j < Object.keys(verses).length) {
|
||||
if (with_verse_numbers) {
|
||||
text += "<sup>" + j + "</sup> " + verses[j];
|
||||
if (j != verse_end) {
|
||||
text += "<br>";
|
||||
}
|
||||
} else {
|
||||
text += verses[j] + "<br>"
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
if (text.length === 0) {
|
||||
text = "<No text found for {1} {0}:{2}-{3} in translation {4}>"
|
||||
.format(String(chapter), book, String(verse), String(verse_end), translation)
|
||||
}
|
||||
}
|
||||
|
||||
let span = div.createSpan({
|
||||
text: "",
|
||||
});
|
||||
|
||||
let tags = text.matchAll(
|
||||
/(?:<\s*([\w]*)\s*>([\s\S]*?)<\s*\/\1\s*>)|<\s*(br|\/br|br\/)\s*>|([\s\S]+?(?:(?=<\s*[/\\\w]*\s*>)|$))/g
|
||||
);
|
||||
for (let match of tags) {
|
||||
let tag_type = match[1];
|
||||
let tag_text = match[2];
|
||||
let lone_tag_type = match[3];
|
||||
let normal_text = match[4];
|
||||
|
||||
if (normal_text !== undefined) {
|
||||
span.createSpan({
|
||||
text: normal_text,
|
||||
});
|
||||
} else if (lone_tag_type === "br") {
|
||||
span.createEl(lone_tag_type);
|
||||
} else if (lone_tag_type === "br/" || lone_tag_type === "/br") {
|
||||
span.createEl("br");
|
||||
span.createSpan({
|
||||
text: "\n",
|
||||
});
|
||||
} else if (lone_tag_type === "/J") {
|
||||
/* Do nothing */
|
||||
} else if (tag_type === "sup") {
|
||||
span.createEl(tag_type, { text: tag_text });
|
||||
} else if (tag_type === "sub") {
|
||||
span.createEl(tag_type, { text: tag_text });
|
||||
} else if (tag_type === "S") {
|
||||
span.createEl(
|
||||
"sub",
|
||||
{ text: tag_text, attr: { style: "opacity: 0.5" } },
|
||||
);
|
||||
} else if (tag_type === "i") {
|
||||
span.createEl(tag_type, { text: tag_text });
|
||||
} else if (tag_type === "b") {
|
||||
span.createEl(tag_type, { text: tag_text });
|
||||
} else if (tag_type === "e") {
|
||||
/// A quote from from elsewhere in the bible.
|
||||
span.createEl("i").createEl("q", { text: tag_text });
|
||||
} else {
|
||||
span.createSpan(
|
||||
{ text: "<{0}>{1}</{0}>".format(tag_type, tag_text) },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MBCommand {
|
||||
keyword: string = ""
|
||||
arguments: MBArg[] = []
|
||||
call: (args:Record<string, any>, el:HTMLElement, plugin: MyBible) => void
|
||||
|
||||
callback(
|
||||
c: (args:Record<string, any>, el:HTMLElement, plugin: MyBible) => void
|
||||
) {
|
||||
this.call = c
|
||||
return this
|
||||
}
|
||||
|
||||
arg(key:string, type:string, default_value:any=undefined) {
|
||||
let arg = new MBArg(key, type, default_value)
|
||||
this.arguments.push(arg)
|
||||
return this
|
||||
}
|
||||
opitonal_args(args: MBArg[]) {
|
||||
this.arguments.concat(args)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
class MBArg {
|
||||
key: string = ""
|
||||
type: string = ""
|
||||
default_value: any = undefined
|
||||
|
||||
constructor(key:string, type:string, default_value:any=undefined) {
|
||||
this.key = key
|
||||
this.type = type
|
||||
this.default_value = default_value
|
||||
async runJS(code:string, context?:any):Promise<any> {
|
||||
let call_result = await async function() {
|
||||
return eval("(async () => { {0} })()".format(code))
|
||||
}.call(context)
|
||||
return call_result
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1318,42 +563,6 @@ class MBGeneralError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
class MBParseError extends Error {
|
||||
name: string = ""
|
||||
code: string = ""
|
||||
stack?: string;
|
||||
offset: number = 0
|
||||
|
||||
toString():string {
|
||||
return this.name
|
||||
}
|
||||
|
||||
constructor(name:string, code:string="", offset:number=0) {
|
||||
super(name)
|
||||
this.name = name
|
||||
this.code = code
|
||||
this.offset = offset
|
||||
}
|
||||
|
||||
render(code_prefix:string="", el:HTMLElement) {
|
||||
let err_block = el.createDiv()
|
||||
err_block.setAttr("class", "callout")
|
||||
err_block.setAttr("data-callout", "error")
|
||||
|
||||
let line_1 = code_prefix + this.code
|
||||
let line_2 = "_".repeat(code_prefix.length+this.offset) + "^".repeat(this.code.length-this.offset)
|
||||
err_block.appendText(this.name)
|
||||
let block = err_block.createEl("div", {
|
||||
"cls": "HyperMD-codeblock HyperMD-codeblock-bg cm-line",
|
||||
})
|
||||
block.setAttr("data-callout", "error")
|
||||
block.appendText(line_1 + "\n")
|
||||
block.createEl("br")
|
||||
block.appendText(line_2)
|
||||
return el
|
||||
}
|
||||
}
|
||||
|
||||
class MBTagError extends MBGeneralError {}
|
||||
|
||||
class MBValueParseError extends MBTagError {
|
||||
|
|
@ -3755,7 +2964,7 @@ function to_hebraic_order(id: BookId) {
|
|||
return HEBRAIC_ORDER[BOOK_NAME]
|
||||
}
|
||||
|
||||
const DEFAULT_NAME_MAP: Record<string, BookId> = {
|
||||
export const DEFAULT_NAME_MAP: Record<string, BookId> = {
|
||||
"Genesis": 1,
|
||||
"Exodus": 2,
|
||||
"Leviticus": 3,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { MBInterpreter } from "main"
|
||||
import { myBibleAPI } from "api"
|
||||
import MyBible, { getPlugin, is_alpha, is_alphanumeric, is_numeric } from "main"
|
||||
|
||||
const ohm = require('ohm-js')
|
||||
|
||||
|
|
@ -97,7 +98,6 @@ const myBibleSemmantics = myBibleGrammar.createSemantics().addOperation('eval',
|
|||
// HACK: Using flat() to remove recursive arrays from _content
|
||||
open["content"] = _content.eval().flat()
|
||||
}
|
||||
console.log(open)
|
||||
return open
|
||||
},
|
||||
unclosedTag(_open:any, _content:any) {
|
||||
|
|
@ -149,39 +149,53 @@ export class BBCodeTag {
|
|||
this.args = args ?? {}
|
||||
}
|
||||
|
||||
async toText(parser:MBInterpreter):Promise<string> {
|
||||
async toText(context:any):Promise<string> {
|
||||
let text = ""
|
||||
|
||||
if (this.name === "verse") {
|
||||
let verse_text = await parser
|
||||
.process_verse_reference(this.args[""] ?? "1 1 1")
|
||||
text += verse_text + await this.contentString(parser)
|
||||
let verse_text = await myBibleAPI.scripture(
|
||||
this.args[""] ?? "1 1 1"
|
||||
)
|
||||
text += verse_text + await this.contentString(context)
|
||||
|
||||
} else if (this.name === "js") {
|
||||
parser.run_js(await this.contentString(parser), {})
|
||||
try {
|
||||
let result = await getPlugin()
|
||||
.runJS(await this.contentString(context), context)
|
||||
result = result ?? ""
|
||||
text += await result.toString()
|
||||
} catch(e) {
|
||||
text += "\n> [!ERROR] JS {0}\n{1}\n"
|
||||
.format(e.name, e.message)
|
||||
}
|
||||
|
||||
} else if (this.name === "randomverse") {
|
||||
let seed = String(this.args["seed"])
|
||||
let verse_numbers = this.args["versenumbers"] ?? false
|
||||
let separator = this.args["separator"] ?? " "
|
||||
let version = this.args["version"]
|
||||
let verse = await parser.plugin.bible_api.pick_random_verse(seed)
|
||||
let verse_text = await parser.process_verse_reference(
|
||||
let verse = await getPlugin().bible_api.pick_random_verse(seed)
|
||||
let verse_text = await myBibleAPI.scripture(
|
||||
verse ?? "1 1 1",
|
||||
verse_numbers,
|
||||
separator,
|
||||
version,
|
||||
)
|
||||
text += verse_text + await this.contentString(parser)
|
||||
text += verse_text + await this.contentString(context)
|
||||
|
||||
} else {
|
||||
text += "> [!ERROR] Tag error\n> Tag `{0}` is not a valid tag\n{1}"
|
||||
.format(this.name, await this.contentString(parser))
|
||||
.format(this.name, await this.contentString(context))
|
||||
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
async contentString(parser:MBInterpreter):Promise<string> {
|
||||
async contentString(context:any):Promise<string> {
|
||||
let text = ""
|
||||
for (const X of this.content) {
|
||||
if (X instanceof BBCodeTag) {
|
||||
text += await X.toText(parser)
|
||||
text += await X.toText(context)
|
||||
} else {
|
||||
text += X
|
||||
}
|
||||
|
|
@ -189,3 +203,235 @@ export class BBCodeTag {
|
|||
return text
|
||||
}
|
||||
}
|
||||
|
||||
export module legacy {
|
||||
class MBCommand {
|
||||
keyword: string = ""
|
||||
arguments: MBArg[] = []
|
||||
call: (args:Record<string, any>, el:HTMLElement, plugin: MyBible) => void
|
||||
|
||||
callback(
|
||||
c: (args:Record<string, any>, el:HTMLElement, plugin: MyBible) => void
|
||||
) {
|
||||
this.call = c
|
||||
return this
|
||||
}
|
||||
|
||||
arg(key:string, type:string, default_value:any=undefined) {
|
||||
let arg = new MBArg(key, type, default_value)
|
||||
this.arguments.push(arg)
|
||||
return this
|
||||
}
|
||||
opitonal_args(args: MBArg[]) {
|
||||
this.arguments.concat(args)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
class MBArg {
|
||||
key: string = ""
|
||||
type: string = ""
|
||||
default_value: any = undefined
|
||||
|
||||
constructor(key:string, type:string, default_value:any=undefined) {
|
||||
this.key = key
|
||||
this.type = type
|
||||
this.default_value = default_value
|
||||
}
|
||||
}
|
||||
export class VerseParser {
|
||||
commands: MBCommand[]
|
||||
context: any
|
||||
|
||||
constructor() {
|
||||
this.commands = []
|
||||
this.context = {}
|
||||
}
|
||||
|
||||
parse(source:string, el:HTMLElement) {
|
||||
for (const LINE of source.split("\n")) {
|
||||
this.render_verse(LINE, el)
|
||||
}
|
||||
}
|
||||
|
||||
async render_verse(
|
||||
line:string,
|
||||
el:HTMLElement,
|
||||
with_verse_numbers:boolean=true,
|
||||
) {
|
||||
let div = el.createDiv()
|
||||
const ref = line.trim().replace(/[:-]/g, " ").split(" ");
|
||||
|
||||
let book:string|null = null;
|
||||
let book_id = -1;
|
||||
let chapter = -1;
|
||||
let verse = -1;
|
||||
let verse_end = -1;
|
||||
let maybe_translation: string | null = null;
|
||||
let i = 0;
|
||||
|
||||
while (i != ref.length) {
|
||||
// Compose book name
|
||||
if (i == 0) {
|
||||
// Always add first item, no matter what it is
|
||||
book = (book || "") + ref[i];
|
||||
} else {
|
||||
// Only add items that are words, and not numbers
|
||||
if (!is_alpha(ref[i])) {
|
||||
break;
|
||||
}
|
||||
book += " " + ref[i]
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
if (is_numeric(book || "")) {
|
||||
// Compose book_id
|
||||
book_id = Number(book);
|
||||
book = null;
|
||||
}
|
||||
|
||||
if (i != ref.length && is_numeric(ref[i])) {
|
||||
// Compose chapter
|
||||
chapter = Number(ref[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (i != ref.length && is_numeric(ref[i])) {
|
||||
// Compose verse
|
||||
verse = Number(ref[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (i != ref.length && is_numeric(ref[i])) {
|
||||
// Compose range
|
||||
verse_end = Number(ref[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (i != ref.length && is_alphanumeric(ref[i])) {
|
||||
// Compose translation
|
||||
maybe_translation = ref[i];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
let translation = maybe_translation || getPlugin().settings.reading_translation;
|
||||
if (book !== null) {
|
||||
book_id = await getPlugin().bible_api
|
||||
.book_id(getPlugin().settings._built_translation||translation, book);
|
||||
}
|
||||
book = (await getPlugin().bible_api.get_book_data(translation, book_id)).name;
|
||||
|
||||
let text = "";
|
||||
if (book.length === 0) {
|
||||
text = "[Book and chapter must be provided]";
|
||||
} else if (chapter === -1) {
|
||||
text = "[Chapter must be provided]";
|
||||
} else if (verse === -1) {
|
||||
// Whole chapter
|
||||
let verses = await getPlugin().bible_api.get_chapter(
|
||||
translation,
|
||||
book_id,
|
||||
chapter,
|
||||
);
|
||||
for (const verse_i_ of Object.keys(verses)) {
|
||||
const verse_i = Number(verse_i_)
|
||||
let verse = verses[verse_i];
|
||||
text += "<sup>" + (verse_i) + "</sup> " + verse;
|
||||
if (verse_i != Object.keys(verses).length+1) {
|
||||
text += " ";
|
||||
// text += "<br>";
|
||||
}
|
||||
}
|
||||
if (text.length === 0) {
|
||||
text = "<No text found for {1} {0} in translation {2}>"
|
||||
.format(String(chapter), book, translation)
|
||||
}
|
||||
} else if (verse_end < verse) {
|
||||
// Single verse
|
||||
text = await getPlugin().bible_api.get_verse(
|
||||
translation,
|
||||
book_id,
|
||||
chapter,
|
||||
verse,
|
||||
)
|
||||
if (text.length === 0) {
|
||||
text = "<No text found for {1} {0}:{2} in translation {3}>"
|
||||
.format(String(chapter), book, String(verse), translation)
|
||||
}
|
||||
} else {
|
||||
// Verse range
|
||||
let verses = await getPlugin().bible_api.get_chapter(
|
||||
translation,
|
||||
book_id,
|
||||
chapter,
|
||||
);
|
||||
let j = verse;
|
||||
while (j < verse_end + 1 && j < Object.keys(verses).length) {
|
||||
if (with_verse_numbers) {
|
||||
text += "<sup>" + j + "</sup> " + verses[j];
|
||||
if (j != verse_end) {
|
||||
text += "<br>";
|
||||
}
|
||||
} else {
|
||||
text += verses[j] + "<br>"
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
if (text.length === 0) {
|
||||
text = "<No text found for {1} {0}:{2}-{3} in translation {4}>"
|
||||
.format(String(chapter), book, String(verse), String(verse_end), translation)
|
||||
}
|
||||
}
|
||||
|
||||
let span = div.createSpan({
|
||||
text: "",
|
||||
});
|
||||
|
||||
let tags = text.matchAll(
|
||||
/(?:<\s*([\w]*)\s*>([\s\S]*?)<\s*\/\1\s*>)|<\s*(br|\/br|br\/)\s*>|([\s\S]+?(?:(?=<\s*[/\\\w]*\s*>)|$))/g
|
||||
);
|
||||
for (let match of tags) {
|
||||
let tag_type = match[1];
|
||||
let tag_text = match[2];
|
||||
let lone_tag_type = match[3];
|
||||
let normal_text = match[4];
|
||||
|
||||
if (normal_text !== undefined) {
|
||||
span.createSpan({
|
||||
text: normal_text,
|
||||
});
|
||||
} else if (lone_tag_type === "br") {
|
||||
span.createEl(lone_tag_type);
|
||||
} else if (lone_tag_type === "br/" || lone_tag_type === "/br") {
|
||||
span.createEl("br");
|
||||
span.createSpan({
|
||||
text: "\n",
|
||||
});
|
||||
} else if (lone_tag_type === "/J") {
|
||||
/* Do nothing */
|
||||
} else if (tag_type === "sup") {
|
||||
span.createEl(tag_type, { text: tag_text });
|
||||
} else if (tag_type === "sub") {
|
||||
span.createEl(tag_type, { text: tag_text });
|
||||
} else if (tag_type === "S") {
|
||||
span.createEl(
|
||||
"sub",
|
||||
{ text: tag_text, attr: { style: "opacity: 0.5" } },
|
||||
);
|
||||
} else if (tag_type === "i") {
|
||||
span.createEl(tag_type, { text: tag_text });
|
||||
} else if (tag_type === "b") {
|
||||
span.createEl(tag_type, { text: tag_text });
|
||||
} else if (tag_type === "e") {
|
||||
/// A quote from from elsewhere in the bible.
|
||||
span.createEl("i").createEl("q", { text: tag_text });
|
||||
} else {
|
||||
span.createSpan(
|
||||
{ text: "<{0}>{1}</{0}>".format(tag_type, tag_text) },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue