mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-08-07 09:01:38 -04:00
Create an EPUB version of the calibre User Manual (linked to in the online documentation)
This commit is contained in:
parent
ce2fe95dbb
commit
50d66d6f3a
@ -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">
|
||||
|
@ -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 = {}
|
||||
|
@ -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('<', '<').replace('>', '>')
|
||||
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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'])
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
29
src/calibre/manual/resources/titlepage.html
Normal file
29
src/calibre/manual/resources/titlepage.html
Normal 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>
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user