Skip to main content

logoxide (bindings)

logoxide's packages with topos's whole API, in one package for TypeScript/WebAssembly, Python, Swift, and Kotlin (through BoltFFI). Topos, ToposTranslations, and the rest are the same as in topos's own package (topos-bible). A book's text is logoxide's own LogoxideBook (pkg.book(id), or LogoxideBook.parse(topos, "usx", text) for a file of your own): its verses, and passages as HTML or plain text. It was topos's ToposBook, with the same methods; its BookVerse and BookTextOptions keep their names.

The host reads and writes the files: an app's file API is often asynchronous (Obsidian's vault), and WebAssembly has none. logoxide reads the text from the bytes it's given, and hands back the cache files it wrote for the host to save. Only the Python (and Swift and Kotlin) packages are native, so only they have openPackageDir, readPassage, and comparePassage, which read folders themselves; the npm package is WebAssembly in Node too.

The examples

Each feature below has an example in TypeScript and in Python. They run in order, as one program: each uses what the ones before it made (kjv, bsb, project, installer, ...). What a line prints is written after it, following → (… stands for text left out).

They're checked: examples/node/readme.ts and examples/python/readme.py run every example here and compare what it prints with its → comments. They run in a folder holding the KJV and BSB packages (kjv/ and bsb/) and the files in examples/fixtures, and answer the downloads the examples make offline: https://example.org/kjv and https://example.org/bsb are those packages' repositories, and https://example.org/catalog.toml and eBible.org's list and zip are fixtures, so what's printed is theirs. With real URLs the code is the same.

Loading

In Node and Bun the WebAssembly module is ready on import (in a browser, await initialized first). Names are camelCase in TypeScript and snake_case in Python. Records are plain objects in TypeScript and frozen dataclasses in Python, every field given; enums without data are numbers in TypeScript (PackageKind.Bible is 0) and IntEnums in Python (PackageKind.BIBLE); enums with data are { tag: "Library", dir } objects in TypeScript and a class each in Python (PackageLocationLibrary(dir=...)). A field or argument named import is _import in TypeScript and import_ in Python, and from is from_ in Python. Bytes are Uint8Array and bytes, and so are lists of small numbers (books(), verse counts); 64-bit numbers (a file's size) are bigints in TypeScript.

The host's own helpers, used by the examples below: a folder's files, writing files, and HTTP.

import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
import { dirname, join, relative, sep } from "node:path";
import { Logoxide, Topos, type PackageFile } from "logoxide";

/** Every file under a folder but git's, with `/`-separated paths relative to it */
function files(dir: string, root = dir): PackageFile[] {
  return readdirSync(dir, { withFileTypes: true })
    .filter((entry) => entry.name !== ".git")
    .flatMap((entry) => {
      const path = join(dir, entry.name);
      if (entry.isDirectory()) return files(path, root);
      return [{ path: relative(root, path).split(sep).join("/"), bytes: new Uint8Array(readFileSync(path)) }];
    });
}

/** Writes files under a folder */
function write(dir: string, written: PackageFile[]) {
  for (const file of written) {
    mkdirSync(dirname(join(dir, file.path)), { recursive: true });
    writeFileSync(join(dir, file.path), file.bytes);
  }
}

/** A file from its text */
const file = (path: string, text: string): PackageFile => ({ path, bytes: new TextEncoder().encode(text) });

/** The host's HTTP GET (`requestUrl` in Obsidian) */
async function get(url: string): Promise<Uint8Array> {
  const response = await fetch(url);
  if (!response.ok) throw new Error(`${url}: ${response.status} ${response.statusText}`);
  return new Uint8Array(await response.arrayBuffer());
}

/** The first of several URLs that answers */
async function getFirst(urls: string[]): Promise<Uint8Array> {
  for (const url of urls.slice(0, -1)) {
    try { return await get(url); } catch { /* the next */ }
  }
  return get(urls[urls.length - 1]);
}

const logoxide = Logoxide.new();
const topos = Topos.new();
console.log(logoxide.version()); // → 0.1.0
import dataclasses
import pathlib
import urllib.request

from logoxide import Logoxide, PackageFile, Topos


def files(folder: str) -> list[PackageFile]:
    """Every file under a folder but git's, with /-separated paths relative to it"""
    root = pathlib.Path(folder)
    return [
        PackageFile(path=p.relative_to(root).as_posix(), bytes=p.read_bytes())
        for p in sorted(root.rglob("*"))
        if p.is_file() and ".git" not in p.relative_to(root).parts
    ]


def write(folder: str, written: list[PackageFile]) -> None:
    """Writes files under a folder"""
    for f in written:
        path = pathlib.Path(folder, f.path)
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_bytes(f.bytes)


def file(path: str, text: str) -> PackageFile:
    """A file from its text"""
    return PackageFile(path=path, bytes=text.encode())


def get(url: str) -> bytes:
    """The host's HTTP GET"""
    with urllib.request.urlopen(url) as response:
        return response.read()


def get_first(urls: list[str]) -> bytes:
    """The first of several URLs that answers"""
    for url in urls[:-1]:
        try:
            return get(url)
        except OSError:
            pass
    return get(urls[-1])


logoxide = Logoxide()
topos = Topos()
print(logoxide.version())  # → 0.1.0

Opening a package

openPackage takes every file under the package's folder (but .git), with paths like usx/JHN.usx, the features to turn on, and the cache files saved before. Opening counts every book's verses unless the cache has them (for USFM that means parsing the whole text, under a second for the KJV), so the host saves what cacheWrites() hands back and gives it back next time. info() is the package's [package]; checksum() is what a lock file's checksum should be, to catch changed files.

import { PackageKind } from "logoxide";

const features = { features: [], defaultFeatures: true }; // Cargo's --features and --no-default-features
const cache: PackageFile[] = []; // the host's cache folder
const kjv = logoxide.openPackage(files("kjv"), features, cache);
cache.push(...kjv.cacheWrites()); // save these, and give them back next time
const info = kjv.info();
console.log(info.id, info.abbreviation, info.aliases, info.version); // → kjv KJV [ 'AV', 'King James' ] 1.0.3
console.log(info.name, info.kind === PackageKind.Bible, info.language, info.rightToLeft); // → King James Version (1769) true en false
console.log(info.license, info.rights); // → LicenseRef-PublicDomain { redistributable: true, derivatives: 0, commercial: true, attribution: false }
console.log(info.work, info.edition, info.editionYear, info.versification); // → kjv 1769 1769 KJV
console.log(info.upstream?.note); // → fetch.bible's copy of eBible.org's eng-kjv2006
console.log(kjv.format(), kjv.books().length, kjv.books()[0], kjv.chapterVerses(43).length); // → usfm 66 1 21
console.log(kjv.checksum()); // → sha256:…
console.log(logoxide.openPackage(files("kjv"), features, cache).cacheWrites().length); // → 0
from logoxide import PackageFeatures, PackageKind

features = PackageFeatures(features=[], default_features=True)
cache: list[PackageFile] = []  # the host's cache folder
kjv = logoxide.open_package(files("kjv"), features, cache)
cache += kjv.cache_writes()  # save these, and give them back next time
info = kjv.info()
print(info.id, info.abbreviation, info.aliases, info.version)  # → kjv KJV ['AV', 'King James'] 1.0.3
print(info.name, info.kind == PackageKind.BIBLE, info.language, info.right_to_left)  # → King James Version (1769) True en False
print(info.license, info.rights)  # → LicenseRef-PublicDomain Rights(redistributable=True, derivatives=<Derivatives.ALLOWED: 0>, commercial=True, attribution=False)
print(info.work, info.edition, info.edition_year, info.versification)  # → kjv 1769 1769 KJV
print(info.upstream.note)  # → fetch.bible's copy of eBible.org's eng-kjv2006
print(kjv.format(), len(kjv.books()), kjv.books()[0], len(kjv.chapter_verses(43)))  # → usfm 66 1 21
print(kjv.checksum())  # → sha256:…
print(len(logoxide.open_package(files("kjv"), features, cache).cache_writes()))  # → 0

Its files: each book's file and title (bookFiles, titles), what its format passed over (skipped: front matter, books logoxide doesn't know), and the hash of each verse's words (verseHashes: verse-hash rule 1, the first 7 bytes of a SHA-256, which links keep to find a verse again in a later version).

console.log(kjv.bookFiles().find((f) => f.bookId === 43)); // → { bookId: 43, path: 'usfm/jhn.usfm', title: 'John' }
console.log(kjv.titles()[0], kjv.skipped()); // → { path: 'usfm/1ch.usfm', title: '1 Chronicles' } []
const hashes = kjv.verseHashes(43);
console.log(hashes.length, hashes.find((v) => v.chapter === 3 && v.verse === 16)); // → 879 { chapter: 3, verse: 16, last: 16, part: 0, hash: '…' }
print(next(f for f in kjv.book_files() if f.book_id == 43))  # → BookFile(book_id=43, path='usfm/jhn.usfm', title='John')
print(kjv.titles()[0], kjv.skipped())  # → TitledFile(path='usfm/1ch.usfm', title='1 Chronicles') []
hashes = kjv.verse_hashes(43)
print(len(hashes), next(v for v in hashes if (v.chapter, v.verse) == (3, 16)))  # → 879 VerseHashEntry(chapter=3, verse=16, last=16, part=0, hash='…')

Natively, openPackageDir reads a folder itself and keeps its cache in a folder of its own (so cacheWrites() is empty); in WebAssembly it's an Io error. readPackageInfo reads a package's logoxide.toml alone, without its text (to name a package that's turned off).

import { LogoxideErrorException } from "logoxide";

const bsb = logoxide.openPackage(files("bsb"), features, cache);
cache.push(...bsb.cacheWrites());
try {
  logoxide.openPackageDir("bsb", features, "cache");
} catch (error) {
  console.log((error as LogoxideErrorException).value); // → { tag: 'Io', message: 'no file access in WebAssembly: use openPackage' }
}
console.log(logoxide.readPackageInfo(readFileSync("bsb/logoxide.toml", "utf8")).name); // → Berean Standard Bible
bsb = logoxide.open_package_dir("bsb", features, "cache")
print(bsb.info().abbreviation, bsb.format(), bsb.cache_writes())  # → BSB usx []
print(logoxide.read_package_info(pathlib.Path("bsb/logoxide.toml").read_text()).name)  # → Berean Standard Bible

References, with topos

topos finds and parses references; everything it does (searching text, completing references, queries, indexes, formats) is in this package as in topos's own, and is described in topos's bindings' guide. A package adds itself to topos's ToposTranslations (addTo: its abbreviation, aliases, and verse counts), so a reference naming it is checked against its own verses; addToAs adds it by the names a project gives it instead.

import { BookStyle, OffsetUnit, ToposTranslations } from "logoxide";

let translations = ToposTranslations.create();
translations = kjv.addTo(translations);
translations = bsb.addToAs(translations, "B", ["Berean"]);
for (const reference of ["John 3:16 AV", "3 John 1:15 B", "3 John 1:14 Berean"]) {
  const found = topos.parseWithTranslation(reference, OffsetUnit.Utf16, translations, BookStyle.Name);
  console.log(found ? `${found.passage.reference} in ${found.translation?.name}` : "no such verse");
}
// → John 3:16 in KJV
// → no such verse
// → 3 John 1:14 in B
from logoxide import BookStyle, OffsetUnit, ToposTranslations

translations = ToposTranslations.create()
translations = kjv.add_to(translations)
translations = bsb.add_to_as(translations, "B", ["Berean"])
for reference in ["John 3:16 AV", "3 John 1:15 B", "3 John 1:14 Berean"]:
    found = topos.parse_with_translation(reference, OffsetUnit.CHAR, translations, BookStyle.NAME)
    print(f"{found.passage.reference} in {found.translation.name}" if found else "no such verse")
# → John 3:16 in KJV
# → no such verse
# → 3 John 1:14 in B

A book's text

book(id) parses a book once (cached) for passages as plain text or HTML, one string for each part of a reference (John 3:16; 4 has two). HTML uses logoxide-markup's classes, each with classPrefix: verse and chapter markers are empty <span class="topos-verse" data-n="16">s a stylesheet can number (data-osis-id is the verse as the book names it). Each verse runs from the end of the one before it, so a heading belongs to the verse after it.

import { type BookTextOptions } from "logoxide";

const john = topos.parse("John 3:16-17", BookStyle.Name)!;
const options: BookTextOptions = { notes: false, headings: true, classPrefix: "topos-" };
const kjvJohn = kjv.book(john.bookId);
console.log(kjvJohn.bookId(), kjvJohn.chapters().length, kjvJohn.chapterVerses()[2], kjvJohn.verses()[0]); // → 43 21 36 { chapter: 1, verse: 1, last: 1, lastChapter: 1, part: 0 }
console.log(kjvJohn.text(john, options)); // → [ '¶ For God so loved the world, …' ]
const html = bsb.book(43).html(john, options)[0];
console.log(html.slice(html.indexOf("<span"), html.indexOf("</span>") + 7)); // → <span class="topos-verse" data-osis-id="JHN 3:16" data-n="16"></span>
console.log(bsb.book(43).chapterHtml(3, options).length > 0); // → true
from logoxide import BookTextOptions

john = topos.parse("John 3:16-17", BookStyle.NAME)
options = BookTextOptions(notes=False, headings=True, class_prefix="topos-")
kjv_john = kjv.book(john.book_id)
print(kjv_john.book_id(), len(kjv_john.chapters()), kjv_john.chapter_verses()[2], kjv_john.verses()[0])  # → 43 21 36 BookVerse(chapter=1, verse=1, last=1, last_chapter=1, part=0)
print(kjv_john.text(john, options))  # → ['¶ For God so loved the world, …']
html = bsb.book(43).html(john, options)[0]
print(html[html.index("<span") : html.index("</span>") + 7])  # → <span class="topos-verse" data-osis-id="JHN 3:16" data-n="16"></span>
print(len(bsb.book(43).chapter_html(3, options)) > 0)  # → True

styledText writes a passage as the language server shows it: verse labels, a chapter label on its own line where a chapter starts inside the passage, words of Jesus and the like in templates, lines wrapped at width, and cut at limit bytes. Its marks say what each run of it is (a verse number, a heading), by byte offsets into the UTF-8 text.

import { NodeKind, type BookTextStyle } from "logoxide";

const style: BookTextStyle = {
  verseLabel: "[{verse}]", chapterLabel: "{name} {chapter}", notes: false, headings: false,
  templates: [{ kind: NodeKind.WordsOfJesus, template: "*{text}*" }], width: null, limit: null,
};
const styled = kjvJohn.styledText(topos.parse("John 3:2-3", BookStyle.Name)!, style, "John")!;
console.log(styled.text); // → [2] The same came to Jesus by night, … [3] Jesus answered and said unto him, *Verily, verily, … kingdom of God.*
const utf8 = new TextEncoder().encode(styled.text);
const runs = (kind: NodeKind) => styled.marks.filter((m) => m.kind === kind).map((m) => new TextDecoder().decode(utf8.slice(m.start, m.end)));
console.log(runs(NodeKind.Verse), runs(NodeKind.WordsOfJesus)); // → [ '[2]', '[3]' ] [ '*Verily, verily, I say unto thee, Except a man be born again, he cannot see the kingdom of God.*' ]
console.log(kjvJohn.styledText(topos.parse("John 2:25-3:1", BookStyle.Name)!, { ...style, width: 40, limit: 120 }, "John")!.text);
// → [25] And needed not that any should
// → testify of man: for he knew what was in
// → man.
// → John 3
// → [1] There was a man of the Phari …
from logoxide import BookTextStyle, KindTemplate, NodeKind

style = BookTextStyle(
    verse_label="[{verse}]", chapter_label="{name} {chapter}", notes=False, headings=False,
    templates=[KindTemplate(kind=NodeKind.WORDS_OF_JESUS, template="*{text}*")], width=None, limit=None,
)
styled = kjv_john.styled_text(topos.parse("John 3:2-3", BookStyle.NAME), style, "John")
print(styled.text)  # → [2] The same came to Jesus by night, … [3] Jesus answered and said unto him, *Verily, verily, … kingdom of God.*
utf8 = styled.text.encode()
runs = lambda kind: [utf8[m.start : m.end].decode() for m in styled.marks if m.kind == kind]
print(runs(NodeKind.VERSE), runs(NodeKind.WORDS_OF_JESUS))  # → ['[2]', '[3]'] ['*Verily, verily, I say unto thee, Except a man be born again, he cannot see the kingdom of God.*']
print(kjv_john.styled_text(topos.parse("John 2:25-3:1", BookStyle.NAME), dataclasses.replace(style, width=40, limit=120), "John").text)
# → [25] And needed not that any should
# → testify of man: for he knew what was in
# → man.
# → John 3
# → [1] There was a man of the Phari …

The tree itself, for a host that renders with its own engine: nodes() lists every node in document order, each naming its parent by index, with its kind, tag name, attributes, and text; verseNodes() says where each verse is in that list (its marker; start, its first node; and end, the first node after it, so nodes.slice(start, end) is the verse: a heading before it belongs to it). A milestone (a USX verse marker, an OSIS sID/eID pair) is a Start or an End.

import { MilestoneKind, type BookNode } from "logoxide";

const nodes = bsb.book(43).nodes();
const at = bsb.book(43).verseNodes().find((v) => v.chapter === 3 && v.verse === 16)!;
const marker = nodes[at.marker];
console.log(marker.kind === NodeKind.Verse, marker.name, marker.milestone === MilestoneKind.Start, marker.attributes[0]); // → true verse true { name: 'number', value: '16' }
const inNote = (n: BookNode): boolean => n.parent !== null && (nodes[n.parent].kind === NodeKind.Note || inNote(nodes[n.parent]));
const words = nodes.slice(at.start, at.end).filter((n) => n.kind === NodeKind.Text && !inNote(n));
console.log(words.map((n) => n.text).join("")); // → For God so loved the world that He gave His one and only Son, that everyone who believes in Him shall not perish but have eternal life.
from logoxide import MilestoneKind

nodes = bsb.book(43).nodes()
at = next(v for v in bsb.book(43).verse_nodes() if (v.chapter, v.verse) == (3, 16))
marker = nodes[at.marker]
print(marker.kind == NodeKind.VERSE, marker.name, marker.milestone == MilestoneKind.START, marker.attributes[0])  # → True verse True NodeAttribute(name='number', value='16')
in_note = lambda n: n.parent is not None and (nodes[n.parent].kind == NodeKind.NOTE or in_note(nodes[n.parent]))
words = [n for n in nodes[at.start : at.end] if n.kind == NodeKind.TEXT and not in_note(n)]
print("".join(n.text for n in words))  # → For God so loved the world that He gave His one and only Son, that everyone who believes in Him shall not perish but have eternal life.

A book file of your own: usfmToUsx turns USFM into USX, LogoxideBook.parse reads usx or osis (an unknown markup is InvalidMarkup, which is null in TypeScript), and LogoxideBook.usxChapterVerses counts a USX book's verses from its markers alone, fast enough for a whole Bible. LogoxideBook.findFragment finds a link's words (:~:text=…) in a verse whose words changed a little since, by byte offsets.

import { LogoxideBook } from "logoxide";

const usx = logoxide.usfmToUsx(readFileSync("kjv/usfm/3jn.usfm", "utf8"));
const thirdJohn = LogoxideBook.parse(topos, "usx", usx)!;
console.log(thirdJohn.bookId(), [...thirdJohn.chapterVerses()], [...LogoxideBook.usxChapterVerses(usx)]); // → 64 [ 14 ] [ 14 ]
console.log(LogoxideBook.parse(topos, "rtf", usx)); // → null
console.log(LogoxideBook.findFragment("For God so greatly loved the world", "so loved the world")); // → { start: 8, end: 34 }
from logoxide import LogoxideBook, LogoxideErrorException

usx = logoxide.usfm_to_usx(pathlib.Path("kjv/usfm/3jn.usfm").read_text())
third_john = LogoxideBook.parse(topos, "usx", usx)
print(third_john.book_id(), list(third_john.chapter_verses()), list(LogoxideBook.usx_chapter_verses(usx)))  # → 64 [14] [14]
try:
    LogoxideBook.parse(topos, "rtf", usx)
except LogoxideErrorException as error:
    print(error.error)  # → LogoxideErrorInvalidMarkup(message='unknown markup "rtf" (usx, osis)')
print(LogoxideBook.find_fragment("For God so greatly loved the world", "so loved the world"))  # → TextRange(start=8, end=34)

Commentaries, dictionaries, and cross-references

A commentary's notes on a passage are each of its notes covering any verse of it, as text and HTML (chapter:verse to lastChapter:last); with stretch, a note also covers the verses after it up to the next, so a verse the commentary skips gets the note before it.

const commentaryFiles = [
  file("logoxide.toml", `[package]
id = "notes"
name = "Short Notes"
abbreviation = "SN"
version = "1.0.0"
language = "en"
license = "CC0-1.0"
kind = "commentary"

[formats]
osis = { path = "osis/" }
`),
  file("osis/John.xml", `<osis><osisText osisIDWork="SN"><div type="book" osisID="John"><chapter osisID="John.3" sID="John.3"/>\
<verse osisID="John.3.16" sID="John.3.16" n="16"/>God so loved: the <hi type="italic">world</hi>.<verse eID="John.3.16"/>\
<verse osisID="John.3.17 John.3.18" sID="John.3.17" n="17-18"/>Not to condemn.<verse eID="John.3.17"/>\
<chapter eID="John.3"/></div></osisText></osis>`),
];
const commentary = logoxide.openPackage(commentaryFiles, features, []);
for (const note of commentary.notes(john, options, false)) console.log(`${note.chapter}:${note.verse}-${note.last}`, note.text);
// → 3:16-16 God so loved: the world.
// → 3:17-18 Not to condemn.
console.log(commentary.notes(john, options, false)[0].html); // → <span class="topos-verse" data-osis-id="John.3.16" data-n="16"></span>God so loved: the <i class="topos-hi topos-italic">world</i>.
const after = topos.parse("John 3:19", BookStyle.Name)!;
console.log(commentary.notes(after, options, false).length, commentary.notes(after, options, true)[0].text); // → 0 Not to condemn.
commentary_files = [
    file("logoxide.toml", """[package]
id = "notes"
name = "Short Notes"
abbreviation = "SN"
version = "1.0.0"
language = "en"
license = "CC0-1.0"
kind = "commentary"

[formats]
osis = { path = "osis/" }
"""),
    file("osis/John.xml", """<osis><osisText osisIDWork="SN"><div type="book" osisID="John"><chapter osisID="John.3" sID="John.3"/>\
<verse osisID="John.3.16" sID="John.3.16" n="16"/>God so loved: the <hi type="italic">world</hi>.<verse eID="John.3.16"/>\
<verse osisID="John.3.17 John.3.18" sID="John.3.17" n="17-18"/>Not to condemn.<verse eID="John.3.17"/>\
<chapter eID="John.3"/></div></osisText></osis>"""),
]
commentary = logoxide.open_package(commentary_files, features, [])
for note in commentary.notes(john, options, False):
    print(f"{note.chapter}:{note.verse}-{note.last}", note.text)
# → 3:16-16 God so loved: the world.
# → 3:17-18 Not to condemn.
print(commentary.notes(john, options, False)[0].html)  # → <span class="topos-verse" data-osis-id="John.3.16" data-n="16"></span>God so loved: the <i class="topos-hi topos-italic">world</i>.
after = topos.parse("John 3:19", BookStyle.NAME)
print(len(commentary.notes(after, options, False)), commentary.notes(after, options, True)[0].text)  # → 0 Not to condemn.

A dictionary's entry for a key (G0025, g25, and G25 are one key; αγαπαω finds ἀγαπάω), null for a key it lacks; keys() lists them. info().keys says what it's keyed by.

const dictionary = logoxide.openPackage([
  file("logoxide.toml", `[package]
id = "strongs"
name = "Strong's Greek"
abbreviation = "STR"
version = "1.0.0"
language = "grc"
license = "CC0-1.0"
kind = "lexicon"
keys = "strongs-greek"

[formats]
dictionary = { path = "entries/" }
`),
  file("entries/greek.xml", `<entries keys="strongs-greek" markup="tei">
<entry key="G0025" headword="ἀγαπάω"><entryFree><orth>ἀγαπάω</orth> to love</entryFree></entry>
<entry key="G26">love</entry>
</entries>
`),
], features, []);
console.log(dictionary.info().keys, dictionary.keys()); // → strongs-greek [ 'G25', 'G26' ]
const entry = dictionary.entry("g25", options)!;
console.log(entry.key, entry.headword, entry.text); // → G25 ἀγαπάω ἀγαπάω to love
console.log(dictionary.entry("G9999", options)); // → null
dictionary = logoxide.open_package([
    file("logoxide.toml", """[package]
id = "strongs"
name = "Strong's Greek"
abbreviation = "STR"
version = "1.0.0"
language = "grc"
license = "CC0-1.0"
kind = "lexicon"
keys = "strongs-greek"

[formats]
dictionary = { path = "entries/" }
"""),
    file("entries/greek.xml", """<entries keys="strongs-greek" markup="tei">
<entry key="G0025" headword="ἀγαπάω"><entryFree><orth>ἀγαπάω</orth> to love</entryFree></entry>
<entry key="G26">love</entry>
</entries>
"""),
], features, [])
print(dictionary.info().keys, dictionary.keys())  # → strongs-greek ['G25', 'G26']
entry = dictionary.entry("g25", options)
print(entry.key, entry.headword, entry.text)  # → G25 ἀγαπάω ἀγαπάω to love
print(dictionary.entry("G9999", options))  # → None

A cross-reference package's crossReferences for a verse (OSIS, or a reference topos reads) are its lines covering any verse of it, as OSIS, each with its weight when the package gives one.

const crossReferences = logoxide.openPackage([
  file("logoxide.toml", `[package]
id = "tsk"
name = "Treasury"
abbreviation = "TSK"
version = "1.0.0"
language = "en"
license = "CC0-1.0"
kind = "cross-references"

[formats]
xref = { path = "xref/" }
`),
  file("xref/gen.tsv", "Gen.1.1\tJohn.1.1-John.1.3\t1\nGen.1.1\tHeb.11.3\t0.8\nGen.1.1-Gen.1.5\tPs.33.6\n"),
], features, []);
for (const x of crossReferences.crossReferences("Genesis 1:1")) console.log(x.from, x.to, x.weight);
// → Gen.1.1 John.1.1-John.1.3 1
// → Gen.1.1 Heb.11.3 0.8
// → Gen.1.1-Gen.1.5 Ps.33.6 null
console.log(crossReferences.keys()); // → [ 'Gen.1.1', 'Gen.1.1-Gen.1.5' ]
cross_references = logoxide.open_package([
    file("logoxide.toml", """[package]
id = "tsk"
name = "Treasury"
abbreviation = "TSK"
version = "1.0.0"
language = "en"
license = "CC0-1.0"
kind = "cross-references"

[formats]
xref = { path = "xref/" }
"""),
    file("xref/gen.tsv", "Gen.1.1\tJohn.1.1-John.1.3\t1\nGen.1.1\tHeb.11.3\t0.8\nGen.1.1-Gen.1.5\tPs.33.6\n"),
], features, [])
for x in cross_references.cross_references("Genesis 1:1"):
    print(x.from_, x.to, x.weight)
# → Gen.1.1 John.1.1-John.1.3 1.0
# → Gen.1.1 Heb.11.3 0.8
# → Gen.1.1-Gen.1.5 Ps.33.6 None
print(cross_references.keys())  # → ['Gen.1.1', 'Gen.1.1-Gen.1.5']

Audio and video

A package lists its recordings by URL, size, and SHA-256 (media()), never storing them; a recording without a text of its own names the dependency whose text it reads (mediaText) or the versification it's numbered by (mediaVersification). timings are where each verse is in a file, from its WebVTT timings (parseTimings reads a timings file that isn't in a package). Download a file when it's played (its url, else each of its mirrors), check it with verifyMedia (an Integrity error says what differs), and keep it at mediaStorePath, by its hash, so versions listing the same file share it.

const audio = logoxide.openPackage([
  file("logoxide.toml", `[package]
id = "bsb-audio"
name = "The Berean Standard Bible, read aloud"
abbreviation = "BSBA"
version = "1.0.0"
language = "en"

[dependencies]
bsb = { git = "https://example.org/bsb", version = "1" }

[media]
text = "bsb"
versification = "KJV"
timings = "timings/"

[[media.files]]
path = "JHN.mp3"
url = "https://example.org/audio/JHN.mp3"
size = 5
sha256 = "sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
book = "JHN"
`),
  file("timings/JHN.vtt", "WEBVTT\n\nJHN 3:16\n00:04.000 --> 00:09.500\nFor God so loved the world\n"),
], features, []);
const [recording] = audio.media();
console.log(recording.path, recording.size, recording.book, audio.mediaText(), audio.mediaVersification()); // → JHN.mp3 5n JHN bsb KJV
console.log(audio.timings(recording)); // → [ { book: 'JHN', bookId: 43, chapter: 3, verse: 16, last: 16, startMs: 4000, endMs: 9500 } ]
console.log(logoxide.parseTimings("WEBVTT\n\nJHN 3:17-18\n00:10.000 --> 00:15.000\n")[0].last); // → 18
const downloaded = new TextEncoder().encode("hello"); // what the host downloaded from recording.url
logoxide.verifyMedia(recording, downloaded);
try {
  logoxide.verifyMedia(recording, new TextEncoder().encode("hellO"));
} catch (error) {
  console.log((error as LogoxideErrorException).value.tag); // → Integrity
}
console.log(logoxide.mediaStorePath(recording)); // → media/2c/2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
audio = logoxide.open_package([
    file("logoxide.toml", """[package]
id = "bsb-audio"
name = "The Berean Standard Bible, read aloud"
abbreviation = "BSBA"
version = "1.0.0"
language = "en"

[dependencies]
bsb = { git = "https://example.org/bsb", version = "1" }

[media]
text = "bsb"
versification = "KJV"
timings = "timings/"

[[media.files]]
path = "JHN.mp3"
url = "https://example.org/audio/JHN.mp3"
size = 5
sha256 = "sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
book = "JHN"
"""),
    file("timings/JHN.vtt", "WEBVTT\n\nJHN 3:16\n00:04.000 --> 00:09.500\nFor God so loved the world\n"),
], features, [])
[recording] = audio.media()
print(recording.path, recording.size, recording.book, audio.media_text(), audio.media_versification())  # → JHN.mp3 5 JHN bsb KJV
print(audio.timings(recording))  # → [VerseTiming(book='JHN', book_id=43, chapter=3, verse=16, last=16, start_ms=4000, end_ms=9500)]
print(logoxide.parse_timings("WEBVTT\n\nJHN 3:17-18\n00:10.000 --> 00:15.000\n")[0].last)  # → 18
downloaded = b"hello"  # what the host downloaded from recording.url
logoxide.verify_media(recording, downloaded)
try:
    logoxide.verify_media(recording, b"hellO")
except LogoxideErrorException as error:
    print(type(error.error).__name__)  # → LogoxideErrorIntegrity
print(logoxide.media_store_path(recording))  # → media/2c/2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

Versification maps

versifications() holds no maps; withMap adds a map package's (kind = "versification-map") and withVrs a .vrs file's text, and map gives where a reference numbered one way is in another, with what of it has no counterpart there. A Bible's numbering is its info().versification; counts are the book's verse counts in the first numbering, for whole chapters (chapterVerses(book); null uses topos's).

const map = logoxide.openPackage([
  file("logoxide.toml", `[package]
id = "english-original"
name = "English to Original"
version = "1.0.0"
kind = "versification-map"
language = "en"
license = "CC0-1.0"
versification = "English"
maps-to = "Original"

[formats]
vrs = { path = "eng-org.vrs" }
`),
  file("eng-org.vrs", "PSA 51:0 = PSA 51:1\nPSA 51:0 = PSA 51:2\nPSA 51:1-19 = PSA 51:3-21\nMAL 4:1-6 = MAL 3:19-24\n"),
], features, []);
console.log(map.info().kind === PackageKind.VersificationMap, map.info().mapsTo); // → true Original
let maps = logoxide.versifications().withMap(map).withVrs("Vulgate", "Original", "PSA 50:1-21 = PSA 51:1-21");
console.log(maps.names()); // → [ 'English', 'Original', 'Vulgate' ]
const mapped = maps.map("Psalm 51:1-2", "English", "Original", null);
console.log(mapped.text, mapped.osis, mapped.verses[0]); // → Psalm 51:3-4 Ps.51.3-Ps.51.4 { book: 19, chapter: 51, verse: 3 }
console.log(maps.map("Psalm 51:1", "English", "Vulgate", kjv.chapterVerses(19)).text); // → Psalm 50:3
const missing = maps.map("Malachi 4:1", "Original", "English", null);
console.log(missing.verses.length, missing.missingText, missing.missing); // → 0 Malachi 4:1 [ { book: 39, chapter: 4, verse: 1 } ]
mapping = logoxide.open_package([
    file("logoxide.toml", """[package]
id = "english-original"
name = "English to Original"
version = "1.0.0"
kind = "versification-map"
language = "en"
license = "CC0-1.0"
versification = "English"
maps-to = "Original"

[formats]
vrs = { path = "eng-org.vrs" }
"""),
    file("eng-org.vrs", "PSA 51:0 = PSA 51:1\nPSA 51:0 = PSA 51:2\nPSA 51:1-19 = PSA 51:3-21\nMAL 4:1-6 = MAL 3:19-24\n"),
], features, [])
print(mapping.info().kind == PackageKind.VERSIFICATION_MAP, mapping.info().maps_to)  # → True Original
maps = logoxide.versifications().with_map(mapping).with_vrs("Vulgate", "Original", "PSA 50:1-21 = PSA 51:1-21")
print(maps.names())  # → ['English', 'Original', 'Vulgate']
mapped = maps.map("Psalm 51:1-2", "English", "Original", None)
print(mapped.text, mapped.osis, mapped.verses[0])  # → Psalm 51:3-4 Ps.51.3-Ps.51.4 MappedVerse(book=19, chapter=51, verse=3)
print(maps.map("Psalm 51:1", "English", "Vulgate", kjv.chapter_verses(19)).text)  # → Psalm 50:3
missing = maps.map("Malachi 4:1", "Original", "English", None)
print(len(missing.verses), missing.missing_text, missing.missing)  # → 0 Malachi 4:1 [MappedVerse(book=39, chapter=4, verse=1)]

A vault's project

A vault's logoxide.toml and logoxide.lock say which packages it uses (parseProject, either null for a new vault). Changing a project gives a new one, its manifestText() (comments kept, dependencies in the order added) to save, and installing it gives the lock file; dependencies() are in name order. A dependency is git (with at most one of version, tag, branch, or rev), registry, path, or _import.

import { type Dependency } from "logoxide";

const dependency: Dependency = {
  git: null, registry: null, path: null, _import: null, version: null, tag: null, branch: null, rev: null,
  features: [], defaultFeatures: true, packageId: null, abbreviation: null, aliases: null,
};
let project = logoxide.parseProject(`# My vault
[dependencies]
kjv = { git = "https://example.org/kjv", version = "1" }

[settings]
default = "kjv"
compare = ["bsb"]
catalogs = ["https://example.org/catalog.toml"]
`, null);
project = project.addDependency("bsb", { ...dependency, git: "https://example.org/bsb", version: "1" });
console.log(project.dependencies(), project.unlocked(), project.packages()); // → [ 'bsb', 'kjv' ] [ 'bsb', 'kjv' ] []
console.log(project.defaultBible(), project.compare()); // → kjv [ 'bsb' ]
console.log(project.catalogs().length, project.catalogs()[0], project.isDefaultCatalog(project.catalogs()[1])); // → 6 https://example.org/catalog.toml true
console.log(project.removeCatalog(logoxide.ebibleCatalogUrl()).catalogs().length); // → 5
console.log(project.addCatalog("https://example.org/more.toml").catalogs().length); // → 7
const named = project.addRegistry("example", "https://example.org/catalog.toml");
console.log(named.registries(), named.removeRegistry("example").registries()); // → [ { name: 'example', location: 'https://example.org/catalog.toml' } ] []
console.log(project.removeDependency("bsb").dependencies()); // → [ 'kjv' ]
mkdirSync("vault", { recursive: true });
writeFileSync("vault/logoxide.toml", project.manifestText());
console.log(project.manifestText());
// → # My vault
// → [dependencies]
// → kjv = { git = "https://example.org/kjv", version = "1" }
// → bsb = { git = "https://example.org/bsb", version = "1" }
// → [settings]
// → default = "kjv"
// → compare = ["bsb"]
// → catalogs = ["https://example.org/catalog.toml"]
from logoxide import Dependency

dependency = Dependency(
    git=None, registry=None, path=None, import_=None, version=None, tag=None, branch=None, rev=None,
    features=[], default_features=True, package_id=None, abbreviation=None, aliases=None,
)
project = logoxide.parse_project("""# My vault
[dependencies]
kjv = { git = "https://example.org/kjv", version = "1" }

[settings]
default = "kjv"
compare = ["bsb"]
catalogs = ["https://example.org/catalog.toml"]
""", None)
project = project.add_dependency("bsb", dataclasses.replace(dependency, git="https://example.org/bsb", version="1"))
print(project.dependencies(), project.unlocked(), project.packages())  # → ['bsb', 'kjv'] ['bsb', 'kjv'] []
print(project.default_bible(), project.compare())  # → kjv ['bsb']
print(len(project.catalogs()), project.catalogs()[0], project.is_default_catalog(project.catalogs()[1]))  # → 6 https://example.org/catalog.toml True
print(len(project.remove_catalog(logoxide.ebible_catalog_url()).catalogs()))  # → 5
print(len(project.add_catalog("https://example.org/more.toml").catalogs()))  # → 7
named = project.add_registry("example", "https://example.org/catalog.toml")
print(named.registries(), named.remove_registry("example").registries())  # → [Registry(name='example', location='https://example.org/catalog.toml')] []
print(project.remove_dependency("bsb").dependencies())  # → ['kjv']
pathlib.Path("vault").mkdir(exist_ok=True)
pathlib.Path("vault/logoxide.toml").write_text(project.manifest_text())
print(project.manifest_text())
# → # My vault
# → [dependencies]
# → kjv = { git = "https://example.org/kjv", version = "1" }
# → bsb = { git = "https://example.org/bsb", version = "1" }
# → [settings]
# → default = "kjv"
# → compare = ["bsb"]
# → catalogs = ["https://example.org/catalog.toml"]

Catalogs

A catalog lists packages to find by name or language: a catalog.toml anyone hosts, or a publisher's own list (eBible.org's translations.csv, fetch.bible's manifest.json, CCEL's page, a SWORD repository's mods.d.tar.gz, which is read as bytes). parseCatalog reads one's text and parseCatalogBytes its bytes, by its location; catalogs merge, so one search covers them all. Each entry's source says how to get it: a Git repository, a Registry of release archives (its releases), or an External publisher's download to import.

import { type LogoxideCatalog } from "logoxide";

const examples = logoxide.parseCatalog("https://example.org/catalog.toml", new TextDecoder().decode(await get("https://example.org/catalog.toml")));
const ebible = logoxide.parseCatalogBytes(logoxide.ebibleCatalogUrl(), await get(logoxide.ebibleCatalogUrl()));
console.log(examples.name(), examples.count(), examples.warnings(), ebible.name(), ebible.count()); // → Example Bibles 3 [] eBible.org 2
const both: LogoxideCatalog = examples.merge(ebible);
for (const found of both.search("english bible", 0)) console.log(found.id, found.abbreviation, found.source.tag);
// → ebible-eng-asv ASV External
// → ebible-engwebp WEBP External
const sermons = both.search("sermons", 1)[0];
console.log(sermons.kind === PackageKind.Book, sermons.source, sermons.releases[0].version, sermons.releases[0].size); // → true { tag: 'Registry', catalog: 'https://example.org/catalog.toml' } 1.0.0 2504n
console.log(both.searchGrouped("king james", 0).map((group) => [group.work, group.name, group.entries.length])); // → [ [ 'kjv', 'King James Version', 1 ] ]
const [kjvWork] = both.works("king james");
console.log(kjvWork.id, kjvWork.from, kjvWork.entries.map((e) => e.id)); // → kjv { tag: 'Logoxide' } [ 'kjv' ]
examples = logoxide.parse_catalog("https://example.org/catalog.toml", get("https://example.org/catalog.toml").decode())
ebible = logoxide.parse_catalog_bytes(logoxide.ebible_catalog_url(), get(logoxide.ebible_catalog_url()))
print(examples.name(), examples.count(), examples.warnings(), ebible.name(), ebible.count())  # → Example Bibles 3 [] eBible.org 2
both = examples.merge(ebible)
for found in both.search("english bible", 0):
    print(found.id, found.abbreviation, type(found.source).__name__)
# → ebible-eng-asv ASV CatalogSourceExternal
# → ebible-engwebp WEBP CatalogSourceExternal
sermons = both.search("sermons", 1)[0]
print(sermons.kind == PackageKind.BOOK, sermons.source, sermons.releases[0].version, sermons.releases[0].size)  # → True CatalogSourceRegistry(catalog='https://example.org/catalog.toml') 1.0.0 2504
print([(group.work, group.name, len(group.entries)) for group in both.search_grouped("king james", 0)])  # → [('kjv', 'King James Version', 1)]
[kjv_work] = both.works("king james")
print(kjv_work.id, kjv_work.from_, [e.id for e in kjv_work.entries])  # → kjv WorkFromLogoxide() ['kjv']

An installer reads a project's catalogs (its [settings] catalogs, then the default ones) as lx search does, asking for each as a Download. A download the host can't make is a warning (provideDownloadFailed), unless none can be read; catalog() is then all of them merged. The same answer serves installing, below. A catalog given by path isn't read there (a warning says so): read it and merge parseCatalogBytes's.

import { type FetchRequest, type LogoxideInstaller, type PackageLocation } from "logoxide";

/** Where a package's files are: in the library, or a path package's in the vault */
const folder = (location: PackageLocation) => join(location.tag === "Library" ? "library" : "vault", location.dir);

/** Answers what an installer asks for */
async function answer(installer: LogoxideInstaller, request: FetchRequest, catalog: LogoxideCatalog | null = null) {
  if (request.tag === "Refs") installer.provideRefs(request.repository, await get(request.url));
  else if (request.tag === "Archive") installer.provideArchive(request.repository, request.commit, await getFirst(request.urls));
  else if (request.tag === "Download") {
    try { installer.provideDownload(request.url, await getFirst(request.urls)); }
    catch (error) { installer.provideDownloadFailed(request.url, String(error)); }
  } else if (request.tag === "ImportEntry") {
    const entry = catalog?.findImport(request._import);
    if (!entry) throw new Error(`no catalog lists ${request._import}`);
    installer.provideImportEntry(entry);
  } else if (existsSync(folder(request.location))) installer.provideFiles(request.location, files(folder(request.location)));
  else installer.provideMissing(request.location);
}

const installer = logoxide.installer(); // keep one: what it's given is kept
let loading = installer.loadCatalogs(project);
while (loading.tag === "Fetch") {
  await answer(installer, loading.request);
  loading = installer.loadCatalogs(project);
}
console.log(loading.warnings.length, loading.warnings[0]); // → 4 …fetch.bible…404…
const catalog = installer.catalog();
console.log(catalog.count(), catalog.warnings().length, catalog.findImport("ebible:eng-asv")?.name); // → 5 4 American Standard Version (1901)
from logoxide import (
    FetchRequestArchive, FetchRequestDownload, FetchRequestFiles, FetchRequestImportEntry,
    FetchRequestRefs, LogoxideCatalog, LogoxideInstaller, PackageLocation, PackageLocationLibrary,
    CatalogsStepFetch,
)


def folder(location: PackageLocation) -> str:
    """Where a package's files are: in the library, or a path package's in the vault"""
    return str(pathlib.Path("library" if isinstance(location, PackageLocationLibrary) else "vault", location.dir))


def answer(installer: LogoxideInstaller, request, catalog: LogoxideCatalog | None = None) -> None:
    """Answers what an installer asks for"""
    match request:
        case FetchRequestRefs(repository=repository, url=url):
            installer.provide_refs(repository, get(url))
        case FetchRequestArchive(repository=repository, commit=commit, urls=urls):
            installer.provide_archive(repository, commit, get_first(urls))
        case FetchRequestDownload(url=url, urls=urls):
            try:
                installer.provide_download(url, get_first(urls))
            except OSError as error:
                installer.provide_download_failed(url, str(error))
        case FetchRequestImportEntry(import_=name):
            entry = catalog.find_import(name) if catalog else None
            if entry is None:
                raise LookupError(f"no catalog lists {name}")
            installer.provide_import_entry(entry)
        case FetchRequestFiles(location=location):
            if pathlib.Path(folder(location)).is_dir():
                installer.provide_files(location, files(folder(location)))
            else:
                installer.provide_missing(location)


installer = logoxide.installer()  # keep one: what it's given is kept
loading = installer.load_catalogs(project)
while isinstance(loading, CatalogsStepFetch):
    answer(installer, loading.request)
    loading = installer.load_catalogs(project)
print(len(loading.warnings), loading.warnings[0])  # → 4 …fetch.bible…404…
catalog = installer.catalog()
print(catalog.count(), len(catalog.warnings()), catalog.find_import("ebible:eng-asv").name)  # → 5 4 American Standard Version (1901)

What logoxide knows of catalogs, imports, and categories: the default catalogs, eBible.org's list, whether a URL is fetched without encryption, a SWORD repository CrossWire lists, the publishers imports come from, and the categories a package can be in (at most maxCategories()).

console.log(logoxide.defaultCatalogUrls().length, logoxide.ebibleCatalogUrl()); // → 5 https://ebible.org/Scriptures/translations.csv
console.log(logoxide.isInsecureUrl("http://ftp.xiphos.org/"), logoxide.isInsecureUrl("https://example.org/kjv")); // → true false
console.log(logoxide.swordCatalogUrl("crosswire"), logoxide.swordCatalogUrl("nowhere")); // → https://crosswire.org/ftpmirror/pub/sword/raw/mods.d.tar.gz null
console.log(logoxide.importPrefixes()); // → [ 'ebible', 'fetch', 'ccel', 'sword', 'door43' ]
console.log(logoxide.categories()[0], logoxide.maxCategories()); // → { name: 'biblical-theology', description: 'Themes traced through Scripture (covenant, kingdom)' } 5
print(len(logoxide.default_catalog_urls()), logoxide.ebible_catalog_url())  # → 5 https://ebible.org/Scriptures/translations.csv
print(logoxide.is_insecure_url("http://ftp.xiphos.org/"), logoxide.is_insecure_url("https://example.org/kjv"))  # → True False
print(logoxide.sword_catalog_url("crosswire"), logoxide.sword_catalog_url("nowhere"))  # → https://crosswire.org/ftpmirror/pub/sword/raw/mods.d.tar.gz None
print(logoxide.import_prefixes())  # → ['ebible', 'fetch', 'ccel', 'sword', 'door43']
print(logoxide.categories()[0], logoxide.max_categories())  # → Category(name='biblical-theology', description='Themes traced through Scripture (covenant, kingdom)') 5

Installing

There's no git in WebAssembly, so the host downloads over HTTP and logoxide asks for what it needs: a repository's refs (git's smart HTTP, which GitHub, GitLab, Gitea, Forgejo, and Bitbucket serve), an archive of a commit (.tar.gz, from the first of urls that answers: the forms hosts use), a registry's downloads, or the files of a package already installed. It resolves exactly as lx install does, so a vault's logoxide.lock works with lx and back. inspect reads a repository's newest release (or a tag, branch, or rev) before it's added, to name it by its id.

let inspecting = installer.inspect("https://example.org/bsb", null, null, null);
while (inspecting.tag === "Fetch") {
  await answer(installer, inspecting.request);
  inspecting = installer.inspect("https://example.org/bsb", null, null, null);
}
const { info: found, tag, commit } = inspecting.inspected;
console.log(found.id, found.version, tag, commit.length); // → bsb 1.0.3 v1.0.3 40
from logoxide import InspectStepFetch

inspecting = installer.inspect("https://example.org/bsb", None, None, None)
while isinstance(inspecting, InspectStepFetch):
    answer(installer, inspecting.request)
    inspecting = installer.inspect("https://example.org/bsb", None, None, None)
found, tag, commit = inspecting.inspected.info, inspecting.inspected.tag, inspecting.inspected.commit
print(found.id, found.version, tag, len(commit))  # → bsb 1.0.3 v1.0.3 40

install(project, update) resolves the project's dependencies, keeping its lock file's versions ({ tag: "Nothing" }), or moving all of them (All) or some (Only) to the newest their requirements allow, and says what it needs until it's done: then write files into the library and lock as the vault's logoxide.lock. A book from a registry is added by the registry's catalog.

import { type InstallStep, type LogoxideProject, type UpdateMode } from "logoxide";

/** Installs a project, answering what's asked; what was asked, by kind */
async function install(installer: LogoxideInstaller, project: LogoxideProject, update: UpdateMode, catalog: LogoxideCatalog | null = null) {
  const asked: string[] = [];
  let step: InstallStep = installer.install(project, update);
  while (step.tag === "Fetch") {
    asked.push(step.request.tag);
    await answer(installer, step.request, catalog);
    step = installer.install(project, update);
  }
  write("library", step.files);
  writeFileSync("vault/logoxide.toml", project.manifestText());
  writeFileSync("vault/logoxide.lock", step.lock);
  return asked;
}

project = project.addDependency("sermons-on-love", { ...dependency, registry: "https://example.org/catalog.toml", version: "1" });
console.log(await install(installer, project, { tag: "Nothing" })); // → [ 'Files', 'Refs', 'Archive', 'Files', 'Files', 'Download' ]
project = logoxide.parseProject(readFileSync("vault/logoxide.toml", "utf8"), readFileSync("vault/logoxide.lock", "utf8"));
console.log(await install(logoxide.installer(), project, { tag: "All" })); // → [ 'Refs', 'Archive', 'Files', 'Refs', 'Archive', 'Files', 'Download', 'Files' ]
console.log(await install(logoxide.installer(), project, { tag: "Only", ids: ["bsb"] })); // → [ 'Refs', 'Archive', 'Files', 'Files', 'Files' ]
from logoxide import InstallStepFetch, LogoxideProject, UpdateMode, UpdateModeAll, UpdateModeNothing, UpdateModeOnly


def install(installer: LogoxideInstaller, project: LogoxideProject, update: UpdateMode, catalog: LogoxideCatalog | None = None) -> list[str]:
    """Installs a project, answering what's asked; what was asked, by kind"""
    asked = []
    step = installer.install(project, update)
    while isinstance(step, InstallStepFetch):
        asked.append(type(step.request).__name__.removeprefix("FetchRequest"))
        answer(installer, step.request, catalog)
        step = installer.install(project, update)
    write("library", step.files)
    pathlib.Path("vault/logoxide.toml").write_text(project.manifest_text())
    pathlib.Path("vault/logoxide.lock").write_text(step.lock)
    return asked


project = project.add_dependency("sermons-on-love", dataclasses.replace(dependency, registry="https://example.org/catalog.toml", version="1"))
print(install(installer, project, UpdateModeNothing()))  # → ['Files', 'Refs', 'Archive', 'Files', 'Files', 'Download']
project = logoxide.parse_project(pathlib.Path("vault/logoxide.toml").read_text(), pathlib.Path("vault/logoxide.lock").read_text())
print(install(logoxide.installer(), project, UpdateModeAll()))  # → ['Refs', 'Archive', 'Files', 'Refs', 'Archive', 'Files', 'Download', 'Files']
print(install(logoxide.installer(), project, UpdateModeOnly(ids=["bsb"])))  # → ['Refs', 'Archive', 'Files', 'Files', 'Files']

A package is what its manifest declares, so files a reader adds beside it (annotations) don't change its checksum, but changing its own files does: installed versions are checked against the lock file's checksum, and { tag: "Repair" } hands back the files of any whose own files changed, to write over them.

const kjvEntry = project.packages().find((e) => e.name === "kjv")!;
writeFileSync(join(folder(kjvEntry.location), "usfm/jhn.usfm"), "changed");
try {
  await install(logoxide.installer(), project, { tag: "Nothing" });
} catch (error) {
  console.log((error as LogoxideErrorException).value.tag); // → Integrity
}
console.log(await install(logoxide.installer(), project, { tag: "Repair" })); // → [ 'Files', 'Files', 'Archive', 'Files' ]
console.log(logoxide.openPackage(files(folder(kjvEntry.location)), kjvEntry.features, cache).checksum() === kjvEntry.checksum); // → true
from logoxide import UpdateModeRepair

kjv_entry = next(e for e in project.packages() if e.name == "kjv")
pathlib.Path(folder(kjv_entry.location), "usfm/jhn.usfm").write_text("changed")
try:
    install(logoxide.installer(), project, UpdateModeNothing())
except LogoxideErrorException as error:
    print(type(error.error).__name__)  # → LogoxideErrorIntegrity
print(install(logoxide.installer(), project, UpdateModeRepair()))  # → ['Files', 'Files', 'Archive', 'Files']
print(logoxide.open_package(files(folder(kjv_entry.location)), kjv_entry.features, cache).checksum() == kjv_entry.checksum)  # → True

The vault's packages

packages() lists each package the manifest asks for, as the lock file has it, followed by what it brings in (dependencyOf), with where its files are: in the library, or a path package's folder in the vault. Open each from there with its features. A dependency turned off (setEnabled) stays locked and installed, and is enabled: false for hosts to leave out.

for (const entry of project.packages()) console.log(entry.name, entry.version, entry.location, entry.git, entry.enabled);
// → bsb 1.0.3 { tag: 'Library', dir: 'packages/bsb/1.0.3+g…' } https://example.org/bsb true
// → kjv 1.0.3 { tag: 'Library', dir: 'packages/kjv/1.0.3+g…' } https://example.org/kjv true
// → sermons-on-love 1.0.0 { tag: 'Library', dir: 'packages/sermons-on-love/1.0.0+a…' } null true
const off = project.setEnabled("bsb", false);
console.log(off.isEnabled("bsb"), off.enabledDependencies(), off.disabledDependencies(), off.packages()[0].enabled); // → false [ 'kjv', 'sermons-on-love' ] [ 'bsb' ] false
for entry in project.packages():
    print(entry.name, entry.version, entry.location, entry.git, entry.enabled)
# → bsb 1.0.3 PackageLocationLibrary(dir='packages/bsb/1.0.3+g…') https://example.org/bsb True
# → kjv 1.0.3 PackageLocationLibrary(dir='packages/kjv/1.0.3+g…') https://example.org/kjv True
# → sermons-on-love 1.0.0 PackageLocationLibrary(dir='packages/sermons-on-love/1.0.0+a…') None True
off = project.set_enabled("bsb", False)
print(off.is_enabled("bsb"), off.enabled_dependencies(), off.disabled_dependencies(), off.packages()[0].enabled)  # → False ['kjv', 'sermons-on-love'] ['bsb'] False

The lock file doesn't say what a package is, so commentaries, dictionaries, and crossReferences take each entry's logoxide.toml from the host (PackageManifest, by the entry's name) and give those in use of that kind. nameClashes says which names two or more of the Bibles a host shows answer to, so a reference naming one can't say which.

write("vault/notes", commentaryFiles);
project = project.addDependency("notes", { ...dependency, path: "notes" });
await install(installer, project, { tag: "Nothing" });
project = logoxide.parseProject(readFileSync("vault/logoxide.toml", "utf8"), readFileSync("vault/logoxide.lock", "utf8"));
const manifests = project.packages().map((entry) => ({ name: entry.name, manifest: readFileSync(join(folder(entry.location), "logoxide.toml"), "utf8") }));
console.log(project.commentaries(manifests).map((e) => [e.name, e.location]), project.dictionaries(manifests), project.crossReferences(manifests)); // → [ [ 'notes', { tag: 'Path', dir: 'notes' } ] ] [] []
console.log(project.nameClashes([
  { label: "kjv 1.0.3", abbreviation: "KJV", aliases: ["AV"] },
  { label: "my-av", abbreviation: "AV", aliases: [] },
])); // → [ { name: 'AV', packages: [ 'kjv 1.0.3', 'my-av' ] } ]
from logoxide import NamedBible, PackageManifest

write("vault/notes", commentary_files)
project = project.add_dependency("notes", dataclasses.replace(dependency, path="notes"))
install(installer, project, UpdateModeNothing())
project = logoxide.parse_project(pathlib.Path("vault/logoxide.toml").read_text(), pathlib.Path("vault/logoxide.lock").read_text())
manifests = [PackageManifest(name=e.name, manifest=pathlib.Path(folder(e.location), "logoxide.toml").read_text()) for e in project.packages()]
print([(e.name, e.location) for e in project.commentaries(manifests)], project.dictionaries(manifests), project.cross_references(manifests))  # → [('notes', PackageLocationPath(dir='notes'))] [] []
print(project.name_clashes([
    NamedBible(label="kjv 1.0.3", abbreviation="KJV", aliases=["AV"]),
    NamedBible(label="my-av", abbreviation="AV", aliases=[]),
]))  # → [NameClash(name='AV', packages=['kjv 1.0.3', 'my-av'])]

Imports

Another publisher's download is made into a package the same way for every publisher: importDownloads(entry) says what to fetch (each from the first of its urls that answers), and importDownloaded(entry, files) makes the package's files from their bytes, by name. Write them into a folder in the vault and add it as a path package, or add { _import } and let installing make it (kept in the library by its checksum, as lx import does).

const asv = catalog.findImport("ebible:eng-asv")!;
if (asv.source.tag !== "External") throw new Error("not an import");
console.log(asv.source._import, asv.source.tracksVersions, asv.version); // → ebible:eng-asv true 2026-10-08
const downloads = logoxide.importDownloads(asv);
console.log(downloads); // → [ { name: 'zip', urls: [ 'https://ebible.org/Scriptures/eng-asv_usfm.zip' ] } ]
const fetched: PackageFile[] = [];
for (const download of downloads) fetched.push({ path: download.name, bytes: await getFirst(download.urls) });
const imported = logoxide.importDownloaded(asv, fetched);
console.log(imported.map((f) => f.path)); // → [ 'copr.htm', 'logoxide.toml', 'usfm/00-FRTeng-asv.usfm', 'usfm/73-JHNeng-asv.usfm', 'usfm/95-3JNeng-asv.usfm' ]
const asvPackage = logoxide.openPackage(imported, features, []);
console.log(asvPackage.info().abbreviation, [...asvPackage.books()], asvPackage.skipped()[0].reason); // → ASV [ 43, 64 ] peripheral
from logoxide import CatalogSourceExternal

asv = catalog.find_import("ebible:eng-asv")
assert isinstance(asv.source, CatalogSourceExternal)
print(asv.source.import_, asv.source.tracks_versions, asv.version)  # → ebible:eng-asv True 2026-10-08
downloads = logoxide.import_downloads(asv)
print(downloads)  # → [ImportDownload(name='zip', urls=['https://ebible.org/Scriptures/eng-asv_usfm.zip'])]
fetched = [PackageFile(path=d.name, bytes=get_first(d.urls)) for d in downloads]
imported = logoxide.import_downloaded(asv, fetched)
print([f.path for f in imported])  # → ['copr.htm', 'logoxide.toml', 'usfm/00-FRTeng-asv.usfm', 'usfm/73-JHNeng-asv.usfm', 'usfm/95-3JNeng-asv.usfm']
asv_package = logoxide.open_package(imported, features, [])
print(asv_package.info().abbreviation, list(asv_package.books()), asv_package.skipped()[0].reason)  # → ASV [43, 64] peripheral

Updating an import: findUpstream finds its entry by info().upstream; importIsCurrent says whether the publisher's version there is the one imported (eBible.org's and SWORD's versions follow their updates; fetch.bible's and CCEL's don't, so those are always false); else download it again and updateImport(current, fresh) gives what changed (null for nothing), the version raised by what changed, the files to write, and those to delete.

import { ChangeKind } from "logoxide";

const upstream = asvPackage.info().upstream!;
console.log(upstream._import, upstream.version, catalog.findUpstream(upstream)?.id, logoxide.importIsCurrent(asv, upstream.version)); // → ebible:eng-asv 2026-10-08 ebible-eng-asv true
console.log(logoxide.updateImport(imported, imported)); // → null
// What a new download makes: here, John 3:16 reworded and 3 John dropped
const fresh = imported
  .filter((f) => !f.path.includes("3JN"))
  .map((f) => (f.path.includes("JHN") ? file(f.path, new TextDecoder().decode(f.bytes).replace("so loved", "so much loved")) : f));
const update = logoxide.updateImport(imported, fresh)!;
console.log(update.from, update.to, update.change === ChangeKind.Major, update.description, update.removed); // → 1.0.0 2.0.0 true 1 verse removed, 1 verse reworded [ 'usfm/95-3JNeng-asv.usfm' ]
from logoxide import ChangeKind

upstream = asv_package.info().upstream
print(upstream.import_, upstream.version, catalog.find_upstream(upstream).id, logoxide.import_is_current(asv, upstream.version))  # → ebible:eng-asv 2026-10-08 ebible-eng-asv True
print(logoxide.update_import(imported, imported))  # → None
# What a new download makes: here, John 3:16 reworded and 3 John dropped
fresh = [
    file(f.path, f.bytes.decode().replace("so loved", "so much loved")) if "JHN" in f.path else f
    for f in imported
    if "3JN" not in f.path
]
update = logoxide.update_import(imported, fresh)
print(update.from_, update.to, update.change == ChangeKind.MAJOR, update.description, update.removed)  # → 1.0.0 2.0.0 True 1 verse removed, 1 verse reworded ['usfm/95-3JNeng-asv.usfm']

An import dependency is made as it installs: the installer asks for its catalog entry (ImportEntry, answered from the catalogs with provideImportEntry) unless loadCatalogs listed it, then for its downloads. Named other than its id (ebible-eng-asv), a dependency says which package it is with packageId.

project = project.addDependency("asv", { ...dependency, _import: "ebible:eng-asv", packageId: "ebible-eng-asv" });
console.log(await install(logoxide.installer(), project, { tag: "Nothing" }, catalog)); // → [ 'ImportEntry', 'Download', 'Files', 'Files', 'Files', 'Files', 'Files' ]
project = logoxide.parseProject(readFileSync("vault/logoxide.toml", "utf8"), readFileSync("vault/logoxide.lock", "utf8"));
const asvEntry = project.packages().find((e) => e.name === "asv")!;
console.log(asvEntry._import, asvEntry.version, asvEntry.location.dir); // → ebible:eng-asv 1.0.0 packages/ebible-eng-asv/1.0.0+i…
project = project.add_dependency("asv", dataclasses.replace(dependency, import_="ebible:eng-asv", package_id="ebible-eng-asv"))
print(install(logoxide.installer(), project, UpdateModeNothing(), catalog))  # → ['ImportEntry', 'Download', 'Files', 'Files', 'Files', 'Files', 'Files']
project = logoxide.parse_project(pathlib.Path("vault/logoxide.toml").read_text(), pathlib.Path("vault/logoxide.lock").read_text())
asv_entry = next(e for e in project.packages() if e.name == "asv")
print(asv_entry.import_, asv_entry.version, asv_entry.location.dir)  # → ebible:eng-asv 1.0.0 packages/ebible-eng-asv/1.0.0+i…

Making packages

A package can be made from one file the person picks: an EPUB fills in its own title, subtitle, authors, language, and publisher (fileMetadata, to fill a form); a Bible's USX or USFM needs a name and a language. What's given wins over the file; the id comes from the name, a Bible's or commentary's abbreviation from its initials (the other kinds have one only when given), and the version is 0.1.0 unless given. logoxide gives back the package's files to write into a new folder (then a git repository, to release it). licenseConcerns says what to know before redistributing one (shown, never enforced).

import { ConcernKind, type NewPackage } from "logoxide";

const epub = new Uint8Array(readFileSync("sermons.epub"));
console.log(logoxide.fileMetadata("sermons.epub", epub)); // → { title: 'Sermons on Love', subtitle: 'Two Short Sermons', authors: [ 'A. Preacher' ], language: 'en', publisher: 'Example Press', rights: 'Public domain' }
const given: NewPackage = {
  id: null, name: null, abbreviation: null, authors: [], description: null, version: "1.0.0", kind: null,
  language: null, license: null, publisher: null, homepage: null, repository: null, keywords: [],
  categories: [], aboutBooks: ["1 John"], // a book's code or name
};
const made = logoxide.newPackage("sermons.epub", epub, given);
console.log(made.map((f) => f.path)); // → [ 'epub/sermons.epub', 'logoxide.toml' ]
const book = logoxide.openPackage(made, features, []);
console.log(book.info().id, book.info().kind === PackageKind.Book, book.info().aboutBooks, book.epubFiles()); // → sermons-on-love true [ '1JN' ] [ 'epub/sermons.epub' ]
const [concern] = book.licenseConcerns();
console.log(concern.kind === ConcernKind.NoLicense, concern.serious); // → true false
from logoxide import ConcernKind, NewPackage

epub = pathlib.Path("sermons.epub").read_bytes()
print(logoxide.file_metadata("sermons.epub", epub))  # → FileMetadata(title='Sermons on Love', subtitle='Two Short Sermons', authors=['A. Preacher'], language='en', publisher='Example Press', rights='Public domain')
given = NewPackage(
    id=None, name=None, abbreviation=None, authors=[], description=None, version="1.0.0", kind=None,
    language=None, license=None, publisher=None, homepage=None, repository=None, keywords=[],
    categories=[], about_books=["1 John"],  # a book's code or name
)
made = logoxide.new_package("sermons.epub", epub, given)
print([f.path for f in made])  # → ['epub/sermons.epub', 'logoxide.toml']
book = logoxide.open_package(made, features, [])
print(book.info().id, book.info().kind == PackageKind.BOOK, book.info().about_books, book.epub_files())  # → sermons-on-love True ['1JN'] ['epub/sermons.epub']
[concern] = book.license_concerns()
print(concern.kind == ConcernKind.NO_LICENSE, concern.serious)  # → True False

A book has no verse text: references() reads its EPUBs for the Bible references in them, each a passage in OSIS (topos.parse reads it), the file, its chapter, a cfi EPUB readers open, and its UTF-16 range, line, column, spine item, and paragraph (what ToposIndex.setEpub takes). parseReferences reads a release's published logoxide.references.bin without its books: the passages cited, and how often.

const [first] = book.references();
console.log(first.passage, first.chapter, first.cfi, first.line, first.column, first.spineIndex); // → John.3.16 Love epubcfi(/6/2!/4/4,/1:28,/1:37) 3 29 0
console.log(topos.parse(first.passage, BookStyle.Name)?.reference); // → John 3:16
console.log(logoxide.parseReferences(new Uint8Array(readFileSync("logoxide.references.bin")))); // → [ { passage: 'John.3.16', count: 2 }, … ]
first = book.references()[0]
print(first.passage, first.chapter, first.cfi, first.line, first.column, first.spine_index)  # → John.3.16 Love epubcfi(/6/2!/4/4,/1:28,/1:37) 3 29 0
print(topos.parse(first.passage, BookStyle.NAME).reference)  # → John 3:16
print(logoxide.parse_references(pathlib.Path("logoxide.references.bin").read_bytes()))  # → [CitedPassage(passage='John.3.16', count=2), …]

unzip gives a .zip's files (folders left out), so a zip of USFM or USX books someone picks is read without a zip library: pass a book's bytes to newPackage. Bytes that aren't a zip, or more than 200,000 entries or 1 GiB, are an error. A collection is a package of packages, its manifest alone (newCollection); add its packages with addDependency on it.

const zipped = logoxide.unzip(new Uint8Array(readFileSync("eng-asv_usfm.zip")));
console.log(zipped.map((f) => f.path)); // → [ '00-FRTeng-asv.usfm', '73-JHNeng-asv.usfm', '95-3JNeng-asv.usfm', 'copr.htm' ]
const gospel = zipped.find((f) => f.path.includes("JHN"))!;
const mine = logoxide.newPackage(gospel.path, gospel.bytes, { ...given, name: "My Gospel", language: "en", license: "CC0-1.0", aboutBooks: [] });
console.log(mine.map((f) => f.path), logoxide.openPackage(mine, features, []).info().abbreviation); // → [ 'logoxide.toml', 'usfm/73-JHNeng-asv.usfm' ] MG
const collection = logoxide.newCollection({ ...given, name: "My Library", aboutBooks: [] });
console.log(new TextDecoder().decode(collection[0].bytes).includes('kind = "collection"')); // → true
zipped = logoxide.unzip(pathlib.Path("eng-asv_usfm.zip").read_bytes())
print([f.path for f in zipped])  # → ['00-FRTeng-asv.usfm', '73-JHNeng-asv.usfm', '95-3JNeng-asv.usfm', 'copr.htm']
gospel = next(f for f in zipped if "JHN" in f.path)
mine = logoxide.new_package(gospel.path, gospel.bytes, dataclasses.replace(given, name="My Gospel", language="en", license="CC0-1.0", about_books=[]))
print([f.path for f in mine], logoxide.open_package(mine, features, []).info().abbreviation)  # → ['logoxide.toml', 'usfm/73-JHNeng-asv.usfm'] MG
collection = logoxide.new_collection(dataclasses.replace(given, name="My Library", about_books=[]))
print('kind = "collection"' in collection[0].bytes.decode())  # → True

Reading natively

In Python (and Swift and Kotlin), readPassage and comparePassage read a project's Bibles from its folder and the library's, as lx read and lx compare do: a passage in the Bible the reference names (else the project's first), and a passage in several Bibles, each in its own numbering through the project's versification maps, with what of it one lacks. In WebAssembly (the npm package, in Node too) they're an Io error: open each package and read its book instead.

const plain: BookTextStyle = { verseLabel: "[{verse}]", chapterLabel: null, notes: false, headings: false, templates: [], width: null, limit: null };
try {
  logoxide.readPassage("vault", "library", "cache", "John 3:16 KJV", plain);
} catch (error) {
  console.log((error as LogoxideErrorException).value); // → { tag: 'Io', message: 'no file access in WebAssembly: open each package with openPackage' }
}
try {
  logoxide.comparePassage("vault", "library", "cache", "John 3:16", ["KJV", "BSB", "ASV"], plain);
} catch (error) {
  console.log((error as LogoxideErrorException).value.tag); // → Io
}
plain = BookTextStyle(verse_label="[{verse}]", chapter_label=None, notes=False, headings=False, templates=[], width=None, limit=None)
read = logoxide.read_passage("vault", "library", "cache", "John 3:16 KJV", plain)
print(read.bible, read.abbreviation, read.passage.reference, read.versification)  # → kjv KJV John 3:16 KJV
print(read.text.text)  # → [16] ¶ For God so loved the world, …
cut = dataclasses.replace(plain, limit=12)
for c in logoxide.compare_passage("vault", "library", "cache", "John 3:16", ["KJV", "BSB", "ASV"], cut):
    print(c.abbreviation, c.reference, c.text.text, c.mapped, repr(c.missing))
# → KJV John 3:16 [16] ¶ For … False ''
# → BSB John 3:16 [16] For God … False ''
# → ASV John 3:16 [16] For God … False ''

Errors

Errors are thrown as LogoxideErrorException in TypeScript (error.value is { tag, message }) and raised as LogoxideErrorException in Python (error.error is a LogoxideErrorNotFound, LogoxideErrorIo, and so on, with its message). The tag is the core's kind: NotFound, Io, Invalid, InvalidMarkup, Network, Git, Archive, Integrity, Resolve, Busy, and Conflict.

try {
  logoxide.openPackage([file("notes.md", "")], features, []);
} catch (error) {
  if (!(error instanceof LogoxideErrorException)) throw error;
  console.log(error.value); // → { tag: 'NotFound', message: 'logoxide.toml: not found' }
}
from logoxide import LogoxideErrorNotFound

try:
    logoxide.open_package([file("notes.md", "")], features, [])
except LogoxideErrorException as error:
    print(isinstance(error.error, LogoxideErrorNotFound), error.error.message)  # → True logoxide.toml: not found

Every export

Each of logoxide's classes, functions, records, and enums (by their TypeScript names), and where an example uses it. The rest (Topos, ToposTranslations, ToposQuery, ToposFormat, ToposOptions, ToposIndex, ToposFiles, Passage, and their records and enums) are topos's: see References, with topos.

Export Example
Logoxide.new, version Loading
Logoxide.openPackage, openPackageDir, readPackageInfo Opening a package
Logoxide.usfmToUsx A book's text
Logoxide.parseTimings, verifyMedia, mediaStorePath Audio and video
Logoxide.versifications Versification maps
Logoxide.parseProject A vault's project
Logoxide.parseCatalog, parseCatalogBytes, installer, defaultCatalogUrls, ebibleCatalogUrl, isInsecureUrl, swordCatalogUrl, importPrefixes, categories, maxCategories Catalogs
Logoxide.importDownloads, importDownloaded, importIsCurrent, updateImport Imports
Logoxide.fileMetadata, newPackage, newCollection, parseReferences, unzip Making packages
Logoxide.readPassage, comparePassage Reading natively
LogoxidePackage.info, checksum, format, books, chapterVerses, cacheWrites, bookFiles, titles, skipped, verseHashes Opening a package
LogoxidePackage.addTo, addToAs References, with topos
LogoxidePackage.book A book's text
LogoxidePackage.notes, entry, keys, crossReferences Commentaries, dictionaries, and cross-references
LogoxidePackage.media, mediaText, mediaVersification, timings Audio and video
LogoxidePackage.licenseConcerns, references, epubFiles Making packages
LogoxideBook.bookId, verses, chapters, chapterVerses, text, html, chapterHtml, styledText, nodes, verseNodes, parse, usxChapterVerses, findFragment A book's text
LogoxideVersifications.withMap, withVrs, names, map Versification maps
LogoxideProject.manifestText, dependencies, unlocked, defaultBible, compare, catalogs, isDefaultCatalog, addCatalog, removeCatalog, registries, addRegistry, removeRegistry, addDependency, removeDependency A vault's project
LogoxideProject.packages, setEnabled, isEnabled, enabledDependencies, disabledDependencies, commentaries, dictionaries, crossReferences, nameClashes The vault's packages
LogoxideCatalog.name, count, warnings, merge, search, searchGrouped, works, findImport Catalogs
LogoxideCatalog.findUpstream Imports
LogoxideInstaller.loadCatalogs, catalog, provideDownload, provideDownloadFailed, provideRefs, provideArchive, provideImportEntry, provideFiles, provideMissing Catalogs (answer)
LogoxideInstaller.inspect, install Installing
PackageFile, PackageFeatures, PackageInfo, PackageKind, Rights, Derivatives, Upstream, BookFile, TitledFile, SkippedFile, VerseHashEntry Opening a package, Imports (SkippedFile, Upstream)
BookTextOptions, BookVerse, BookTextStyle, KindTemplate, StyledText, TextMark, NodeKind, BookNode, NodeAttribute, MilestoneKind, VerseNodes, TextRange A book's text
Note, Entry, CrossReference Commentaries, dictionaries, and cross-references
MediaFile, VerseTiming Audio and video
MappedPassage, MappedVerse Versification maps
Dependency, Registry A vault's project
CatalogEntry, CatalogSource, CatalogRelease, ImportDownload, WorkGroup, WorkInfo, WorkFrom, Category, CatalogsStep, FetchRequest Catalogs
InspectStep, Inspected, InstallStep, UpdateMode Installing
LockedEntry, PackageLocation, PackageManifest, NamedBible, NameClash The vault's packages
ImportUpdate, ChangeKind Imports
FileMetadata, NewPackage, LicenseConcern, ConcernKind, FoundReference, CitedPassage Making packages
ReadPassage, ComparedPassage Reading natively
LogoxideError, LogoxideErrorException Errors

Building

npm install && npm run pack:wasm   # dist/wasm/pkg (npm: logoxide)
npm run pack:python                # dist/python/wheelhouse (PyPI: logoxide)

The examples run against the KJV and BSB packages (git repositories with release tags; the owner's are in ~/Development/logoxide-packages, the default) and examples/fixtures: a made-up two-chapter EPUB (sermons.epub) with its release archive and logoxide.references.bin (made by lx new and lx release), a few ASV books in eBible.org's zip and the list naming them, and a catalog.toml listing the KJV, the BSB, and the EPUB's release.

cd examples/node && npm install
node readme.ts ~/Development/logoxide-packages   # every TypeScript example above (--show prints what each printed)
node index.ts ~/Development/logoxide-packages/bsb ~/Development/logoxide-packages/kjv
node install.ts file://$HOME/Development/logoxide-packages/kjv file://$HOME/Development/logoxide-packages/bsb
node book.ts ../fixtures/sermons.epub
cd ../.. && pip install dist/python/wheelhouse/*.whl
python examples/python/readme.py ~/Development/logoxide-packages   # every Python example above
python examples/python/packages.py ~/Development/logoxide-packages/bsb ~/Development/logoxide-packages/kjv

topos's crate is a path dependency (../topos), as for the rest of logoxide.

Metadata

Release files for logoxide 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for logoxide 0.1.0
File
logoxide-0.1.0-cp314-cp314-win_amd64.whl CPython 3.14 CPython 3.14 Windows x86-64 Details
logoxide-0.1.0-cp314-cp314-manylinux_2_28_x86_64.whl CPython 3.14 CPython 3.14 Linux glibc 2.28+ x86-64 Details
logoxide-0.1.0-cp314-cp314-manylinux_2_28_aarch64.whl CPython 3.14 CPython 3.14 Linux glibc 2.28+ ARM64 Details
logoxide-0.1.0-cp314-cp314-macosx_11_0_x86_64.whl CPython 3.14 CPython 3.14 macOS 11.0+ x86-64 Details
logoxide-0.1.0-cp314-cp314-macosx_11_0_arm64.whl CPython 3.14 CPython 3.14 macOS 11.0+ ARM64 Details
logoxide-0.1.0-cp313-cp313-win_amd64.whl CPython 3.13 CPython 3.13 Windows x86-64 Details
logoxide-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.28+ x86-64 Details
logoxide-0.1.0-cp313-cp313-manylinux_2_28_aarch64.whl CPython 3.13 CPython 3.13 Linux glibc 2.28+ ARM64 Details
logoxide-0.1.0-cp313-cp313-macosx_11_0_x86_64.whl CPython 3.13 CPython 3.13 macOS 11.0+ x86-64 Details
logoxide-0.1.0-cp313-cp313-macosx_11_0_arm64.whl CPython 3.13 CPython 3.13 macOS 11.0+ ARM64 Details
logoxide-0.1.0-cp312-cp312-win_amd64.whl CPython 3.12 CPython 3.12 Windows x86-64 Details
logoxide-0.1.0-cp312-cp312-manylinux_2_28_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.28+ x86-64 Details
logoxide-0.1.0-cp312-cp312-manylinux_2_28_aarch64.whl CPython 3.12 CPython 3.12 Linux glibc 2.28+ ARM64 Details
logoxide-0.1.0-cp312-cp312-macosx_11_0_x86_64.whl CPython 3.12 CPython 3.12 macOS 11.0+ x86-64 Details
logoxide-0.1.0-cp312-cp312-macosx_11_0_arm64.whl CPython 3.12 CPython 3.12 macOS 11.0+ ARM64 Details
logoxide-0.1.0-cp311-cp311-win_amd64.whl CPython 3.11 CPython 3.11 Windows x86-64 Details
logoxide-0.1.0-cp311-cp311-manylinux_2_28_x86_64.whl CPython 3.11 CPython 3.11 Linux glibc 2.28+ x86-64 Details
logoxide-0.1.0-cp311-cp311-manylinux_2_28_aarch64.whl CPython 3.11 CPython 3.11 Linux glibc 2.28+ ARM64 Details
logoxide-0.1.0-cp311-cp311-macosx_11_0_x86_64.whl CPython 3.11 CPython 3.11 macOS 11.0+ x86-64 Details
logoxide-0.1.0-cp311-cp311-macosx_11_0_arm64.whl CPython 3.11 CPython 3.11 macOS 11.0+ ARM64 Details
logoxide-0.1.0-cp310-cp310-win_amd64.whl CPython 3.10 CPython 3.10 Windows x86-64 Details
logoxide-0.1.0-cp310-cp310-manylinux_2_28_x86_64.whl CPython 3.10 CPython 3.10 Linux glibc 2.28+ x86-64 Details
logoxide-0.1.0-cp310-cp310-manylinux_2_28_aarch64.whl CPython 3.10 CPython 3.10 Linux glibc 2.28+ ARM64 Details
logoxide-0.1.0-cp310-cp310-macosx_11_0_x86_64.whl CPython 3.10 CPython 3.10 macOS 11.0+ x86-64 Details
logoxide-0.1.0-cp310-cp310-macosx_11_0_arm64.whl CPython 3.10 CPython 3.10 macOS 11.0+ ARM64 Details

Total release size: 113.8 MB

Release files / logoxide-0.1.0-cp314-cp314-win_amd64.whl

Download URL logoxide-0.1.0-cp314-cp314-win_amd64.whl
Size 4.1 MB
Tags CPython 3.14 Windows x86-64
SHA-256 checksum
How to use checksums
b76049b4b42143997f4e74b76d42d06999eee54c1e91e9a42b88edc4df247f3e
BLAKE2b-256 checksum
How to use checksums
0cdd139c325f0651815820f8e96c6aa756c3d91744d0a28145bdf03d301fd013
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp314-cp314-manylinux_2_28_x86_64.whl

Download URL logoxide-0.1.0-cp314-cp314-manylinux_2_28_x86_64.whl
Size 5.2 MB
Tags CPython 3.14 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
dd5a9636526c1e80fce86eea86ef4b822b16541f6cdef95cb4bddefdd5125c68
BLAKE2b-256 checksum
How to use checksums
1846ee6c1a98ab4948acca914e8844d4f8414e10de1cd81a36842796bd7e0638
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp314-cp314-manylinux_2_28_aarch64.whl

Download URL logoxide-0.1.0-cp314-cp314-manylinux_2_28_aarch64.whl
Size 5.1 MB
Tags CPython 3.14 Linux glibc 2.28+ ARM64
SHA-256 checksum
How to use checksums
785a209b633b517561571c7ca60b990a75fe3d3df1002042363678b6385a4d77
BLAKE2b-256 checksum
How to use checksums
2ee3fcd7451c8216a3d342b19e2a472b4886b196b7ce73783f8e4f761932611f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp314-cp314-macosx_11_0_x86_64.whl

Download URL logoxide-0.1.0-cp314-cp314-macosx_11_0_x86_64.whl
Size 4.3 MB
Tags CPython 3.14 macOS 11.0+ x86-64
SHA-256 checksum
How to use checksums
0c4613a41db164603528ebee18052e5886bda5d477c63971f057eaa6777157f1
BLAKE2b-256 checksum
How to use checksums
fa26b3848f88058ec148ed58b9e829616c955f1aa601fcc33afe3e4502b87b2f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp314-cp314-macosx_11_0_arm64.whl

Download URL logoxide-0.1.0-cp314-cp314-macosx_11_0_arm64.whl
Size 4.1 MB
Tags CPython 3.14 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
1ab9503ec354d165d3e7f1225c592449182be92da096c6ba81858df015526b76
BLAKE2b-256 checksum
How to use checksums
175c5553aa60b24e8afd2b586ba59d7557e4eed5d3b401b7d24e7fb085230706
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp313-cp313-win_amd64.whl

Download URL logoxide-0.1.0-cp313-cp313-win_amd64.whl
Size 4.0 MB
Tags CPython 3.13 Windows x86-64
SHA-256 checksum
How to use checksums
7004402628d2f84ad7a696d6683b58b045554afa9201d5557c706ff9cdd0d353
BLAKE2b-256 checksum
How to use checksums
b46dc9e4d495a56efa0b2002901cce2244c5b48ada52e2a6e71d6dfdb3231833
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl

Download URL logoxide-0.1.0-cp313-cp313-manylinux_2_28_x86_64.whl
Size 5.2 MB
Tags CPython 3.13 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
f9068023fd569c3bc981ed7e51ccaf9e950381ffd8fa2bd18b796aba6b7ffc4b
BLAKE2b-256 checksum
How to use checksums
ce5f95d9a5b153cc1f9d530be1ff1831ba74970e8cf88d0070c50d292ced328a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp313-cp313-manylinux_2_28_aarch64.whl

Download URL logoxide-0.1.0-cp313-cp313-manylinux_2_28_aarch64.whl
Size 5.1 MB
Tags CPython 3.13 Linux glibc 2.28+ ARM64
SHA-256 checksum
How to use checksums
4d18d25645657e2ea6f430c7b33977238c4087a5842978e784b6c90e07898de5
BLAKE2b-256 checksum
How to use checksums
a8ed5143b87ee0b98c8425777f06f42014876c8a0a7f74d2fdadf62037188eaa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp313-cp313-macosx_11_0_x86_64.whl

Download URL logoxide-0.1.0-cp313-cp313-macosx_11_0_x86_64.whl
Size 4.3 MB
Tags CPython 3.13 macOS 11.0+ x86-64
SHA-256 checksum
How to use checksums
21b0e3f533742794675670d8b450db822662b70b3908abfef6020c6f6803965f
BLAKE2b-256 checksum
How to use checksums
9c59aabeba30c58164346b0964339424eba04716da1a3985f4760074ed511382
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp313-cp313-macosx_11_0_arm64.whl

Download URL logoxide-0.1.0-cp313-cp313-macosx_11_0_arm64.whl
Size 4.1 MB
Tags CPython 3.13 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
61a720dd58a13086612cc218c262560872e03493986559cb3fa101b76b98fc0f
BLAKE2b-256 checksum
How to use checksums
2cda6678eda6ec03d01e2202886dcb47a3ee2c52a6e2efa3ad9a5fa69f5e9c47
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp312-cp312-win_amd64.whl

Download URL logoxide-0.1.0-cp312-cp312-win_amd64.whl
Size 4.0 MB
Tags CPython 3.12 Windows x86-64
SHA-256 checksum
How to use checksums
77ca9bb8624cb1185e8a04dac799e99d12e2f9c575b6a990370179c3bf24e8bf
BLAKE2b-256 checksum
How to use checksums
cc91d7cf51c5c84522fbb1d211ed4ab18f1c2f4b665351696b8403514960b350
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp312-cp312-manylinux_2_28_x86_64.whl

Download URL logoxide-0.1.0-cp312-cp312-manylinux_2_28_x86_64.whl
Size 5.2 MB
Tags CPython 3.12 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
a69efab59a5e80ee38c60d64006706e1feb4966774f418532519d45b1887c4e2
BLAKE2b-256 checksum
How to use checksums
0f271bb3e76402d386b738ae68d15280d2e7acf124d034b6fc0c929a31260052
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp312-cp312-manylinux_2_28_aarch64.whl

Download URL logoxide-0.1.0-cp312-cp312-manylinux_2_28_aarch64.whl
Size 5.1 MB
Tags CPython 3.12 Linux glibc 2.28+ ARM64
SHA-256 checksum
How to use checksums
ff897613b3e9611dd4ef9d4b659d5a939c5ac5501db33d348e514e30751de93d
BLAKE2b-256 checksum
How to use checksums
c89ec8e16e1b81f2b0a6208ec345a49ac6368705b36cacab1d77c27d3325a555
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp312-cp312-macosx_11_0_x86_64.whl

Download URL logoxide-0.1.0-cp312-cp312-macosx_11_0_x86_64.whl
Size 4.3 MB
Tags CPython 3.12 macOS 11.0+ x86-64
SHA-256 checksum
How to use checksums
9b42ff3a3eb1aaa21eac8237698a8474a0fda53a1ad4a4dbb25492e7e3ed92a7
BLAKE2b-256 checksum
How to use checksums
69e3c28d656c5ed54ccc431eb5734f913073308a5f0e923237de26ad05a2b17b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp312-cp312-macosx_11_0_arm64.whl

Download URL logoxide-0.1.0-cp312-cp312-macosx_11_0_arm64.whl
Size 4.1 MB
Tags CPython 3.12 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
80bf5b09fbcee949f3cab9e3afa48c58151cce56a11244c65d8f24ec9abb42fb
BLAKE2b-256 checksum
How to use checksums
9637eb913f7f14ed6b4968689c6cbf8a9ce5e13745f4eb74e36b4cf07b897820
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp311-cp311-win_amd64.whl

Download URL logoxide-0.1.0-cp311-cp311-win_amd64.whl
Size 4.0 MB
Tags CPython 3.11 Windows x86-64
SHA-256 checksum
How to use checksums
30fffc7c9b28f8b8c3cc15136518a55158316945e6b45bfbdca226893f0943cc
BLAKE2b-256 checksum
How to use checksums
8ce215fd4592ff3651fcac802d0b9d1d3cde9da14924cb339020e74c9ea91bce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp311-cp311-manylinux_2_28_x86_64.whl

Download URL logoxide-0.1.0-cp311-cp311-manylinux_2_28_x86_64.whl
Size 5.2 MB
Tags CPython 3.11 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
a56fe98eb052e769e02355e0f84201cc2c7096444891580c35a8b6cd608e2b51
BLAKE2b-256 checksum
How to use checksums
71babf4b36fc98fbdd70a886285ab7c0b7179f9cec5e77cf4e29cf5869793fd8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp311-cp311-manylinux_2_28_aarch64.whl

Download URL logoxide-0.1.0-cp311-cp311-manylinux_2_28_aarch64.whl
Size 5.1 MB
Tags CPython 3.11 Linux glibc 2.28+ ARM64
SHA-256 checksum
How to use checksums
f1eb1dded93370794bf1f899013631220c7febedd0695dac7b02ea07b58b31eb
BLAKE2b-256 checksum
How to use checksums
6a2e5459f3483c354f00ef3ea0fb6e85cf72d4441f45318efd601bc7fc18d4a4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp311-cp311-macosx_11_0_x86_64.whl

Download URL logoxide-0.1.0-cp311-cp311-macosx_11_0_x86_64.whl
Size 4.3 MB
Tags CPython 3.11 macOS 11.0+ x86-64
SHA-256 checksum
How to use checksums
b68748b4bbf12f7bb3db5fb87fc6bf32a4251d8ea49722f8fbf3fb7bfb9451bb
BLAKE2b-256 checksum
How to use checksums
dbb4557d34667f762fa161a4154d2afb0db43f74d7d3b81e19d884246a2d5153
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp311-cp311-macosx_11_0_arm64.whl

Download URL logoxide-0.1.0-cp311-cp311-macosx_11_0_arm64.whl
Size 4.1 MB
Tags CPython 3.11 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
245c2fa74bf638d33e631897062b4069c2ceff75c36aff257a16668b2be3011d
BLAKE2b-256 checksum
How to use checksums
7ed6c136fee9c94b0380b98e5801adcaccefc9b0ade5cd2c2dfafd26492d6df6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp310-cp310-win_amd64.whl

Download URL logoxide-0.1.0-cp310-cp310-win_amd64.whl
Size 4.0 MB
Tags CPython 3.10 Windows x86-64
SHA-256 checksum
How to use checksums
417b3e4a06406ee6e3091f97137c0de94c7880cd44a995c534e7ea336630e9e4
BLAKE2b-256 checksum
How to use checksums
411bed9366892889cfdaccec4cc336f25fa8b3167efa7c292be1b9caa28ae831
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp310-cp310-manylinux_2_28_x86_64.whl

Download URL logoxide-0.1.0-cp310-cp310-manylinux_2_28_x86_64.whl
Size 5.2 MB
Tags CPython 3.10 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
bef3bb556713c5e4a829f8f2667883c5e800c55e311f4774b13b3a9bf27e41b4
BLAKE2b-256 checksum
How to use checksums
8417f55ac9bb87cc0f6928a1d029e243f7c027e3a29d26f409d4e4a6d1e71020
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp310-cp310-manylinux_2_28_aarch64.whl

Download URL logoxide-0.1.0-cp310-cp310-manylinux_2_28_aarch64.whl
Size 5.1 MB
Tags CPython 3.10 Linux glibc 2.28+ ARM64
SHA-256 checksum
How to use checksums
b528de889ae0de27d42d2a7cc35d115cbad30a73b6c40078ad1d7b584705a274
BLAKE2b-256 checksum
How to use checksums
3a179d23d994f2817cb60a500329b0786384d61311ad1083ad873883210f499a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp310-cp310-macosx_11_0_x86_64.whl

Download URL logoxide-0.1.0-cp310-cp310-macosx_11_0_x86_64.whl
Size 4.3 MB
Tags CPython 3.10 macOS 11.0+ x86-64
SHA-256 checksum
How to use checksums
68f6c876b70cf44534d5f146897105d396a997b8d184ba2ea78affc77ee74981
BLAKE2b-256 checksum
How to use checksums
ef0d853d6fd25e18cb53dc2c1f4c25e39ee3815a66231d0880365f3adfa17713
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / logoxide-0.1.0-cp310-cp310-macosx_11_0_arm64.whl

Download URL logoxide-0.1.0-cp310-cp310-macosx_11_0_arm64.whl
Size 4.1 MB
Tags CPython 3.10 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
3d74e709a3674c3949641db028e172a7af216409c6c4e0a5ce8501deddb9c3c0
BLAKE2b-256 checksum
How to use checksums
c5581a419fc341cdc56ce740ea74c362bce31041951634bd46530a85f336dc4b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Security for logoxide 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 25 files in this release have been uploaded with provenance metadata.

Select from 25 files

Attestation for logoxide-0.1.0-cp314-cp314-win_amd64.whl

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page