updated with functional changes, testing
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
[pytest]
|
||||||
|
pythonpath = src
|
||||||
|
testpaths = tests
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pytest
|
||||||
|
|||||||
+27
-32
@@ -1,50 +1,45 @@
|
|||||||
import os
|
import os
|
||||||
import argparse
|
import argparse
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from . import state
|
from .models import BuildContext
|
||||||
from .models import Book
|
from .parser import parse_book
|
||||||
from .parser import parseBook
|
|
||||||
from .writer import (
|
from .writer import (
|
||||||
writeStructure, writeMisc, writeStyles, writeNCX,
|
write_structure, write_misc, write_styles, write_ncx,
|
||||||
writeCover, writeContents, writeSections, writeIllustrations, writeEpub,
|
write_cover, write_contents, write_sections, write_illustrations, write_epub,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def parseArgs():
|
def parse_args(ctx: BuildContext) -> bool:
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("fileName", help="File to process")
|
parser.add_argument("fileName", help="File to process")
|
||||||
parser.add_argument("-f", "--force", help="Force overwrite an existing book directory", action="store_true")
|
parser.add_argument("-f", "--force", help="Force overwrite an existing book directory", action="store_true")
|
||||||
parser.add_argument("-v", "--verbose", help="Provide updates while processing", action="store_true")
|
parser.add_argument("-v", "--verbose", help="Provide updates while processing", action="store_true")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
state.book.filename = str(args.fileName)
|
ctx.book.filename = str(args.fileName)
|
||||||
if not os.path.isfile(state.book.filename):
|
if not os.path.isfile(ctx.book.filename):
|
||||||
print("File '{}' is not found. Exiting...".format(state.book.filename))
|
print("File '{}' is not found. Exiting...".format(ctx.book.filename))
|
||||||
exit()
|
exit()
|
||||||
state.verbose = args.verbose
|
ctx.verbose = args.verbose
|
||||||
return args.force
|
return args.force
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
state.book = Book()
|
ctx = BuildContext()
|
||||||
state.book.publishDate = (
|
ctx.book.publish_date = "{}-{}-{}".format(
|
||||||
str(datetime.now().year) + "-" +
|
datetime.now().year,
|
||||||
str(datetime.now().month) + "-" +
|
datetime.now().month,
|
||||||
str(datetime.now().day)
|
datetime.now().day,
|
||||||
)
|
)
|
||||||
state.inSection = False
|
overwrite = parse_args(ctx)
|
||||||
state.inStyle = False
|
parse_book(ctx)
|
||||||
state.verbose = False
|
if overwrite and ctx.verbose:
|
||||||
|
|
||||||
overwrite = parseArgs()
|
|
||||||
parseBook()
|
|
||||||
if overwrite and state.verbose:
|
|
||||||
print("Force flag, overwriting existing directory")
|
print("Force flag, overwriting existing directory")
|
||||||
writeStructure(overwrite)
|
write_structure(ctx, overwrite)
|
||||||
writeMisc()
|
write_misc(ctx)
|
||||||
writeStyles()
|
write_styles(ctx)
|
||||||
writeNCX()
|
write_ncx(ctx)
|
||||||
writeCover()
|
write_cover(ctx)
|
||||||
writeContents()
|
write_contents(ctx)
|
||||||
writeSections()
|
write_sections(ctx)
|
||||||
writeIllustrations()
|
write_illustrations(ctx)
|
||||||
writeEpub()
|
write_epub(ctx)
|
||||||
|
|||||||
+47
-36
@@ -1,49 +1,60 @@
|
|||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class Name:
|
class Name:
|
||||||
def __init__(self):
|
first: str = ""
|
||||||
self.first = ""
|
last: str = ""
|
||||||
self.last = ""
|
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class Series:
|
class Series:
|
||||||
def __init__(self):
|
number: str = ""
|
||||||
self.number = ""
|
name: str = ""
|
||||||
self.name = ""
|
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class Image:
|
class Image:
|
||||||
def __init__(self):
|
name: str = ""
|
||||||
self.name = ""
|
type: str = ""
|
||||||
self.type = ""
|
alt: str = ""
|
||||||
self.alt = ""
|
caption: str = ""
|
||||||
self.caption = ""
|
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class Section:
|
class Section:
|
||||||
def __init__(self):
|
type: str = ""
|
||||||
self.type = ""
|
name: str = ""
|
||||||
self.name = ""
|
title: str = ""
|
||||||
self.title = ""
|
body: str = ""
|
||||||
self.body = ""
|
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class Book:
|
class Book:
|
||||||
def __init__(self):
|
filename: str = ""
|
||||||
self.filename = ""
|
title: str = ""
|
||||||
self.title = ""
|
titlesort: str = ""
|
||||||
self.titlesort = ""
|
series: Series = field(default_factory=Series)
|
||||||
self.series = Series()
|
author: List[Name] = field(default_factory=list)
|
||||||
self.author = []
|
artist: Name = field(default_factory=Name)
|
||||||
self.artist = Name()
|
editor: Name = field(default_factory=Name)
|
||||||
self.editor = Name()
|
publisher: str = ""
|
||||||
self.publisher = ""
|
copyright: str = ""
|
||||||
self.copyright = ""
|
subjects: str = ""
|
||||||
self.subjects = ""
|
style: str = ""
|
||||||
self.style = ""
|
directory: str = ""
|
||||||
self.directory = ""
|
isbn: str = ""
|
||||||
self.isbn = ""
|
uuid: str = ""
|
||||||
self.uuid = ""
|
source: str = ""
|
||||||
self.source = ""
|
images: List[Image] = field(default_factory=list)
|
||||||
self.images = []
|
sections: List[Section] = field(default_factory=list)
|
||||||
self.sections = []
|
cover: Image = field(default_factory=Image)
|
||||||
self.cover = Image()
|
publish_date: str = ""
|
||||||
self.publishDate = ""
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BuildContext:
|
||||||
|
book: Book = field(default_factory=Book)
|
||||||
|
verbose: bool = False
|
||||||
|
in_section: bool = False
|
||||||
|
|||||||
+105
-123
@@ -1,174 +1,156 @@
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from . import state
|
from .models import Name, Image, Section, BuildContext
|
||||||
from .models import Name, Image, Section
|
|
||||||
|
|
||||||
|
|
||||||
def extract(line):
|
def extract(line: str) -> str | None:
|
||||||
if line.startswith("<!-- "):
|
if line.startswith("<!-- "):
|
||||||
return line[5:-4]
|
return line[5:-4]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def identify(line):
|
def identify(line: str, ctx: BuildContext) -> None:
|
||||||
key, value = extract(line).split(" ", 1)
|
key, value = extract(line).split(" ", 1)
|
||||||
match key:
|
match key:
|
||||||
case "Directory:":
|
case "Directory:":
|
||||||
state.inSection = False
|
ctx.in_section = False
|
||||||
state.book.directory = value.strip()
|
ctx.book.directory = value.strip()
|
||||||
case "Title:":
|
case "Title:":
|
||||||
state.inSection = False
|
ctx.in_section = False
|
||||||
state.book.title = value.strip()
|
ctx.book.title = value.strip()
|
||||||
if state.book.title.startswith("A "):
|
if ctx.book.title.startswith("A "):
|
||||||
state.book.titlesort = state.book.title[2:] + ", " + state.book.title[:1]
|
ctx.book.titlesort = ctx.book.title[2:] + ", " + ctx.book.title[:1]
|
||||||
if state.book.title.startswith("An "):
|
if ctx.book.title.startswith("An "):
|
||||||
state.book.titlesort = state.book.title[3:] + ", " + state.book.title[:2]
|
ctx.book.titlesort = ctx.book.title[3:] + ", " + ctx.book.title[:2]
|
||||||
if state.book.title.startswith("The "):
|
if ctx.book.title.startswith("The "):
|
||||||
state.book.titlesort = state.book.title[4:] + ", " + state.book.title[:3]
|
ctx.book.titlesort = ctx.book.title[4:] + ", " + ctx.book.title[:3]
|
||||||
case "Titlesort":
|
case "Titlesort":
|
||||||
state.inSection = False
|
ctx.in_section = False
|
||||||
state.book.titlesort = value.strip()
|
ctx.book.titlesort = value.strip()
|
||||||
case "Author:":
|
case "Author:":
|
||||||
state.inSection = False
|
ctx.in_section = False
|
||||||
last, first = value.strip().split(', ')
|
last, first = value.strip().split(", ")
|
||||||
name = Name()
|
ctx.book.author.append(Name(first=first, last=last))
|
||||||
name.first = first
|
|
||||||
name.last = last
|
|
||||||
state.book.author.append(name)
|
|
||||||
case "Editor:":
|
case "Editor:":
|
||||||
state.inSection = False
|
ctx.in_section = False
|
||||||
state.book.editor.last, state.book.editor.first = value.strip().split(', ')
|
ctx.book.editor.last, ctx.book.editor.first = value.strip().split(", ")
|
||||||
case "Artist:":
|
case "Artist:":
|
||||||
state.inSection = False
|
ctx.in_section = False
|
||||||
state.book.artist.last, state.book.artist.first = value.strip().split(', ')
|
ctx.book.artist.last, ctx.book.artist.first = value.strip().split(", ")
|
||||||
case "Publisher:":
|
case "Publisher:":
|
||||||
state.inSection = False
|
ctx.in_section = False
|
||||||
state.book.publisher = value.strip()
|
ctx.book.publisher = value.strip()
|
||||||
case "Copyright:":
|
case "Copyright:":
|
||||||
state.inSection = False
|
ctx.in_section = False
|
||||||
state.book.copyright = value.strip()
|
ctx.book.copyright = value.strip()
|
||||||
case "ISBN:":
|
case "ISBN:":
|
||||||
state.inSection = False
|
ctx.in_section = False
|
||||||
state.book.isbn = value.strip()
|
ctx.book.isbn = value.strip()
|
||||||
case "UUID:":
|
case "UUID:":
|
||||||
state.inSection = False
|
ctx.in_section = False
|
||||||
if value.strip() == "":
|
ctx.book.uuid = str(uuid4()) if value.strip() == "" else value.strip()
|
||||||
state.book.uuid = uuid4()
|
|
||||||
else:
|
|
||||||
state.book.uuid = value.strip()
|
|
||||||
case "Source:":
|
case "Source:":
|
||||||
state.inSection = False
|
ctx.in_section = False
|
||||||
state.book.source = value.strip()
|
ctx.book.source = value.strip()
|
||||||
case "Subjects:":
|
case "Subjects:":
|
||||||
state.inSection = False
|
ctx.in_section = False
|
||||||
state.book.subjects = value.strip()
|
ctx.book.subjects = value.strip()
|
||||||
case "Series:":
|
case "Series:":
|
||||||
state.inSection = False
|
ctx.in_section = False
|
||||||
state.book.series.number, state.book.series.name = value.strip().split(' ', 1)
|
ctx.book.series.number, ctx.book.series.name = value.strip().split(" ", 1)
|
||||||
case "Cover:":
|
case "Cover:":
|
||||||
state.inSection = False
|
ctx.in_section = False
|
||||||
state.book.cover.name, state.book.cover.type = value.strip().split('.')
|
ctx.book.cover.name, ctx.book.cover.type = value.strip().split(".")
|
||||||
case "TitlePage:":
|
case "TitlePage:":
|
||||||
state.inSection = True
|
ctx.in_section = True
|
||||||
section = Section()
|
section = Section(type="title")
|
||||||
section.type = "title"
|
section.name, section.title = value.strip().split(" ", 1)
|
||||||
section.name, section.title = value.strip().split(' ', 1)
|
ctx.book.sections.append(section)
|
||||||
state.book.sections.append(section)
|
|
||||||
case "CopyrightPage:":
|
case "CopyrightPage:":
|
||||||
state.inSection = True
|
ctx.in_section = True
|
||||||
section = Section()
|
section = Section(type="copyright-page")
|
||||||
section.type = "copyright-page"
|
section.name, section.title = value.strip().split(" ", 1)
|
||||||
section.name, section.title = value.strip().split(' ', 1)
|
ctx.book.sections.append(section)
|
||||||
state.book.sections.append(section)
|
|
||||||
case "Dedication:":
|
case "Dedication:":
|
||||||
state.inSection = True
|
ctx.in_section = True
|
||||||
section = Section()
|
section = Section(type="dedication")
|
||||||
section.type = "dedication"
|
section.name, section.title = value.strip().split(" ", 1)
|
||||||
section.name, section.title = value.strip().split(' ', 1)
|
ctx.book.sections.append(section)
|
||||||
state.book.sections.append(section)
|
|
||||||
case "Text:":
|
case "Text:":
|
||||||
state.inSection = True
|
ctx.in_section = True
|
||||||
section = Section()
|
section = Section(type="text")
|
||||||
section.type = "text"
|
section.name, section.title = value.strip().split(" ", 1)
|
||||||
section.name, section.title = value.strip().split(' ', 1)
|
ctx.book.sections.append(section)
|
||||||
state.book.sections.append(section)
|
|
||||||
case "Foreward:":
|
case "Foreward:":
|
||||||
state.inSection = True
|
ctx.in_section = True
|
||||||
section = Section()
|
section = Section(type="forward")
|
||||||
section.type = "forward"
|
section.name, section.title = value.strip().split(" ", 1)
|
||||||
section.name, section.title = value.strip().split(' ', 1)
|
ctx.book.sections.append(section)
|
||||||
state.book.sections.append(section)
|
|
||||||
case "Notes:":
|
case "Notes:":
|
||||||
state.inSection = True
|
ctx.in_section = True
|
||||||
section = Section()
|
section = Section(type="notes")
|
||||||
section.type = "notes"
|
section.name, section.title = value.strip().split(" ", 1)
|
||||||
section.name, section.title = value.strip().split(' ', 1)
|
ctx.book.sections.append(section)
|
||||||
state.book.sections.append(section)
|
|
||||||
case "License:":
|
case "License:":
|
||||||
state.inSection = True
|
ctx.in_section = True
|
||||||
section = Section()
|
section = Section(type="license")
|
||||||
section.type = "license"
|
section.name, section.title = value.strip().split(" ", 1)
|
||||||
section.name, section.title = value.strip().split(' ', 1)
|
ctx.book.sections.append(section)
|
||||||
state.book.sections.append(section)
|
|
||||||
case "Acknowledgement:":
|
case "Acknowledgement:":
|
||||||
state.inSection = True
|
ctx.in_section = True
|
||||||
section = Section()
|
section = Section(type="acknowledgement")
|
||||||
section.type = "acknowledgement"
|
section.name, section.title = value.strip().split(" ", 1)
|
||||||
section.name, section.title = value.strip().split(' ', 1)
|
ctx.book.sections.append(section)
|
||||||
state.book.sections.append(section)
|
|
||||||
case "TOC:":
|
case "TOC:":
|
||||||
state.inSection = True
|
ctx.in_section = True
|
||||||
section = Section()
|
section = Section(type="toc")
|
||||||
section.type = "toc"
|
section.name, section.title = value.strip().split(" ", 1)
|
||||||
section.name, section.title = value.strip().split(' ', 1)
|
ctx.book.sections.append(section)
|
||||||
state.book.sections.append(section)
|
|
||||||
case "Image:":
|
case "Image:":
|
||||||
image = Image()
|
image = Image()
|
||||||
image.name, imageText = value.strip().split(' ', 1)
|
image.name, image_text = value.strip().split(" ", 1)
|
||||||
extension = image.name.strip().split('.')[1]
|
extension = image.name.strip().split(".")[1]
|
||||||
match extension:
|
match extension:
|
||||||
case "jpg":
|
case "jpg" | "jpeg":
|
||||||
image.type = "jpeg"
|
|
||||||
case "jpeg":
|
|
||||||
image.type = "jpeg"
|
image.type = "jpeg"
|
||||||
case "png":
|
case "png":
|
||||||
image.type = "png"
|
image.type = "png"
|
||||||
image.alt, image.caption = imageText.strip().split('::')
|
image.alt, image.caption = image_text.strip().split("::")
|
||||||
state.book.images.append(image)
|
ctx.book.images.append(image)
|
||||||
if not image.caption:
|
if not image.caption:
|
||||||
imageCode = '<img src="../Images/' + image.name + '" alt="' + image.alt + '"/>\n'
|
image_code = '<img src="../Images/' + image.name + '" alt="' + image.alt + '"/>\n'
|
||||||
imageCode += ' <a id="' + image.name.split('.')[0] + '"><!-- Anchor --></a>\n'
|
image_code += ' <a id="' + image.name.split(".")[0] + '"><!-- Anchor --></a>\n'
|
||||||
else:
|
else:
|
||||||
imageCode = '<div class="center group">\n'
|
image_code = '<div class="center group">\n'
|
||||||
imageCode += ' <img src="../Images/' + image.name + '" alt="' + image.alt + '" />\n'
|
image_code += ' <img src="../Images/' + image.name + '" alt="' + image.alt + '" />\n'
|
||||||
imageCode += ' <a id="' + image.name.split('.')[0] + '"><!-- Anchor --></a>\n'
|
image_code += ' <a id="' + image.name.split(".")[0] + '"><!-- Anchor --></a>\n'
|
||||||
imageCode += ' <p class="caption">' + image.caption + '</p>\n'
|
image_code += ' <p class="caption">' + image.caption + "</p>\n"
|
||||||
imageCode += '</div>\n<br />\n'
|
image_code += "</div>\n<br />\n"
|
||||||
state.book.sections[-1].body += imageCode
|
ctx.book.sections[-1].body += image_code
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def findStyle(line):
|
def find_style(line: str, ctx: BuildContext) -> None:
|
||||||
stylesheetFile = line.split(' ')[2].split('=')[1].replace('"', '').replace('>', '').strip()
|
stylesheet_file = line.split(" ")[2].split("=")[1].replace('"', "").replace(">", "").strip()
|
||||||
contents = ""
|
contents = ""
|
||||||
with open(stylesheetFile) as file:
|
with open(stylesheet_file) as f:
|
||||||
for line in file:
|
for line in f:
|
||||||
if line != '<style>\n' and line != '</style>' and not line.strip().startswith("/*"):
|
if line != "<style>\n" and line != "</style>" and not line.strip().startswith("/*"):
|
||||||
contents += line
|
contents += line
|
||||||
state.book.style = contents
|
ctx.book.style = contents
|
||||||
print("found style")
|
print("found style")
|
||||||
|
|
||||||
|
|
||||||
def parseBook():
|
def parse_book(ctx: BuildContext) -> None:
|
||||||
filename = state.book.filename
|
if ctx.verbose:
|
||||||
if state.verbose:
|
print("Parsing {}".format(ctx.book.filename))
|
||||||
print("Parsing {}".format(filename))
|
with open(ctx.book.filename) as source:
|
||||||
with open(filename) as source:
|
|
||||||
for line in source:
|
for line in source:
|
||||||
if line.startswith('<!-- '):
|
if line.startswith("<!-- "):
|
||||||
identify(line)
|
identify(line, ctx)
|
||||||
elif line.startswith('<link rel="stylesheet"'):
|
elif line.startswith('<link rel="stylesheet"'):
|
||||||
findStyle(line)
|
find_style(line, ctx)
|
||||||
elif state.inSection:
|
elif ctx.in_section:
|
||||||
state.book.sections[-1].body += line
|
ctx.book.sections[-1].body += line
|
||||||
if state.verbose:
|
if ctx.verbose:
|
||||||
print("Parse complete for {}".format(state.book.title))
|
print("Parse complete for {}".format(ctx.book.title))
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
book = None
|
|
||||||
inSection = False
|
|
||||||
inStyle = False
|
|
||||||
verbose = False
|
|
||||||
+185
-186
@@ -1,202 +1,202 @@
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from shutil import rmtree, copy
|
from shutil import rmtree, copy
|
||||||
from . import state
|
from .models import BuildContext
|
||||||
|
|
||||||
|
|
||||||
def writeStructure(overwrite):
|
def write_structure(ctx: BuildContext, overwrite: bool) -> None:
|
||||||
mode = 0o775
|
mode = 0o775
|
||||||
|
|
||||||
if os.path.isdir(state.book.directory):
|
if os.path.isdir(ctx.book.directory):
|
||||||
if overwrite:
|
if overwrite:
|
||||||
rmtree(state.book.directory)
|
rmtree(ctx.book.directory)
|
||||||
else:
|
else:
|
||||||
print("writeStructure: Directory exists.")
|
print("write_structure: Directory exists.")
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
os.mkdir(state.book.directory, mode)
|
os.mkdir(ctx.book.directory, mode)
|
||||||
|
|
||||||
for directory in ["META-INF", "OEBPS", "OEBPS/Images", "OEBPS/Styles", "OEBPS/Text"]:
|
for directory in ["META-INF", "OEBPS", "OEBPS/Images", "OEBPS/Styles", "OEBPS/Text"]:
|
||||||
path = os.path.join(state.book.directory, directory)
|
os.mkdir(os.path.join(ctx.book.directory, directory), mode)
|
||||||
os.mkdir(path, mode)
|
|
||||||
if state.verbose:
|
if ctx.verbose:
|
||||||
print("Finished writing ePub structure")
|
print("Finished writing ePub structure")
|
||||||
|
|
||||||
|
|
||||||
def writeMisc():
|
def write_misc(ctx: BuildContext) -> None:
|
||||||
access = 'w'
|
directory = ctx.book.directory
|
||||||
directory = state.book.directory
|
|
||||||
|
|
||||||
file = os.path.join(state.book.directory, "mimetype")
|
with open(os.path.join(directory, "mimetype"), "w") as f:
|
||||||
mimetype = open(file, access)
|
f.write("application/epub+zip")
|
||||||
mimetype.write("application/epub+zip")
|
|
||||||
mimetype.close()
|
|
||||||
|
|
||||||
file = os.path.join(directory, "META-INF", "com.apple.ibooks.display-options.xml")
|
with open(os.path.join(directory, "META-INF", "com.apple.ibooks.display-options.xml"), "w") as f:
|
||||||
meta1 = open(file, access)
|
f.write(
|
||||||
meta1.write('<?xml version="1.0" encoding="UTF-8"?>\n<display_options>\n <platform name="*">\n <option name="specified-fonts">false</option>\n </platform>\n</display_options>')
|
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||||
meta1.close()
|
"<display_options>\n"
|
||||||
|
' <platform name="*">\n'
|
||||||
|
' <option name="specified-fonts">false</option>\n'
|
||||||
|
" </platform>\n"
|
||||||
|
"</display_options>"
|
||||||
|
)
|
||||||
|
|
||||||
file = os.path.join(directory, "META-INF", "container.xml")
|
with open(os.path.join(directory, "META-INF", "container.xml"), "w") as f:
|
||||||
meta2 = open(file, access)
|
f.write(
|
||||||
meta2.write('<?xml version="1.0" encoding="UTF-8"?>\n<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">\n <rootfiles>\n <rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>\n </rootfiles>\n</container>')
|
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||||
meta2.close()
|
'<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">\n'
|
||||||
if state.verbose:
|
" <rootfiles>\n"
|
||||||
|
' <rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>\n'
|
||||||
|
" </rootfiles>\n"
|
||||||
|
"</container>"
|
||||||
|
)
|
||||||
|
|
||||||
|
if ctx.verbose:
|
||||||
print("Finished writing metadata")
|
print("Finished writing metadata")
|
||||||
|
|
||||||
|
|
||||||
def writeStyles():
|
def write_styles(ctx: BuildContext) -> None:
|
||||||
access = 'w'
|
with open(os.path.join(ctx.book.directory, "OEBPS", "Styles", "style.css"), "w") as f:
|
||||||
file = os.path.join(state.book.directory, "OEBPS", "Styles", "style.css")
|
f.write(ctx.book.style)
|
||||||
style = open(file, access)
|
|
||||||
style.write(state.book.style)
|
|
||||||
style.close()
|
|
||||||
|
|
||||||
source_dir = os.path.split(state.book.filename)[0]
|
source_dir = os.path.split(ctx.book.filename)[0]
|
||||||
styles_dir = os.path.join(state.book.directory, "OEBPS", "Styles")
|
styles_dir = os.path.join(ctx.book.directory, "OEBPS", "Styles")
|
||||||
for font in ["Inconsolata-Regular.otf", "invisible1.ttf", "MgOpenModataRegular.ttf"]:
|
for font in ["Inconsolata-Regular.otf", "invisible1.ttf", "MgOpenModataRegular.ttf"]:
|
||||||
copy(os.path.join(source_dir, "Images", font), os.path.join(styles_dir, font))
|
copy(os.path.join(source_dir, "Images", font), os.path.join(styles_dir, font))
|
||||||
|
|
||||||
if state.verbose:
|
if ctx.verbose:
|
||||||
print("Finished writing style")
|
print("Finished writing style")
|
||||||
|
|
||||||
|
|
||||||
def writeNCX():
|
def write_ncx(ctx: BuildContext) -> None:
|
||||||
directory = state.book.directory
|
|
||||||
access = 'w'
|
|
||||||
file = os.path.join(directory, 'OEBPS', 'toc.ncx')
|
|
||||||
navpoints = []
|
navpoints = []
|
||||||
|
|
||||||
contentsstart = (
|
|
||||||
'<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n'
|
|
||||||
'<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">\n'
|
|
||||||
'<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">\n'
|
|
||||||
' <head>\n'
|
|
||||||
' <meta content="urn:isbn:' + str(state.book.uuid) + '" name="dtb:uid"/>\n'
|
|
||||||
' <meta content="1" name="dtb:depth"/>\n'
|
|
||||||
' <meta content="-1" name="dtb:totalPageCount"/>\n'
|
|
||||||
' <meta content="-1" name="dtb:maxPageNumber"/>\n'
|
|
||||||
' </head>\n'
|
|
||||||
' <docTitle>\n'
|
|
||||||
' <text>' + state.book.title.strip() + '</text>\n'
|
|
||||||
' </docTitle>\n'
|
|
||||||
' <navMap>\n'
|
|
||||||
' <navPoint id="cover" playOrder="1">\n'
|
|
||||||
' <navLabel>\n'
|
|
||||||
' <text>Cover</text>\n'
|
|
||||||
' </navLabel>\n'
|
|
||||||
' <content src="Text/cover.xhtml"/>\n'
|
|
||||||
' </navPoint>\n'
|
|
||||||
)
|
|
||||||
contentsend = " </navMap>\n</ncx>"
|
|
||||||
|
|
||||||
number = 2
|
number = 2
|
||||||
for section in state.book.sections:
|
for section in ctx.book.sections:
|
||||||
if section.type != "toc":
|
if section.type != "toc":
|
||||||
navpoints.append(
|
navpoints.append(
|
||||||
'\t<navPoint id ="' + section.name.strip() + '" playOrder="' + str(number) + '">\n'
|
'\t<navPoint id ="' + section.name.strip() + '" playOrder="' + str(number) + '">\n'
|
||||||
'\t\t<navLabel>\n\t\t\t<text>' + section.title.strip() + '</text>\n\t\t</navLabel>\n'
|
"\t\t<navLabel>\n\t\t\t<text>" + section.title.strip() + "</text>\n\t\t</navLabel>\n"
|
||||||
'\t\t<content src="Text/' + section.name.strip() + '.xhtml"/>\n\t</navPoint>\n'
|
'\t\t<content src="Text/' + section.name.strip() + '.xhtml"/>\n\t</navPoint>\n'
|
||||||
)
|
)
|
||||||
number += 1
|
number += 1
|
||||||
|
|
||||||
contents = contentsstart + "".join(navpoints) + contentsend
|
contents = (
|
||||||
|
'<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n'
|
||||||
|
'<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">\n'
|
||||||
|
'<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">\n'
|
||||||
|
" <head>\n"
|
||||||
|
' <meta content="urn:isbn:' + str(ctx.book.uuid) + '" name="dtb:uid"/>\n'
|
||||||
|
' <meta content="1" name="dtb:depth"/>\n'
|
||||||
|
' <meta content="-1" name="dtb:totalPageCount"/>\n'
|
||||||
|
' <meta content="-1" name="dtb:maxPageNumber"/>\n'
|
||||||
|
" </head>\n"
|
||||||
|
" <docTitle>\n"
|
||||||
|
" <text>" + ctx.book.title.strip() + "</text>\n"
|
||||||
|
" </docTitle>\n"
|
||||||
|
" <navMap>\n"
|
||||||
|
' <navPoint id="cover" playOrder="1">\n'
|
||||||
|
" <navLabel>\n"
|
||||||
|
" <text>Cover</text>\n"
|
||||||
|
" </navLabel>\n"
|
||||||
|
' <content src="Text/cover.xhtml"/>\n'
|
||||||
|
" </navPoint>\n"
|
||||||
|
+ "".join(navpoints)
|
||||||
|
+ " </navMap>\n</ncx>"
|
||||||
|
)
|
||||||
|
|
||||||
ncx = open(file, access)
|
with open(os.path.join(ctx.book.directory, "OEBPS", "toc.ncx"), "w") as f:
|
||||||
ncx.write(contents)
|
f.write(contents)
|
||||||
ncx.close()
|
|
||||||
if state.verbose:
|
if ctx.verbose:
|
||||||
print("Finished writing ncx")
|
print("Finished writing ncx")
|
||||||
|
|
||||||
|
|
||||||
def writeTOC(section):
|
def write_toc(ctx: BuildContext, section) -> None:
|
||||||
access = 'w'
|
|
||||||
file = os.path.join(state.book.directory, 'OEBPS', 'toc.xhtml')
|
|
||||||
contents = (
|
contents = (
|
||||||
'<?xml version="1.0" encoding="utf-8"?>\n'
|
'<?xml version="1.0" encoding="utf-8"?>\n'
|
||||||
'<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">\n'
|
'<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">\n'
|
||||||
' <head>\n'
|
" <head>\n"
|
||||||
' <title>Table of Contents</title>\n'
|
" <title>Table of Contents</title>\n"
|
||||||
' <link href="Styles/style.css" rel="stylesheet" type="text/css" />\n'
|
' <link href="Styles/style.css" rel="stylesheet" type="text/css" />\n'
|
||||||
'</head>\n'
|
"</head>\n"
|
||||||
'<body>\n'
|
"<body>\n"
|
||||||
|
+ section.body
|
||||||
|
+ "\n</body>\n</html>"
|
||||||
)
|
)
|
||||||
contents += section.body
|
|
||||||
contents += '\n</body>\n</html>'
|
with open(os.path.join(ctx.book.directory, "OEBPS", "toc.xhtml"), "w") as f:
|
||||||
toc = open(file, access)
|
f.write(contents)
|
||||||
toc.write(contents)
|
|
||||||
toc.close()
|
if ctx.verbose:
|
||||||
if state.verbose:
|
|
||||||
print("Finished writing toc")
|
print("Finished writing toc")
|
||||||
|
|
||||||
|
|
||||||
def writeCover():
|
def write_cover(ctx: BuildContext) -> None:
|
||||||
access = 'w'
|
|
||||||
file = os.path.join(state.book.directory, 'OEBPS', 'Text', 'cover.xhtml')
|
|
||||||
contents = (
|
contents = (
|
||||||
'<?xml version="1.0" encoding="utf-8" standalone="no"?>\n'
|
'<?xml version="1.0" encoding="utf-8" standalone="no"?>\n'
|
||||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"\n'
|
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"\n'
|
||||||
'"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n\n'
|
'"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n\n'
|
||||||
'<html xmlns="http://www.w3.org/1999/xhtml">\n'
|
'<html xmlns="http://www.w3.org/1999/xhtml">\n'
|
||||||
' <head>\n'
|
" <head>\n"
|
||||||
' <title>Cover</title>\n'
|
" <title>Cover</title>\n"
|
||||||
' <style type="text/css">\n'
|
' <style type="text/css">\n'
|
||||||
' /* <![CDATA[ */\n'
|
" /* <![CDATA[ */\n"
|
||||||
' img { max-width: 100%; }\n'
|
" img { max-width: 100%; }\n"
|
||||||
' body { oeb-column-number: 1; }\n'
|
" body { oeb-column-number: 1; }\n"
|
||||||
' #cover-image { text-align: center; }\n'
|
" #cover-image { text-align: center; }\n"
|
||||||
' /* ]]> */\n'
|
" /* ]]> */\n"
|
||||||
' </style>\n'
|
" </style>\n"
|
||||||
' </head>\n'
|
" </head>\n"
|
||||||
' <body>\n'
|
" <body>\n"
|
||||||
' <div id="cover-image">\n'
|
' <div id="cover-image">\n'
|
||||||
' <img alt="' + state.book.title.strip() + '" src="../Images/cover.jpeg" />\n'
|
' <img alt="' + ctx.book.title.strip() + '" src="../Images/cover.jpeg" />\n'
|
||||||
' </div>\n'
|
" </div>\n"
|
||||||
' </body>\n'
|
" </body>\n"
|
||||||
'</html>\n'
|
"</html>\n"
|
||||||
)
|
)
|
||||||
cover = open(file, access)
|
|
||||||
cover.write(contents)
|
|
||||||
cover.close()
|
|
||||||
|
|
||||||
new = os.path.join(state.book.directory, "OEBPS", "Images", "cover.jpeg")
|
with open(os.path.join(ctx.book.directory, "OEBPS", "Text", "cover.xhtml"), "w") as f:
|
||||||
old = os.path.join(os.path.split(state.book.filename)[0], "Images", "cover.jpeg")
|
f.write(contents)
|
||||||
copy(old, new)
|
|
||||||
if state.verbose:
|
source_dir = os.path.split(ctx.book.filename)[0]
|
||||||
|
copy(
|
||||||
|
os.path.join(source_dir, "Images", "cover.jpeg"),
|
||||||
|
os.path.join(ctx.book.directory, "OEBPS", "Images", "cover.jpeg"),
|
||||||
|
)
|
||||||
|
|
||||||
|
if ctx.verbose:
|
||||||
print("Finished writing cover")
|
print("Finished writing cover")
|
||||||
|
|
||||||
|
|
||||||
def writeContents():
|
def write_contents(ctx: BuildContext) -> None:
|
||||||
contents = '<?xml version="1.0" encoding="UTF-8"?>\n'
|
contents = '<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||||
contents += '<package xmlns="http://www.idpf.org/2007/opf" version="3.0" xml:lang="en" unique-identifier="bookid" prefix="rendition: http://www.idpf.org/vocab/rendition/# cc: http://creativecommons.org/ns#">\n'
|
contents += '<package xmlns="http://www.idpf.org/2007/opf" version="3.0" xml:lang="en" unique-identifier="bookid" prefix="rendition: http://www.idpf.org/vocab/rendition/# cc: http://creativecommons.org/ns#">\n'
|
||||||
contents += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/">\n'
|
contents += ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/">\n'
|
||||||
contents += ' <dc:identifier id="bookid">urn:uuid' + state.book.isbn + '</dc:identifier>\n'
|
contents += ' <dc:identifier id="bookid">urn:uuid' + ctx.book.isbn + "</dc:identifier>\n"
|
||||||
contents += ' <dc:title id="pub-title">' + state.book.title.strip() + '</dc:title>\n'
|
contents += ' <dc:title id="pub-title">' + ctx.book.title.strip() + "</dc:title>\n"
|
||||||
contents += ' <meta refines="#pub-title" property="title-type">main</meta>\n'
|
contents += ' <meta refines="#pub-title" property="title-type">main</meta>\n'
|
||||||
if state.book.titlesort != "":
|
if ctx.book.titlesort:
|
||||||
contents += ' <meta refines="#pub-title" property="file-as">' + state.book.titlesort + '</meta>\n'
|
contents += ' <meta refines="#pub-title" property="file-as">' + ctx.book.titlesort + "</meta>\n"
|
||||||
if state.book.series.name != "":
|
if ctx.book.series.name:
|
||||||
print("found a book series")
|
print("found a book series")
|
||||||
contents += ' <dc:title id="pub-title2">' + state.book.series.name.strip() + '</dc:title>\n'
|
contents += ' <dc:title id="pub-title2">' + ctx.book.series.name.strip() + "</dc:title>\n"
|
||||||
contents += ' <meta refines="#pub-title2" property="title-type">collection</meta>\n'
|
contents += ' <meta refines="#pub-title2" property="title-type">collection</meta>\n'
|
||||||
contents += ' <dc:title id="pub-title3">' + state.book.series.number + '</dc:title>\n'
|
contents += ' <dc:title id="pub-title3">' + ctx.book.series.number + "</dc:title>\n"
|
||||||
contents += ' <meta refines="#pub-title3" property="title-type">display-seq</meta>\n'
|
contents += ' <meta refines="#pub-title3" property="title-type">display-seq</meta>\n'
|
||||||
contents += ' <meta name="calibre:series" content="' + state.book.series.name.strip() + '" />\n'
|
contents += ' <meta name="calibre:series" content="' + ctx.book.series.name.strip() + '" />\n'
|
||||||
contents += ' <meta name="calibre:series_index" content="' + state.book.series.number.strip() + '" />\n'
|
contents += ' <meta name="calibre:series_index" content="' + ctx.book.series.number.strip() + '" />\n'
|
||||||
contents += ' <dc:language id="pub-language">en</dc:language>\n'
|
contents += ' <dc:language id="pub-language">en</dc:language>\n'
|
||||||
contents += ' <dc:date>' + state.book.publishDate + '</dc:date>\n'
|
contents += " <dc:date>" + ctx.book.publish_date + "</dc:date>\n"
|
||||||
for name in state.book.author:
|
for name in ctx.book.author:
|
||||||
contents += ' <dc:creator id="author">' + name.first.strip() + ' ' + name.last.strip() + '</dc:creator>\n'
|
contents += ' <dc:creator id="author">' + name.first.strip() + " " + name.last.strip() + "</dc:creator>\n"
|
||||||
contents += ' <meta refines="#author" scheme="marc:relators" property="role">aut</meta>\n'
|
contents += ' <meta refines="#author" scheme="marc:relators" property="role">aut</meta>\n'
|
||||||
for name in state.book.author:
|
for name in ctx.book.author:
|
||||||
contents += ' <meta refines="#author" property="file-as">' + name.last.strip() + ', ' + name.first.strip() + '</meta>\n'
|
contents += ' <meta refines="#author" property="file-as">' + name.last.strip() + ", " + name.first.strip() + "</meta>\n"
|
||||||
contents += ' <dc:publisher>' + state.book.publisher.strip() + '</dc:publisher>\n'
|
contents += " <dc:publisher>" + ctx.book.publisher.strip() + "</dc:publisher>\n"
|
||||||
contents += ' <dc:rights>' + state.book.copyright.strip() + '</dc:rights>\n'
|
contents += " <dc:rights>" + ctx.book.copyright.strip() + "</dc:rights>\n"
|
||||||
contents += ' <dc:subject>' + state.book.subjects.strip() + '</dc:subject>\n'
|
contents += " <dc:subject>" + ctx.book.subjects.strip() + "</dc:subject>\n"
|
||||||
contents += ' <dc:source>' + state.book.source.strip() + '</dc:source>\n'
|
contents += " <dc:source>" + ctx.book.source.strip() + "</dc:source>\n"
|
||||||
contents += ' <meta name="cover" content="cover.jpeg" />\n'
|
contents += ' <meta name="cover" content="cover.jpeg" />\n'
|
||||||
contents += ' </metadata>\n'
|
contents += " </metadata>\n"
|
||||||
contents += ' <manifest>\n'
|
contents += " <manifest>\n"
|
||||||
contents += ' <item href="Images/cover.jpeg" id="cover.jpeg" media-type="image/jpeg" properties="cover-image" />\n'
|
contents += ' <item href="Images/cover.jpeg" id="cover.jpeg" media-type="image/jpeg" properties="cover-image" />\n'
|
||||||
contents += ' <item href="Text/cover.xhtml" id="cover" media-type="application/xhtml+xml" />\n'
|
contents += ' <item href="Text/cover.xhtml" id="cover" media-type="application/xhtml+xml" />\n'
|
||||||
contents += ' <item href="toc.xhtml" id="toc" media-type="application/xhtml+xml" properties="nav"/>\n'
|
contents += ' <item href="toc.xhtml" id="toc" media-type="application/xhtml+xml" properties="nav"/>\n'
|
||||||
@@ -206,107 +206,106 @@ def writeContents():
|
|||||||
contents += ' <item href="Styles/invisible1.ttf" id="invisible1" media-type="application/vnd.ms-opentype" />\n'
|
contents += ' <item href="Styles/invisible1.ttf" id="invisible1" media-type="application/vnd.ms-opentype" />\n'
|
||||||
contents += ' <item href="Styles/Inconsolata-Regular.otf" id="inconsolate" media-type="application/vnd.ms-opentype" />\n'
|
contents += ' <item href="Styles/Inconsolata-Regular.otf" id="inconsolate" media-type="application/vnd.ms-opentype" />\n'
|
||||||
|
|
||||||
if len(state.book.images) > 0:
|
if ctx.book.images:
|
||||||
contents += ' <item href="Text/illustrations.xhtml" id="illustrations" media-type="application/xhtml+xml" />\n'
|
contents += ' <item href="Text/illustrations.xhtml" id="illustrations" media-type="application/xhtml+xml" />\n'
|
||||||
|
|
||||||
for section in state.book.sections:
|
for section in ctx.book.sections:
|
||||||
if section.type != 'toc':
|
if section.type != "toc":
|
||||||
contents += ' <item href="Text/' + section.name + '.xhtml" id="' + section.name + '" media-type="application/xhtml+xml" />\n'
|
contents += ' <item href="Text/' + section.name + '.xhtml" id="' + section.name + '" media-type="application/xhtml+xml" />\n'
|
||||||
|
|
||||||
for image in state.book.images:
|
source_dir = os.path.split(ctx.book.filename)[0]
|
||||||
contents += ' <item href="Images/' + image.name + '" id="' + image.name.split('.')[0] + '" media-type="image/' + image.type + '" />\n'
|
for image in ctx.book.images:
|
||||||
new = os.path.join(state.book.directory, "OEBPS", "Images", image.name)
|
contents += ' <item href="Images/' + image.name + '" id="' + image.name.split(".")[0] + '" media-type="image/' + image.type + '" />\n'
|
||||||
old = os.path.join(os.path.split(state.book.filename)[0], "Images", image.name)
|
copy(
|
||||||
copy(old, new)
|
os.path.join(source_dir, "Images", image.name),
|
||||||
|
os.path.join(ctx.book.directory, "OEBPS", "Images", image.name),
|
||||||
|
)
|
||||||
|
|
||||||
contents += ' </manifest>\n'
|
contents += " </manifest>\n"
|
||||||
contents += ' <spine toc="ncx">\n'
|
contents += ' <spine toc="ncx">\n'
|
||||||
contents += ' <itemref idref="cover" />\n'
|
contents += ' <itemref idref="cover" />\n'
|
||||||
if len(state.book.images) > 0:
|
if ctx.book.images:
|
||||||
contents += ' <itemref idref="illustrations" />\n'
|
contents += ' <itemref idref="illustrations" />\n'
|
||||||
for section in state.book.sections:
|
for section in ctx.book.sections:
|
||||||
contents += ' <itemref idref="' + section.name + '" />\n'
|
contents += ' <itemref idref="' + section.name + '" />\n'
|
||||||
contents += ' </spine>\n'
|
contents += " </spine>\n"
|
||||||
contents += ' <guide>\n'
|
contents += " <guide>\n"
|
||||||
contents += ' <reference href="Text/cover.xhtml" title="Cover" type="cover" />\n'
|
contents += ' <reference href="Text/cover.xhtml" title="Cover" type="cover" />\n'
|
||||||
contents += ' <reference href="toc.xhtml" title="Table of Contents" type="toc" />\n'
|
contents += ' <reference href="toc.xhtml" title="Table of Contents" type="toc" />\n'
|
||||||
if len(state.book.images) > 0:
|
if ctx.book.images:
|
||||||
contents += ' <reference href="Text/illustrations.xhtml" title="Illustrations" type="text" />\n'
|
contents += ' <reference href="Text/illustrations.xhtml" title="Illustrations" type="text" />\n'
|
||||||
for section in state.book.sections:
|
for section in ctx.book.sections:
|
||||||
if section.type != 'toc':
|
if section.type != "toc":
|
||||||
contents += ' <reference href="Text/' + section.name.strip() + '.xhtml" title="' + section.title.strip() + '" type="' + section.type.strip() + '" />\n'
|
contents += ' <reference href="Text/' + section.name.strip() + '.xhtml" title="' + section.title.strip() + '" type="' + section.type.strip() + '" />\n'
|
||||||
contents += ' </guide>\n'
|
contents += " </guide>\n"
|
||||||
contents += '</package>\n'
|
contents += "</package>\n"
|
||||||
|
|
||||||
access = 'w'
|
with open(os.path.join(ctx.book.directory, "OEBPS", "content.opf"), "w") as f:
|
||||||
file = os.path.join(state.book.directory, 'OEBPS', 'content.opf')
|
f.write(contents)
|
||||||
content = open(file, access)
|
|
||||||
content.write(contents)
|
if ctx.verbose:
|
||||||
content.close()
|
|
||||||
if state.verbose:
|
|
||||||
print("Finished writing content")
|
print("Finished writing content")
|
||||||
|
|
||||||
|
|
||||||
def writeSections():
|
def write_sections(ctx: BuildContext) -> None:
|
||||||
access = 'w'
|
for section in ctx.book.sections:
|
||||||
for section in state.book.sections:
|
|
||||||
if section.type.strip() == "toc":
|
if section.type.strip() == "toc":
|
||||||
writeTOC(section)
|
write_toc(ctx, section)
|
||||||
continue
|
continue
|
||||||
contents = (
|
contents = (
|
||||||
'<?xml version="1.0" encoding="utf-8" standalone="no"?>\n'
|
'<?xml version="1.0" encoding="utf-8" standalone="no"?>\n'
|
||||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"\n'
|
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"\n'
|
||||||
'"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n\n'
|
'"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n\n'
|
||||||
'<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">\n'
|
'<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">\n'
|
||||||
' <head>\n'
|
" <head>\n"
|
||||||
' <title>' + section.title.strip() + '</title>\n'
|
" <title>" + section.title.strip() + "</title>\n"
|
||||||
' <link href="../Styles/style.css" rel="stylesheet" type="text/css" />\n'
|
' <link href="../Styles/style.css" rel="stylesheet" type="text/css" />\n'
|
||||||
' </head>\n'
|
" </head>\n"
|
||||||
' <body>\n'
|
" <body>\n"
|
||||||
' <div id="' + section.name.strip() + '" xml:lang="en-US">\n'
|
' <div id="' + section.name.strip() + '" xml:lang="en-US">\n'
|
||||||
|
+ section.body
|
||||||
|
+ " </div>\n </body>\n</html>\n"
|
||||||
)
|
)
|
||||||
contents += section.body
|
with open(os.path.join(ctx.book.directory, "OEBPS", "Text", section.name.strip() + ".xhtml"), "w") as f:
|
||||||
contents += " </div>\n </body>\n</html>\n"
|
f.write(contents)
|
||||||
file = os.path.join(state.book.directory, 'OEBPS', 'Text', section.name.strip() + ".xhtml")
|
if ctx.verbose:
|
||||||
content = open(file, access)
|
|
||||||
content.write(contents)
|
|
||||||
content.close()
|
|
||||||
if state.verbose:
|
|
||||||
print(f"Finished writing section {section.name}.xhtml")
|
print(f"Finished writing section {section.name}.xhtml")
|
||||||
|
|
||||||
|
|
||||||
def writeIllustrations():
|
def write_illustrations(ctx: BuildContext) -> None:
|
||||||
if len(state.book.images) == 0:
|
if not ctx.book.images:
|
||||||
return
|
return
|
||||||
|
|
||||||
access = 'w'
|
|
||||||
contents = (
|
contents = (
|
||||||
'<?xml version="1.0" encoding="utf-8" standalone="no"?>\n'
|
'<?xml version="1.0" encoding="utf-8" standalone="no"?>\n'
|
||||||
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"\n'
|
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"\n'
|
||||||
'"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n\n'
|
'"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n\n'
|
||||||
'<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">\n'
|
'<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">\n'
|
||||||
'<head>\n'
|
"<head>\n"
|
||||||
'<title>Illustrations</title>\n'
|
"<title>Illustrations</title>\n"
|
||||||
'<link href="../Styles/style.css" rel="stylesheet" type="text/css" />\n'
|
'<link href="../Styles/style.css" rel="stylesheet" type="text/css" />\n'
|
||||||
'</head>\n'
|
"</head>\n"
|
||||||
'<body>\n'
|
"<body>\n"
|
||||||
' <h1 class="title">Illustrations</h1>\n'
|
' <h1 class="title">Illustrations</h1>\n'
|
||||||
' <table>\n'
|
" <table>\n"
|
||||||
)
|
)
|
||||||
for image in state.book.images:
|
for image in ctx.book.images:
|
||||||
contents += ' <tr><td><a href="../Images/' + image.name + '">' + image.caption + '</a></td></tr>\n'
|
contents += ' <tr><td><a href="../Images/' + image.name + '">' + image.caption + "</a></td></tr>\n"
|
||||||
contents += ' </table>\n</body>\n</html>'
|
contents += " </table>\n</body>\n</html>"
|
||||||
|
|
||||||
file = os.path.join(state.book.directory, 'OEBPS', 'Text', "illustrations.xhtml")
|
with open(os.path.join(ctx.book.directory, "OEBPS", "Text", "illustrations.xhtml"), "w") as f:
|
||||||
content = open(file, access)
|
f.write(contents)
|
||||||
content.write(contents)
|
|
||||||
content.close()
|
if ctx.verbose:
|
||||||
if state.verbose:
|
|
||||||
print("Finished writing section illustrations.xhtml")
|
print("Finished writing section illustrations.xhtml")
|
||||||
|
|
||||||
|
|
||||||
def writeEpub():
|
def write_epub(ctx: BuildContext) -> None:
|
||||||
cmd = f'cd {state.book.directory}; zip -X0 "{state.book.directory}.epub" mimetype; zip -X9Dr "{state.book.directory}.epub" META-INF OEBPS'
|
cmd = (
|
||||||
|
f'cd {ctx.book.directory}; '
|
||||||
|
f'zip -X0 "{ctx.book.directory}.epub" mimetype; '
|
||||||
|
f'zip -X9Dr "{ctx.book.directory}.epub" META-INF OEBPS'
|
||||||
|
)
|
||||||
subprocess.run(cmd, shell=True)
|
subprocess.run(cmd, shell=True)
|
||||||
if state.verbose:
|
if ctx.verbose:
|
||||||
print(f"Finished writing {state.book.directory}.epub")
|
print(f"Finished writing {ctx.book.directory}.epub")
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
from epubmaker.models import Name, Series, Image, Section, Book, BuildContext
|
||||||
|
|
||||||
|
|
||||||
|
def test_name_defaults():
|
||||||
|
n = Name()
|
||||||
|
assert n.first == ""
|
||||||
|
assert n.last == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_series_defaults():
|
||||||
|
s = Series()
|
||||||
|
assert s.number == ""
|
||||||
|
assert s.name == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_image_defaults():
|
||||||
|
img = Image()
|
||||||
|
assert img.name == ""
|
||||||
|
assert img.type == ""
|
||||||
|
assert img.alt == ""
|
||||||
|
assert img.caption == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_section_defaults():
|
||||||
|
s = Section()
|
||||||
|
assert s.type == ""
|
||||||
|
assert s.name == ""
|
||||||
|
assert s.title == ""
|
||||||
|
assert s.body == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_book_defaults():
|
||||||
|
b = Book()
|
||||||
|
assert b.filename == ""
|
||||||
|
assert b.title == ""
|
||||||
|
assert b.titlesort == ""
|
||||||
|
assert b.publisher == ""
|
||||||
|
assert b.isbn == ""
|
||||||
|
assert b.uuid == ""
|
||||||
|
assert b.author == []
|
||||||
|
assert b.images == []
|
||||||
|
assert b.sections == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_book_nested_objects():
|
||||||
|
b = Book()
|
||||||
|
assert isinstance(b.series, Series)
|
||||||
|
assert isinstance(b.artist, Name)
|
||||||
|
assert isinstance(b.editor, Name)
|
||||||
|
assert isinstance(b.cover, Image)
|
||||||
|
|
||||||
|
|
||||||
|
def test_book_author_lists_are_independent():
|
||||||
|
b1 = Book()
|
||||||
|
b2 = Book()
|
||||||
|
b1.author.append(Name(first="Jane", last="Doe"))
|
||||||
|
assert len(b2.author) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_context_defaults():
|
||||||
|
ctx = BuildContext()
|
||||||
|
assert isinstance(ctx.book, Book)
|
||||||
|
assert ctx.verbose is False
|
||||||
|
assert ctx.in_section is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_context_books_are_independent():
|
||||||
|
ctx1 = BuildContext()
|
||||||
|
ctx2 = BuildContext()
|
||||||
|
ctx1.book.title = "Book One"
|
||||||
|
assert ctx2.book.title == ""
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
import pytest
|
||||||
|
from epubmaker.models import BuildContext
|
||||||
|
from epubmaker.parser import extract, identify, parse_book
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def ctx():
|
||||||
|
return BuildContext()
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_returns_inner_content():
|
||||||
|
assert extract("<!-- Title: Foo Bar -->") == "Title: Foo Bar"
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_with_newline():
|
||||||
|
assert extract("<!-- Title: Foo Bar -->\n") == "Title: Foo Bar"
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_returns_none_for_non_comment():
|
||||||
|
assert extract("<p>Not a comment</p>") is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_directory(ctx):
|
||||||
|
identify("<!-- Directory: mybook -->", ctx)
|
||||||
|
assert ctx.book.directory == "mybook"
|
||||||
|
assert ctx.in_section is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_title_plain(ctx):
|
||||||
|
identify("<!-- Title: My Book -->", ctx)
|
||||||
|
assert ctx.book.title == "My Book"
|
||||||
|
assert ctx.book.titlesort == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_title_the(ctx):
|
||||||
|
identify("<!-- Title: The Great Book -->", ctx)
|
||||||
|
assert ctx.book.title == "The Great Book"
|
||||||
|
assert ctx.book.titlesort == "Great Book, The"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_title_a(ctx):
|
||||||
|
identify("<!-- Title: A Good Story -->", ctx)
|
||||||
|
assert ctx.book.title == "A Good Story"
|
||||||
|
assert ctx.book.titlesort == "Good Story, A"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_title_an(ctx):
|
||||||
|
identify("<!-- Title: An Old Tale -->", ctx)
|
||||||
|
assert ctx.book.title == "An Old Tale"
|
||||||
|
assert ctx.book.titlesort == "Old Tale, An"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_author(ctx):
|
||||||
|
identify("<!-- Author: Smith, John -->", ctx)
|
||||||
|
assert len(ctx.book.author) == 1
|
||||||
|
assert ctx.book.author[0].last == "Smith"
|
||||||
|
assert ctx.book.author[0].first == "John"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_multiple_authors(ctx):
|
||||||
|
identify("<!-- Author: Smith, John -->", ctx)
|
||||||
|
identify("<!-- Author: Jones, Jane -->", ctx)
|
||||||
|
assert len(ctx.book.author) == 2
|
||||||
|
assert ctx.book.author[1].last == "Jones"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_editor(ctx):
|
||||||
|
identify("<!-- Editor: Brown, Alice -->", ctx)
|
||||||
|
assert ctx.book.editor.last == "Brown"
|
||||||
|
assert ctx.book.editor.first == "Alice"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_publisher(ctx):
|
||||||
|
identify("<!-- Publisher: Acme Press -->", ctx)
|
||||||
|
assert ctx.book.publisher == "Acme Press"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_copyright(ctx):
|
||||||
|
identify("<!-- Copyright: 2024 Author -->", ctx)
|
||||||
|
assert ctx.book.copyright == "2024 Author"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_isbn(ctx):
|
||||||
|
identify("<!-- ISBN: 978-0-000-00000-0 -->", ctx)
|
||||||
|
assert ctx.book.isbn == "978-0-000-00000-0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_uuid_explicit(ctx):
|
||||||
|
identify("<!-- UUID: abc-123 -->", ctx)
|
||||||
|
assert ctx.book.uuid == "abc-123"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_uuid_empty_generates_uuid(ctx):
|
||||||
|
identify("<!-- UUID: -->", ctx)
|
||||||
|
assert ctx.book.uuid != ""
|
||||||
|
assert ctx.book.uuid is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_source(ctx):
|
||||||
|
identify("<!-- Source: Some Original Work -->", ctx)
|
||||||
|
assert ctx.book.source == "Some Original Work"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_subjects(ctx):
|
||||||
|
identify("<!-- Subjects: Fiction, Mystery -->", ctx)
|
||||||
|
assert ctx.book.subjects == "Fiction, Mystery"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_series(ctx):
|
||||||
|
identify("<!-- Series: 1 My Series Name -->", ctx)
|
||||||
|
assert ctx.book.series.number == "1"
|
||||||
|
assert ctx.book.series.name == "My Series Name"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_cover(ctx):
|
||||||
|
identify("<!-- Cover: cover.jpeg -->", ctx)
|
||||||
|
assert ctx.book.cover.name == "cover"
|
||||||
|
assert ctx.book.cover.type == "jpeg"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_text_section(ctx):
|
||||||
|
identify("<!-- Text: chapter1 Chapter One -->", ctx)
|
||||||
|
assert ctx.in_section is True
|
||||||
|
assert len(ctx.book.sections) == 1
|
||||||
|
assert ctx.book.sections[0].type == "text"
|
||||||
|
assert ctx.book.sections[0].name == "chapter1"
|
||||||
|
assert ctx.book.sections[0].title == "Chapter One"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_toc_section(ctx):
|
||||||
|
identify("<!-- TOC: toc Table of Contents -->", ctx)
|
||||||
|
assert ctx.in_section is True
|
||||||
|
assert ctx.book.sections[0].type == "toc"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_title_page(ctx):
|
||||||
|
identify("<!-- TitlePage: tp Title Page -->", ctx)
|
||||||
|
assert ctx.book.sections[0].type == "title"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_copyright_page(ctx):
|
||||||
|
identify("<!-- CopyrightPage: cp Copyright -->", ctx)
|
||||||
|
assert ctx.book.sections[0].type == "copyright-page"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_dedication(ctx):
|
||||||
|
identify("<!-- Dedication: ded For someone -->", ctx)
|
||||||
|
assert ctx.book.sections[0].type == "dedication"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_foreward(ctx):
|
||||||
|
identify("<!-- Foreward: fw Foreword -->", ctx)
|
||||||
|
assert ctx.book.sections[0].type == "forward"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_notes(ctx):
|
||||||
|
identify("<!-- Notes: notes Notes -->", ctx)
|
||||||
|
assert ctx.book.sections[0].type == "notes"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_acknowledgement(ctx):
|
||||||
|
identify("<!-- Acknowledgement: ack Acknowledgements -->", ctx)
|
||||||
|
assert ctx.book.sections[0].type == "acknowledgement"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_metadata_clears_in_section(ctx):
|
||||||
|
identify("<!-- Text: ch1 Chapter One -->", ctx)
|
||||||
|
assert ctx.in_section is True
|
||||||
|
identify("<!-- Title: My Book -->", ctx)
|
||||||
|
assert ctx.in_section is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_image_jpeg(ctx):
|
||||||
|
identify("<!-- Text: ch1 Chapter One -->", ctx)
|
||||||
|
identify("<!-- Image: photo.jpg Alt text::Caption text -->", ctx)
|
||||||
|
assert len(ctx.book.images) == 1
|
||||||
|
assert ctx.book.images[0].name == "photo.jpg"
|
||||||
|
assert ctx.book.images[0].type == "jpeg"
|
||||||
|
assert ctx.book.images[0].alt == "Alt text"
|
||||||
|
assert ctx.book.images[0].caption == "Caption text"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_image_png(ctx):
|
||||||
|
identify("<!-- Text: ch1 Chapter One -->", ctx)
|
||||||
|
identify("<!-- Image: photo.png Alt text::Caption -->", ctx)
|
||||||
|
assert ctx.book.images[0].type == "png"
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_image_appends_to_section_body(ctx):
|
||||||
|
identify("<!-- Text: ch1 Chapter One -->", ctx)
|
||||||
|
identify("<!-- Image: photo.jpg Alt::Caption -->", ctx)
|
||||||
|
assert "photo.jpg" in ctx.book.sections[-1].body
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_image_with_caption_uses_div(ctx):
|
||||||
|
identify("<!-- Text: ch1 Chapter One -->", ctx)
|
||||||
|
identify("<!-- Image: photo.jpg Alt::My Caption -->", ctx)
|
||||||
|
assert '<div class="center group">' in ctx.book.sections[-1].body
|
||||||
|
assert "My Caption" in ctx.book.sections[-1].body
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_image_without_caption_uses_img(ctx):
|
||||||
|
identify("<!-- Text: ch1 Chapter One -->", ctx)
|
||||||
|
identify("<!-- Image: photo.jpg Alt:: -->", ctx)
|
||||||
|
body = ctx.book.sections[-1].body
|
||||||
|
assert "<img" in body
|
||||||
|
assert '<div class="center group">' not in body
|
||||||
|
|
||||||
|
|
||||||
|
def test_identify_unknown_key_ignored(ctx):
|
||||||
|
identify("<!-- UnknownKey: some value -->", ctx)
|
||||||
|
assert ctx.book.title == ""
|
||||||
|
assert ctx.book.directory == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_book_populates_book(tmp_path):
|
||||||
|
source = tmp_path / "book.html"
|
||||||
|
source.write_text(
|
||||||
|
"<!-- Directory: mybook -->\n"
|
||||||
|
"<!-- Title: Test Book -->\n"
|
||||||
|
"<!-- Author: Doe, Jane -->\n"
|
||||||
|
"<!-- Text: ch1 Chapter One -->\n"
|
||||||
|
"<p>Body content</p>\n"
|
||||||
|
)
|
||||||
|
ctx = BuildContext()
|
||||||
|
ctx.book.filename = str(source)
|
||||||
|
parse_book(ctx)
|
||||||
|
assert ctx.book.title == "Test Book"
|
||||||
|
assert ctx.book.directory == "mybook"
|
||||||
|
assert len(ctx.book.author) == 1
|
||||||
|
assert ctx.book.author[0].last == "Doe"
|
||||||
|
assert len(ctx.book.sections) == 1
|
||||||
|
assert "<p>Body content</p>\n" in ctx.book.sections[0].body
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_book_body_not_captured_outside_section(tmp_path):
|
||||||
|
source = tmp_path / "book.html"
|
||||||
|
source.write_text(
|
||||||
|
"<!-- Title: Test Book -->\n"
|
||||||
|
"<p>This is outside any section</p>\n"
|
||||||
|
"<!-- Text: ch1 Chapter One -->\n"
|
||||||
|
"<p>Inside section</p>\n"
|
||||||
|
)
|
||||||
|
ctx = BuildContext()
|
||||||
|
ctx.book.filename = str(source)
|
||||||
|
parse_book(ctx)
|
||||||
|
assert len(ctx.book.sections) == 1
|
||||||
|
assert "outside any section" not in ctx.book.sections[0].body
|
||||||
|
assert "Inside section" in ctx.book.sections[0].body
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
from unittest.mock import patch
|
||||||
|
from epubmaker.models import BuildContext, Book, Section, Image, Name
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def ctx(tmp_path):
|
||||||
|
context = BuildContext()
|
||||||
|
context.book.title = "Test Book"
|
||||||
|
context.book.uuid = "test-uuid-1234"
|
||||||
|
context.book.isbn = "978-0-000-00000-0"
|
||||||
|
context.book.publisher = "Test Publisher"
|
||||||
|
context.book.copyright = "2024 Test"
|
||||||
|
context.book.subjects = "Fiction"
|
||||||
|
context.book.source = "Original Source"
|
||||||
|
context.book.publish_date = "2024-1-1"
|
||||||
|
context.book.directory = str(tmp_path / "testbook")
|
||||||
|
context.book.filename = str(tmp_path / "source" / "book.html")
|
||||||
|
context.book.author.append(Name(first="Jane", last="Doe"))
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
from epubmaker.writer import (
|
||||||
|
write_structure, write_misc, write_ncx, write_toc,
|
||||||
|
write_cover, write_contents, write_sections, write_illustrations,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- write_structure ---
|
||||||
|
|
||||||
|
def test_write_structure_creates_epub_directories(ctx):
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
for subdir in ["META-INF", "OEBPS", "OEBPS/Images", "OEBPS/Styles", "OEBPS/Text"]:
|
||||||
|
assert os.path.isdir(os.path.join(ctx.book.directory, subdir))
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_structure_exits_if_exists(ctx):
|
||||||
|
os.makedirs(ctx.book.directory)
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_structure_overwrite_replaces_existing(ctx):
|
||||||
|
os.makedirs(ctx.book.directory)
|
||||||
|
sentinel = os.path.join(ctx.book.directory, "old_file.txt")
|
||||||
|
open(sentinel, "w").close()
|
||||||
|
write_structure(ctx, overwrite=True)
|
||||||
|
assert os.path.isdir(ctx.book.directory)
|
||||||
|
assert not os.path.exists(sentinel)
|
||||||
|
|
||||||
|
|
||||||
|
# --- write_misc ---
|
||||||
|
|
||||||
|
def test_write_misc_mimetype_content(ctx):
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_misc(ctx)
|
||||||
|
with open(os.path.join(ctx.book.directory, "mimetype")) as f:
|
||||||
|
assert f.read() == "application/epub+zip"
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_misc_container_xml_references_opf(ctx):
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_misc(ctx)
|
||||||
|
with open(os.path.join(ctx.book.directory, "META-INF", "container.xml")) as f:
|
||||||
|
assert 'full-path="OEBPS/content.opf"' in f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_misc_ibooks_display_options_created(ctx):
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_misc(ctx)
|
||||||
|
path = os.path.join(ctx.book.directory, "META-INF", "com.apple.ibooks.display-options.xml")
|
||||||
|
assert os.path.isfile(path)
|
||||||
|
|
||||||
|
|
||||||
|
# --- write_ncx ---
|
||||||
|
|
||||||
|
def test_write_ncx_contains_title_and_uuid(ctx):
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_ncx(ctx)
|
||||||
|
with open(os.path.join(ctx.book.directory, "OEBPS", "toc.ncx")) as f:
|
||||||
|
content = f.read()
|
||||||
|
assert ctx.book.title in content
|
||||||
|
assert ctx.book.uuid in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_ncx_includes_non_toc_sections(ctx):
|
||||||
|
ctx.book.sections.append(Section(type="text", name="chapter1", title="Chapter One"))
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_ncx(ctx)
|
||||||
|
with open(os.path.join(ctx.book.directory, "OEBPS", "toc.ncx")) as f:
|
||||||
|
content = f.read()
|
||||||
|
assert "chapter1" in content
|
||||||
|
assert "Chapter One" in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_ncx_excludes_toc_type_sections(ctx):
|
||||||
|
ctx.book.sections.append(Section(type="toc", name="toc", title="Table of Contents"))
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_ncx(ctx)
|
||||||
|
with open(os.path.join(ctx.book.directory, "OEBPS", "toc.ncx")) as f:
|
||||||
|
content = f.read()
|
||||||
|
assert 'id ="toc"' not in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_ncx_play_order_increments(ctx):
|
||||||
|
for i in range(3):
|
||||||
|
ctx.book.sections.append(Section(type="text", name=f"ch{i}", title=f"Chapter {i}"))
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_ncx(ctx)
|
||||||
|
with open(os.path.join(ctx.book.directory, "OEBPS", "toc.ncx")) as f:
|
||||||
|
content = f.read()
|
||||||
|
assert 'playOrder="2"' in content
|
||||||
|
assert 'playOrder="3"' in content
|
||||||
|
assert 'playOrder="4"' in content
|
||||||
|
|
||||||
|
|
||||||
|
# --- write_toc ---
|
||||||
|
|
||||||
|
def test_write_toc_writes_section_body(ctx):
|
||||||
|
section = Section(body="<p>Contents here</p>\n")
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_toc(ctx, section)
|
||||||
|
with open(os.path.join(ctx.book.directory, "OEBPS", "toc.xhtml")) as f:
|
||||||
|
content = f.read()
|
||||||
|
assert "<p>Contents here</p>" in content
|
||||||
|
assert "Table of Contents" in content
|
||||||
|
|
||||||
|
|
||||||
|
# --- write_cover ---
|
||||||
|
|
||||||
|
def test_write_cover_creates_xhtml_with_title(ctx, tmp_path):
|
||||||
|
source_images = tmp_path / "source" / "Images"
|
||||||
|
source_images.mkdir(parents=True)
|
||||||
|
(source_images / "cover.jpeg").write_bytes(b"fake")
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_cover(ctx)
|
||||||
|
with open(os.path.join(ctx.book.directory, "OEBPS", "Text", "cover.xhtml")) as f:
|
||||||
|
content = f.read()
|
||||||
|
assert ctx.book.title in content
|
||||||
|
assert 'src="../Images/cover.jpeg"' in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_cover_copies_image_to_output(ctx, tmp_path):
|
||||||
|
source_images = tmp_path / "source" / "Images"
|
||||||
|
source_images.mkdir(parents=True)
|
||||||
|
(source_images / "cover.jpeg").write_bytes(b"image-data")
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_cover(ctx)
|
||||||
|
assert os.path.isfile(os.path.join(ctx.book.directory, "OEBPS", "Images", "cover.jpeg"))
|
||||||
|
|
||||||
|
|
||||||
|
# --- write_sections ---
|
||||||
|
|
||||||
|
def test_write_sections_creates_xhtml_per_section(ctx):
|
||||||
|
ctx.book.sections.append(Section(type="text", name="chapter1", title="Chapter One", body="<p>Content</p>\n"))
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_sections(ctx)
|
||||||
|
path = os.path.join(ctx.book.directory, "OEBPS", "Text", "chapter1.xhtml")
|
||||||
|
assert os.path.isfile(path)
|
||||||
|
with open(path) as f:
|
||||||
|
content = f.read()
|
||||||
|
assert "Chapter One" in content
|
||||||
|
assert "<p>Content</p>" in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_sections_routes_toc_to_toc_xhtml(ctx):
|
||||||
|
ctx.book.sections.append(Section(type="toc", name="toc", title="Table of Contents", body="<p>toc</p>\n"))
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_sections(ctx)
|
||||||
|
assert os.path.isfile(os.path.join(ctx.book.directory, "OEBPS", "toc.xhtml"))
|
||||||
|
assert not os.path.isfile(os.path.join(ctx.book.directory, "OEBPS", "Text", "toc.xhtml"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_sections_multiple_sections(ctx):
|
||||||
|
for i in range(3):
|
||||||
|
ctx.book.sections.append(Section(type="text", name=f"ch{i}", title=f"Chapter {i}", body=f"<p>ch{i}</p>\n"))
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_sections(ctx)
|
||||||
|
for i in range(3):
|
||||||
|
assert os.path.isfile(os.path.join(ctx.book.directory, "OEBPS", "Text", f"ch{i}.xhtml"))
|
||||||
|
|
||||||
|
|
||||||
|
# --- write_illustrations ---
|
||||||
|
|
||||||
|
def test_write_illustrations_skips_when_no_images(ctx):
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_illustrations(ctx)
|
||||||
|
assert not os.path.isfile(os.path.join(ctx.book.directory, "OEBPS", "Text", "illustrations.xhtml"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_illustrations_creates_file_with_image_links(ctx):
|
||||||
|
ctx.book.images.append(Image(name="photo.jpg", caption="A scenic photo"))
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_illustrations(ctx)
|
||||||
|
path = os.path.join(ctx.book.directory, "OEBPS", "Text", "illustrations.xhtml")
|
||||||
|
assert os.path.isfile(path)
|
||||||
|
with open(path) as f:
|
||||||
|
content = f.read()
|
||||||
|
assert "photo.jpg" in content
|
||||||
|
assert "A scenic photo" in content
|
||||||
|
|
||||||
|
|
||||||
|
# --- write_contents ---
|
||||||
|
|
||||||
|
def test_write_contents_creates_opf_with_metadata(ctx):
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_contents(ctx)
|
||||||
|
with open(os.path.join(ctx.book.directory, "OEBPS", "content.opf")) as f:
|
||||||
|
content = f.read()
|
||||||
|
assert ctx.book.title in content
|
||||||
|
assert ctx.book.publisher in content
|
||||||
|
assert ctx.book.isbn in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_contents_includes_section_in_manifest(ctx):
|
||||||
|
ctx.book.sections.append(Section(type="text", name="chapter1", title="Chapter One"))
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_contents(ctx)
|
||||||
|
with open(os.path.join(ctx.book.directory, "OEBPS", "content.opf")) as f:
|
||||||
|
content = f.read()
|
||||||
|
assert "chapter1.xhtml" in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_contents_includes_series_metadata(ctx):
|
||||||
|
ctx.book.series.name = "My Series"
|
||||||
|
ctx.book.series.number = "1"
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
write_contents(ctx)
|
||||||
|
with open(os.path.join(ctx.book.directory, "OEBPS", "content.opf")) as f:
|
||||||
|
content = f.read()
|
||||||
|
assert "My Series" in content
|
||||||
|
assert "calibre:series" in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_contents_includes_illustrations_when_images_present(ctx):
|
||||||
|
ctx.book.images.append(Image(name="photo.jpg", type="jpeg", caption="caption"))
|
||||||
|
write_structure(ctx, overwrite=False)
|
||||||
|
with patch("epubmaker.writer.copy"):
|
||||||
|
write_contents(ctx)
|
||||||
|
with open(os.path.join(ctx.book.directory, "OEBPS", "content.opf")) as f:
|
||||||
|
content = f.read()
|
||||||
|
assert "illustrations.xhtml" in content
|
||||||
Reference in New Issue
Block a user