699 lines
22 KiB
Python
699 lines
22 KiB
Python
"""Compile the eBible.org HTML export into the fragments the app serves."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import html
|
||
import json
|
||
import os
|
||
import re
|
||
import sys
|
||
from html.parser import HTMLParser
|
||
|
||
CHAPTER_RE = re.compile(r"^([0-9A-Z]{3})(\d{2,3})\.htm$")
|
||
|
||
HREF_RE = re.compile(r"^(?:([0-9A-Z]{3})(\d{2,3})\.htm)?(?:#V(\d+))?$")
|
||
|
||
BLOCK_TYPES = {
|
||
"p": "paragraph",
|
||
"m": "paragraph",
|
||
"nb": "paragraph",
|
||
"pi": "paragraph",
|
||
"mi": "paragraph",
|
||
"pc": "paragraph",
|
||
"ip": "paragraph",
|
||
"q": "line",
|
||
"q2": "line",
|
||
"q3": "line",
|
||
"qs": "line",
|
||
"b": "break",
|
||
"d": "title",
|
||
"s": "heading",
|
||
"ms": "heading",
|
||
"is": "heading",
|
||
"sp": "speaker",
|
||
"li": "item",
|
||
"ili": "item",
|
||
"mt": "booktitle",
|
||
"mt2": "booktitle",
|
||
"mt3": "booktitle",
|
||
}
|
||
|
||
INDENT = {"q": 1, "q2": 2, "q3": 3, "qs": 3, "pi": 1, "mi": 1, "li": 1, "ili": 1}
|
||
|
||
LEVEL = {"mt": 1, "mt2": 2, "mt3": 3, "ms": 1, "s": 2, "is": 2}
|
||
|
||
INLINE = {
|
||
"wj": "wj",
|
||
"k": "keyword",
|
||
"bk": "bookref",
|
||
"fq": "fq",
|
||
"fqa": "fqa",
|
||
"fl": "fl",
|
||
"fine": "fine",
|
||
"xt": "xt",
|
||
"ft": None,
|
||
}
|
||
|
||
DROP_CLASSES = {"copyright", "tnav", "vnav", "popup", "toc", "toc1", "toc2"}
|
||
|
||
def parse_href(href):
|
||
m = HREF_RE.match(href or "")
|
||
if not m or not any(m.groups()):
|
||
return None
|
||
book, chapter, verse = m.groups()
|
||
ref = {}
|
||
if book:
|
||
ref["book"] = book
|
||
ref["chapter"] = int(chapter)
|
||
if verse:
|
||
ref["verse"] = int(verse)
|
||
return ref or None
|
||
|
||
def normalize(nodes):
|
||
state = {"prev_space": True}
|
||
|
||
def walk(lst):
|
||
out = []
|
||
for node in lst:
|
||
if node["t"] == "text":
|
||
text = re.sub(r"[\s ]+", " ", node["v"])
|
||
if state["prev_space"]:
|
||
text = text.lstrip(" ")
|
||
if text:
|
||
state["prev_space"] = text.endswith(" ")
|
||
out.append({"t": "text", "v": text})
|
||
elif "c" in node:
|
||
node["c"] = walk(node["c"])
|
||
if node["c"]:
|
||
out.append(node)
|
||
else:
|
||
state["prev_space"] = False
|
||
out.append(node)
|
||
return out
|
||
|
||
result = walk(nodes)
|
||
rstrip_last(result)
|
||
return result
|
||
|
||
def rstrip_last(nodes):
|
||
if not nodes:
|
||
return False
|
||
node = nodes[-1]
|
||
if node["t"] == "text":
|
||
node["v"] = node["v"].rstrip()
|
||
if not node["v"]:
|
||
nodes.pop()
|
||
return rstrip_last(nodes)
|
||
return True
|
||
if "c" in node:
|
||
return rstrip_last(node["c"])
|
||
return False
|
||
|
||
def plain_text(nodes, skip=("noteref",)):
|
||
parts = []
|
||
for node in nodes:
|
||
if node["t"] in skip:
|
||
continue
|
||
if node["t"] == "text":
|
||
parts.append(node["v"])
|
||
elif node["t"] == "br":
|
||
parts.append(" ")
|
||
elif "c" in node:
|
||
parts.append(plain_text(node["c"], skip))
|
||
return "".join(parts)
|
||
|
||
class ChapterParser(HTMLParser):
|
||
|
||
def __init__(self):
|
||
super().__init__(convert_charrefs=True)
|
||
self.blocks = []
|
||
self.notes = []
|
||
self.titles = []
|
||
self.chapter_label = None
|
||
self.is_toc = False
|
||
|
||
self.in_main = False
|
||
self.stack = []
|
||
self.sink = None
|
||
|
||
def _push(self, tag, sink, on_close=None, **extra):
|
||
frame = {"tag": tag, "prev": self.sink, "close": on_close}
|
||
frame.update(extra)
|
||
self.stack.append(frame)
|
||
self.sink = sink
|
||
return frame
|
||
|
||
def _emit(self, node):
|
||
if self.sink is not None:
|
||
self.sink.append(node)
|
||
|
||
def handle_starttag(self, tag, attrs):
|
||
attrs = dict(attrs)
|
||
classes = (attrs.get("class") or "").split()
|
||
cls = classes[0] if classes else ""
|
||
|
||
if tag in ("br", "hr", "img", "meta", "link", "input"):
|
||
if tag == "br":
|
||
self._emit({"t": "br"})
|
||
return
|
||
|
||
if not self.in_main:
|
||
if tag == "div" and "main" in classes:
|
||
self.in_main = True
|
||
self._push(tag, None)
|
||
return
|
||
|
||
if cls in DROP_CLASSES:
|
||
if cls in ("toc", "toc1", "toc2"):
|
||
self.is_toc = True
|
||
self._push(tag, None)
|
||
return
|
||
|
||
if tag == "div" and cls == "chapterlabel":
|
||
buf = []
|
||
self._push(tag, buf, self._close_chapter_label, nodes=buf)
|
||
return
|
||
|
||
if tag == "div" and cls in BLOCK_TYPES:
|
||
block = {"type": BLOCK_TYPES[cls], "style": cls, "content": []}
|
||
if cls in INDENT:
|
||
block["indent"] = INDENT[cls]
|
||
if cls in LEVEL:
|
||
block["level"] = LEVEL[cls]
|
||
self._push(tag, block["content"], self._close_block, block=block)
|
||
return
|
||
|
||
if tag == "p" and cls in ("f", "x"):
|
||
note = {
|
||
"id": attrs.get("id"),
|
||
"kind": "footnote" if cls == "f" else "xref",
|
||
"marker": None,
|
||
"ref": None,
|
||
"verse": None,
|
||
"content": [],
|
||
}
|
||
self._push(tag, note["content"], self._close_note, note=note)
|
||
return
|
||
|
||
if tag == "span" and "verse" in classes:
|
||
buf = []
|
||
self._push(tag, buf, self._close_verse, nodes=buf)
|
||
return
|
||
|
||
if tag == "span" and cls == "notemark":
|
||
buf = []
|
||
self._push(tag, buf, self._close_note_marker, nodes=buf)
|
||
return
|
||
|
||
if tag == "a" and cls == "notemark":
|
||
node = {"t": "noteref", "id": (attrs.get("href") or "").lstrip("#")}
|
||
self._emit(node)
|
||
buf = []
|
||
self._push(tag, buf, self._close_noteref, node=node, nodes=buf)
|
||
return
|
||
|
||
if tag == "a" and cls == "notebackref":
|
||
buf = []
|
||
self._push(tag, buf, self._close_backref, nodes=buf)
|
||
return
|
||
|
||
if tag == "span" and cls in INLINE:
|
||
kind = INLINE[cls]
|
||
if kind is None:
|
||
self._push(tag, self.sink)
|
||
else:
|
||
node = {"t": kind, "c": []}
|
||
self._emit(node)
|
||
self._push(tag, node["c"])
|
||
return
|
||
|
||
if tag == "a":
|
||
ref = parse_href(attrs.get("href"))
|
||
if ref is not None and self.sink is not None:
|
||
node = {"t": "ref", "c": []}
|
||
node.update(ref)
|
||
self._emit(node)
|
||
self._push(tag, node["c"])
|
||
else:
|
||
self._push(tag, self.sink)
|
||
return
|
||
|
||
self._push(tag, self.sink)
|
||
|
||
def handle_endtag(self, tag):
|
||
if not self.in_main or not self.stack:
|
||
return
|
||
for i in range(len(self.stack) - 1, -1, -1):
|
||
if self.stack[i]["tag"] == tag:
|
||
break
|
||
else:
|
||
return
|
||
while len(self.stack) > i:
|
||
frame = self.stack.pop()
|
||
self.sink = frame["prev"]
|
||
if frame["close"]:
|
||
frame["close"](frame)
|
||
if tag == "div" and not self.stack:
|
||
self.in_main = False
|
||
|
||
def handle_data(self, data):
|
||
if self.sink is not None and data:
|
||
self.sink.append({"t": "text", "v": data})
|
||
|
||
def _close_chapter_label(self, frame):
|
||
self.chapter_label = plain_text(frame["nodes"]).strip()
|
||
|
||
def _close_block(self, frame):
|
||
block = frame["block"]
|
||
block["content"] = normalize(block["content"])
|
||
if block["type"] == "booktitle":
|
||
if block["content"]:
|
||
self.titles.append({
|
||
"style": block["style"],
|
||
"level": block.get("level"),
|
||
"text": plain_text(block["content"]),
|
||
})
|
||
return
|
||
if block["type"] == "break":
|
||
block.pop("content")
|
||
self.blocks.append(block)
|
||
return
|
||
if block["content"]:
|
||
self.blocks.append(block)
|
||
|
||
def _close_note(self, frame):
|
||
note = frame["note"]
|
||
note["content"] = normalize(note["content"])
|
||
note["text"] = plain_text(note["content"])
|
||
self.notes.append(note)
|
||
|
||
def _close_verse(self, frame):
|
||
label = plain_text(frame["nodes"]).replace(" ", " ").strip()
|
||
node = {"t": "verse", "label": label}
|
||
m = re.match(r"^(\d+)(?:[-–](\d+))?$", label)
|
||
if m:
|
||
node["n"] = int(m.group(1))
|
||
if m.group(2):
|
||
node["end"] = int(m.group(2))
|
||
self._emit(node)
|
||
|
||
def _close_note_marker(self, frame):
|
||
marker = plain_text(frame["nodes"]).strip()
|
||
for f in reversed(self.stack):
|
||
if "note" in f:
|
||
f["note"]["marker"] = marker
|
||
return
|
||
|
||
def _close_noteref(self, frame):
|
||
frame["node"]["marker"] = plain_text(frame["nodes"]).strip()
|
||
|
||
def _close_backref(self, frame):
|
||
text = plain_text(frame["nodes"]).strip()
|
||
for f in reversed(self.stack):
|
||
if "note" in f:
|
||
f["note"]["ref"] = text
|
||
m = re.search(r":(\d+)", text)
|
||
if m:
|
||
f["note"]["verse"] = int(m.group(1))
|
||
return
|
||
|
||
class IndexParser(HTMLParser):
|
||
|
||
SECTIONS = {"oo": "ot", "nn": "nt", "aa": "deuterocanon", "xx": "other"}
|
||
|
||
def __init__(self):
|
||
super().__init__(convert_charrefs=True)
|
||
self.books = []
|
||
self.title = None
|
||
self._link = None
|
||
self._in_title = False
|
||
|
||
def handle_starttag(self, tag, attrs):
|
||
attrs = dict(attrs)
|
||
if tag == "title":
|
||
self._in_title = True
|
||
elif tag == "a":
|
||
m = CHAPTER_RE.match(os.path.basename(attrs.get("href") or ""))
|
||
section = self.SECTIONS.get((attrs.get("class") or "").strip())
|
||
if m and section:
|
||
self._link = {
|
||
"code": m.group(1),
|
||
"section": section,
|
||
"order": len(self.books) + 1,
|
||
"name": "",
|
||
}
|
||
|
||
def handle_endtag(self, tag):
|
||
if tag == "title":
|
||
self._in_title = False
|
||
elif tag == "a" and self._link:
|
||
self._link["name"] = self._link["name"].strip()
|
||
self.books.append(self._link)
|
||
self._link = None
|
||
|
||
def handle_data(self, data):
|
||
if self._in_title and self.title is None:
|
||
self.title = data.strip()
|
||
elif self._link is not None:
|
||
self._link["name"] += data
|
||
|
||
def parse_chapter(path):
|
||
parser = ChapterParser()
|
||
with open(path, encoding="utf-8-sig") as fh:
|
||
parser.feed(fh.read())
|
||
parser.close()
|
||
return parser
|
||
|
||
def extract(source_dir):
|
||
index_path = os.path.join(source_dir, "index.htm")
|
||
index = IndexParser()
|
||
with open(index_path, encoding="utf-8-sig") as fh:
|
||
index.feed(fh.read())
|
||
|
||
meta = {b["code"]: b for b in index.books}
|
||
|
||
by_book = {}
|
||
for name in sorted(os.listdir(source_dir)):
|
||
m = CHAPTER_RE.match(name)
|
||
if m:
|
||
by_book.setdefault(m.group(1), []).append((int(m.group(2)), name))
|
||
|
||
books = []
|
||
skipped = []
|
||
for code in sorted(by_book, key=lambda c: (meta.get(c, {}).get("order", 999), c)):
|
||
info = meta.get(code, {"code": code, "name": code, "section": "other",
|
||
"order": 999})
|
||
book = {
|
||
"code": code,
|
||
"name": info["name"],
|
||
"section": info["section"],
|
||
"titles": [],
|
||
"chapters": [],
|
||
}
|
||
for number, name in sorted(by_book[code]):
|
||
parsed = parse_chapter(os.path.join(source_dir, name))
|
||
if parsed.is_toc or not parsed.blocks:
|
||
skipped.append(name)
|
||
continue
|
||
if parsed.titles and not book["titles"]:
|
||
book["titles"] = parsed.titles
|
||
label = parsed.chapter_label
|
||
chapter = {
|
||
"number": int(label) if label and label.isdigit() else number,
|
||
"blocks": parsed.blocks,
|
||
"notes": parsed.notes,
|
||
}
|
||
book["chapters"].append(chapter)
|
||
if book["chapters"]:
|
||
books.append(book)
|
||
|
||
return {
|
||
"translation": {
|
||
"id": "engwebu",
|
||
"name": index.title or "World English Bible Updated",
|
||
"license": "Public Domain",
|
||
"source": "eBible.org",
|
||
},
|
||
"books": books,
|
||
}, skipped
|
||
|
||
INLINE_TAGS = {
|
||
"wj": ("span", "wj"),
|
||
"keyword": ("dfn", "k"),
|
||
"bookref": ("cite", "bk"),
|
||
"fq": ("span", "fq"),
|
||
"fqa": ("span", "fqa"),
|
||
"fl": ("span", "fl"),
|
||
"fine": ("span", "fine"),
|
||
"xt": ("span", "xt"),
|
||
}
|
||
|
||
BLOCK_TAGS = {
|
||
"paragraph": "p",
|
||
"line": "p",
|
||
"heading": "h3",
|
||
"title": "p",
|
||
"speaker": "p",
|
||
"item": "li",
|
||
}
|
||
|
||
def esc(text):
|
||
return html.escape(text, quote=False)
|
||
|
||
def verse_id(chapter, label):
|
||
return "v%d_%s" % (chapter, label)
|
||
|
||
def ref_href(node, book, chapter):
|
||
target_book = node.get("book") or book
|
||
target_ch = node.get("chapter", chapter)
|
||
verse = node.get("verse")
|
||
base = "" if target_book == book else target_book
|
||
if verse:
|
||
return "%s#%s" % (base, verse_id(target_ch, verse))
|
||
return "%s#ch%d" % (base, target_ch)
|
||
|
||
def render_inline(nodes, book, chapter, out):
|
||
for node in nodes:
|
||
kind = node["t"]
|
||
if kind == "text":
|
||
out.append(esc(node["v"]))
|
||
elif kind == "verse":
|
||
label = esc(node["label"])
|
||
out.append('<span class="v" id="%s" role="button" tabindex="0" '
|
||
'aria-label="Verse %s, options">%s</span> '
|
||
% (verse_id(chapter, node["label"]), label, label))
|
||
elif kind == "noteref":
|
||
n = node["id"].replace("FN", "")
|
||
out.append('<a class="noteref" id="fnref%s_%s" href="#fn%s_%s">'
|
||
"<sup>%s</sup></a>" % (chapter, n, chapter, n,
|
||
esc(caller(node.get("marker")))))
|
||
elif kind == "br":
|
||
out.append("<br>")
|
||
elif kind == "ref":
|
||
# htmx does not scroll to a fragment, so a reference into another
|
||
# book has to be a real navigation for the browser to do it.
|
||
href = ref_href(node, book, chapter)
|
||
boost = "" if href.startswith("#") else ' hx-boost="false"'
|
||
out.append('<a class="ref" href="%s"%s>' % (href, boost))
|
||
render_inline(node.get("c", []), book, chapter, out)
|
||
out.append("</a>")
|
||
elif kind in INLINE_TAGS:
|
||
tag, cls = INLINE_TAGS[kind]
|
||
out.append('<%s class="%s">' % (tag, cls))
|
||
render_inline(node.get("c", []), book, chapter, out)
|
||
out.append("</%s>" % tag)
|
||
else:
|
||
render_inline(node.get("c", []), book, chapter, out)
|
||
|
||
def render_block(block, book, chapter, out):
|
||
btype = block["type"]
|
||
tag = BLOCK_TAGS.get(btype, "p")
|
||
classes = [block["style"]]
|
||
attrs = ""
|
||
if "indent" in block:
|
||
attrs += ' data-indent="%d"' % block["indent"]
|
||
if btype == "line":
|
||
classes.append("line")
|
||
out.append(" <%s class=\"%s\"%s>" % (tag, " ".join(classes), attrs))
|
||
render_inline(block.get("content", []), book, chapter, out)
|
||
out.append("</%s>\n" % tag)
|
||
|
||
TEXT_PRESENTATION = "\ufe0e"
|
||
EMOJI_CAPABLE = "\u2721"
|
||
|
||
def caller(marker):
|
||
return "".join(c + TEXT_PRESENTATION if c in EMOJI_CAPABLE else c
|
||
for c in (marker or "*"))
|
||
|
||
def render_notes(notes, chapter, book, out):
|
||
if not notes:
|
||
return
|
||
out.append(' <section class="footnotes">\n')
|
||
out.append(' <h4 class="footnotes-title">Notes</h4>\n')
|
||
out.append(" <ol>\n")
|
||
for note in notes:
|
||
n = (note["id"] or "").replace("FN", "")
|
||
out.append(' <li class="note" id="fn%s_%s" data-kind="%s">'
|
||
% (chapter, n, note["kind"]))
|
||
out.append('<span class="marker">%s</span> ' % esc(caller(note.get("marker"))))
|
||
if note.get("ref"):
|
||
out.append('<a class="backref" href="#fnref%s_%s">%s</a> '
|
||
% (chapter, n, esc(note["ref"])))
|
||
out.append('<span class="ft">')
|
||
render_inline(note["content"], book, chapter, out)
|
||
out.append("</span></li>\n")
|
||
out.append(" </ol>\n </section>\n")
|
||
|
||
def render_chapter(chapter, book, out):
|
||
num = chapter["number"]
|
||
out.append(' <section class="chapter" id="ch%d" data-chapter="%d">\n'
|
||
% (num, num))
|
||
out.append(' <h2 class="chapter-number">%d</h2>\n' % num)
|
||
|
||
group = None
|
||
|
||
def close_group():
|
||
nonlocal group
|
||
if group == "poetry":
|
||
out.append(" </div>\n")
|
||
elif group == "list":
|
||
out.append(" </ul>\n")
|
||
group = None
|
||
|
||
for block in chapter["blocks"]:
|
||
btype = block["type"]
|
||
if btype == "break":
|
||
close_group()
|
||
continue
|
||
want = {"line": "poetry", "item": "list"}.get(btype)
|
||
if want != group:
|
||
close_group()
|
||
if want == "poetry":
|
||
out.append(' <div class="poetry">\n')
|
||
elif want == "list":
|
||
out.append(' <ul class="list">\n')
|
||
group = want
|
||
render_block(block, book, num, out)
|
||
close_group()
|
||
|
||
render_notes(chapter["notes"], num, book, out)
|
||
out.append(" </section>\n")
|
||
|
||
def render_book(book, translation, out_dir):
|
||
code, name = book["code"], book["name"]
|
||
out = []
|
||
title = "%s — %s" % (name, translation["name"])
|
||
out.append("<title>%s</title>\n" % esc(title))
|
||
out.append('<article class="book" data-template="book" id="%s" data-book="%s" '
|
||
'data-name="%s" data-section="%s">\n'
|
||
% (code, code, esc(name), book["section"]))
|
||
|
||
out.append(' <header class="book-header">\n')
|
||
out.append(" <h1>")
|
||
if book["titles"]:
|
||
out.append(" ".join('<span class="%s">%s</span>'
|
||
% (part["style"], esc(part["text"]))
|
||
for part in book["titles"]))
|
||
else:
|
||
out.append(esc(name))
|
||
out.append("</h1>\n")
|
||
out.append(" </header>\n")
|
||
|
||
out.append(' <div class="chapters">\n')
|
||
for chapter in book["chapters"]:
|
||
render_chapter(chapter, code, out)
|
||
out.append(" </div>\n")
|
||
|
||
out.append(' <footer class="colophon"><p>%s. %s.</p></footer>\n'
|
||
% (esc(translation["name"]), esc(translation["license"])))
|
||
out.append("</article>\n")
|
||
|
||
path = os.path.join(out_dir, "%s.html" % code)
|
||
with open(path, "w", encoding="utf-8") as fh:
|
||
fh.write("".join(out))
|
||
return path
|
||
|
||
SECTION_LABELS = [
|
||
("ot", "Old Testament"),
|
||
("nt", "New Testament"),
|
||
("deuterocanon", "Deuterocanon"),
|
||
("other", "Other"),
|
||
]
|
||
|
||
def render_index(books, translation, out_dir):
|
||
out = ["<title>%s</title>\n" % esc(translation["name"]),
|
||
'<div class="index" data-template="index">\n']
|
||
for key, label in SECTION_LABELS:
|
||
group = [b for b in books if b["section"] == key]
|
||
if not group:
|
||
continue
|
||
out.append(' <section class="book-group" data-section="%s">\n' % key)
|
||
out.append(" <h2>%s</h2>\n <ul>\n" % esc(label))
|
||
for book in group:
|
||
out.append(' <li><a href="%s">%s</a> '
|
||
'<span class="count">%d</span></li>\n'
|
||
% (book["code"], esc(book["name"]), len(book["chapters"])))
|
||
out.append(" </ul>\n </section>\n")
|
||
out.append(' <footer class="colophon"><p>%s. %s.</p></footer>\n'
|
||
% (esc(translation["name"]), esc(translation["license"])))
|
||
out.append("</div>\n")
|
||
path = os.path.join(out_dir, "index.html")
|
||
with open(path, "w", encoding="utf-8") as fh:
|
||
fh.write("".join(out))
|
||
return path
|
||
|
||
VERSES_FILE = "verses.json"
|
||
|
||
def chapter_verses(chapter):
|
||
labels = []
|
||
|
||
def walk(nodes):
|
||
for node in nodes:
|
||
if node["t"] == "verse":
|
||
labels.append(node["label"])
|
||
elif "c" in node:
|
||
walk(node["c"])
|
||
|
||
for block in chapter["blocks"]:
|
||
walk(block.get("content", []))
|
||
return labels
|
||
|
||
def render_verses(books, out_dir):
|
||
out = {}
|
||
total = 0
|
||
for book in books:
|
||
numbers = [c["number"] for c in book["chapters"]]
|
||
if numbers != list(range(1, len(numbers) + 1)):
|
||
raise RuntimeError("%s: chapters are not 1..%d (%s)"
|
||
% (book["code"], len(numbers), numbers))
|
||
chapters = []
|
||
for chapter in book["chapters"]:
|
||
labels = chapter_verses(chapter)
|
||
plain = [str(i + 1) for i in range(len(labels))]
|
||
chapters.append(len(labels) if labels == plain else labels)
|
||
total += len(labels)
|
||
out[book["code"]] = chapters
|
||
|
||
path = os.path.join(out_dir, VERSES_FILE)
|
||
with open(path, "w", encoding="utf-8") as fh:
|
||
json.dump(out, fh, separators=(",", ":"), ensure_ascii=False)
|
||
fh.write("\n")
|
||
return path, total
|
||
|
||
def main(argv=None):
|
||
ap = argparse.ArgumentParser(description=__doc__,
|
||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||
ap.add_argument("source", nargs="?", default="source",
|
||
help="directory of eBible .htm files (default: source)")
|
||
ap.add_argument("-o", "--out", default="bible",
|
||
help="output directory (default: bible)")
|
||
args = ap.parse_args(argv)
|
||
|
||
data, skipped = extract(args.source)
|
||
os.makedirs(args.out, exist_ok=True)
|
||
|
||
total = 0
|
||
for book in data["books"]:
|
||
total += os.path.getsize(render_book(book, data["translation"], args.out))
|
||
render_index(data["books"], data["translation"], args.out)
|
||
verses, count = render_verses(data["books"], args.out)
|
||
|
||
chapters = sum(len(b["chapters"]) for b in data["books"])
|
||
notes = sum(len(c["notes"]) for b in data["books"] for c in b["chapters"])
|
||
print("read %s/" % args.source.rstrip("/"), file=sys.stderr)
|
||
print("books %d" % len(data["books"]), file=sys.stderr)
|
||
print("chapters %d" % chapters, file=sys.stderr)
|
||
print("notes %d" % notes, file=sys.stderr)
|
||
print("skipped %d (contents pages)" % len(skipped), file=sys.stderr)
|
||
print("html %.1f MB total, %.0f KB average"
|
||
% (total / 1e6, total / len(data["books"]) / 1e3), file=sys.stderr)
|
||
print("verses %d in %s (%.1f KB)"
|
||
% (count, os.path.basename(verses), os.path.getsize(verses) / 1e3),
|
||
file=sys.stderr)
|
||
print("wrote %s/" % args.out.rstrip("/"), file=sys.stderr)
|
||
return 0
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|