Create an EPUB version of the calibre User Manual (linked to in the online documentation)

This commit is contained in:
Kovid Goyal 2009-08-21 12:01:07 -06:00
parent ce2fe95dbb
commit 50d66d6f3a
9 changed files with 324 additions and 32 deletions

View File

@ -107,7 +107,7 @@ class EPUBOutput(OutputFormatPlugin):
'''
TITLEPAGE = '''\
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>%(title)s</title>
<style type="text/css">

View File

@ -91,12 +91,15 @@ html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
html_use_smartypants = True
# Overall title of the documentation
html_title = 'calibre User Manual'
html_short_title = 'Start'
html_logo = 'resources/logo.png'
epub_titlepage = 'resources/titlepage.html'
epub_logo = 'resources/logo.png'
epub_author = 'Kovid Goyal'
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}

View File

@ -3,7 +3,7 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import sys, os, inspect, re, textwrap
import sys, os, inspect, re, textwrap, cStringIO
from sphinx.builder import StandaloneHTMLBuilder
from qthelp import QtHelpBuilder
@ -116,7 +116,7 @@ $groups
CLI_CMD += CLI_GROUPS
def generate_ebook_convert_help():
def generate_ebook_convert_help(info):
from calibre.ebooks.conversion.cli import create_option_parser
from calibre.customize.ui import input_format_plugins, output_format_plugins
from calibre.utils.logging import default_log
@ -139,22 +139,26 @@ def generate_ebook_convert_help():
''')
for i, ip in enumerate(input_format_plugins()):
with open(os.path.join('cli', 'ebook-convert-%d.rst'%i), 'wb') as f:
f.write(sec_templ.format(ip.name))
toc[ip.name] = 'ebook-convert-%d'%i
for op in output_format_plugins():
title = ip.name + ' to ' + op.name
parser, plumber = create_option_parser(['ebook-convert',
'dummyi.'+list(ip.file_types)[0],
'dummyo.'+op.file_type, '-h'], default_log)
groups = [(None, None, parser.option_list)]
for grp in parser.option_groups:
groups.append((grp.title, grp.description, grp.option_list))
template = str(CLI_GROUPS)
template = TextTemplate(template[template.find('||'):])
options = template.generate(groups=groups).render()
f.write(title+'\n------------------------------------------------------')
f.write('\n\n'+options.replace('||', '\n'))
f = cStringIO.StringIO()
path = os.path.join('cli', 'ebook-convert-%d.rst'%i)
f.write(sec_templ.format(ip.name))
toc[ip.name] = 'ebook-convert-%d'%i
for op in output_format_plugins():
title = ip.name + ' to ' + op.name
parser, plumber = create_option_parser(['ebook-convert',
'dummyi.'+list(ip.file_types)[0],
'dummyo.'+op.file_type, '-h'], default_log)
groups = [(None, None, parser.option_list)]
for grp in parser.option_groups:
groups.append((grp.title, grp.description, grp.option_list))
template = str(CLI_GROUPS)
template = TextTemplate(template[template.find('||'):])
options = template.generate(groups=groups).render()
f.write(title+'\n------------------------------------------------------')
f.write('\n\n'+options.replace('||', '\n'))
if not os.path.exists(path):
info('creating '+path)
open(path, 'wb').write(f.getvalue())
toct = '||||.. toctree::|| :maxdepth: 2||||'
for ip in sorted(toc):
@ -204,7 +208,7 @@ def cli_docs(app):
for grp in parser.option_groups:
groups.append((grp.title, grp.description, grp.option_list))
if cmd == 'ebook-convert':
groups = generate_ebook_convert_help()
groups = generate_ebook_convert_help(info)
templ = TextTemplate(EBOOK_CONVERT)
raw = templ.generate(cmd=cmd, cmdline=cmdline, usage=usage, groups=groups).render()
raw = raw.replace('||', '\n').replace('&lt;', '<').replace('&gt;', '>')
@ -265,6 +269,9 @@ def auto_member(dirname, arguments, options, content, lineno,
return list(node)
def setup(app):
app.add_config_value('epub_titlepage', None, False)
app.add_config_value('epub_author', '', False)
app.add_config_value('epub_logo', None, False)
app.add_builder(CustomBuilder)
app.add_builder(CustomQtBuild)
app.add_builder(EPUBHelpBuilder)

View File

@ -112,3 +112,8 @@ Metadata plugins
-------------------
Metadata plugins add the ability to read/write metadata from ebook files to |app|. See :ref:`pluginsMetadataPlugin` for details.
.. toctree::
:hidden:
plugins

View File

@ -6,10 +6,88 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os
import os, mimetypes, uuid, shutil
from datetime import datetime
from docutils import nodes
from xml.sax.saxutils import escape, quoteattr
from urlparse import urldefrag
from zipfile import ZipFile, ZIP_STORED, ZipInfo
from sphinx import addnodes
from sphinx.builders.html import StandaloneHTMLBuilder
NCX = '''\
<?xml version="1.0" encoding="UTF-8"?>
<ncx version="2005-1"
xml:lang="en"
xmlns="http://www.daisy.org/z3986/2005/ncx/"
xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata"
>
<head>
<meta name="dtb:uid" content="{uid}"/>
<meta name="dtb:depth" content="{depth}"/>
<meta name="dtb:generator" content="sphinx"/>
<meta name="dtb:totalPageCount" content="0"/>
<meta name="dtb:maxPageNumber" content="0"/>
</head>
<docTitle><text>Table of Contents</text></docTitle>
<navMap>
{navpoints}
</navMap>
</ncx>
'''
OPF = '''\
<?xml version="1.0" encoding="UTF-8"?>
<package version="2.0"
xmlns="http://www.idpf.org/2007/opf"
unique-identifier="sphinx_id"
>
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf" xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata">
<dc:title>{title}</dc:title>
<dc:creator opf:role="aut">{author}</dc:creator>
<dc:contributor opf:role="bkp">Sphinx</dc:contributor>
<dc:identifier opf:scheme="sphinx" id="sphinx_id">{uid}</dc:identifier>
<dc:date>{date}</dc:date>
<meta name="calibre:publication_type" content="sphinx_manual" />
</metadata>
<manifest>
{manifest}
</manifest>
<spine toc="ncx">
{spine}
</spine>
<guide>
{guide}
</guide>
</package>
'''
CONTAINER='''\
<?xml version="1.0"?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
<rootfiles>
<rootfile full-path="{0}" media-type="application/oebps-package+xml"/>
</rootfiles>
</container>
'''
class TOC(list):
def __init__(self, title=None, href=None):
list.__init__(self)
self.title, self.href = title, href
def create_child(self, title, href):
self.append(TOC(title, href))
return self[-1]
def depth(self):
try:
return max(node.depth() for node in self)+1
except ValueError:
return 1
class EPUBHelpBuilder(StandaloneHTMLBuilder):
"""
Builder that also outputs Qt help project, contents and index files.
@ -29,11 +107,174 @@ class EPUBHelpBuilder(StandaloneHTMLBuilder):
def init(self):
StandaloneHTMLBuilder.init(self)
# the output files for HTML help must be .html only
self.out_suffix = '.xhtml'
self.outdir = os.path.join(self.outdir, 'src')
#self.config.html_style = 'traditional.css'
self.out_suffix = '.html'
self.link_suffix = '.html'
self.html_outdir = self.outdir = os.path.join(self.outdir, 'src')
self.conf = self.config
def finish(self):
StandaloneHTMLBuilder.finish(self)
print 11111111
self.create_titlepage()
self.outdir = os.path.dirname(self.outdir)
cwd = os.getcwd()
os.chdir(self.html_outdir)
try:
self.generate_manifest()
self.generate_toc()
self.render_opf()
self.render_epub()
finally:
os.chdir(cwd)
def render_epub(self):
container = CONTAINER.format('content.opf')
path = os.path.abspath('..'+os.sep+self.conf.project+'.epub')
zf = ZipFile(path, 'w')
zi = ZipInfo('mimetype')
zi.compress_type = ZIP_STORED
zf.writestr(zi, 'application/epub+zip')
zf.writestr('META-INF/container.xml', container)
for url in self.manifest:
fp = os.path.join(self.html_outdir, *url.split('/'))
zf.write(fp, url)
zf.close()
self.info('EPUB created at: '+path)
def render_opf(self):
manifest = []
for href in self.manifest:
mt, id = self.manifest[href]
manifest.append(' '*8 + '<item id=%s href=%s media-type=%s />'%\
tuple(map(quoteattr, (id, href, mt))))
manifest = '\n'.join(manifest)
spine = [' '*8+'<itemref idref=%s />'%quoteattr(x) for x in self.spine]
spine = '\n'.join(spine)
guide = ''
if self.conf.epub_titlepage:
guide = ' '*8 + '<reference type="cover" href="_static/titlepage.html" />'
opf = OPF.format(title=escape(self.conf.html_title),
author=escape(self.conf.epub_author), uid=str(uuid.uuid4()),
date=datetime.now().isoformat(), manifest=manifest, spine=spine,
guide=guide)
open('content.opf', 'wb').write(opf)
self.manifest['content.opf'] = ('application/oebps-package+xml', 'opf')
def create_titlepage(self):
if self.conf.epub_titlepage:
img = ''
if self.conf.epub_logo:
img = '_static/epub_logo'+os.path.splitext(self.conf.epub_logo)[1]
shutil.copyfile(self.conf.epub_logo,
os.path.join(self.html_outdir, *img.split('/')))
raw = open(self.conf.epub_titlepage, 'rb').read()
raw = raw%dict(title=self.conf.html_title,
version=self.conf.version,
img=img.split('/')[-1],
author=self.conf.epub_author)
open(os.path.join(self.html_outdir, '_static', 'titlepage.html'), 'wb').write(raw)
def generate_manifest(self):
self.manifest = {}
id = 1
for dirpath, dirnames, filenames in os.walk('.'):
for fname in filenames:
if fname == '.buildinfo':
continue
fpath = os.path.abspath(os.path.join(dirpath, fname))
url = os.path.relpath(fpath).replace(os.sep, '/')
self.manifest[url] = mimetypes.guess_type(url, False)[0]
if self.manifest[url] is None:
self.warn('Unknown mimetype for: ' + url)
self.manifest[url] = 'application/octet-stream'
if self.manifest[url] == 'text/html':
self.manifest[url] = 'application/xhtml+xml'
self.manifest[url] = (self.manifest[url], 'id'+str(id))
id += 1
def isdocnode(self, node):
if not isinstance(node, nodes.list_item):
return False
if len(node.children) != 2:
return False
if not isinstance(node.children[0], addnodes.compact_paragraph):
return False
if not isinstance(node.children[0][0], nodes.reference):
return False
if not isinstance(node.children[1], nodes.bullet_list):
return False
return True
def generate_toc(self):
tocdoc = self.env.get_and_resolve_doctree(self.config.master_doc, self,
prune_toctrees=False)
istoctree = lambda node: (
isinstance(node, addnodes.compact_paragraph)
and node.has_key('toctree'))
toc = TOC()
for node in tocdoc.traverse(istoctree):
self.extend_toc(toc, node)
self._parts = []
self._po = 0
self._po_map = {}
self.spine_map = {}
self.spine = []
self.render_toc(toc)
navpoints = '\n'.join(self._parts).strip()
ncx = NCX.format(uid=str(uuid.uuid4()), depth=toc.depth(),
navpoints=navpoints)
open('toc.ncx', 'wb').write(ncx)
self.manifest['toc.ncx'] = ('application/x-dtbncx+xml', 'ncx')
self.spine.insert(0, self.manifest[self.conf.master_doc+'.html'][1])
if self.conf.epub_titlepage:
self.spine.insert(0, self.manifest['_static/titlepage.html'][1])
def add_to_spine(self, href):
href = urldefrag(href)[0]
if href not in self.spine_map:
for url in self.manifest:
if url == href:
self.spine_map[href]= self.manifest[url][1]
self.spine.append(self.spine_map[href])
def render_toc(self, toc, level=2):
for child in toc:
if child.title and child.href:
href = child.href
self.add_to_spine(href)
title = escape(child.title)
if isinstance(title, unicode):
title = title.encode('utf-8')
if child.href in self._po_map:
po = self._po_map[child.href]
else:
self._po += 1
po = self._po
self._parts.append(' '*(level*4)+
'<navPoint id="%s" playOrder="%d">'%(uuid.uuid4(),
po))
self._parts.append(' '*((level+1)*4)+
'<navLabel><text>%s</text></navLabel>'%title)
self._parts.append(' '*((level+1)*4)+
'<content src=%s />'%quoteattr(href))
self.render_toc(child, level+1)
self._parts.append(' '*(level*4)+'</navPoint>')
def extend_toc(self, toc, node):
if self.isdocnode(node):
refnode = node.children[0][0]
parent = toc.create_child(refnode.astext(), refnode['refuri'])
for subnode in node.children[1]:
self.extend_toc(parent, subnode)
elif isinstance(node, (nodes.list_item, nodes.bullet_list,
addnodes.compact_paragraph)):
for subnode in node:
self.extend_toc(toc, subnode)
elif isinstance(node, nodes.reference):
parent = toc.create_child(node.astext(), node['refuri'])

View File

@ -17,6 +17,10 @@ To get started with more advanced usage, you should read about the :ref:`Graphic
You will find the list of :ref:`Frequently Asked Questions <faq>` useful as well.
.. only:: html and online
An e-book version of this User Manual is available in `EPUB format <calibre.epub>`_. Because the User Manual uses advanced formatting, it is only suitable for use with the |app| e-book viewer.
Sections
------------
@ -25,15 +29,12 @@ Sections
gui
news
cli/cli-index
conversion
metadata
faq
xpath
customize
cli/cli-index
glossary
.. toctree::
:hidden:
plugins

View File

@ -120,6 +120,7 @@ class QtHelpBuilder(StandaloneHTMLBuilder):
StandaloneHTMLBuilder.init(self)
# the output files for HTML help must be .html only
self.out_suffix = '.html'
self.link_suffix = '.html'
#self.config.html_style = 'traditional.css'
def handle_finish(self):

View File

@ -0,0 +1,29 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>%(title)s</title>
<style type="text/css">
body {
text-align: center;
vertical-align: center;
overflow: hidden;
font-size: 16pt;
}
.logo {
text-align:center;
font-size: 1pt;
overflow:hidden;
}
h1 { font-family: serif; }
h2, h4 { font-family: monospace; }
</style>
</head>
<body>
<h1>%(title)s</h1>
<h4 style="font-family:monospace">%(version)s</h4>
<div style="text-align:center">
<img class="logo" src="%(img)s" alt="calibre logo" />
</div>
<h2>%(author)s</h2>
</body>
</html>

View File

@ -123,8 +123,13 @@ class manual(OptionlessCommand):
os.makedirs(d)
if not os.path.exists('.build'+os.sep+'html'):
os.makedirs('.build'+os.sep+'html')
check_call(['sphinx-build', '-b', 'custom', '-d',
check_call(['sphinx-build', '-b', 'custom', '-d', '-t', 'online',
'.build/doctrees', '.', '.build/html'])
check_call(['sphinx-build', '-b', 'epub', '-d',
'.build/doctrees', '.', '.build/epub'])
j = os.path.join
shutil.copyfile(j('.build', 'epub', 'calibre.epub'), j('.build',
'html', 'calibre.epub'))
finally:
os.chdir(cwd)