mirror of
				https://github.com/kovidgoyal/calibre.git
				synced 2025-10-31 10:37:00 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			297 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			297 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8 -*-
 | |
| import os
 | |
| import re
 | |
| import cgi
 | |
| import subprocess
 | |
| from os import path
 | |
| 
 | |
| from docutils import nodes
 | |
| 
 | |
| from sphinx import addnodes
 | |
| from sphinx.builders.html import StandaloneHTMLBuilder
 | |
| 
 | |
| _idpattern = re.compile(
 | |
|     r'(?P<title>.+) (\((?P<id>[\w\.]+)( (?P<descr>\w+))?\))$')
 | |
| 
 | |
| 
 | |
| # Qt Help Collection Project (.qhcp).
 | |
| # Is the input file for the help collection generator.
 | |
| # It contains references to compressed help files which should be
 | |
| # included in the collection.
 | |
| # It may contain various other information for customizing Qt Assistant.
 | |
| collection_template = '''\
 | |
| <?xml version="1.0" encoding="utf-8" ?>
 | |
| <QHelpCollectionProject version="1.0">
 | |
|     <docFiles>
 | |
|         <generate>
 | |
|             <file>
 | |
|                 <input>%(outname)s.qhp</input>
 | |
|                 <output>%(outname)s.qch</output>
 | |
|             </file>
 | |
|         </generate>
 | |
|         <register>
 | |
|             <file>%(outname)s.qch</file>
 | |
|         </register>
 | |
|     </docFiles>
 | |
|     <assistant>
 | |
|          <title>calibre User Manual</title>
 | |
|          <applicationIcon>_static/logo.png</applicationIcon>
 | |
|          <enableDocumentationManager>false</enableDocumentationManager>
 | |
|          <enableAddressBar visible="false">true</enableAddressBar>
 | |
|          <cacheDirectory>calibre/user_manual</cacheDirectory>
 | |
|          <aboutMenuText>
 | |
|              <text>About calibre</text>
 | |
|          </aboutMenuText>
 | |
|          <aboutDialog>
 | |
|              <file>about.txt</file>
 | |
|              <icon>_static/logo.png</icon>
 | |
|          </aboutDialog>
 | |
|      </assistant>
 | |
| </QHelpCollectionProject>
 | |
| '''
 | |
| 
 | |
| ABOUT='''\
 | |
| calibre is the one stop solution for all your ebook needs. It was created
 | |
| originally by Kovid Goyal, to help him manage his ebook collection and is now
 | |
| very actively developed by an international community of ebook enthusiasts.
 | |
| 
 | |
| Its goal is to empower you, the user, to do whatever you like with the ebooks
 | |
| in your collection. You can convert them to many different formats, read them
 | |
| on your computer, send them to many different devices, edit their metadata
 | |
| and covers, etc.
 | |
| 
 | |
| calibre also allows you to download news from a variety of different sources all
 | |
| over the Internet and read conveniently in ebooks form. In keeping with its
 | |
| philosophy of empowering the user, it has a simple system to allow you to add
 | |
| your own favorite news sources. In fact, most the built-in news sources in
 | |
| calibre were originally contributed by users.
 | |
| '''
 | |
| 
 | |
| # Qt Help Project (.qhp)
 | |
| # This is the input file for the help generator.
 | |
| # It contains the table of contents, indices and references to the
 | |
| # actual documentation files (*.html).
 | |
| # In addition it defines a unique namespace for the documentation.
 | |
| project_template = '''\
 | |
| <?xml version="1.0" encoding="UTF-8"?>
 | |
| <QtHelpProject version="1.0">
 | |
|     <namespace>%(outname)s.org.%(outname)s.%(nversion)s</namespace>
 | |
|     <virtualFolder>doc</virtualFolder>
 | |
|     <customFilter name="%(project)s %(version)s">
 | |
|         <filterAttribute>%(outname)s</filterAttribute>
 | |
|         <filterAttribute>%(version)s</filterAttribute>
 | |
|     </customFilter>
 | |
|     <filterSection>
 | |
|         <filterAttribute>%(outname)s</filterAttribute>
 | |
|         <filterAttribute>%(version)s</filterAttribute>
 | |
|         <toc>
 | |
|             <section title="%(title)s" ref="%(masterdoc)s.html">
 | |
| %(sections)s
 | |
|             </section>
 | |
|         </toc>
 | |
|         <files>
 | |
| %(files)s
 | |
|         </files>
 | |
|     </filterSection>
 | |
| </QtHelpProject>
 | |
| '''
 | |
| 
 | |
| section_template = '<section title="%(title)s" ref="%(ref)s"/>'
 | |
| file_template = ' '*12 + '<file>%(filename)s</file>'
 | |
| 
 | |
| 
 | |
| class QtHelpBuilder(StandaloneHTMLBuilder):
 | |
|     """
 | |
|     Builder that also outputs Qt help project, contents and index files.
 | |
|     """
 | |
|     name = 'qthelp'
 | |
| 
 | |
|     # don't copy the reST source
 | |
|     copysource = False
 | |
|     supported_image_types = ['image/svg+xml', 'image/png', 'image/gif',
 | |
|                              'image/jpeg']
 | |
| 
 | |
|     # don't add links
 | |
|     add_permalinks = False
 | |
|     # don't add sidebar etc.
 | |
|     embedded = True
 | |
| 
 | |
|     def init(self):
 | |
|         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):
 | |
|         self.build_qhcp(self.outdir, self.config.qthelp_basename)
 | |
|         self.build_qhp(self.outdir, self.config.qthelp_basename)
 | |
|         self.build_qhc(self.outdir, self.config.qthelp_basename)
 | |
| 
 | |
|     def build_qhc(self, outdir, outname):
 | |
|         self.info('create Qt Help Collection...')
 | |
|         with open(os.path.join(outdir, 'about.txt'), 'wb') as f:
 | |
|             f.write(ABOUT)
 | |
|         qhcp = os.path.abspath(os.path.join(outdir, outname+'.qhcp'))
 | |
|         subprocess.check_call(['qcollectiongenerator', qhcp])
 | |
|         qhc = qhcp[:-5]+'.qhc'
 | |
|         self.info('Qt Help Collection: '+qhc)
 | |
|         self.info('To test: assistant -collectionFile '+qhc)
 | |
| 
 | |
|     def build_qhcp(self, outdir, outname):
 | |
|         self.info('writing collection project file...')
 | |
|         f = open(path.join(outdir, outname+'.qhcp'), 'w')
 | |
|         try:
 | |
|             f.write(collection_template % {'outname': outname})
 | |
|         finally:
 | |
|             f.close()
 | |
| 
 | |
|     def build_qhp(self, outdir, outname):
 | |
|         self.info('writing project file...')
 | |
| 
 | |
|         # sections
 | |
|         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'))
 | |
|         sections = []
 | |
|         for node in tocdoc.traverse(istoctree):
 | |
|             sections.extend(self.write_toc(node))
 | |
| 
 | |
|         if self.config.html_use_modindex:
 | |
|             item = section_template % {'title': 'Global Module Index',
 | |
|                                        'ref': 'modindex.html'}
 | |
|             sections.append(' '*4*4 + item)
 | |
|         sections = '\n'.join(sections)
 | |
| 
 | |
|         # keywords
 | |
|         keywords = []
 | |
|         index = self.env.create_index(self)
 | |
|         for (key, group) in index:
 | |
|             for title, (refs, subitems) in group:
 | |
|                 keywords.extend(self.build_keywords(title, refs, subitems))
 | |
|         keywords = '\n'.join(keywords)
 | |
| 
 | |
|         # files
 | |
|         if not outdir.endswith(os.sep):
 | |
|             outdir += os.sep
 | |
|         olen = len(outdir)
 | |
|         projectfiles = []
 | |
|         staticdir = path.join(outdir, '_static')
 | |
|         imagesdir = path.join(outdir, '_images')
 | |
|         for root, dirs, files in os.walk(outdir):
 | |
|             resourcedir = root.startswith(staticdir) or root.startswith(imagesdir)
 | |
|             for fn in files:
 | |
|                 if (resourcedir and not fn.endswith('.js')) or \
 | |
|                        fn.endswith('.html'):
 | |
|                     filename = path.join(root, fn)[olen:]
 | |
|                     #filename = filename.replace(os.sep, '\\') # XXX
 | |
|                     projectfiles.append(file_template % {'filename': filename})
 | |
|         projectfiles = '\n'.join(projectfiles)
 | |
| 
 | |
|         # write the project file
 | |
|         f = open(path.join(outdir, outname+'.qhp'), 'w')
 | |
|         try:
 | |
|             nversion = self.config.version.replace('.', '_')
 | |
|             nversion = nversion.replace(' ', '_')
 | |
|             f.write(project_template % {'outname': outname,
 | |
|                                         'title': self.config.html_title,
 | |
|                                         'version': self.config.version,
 | |
|                                         'project': self.config.project,
 | |
|                                         'nversion': nversion,
 | |
|                                         'masterdoc': self.config.master_doc,
 | |
|                                         'sections': sections,
 | |
|                                         'keywords': keywords,
 | |
|                                         'files': projectfiles})
 | |
|         finally:
 | |
|             f.close()
 | |
| 
 | |
|     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 write_toc(self, node, indentlevel=4):
 | |
|         parts = []
 | |
|         if self.isdocnode(node):
 | |
|             refnode = node.children[0][0]
 | |
|             link = refnode['refuri']
 | |
|             title = cgi.escape(refnode.astext()).replace('"','"')
 | |
|             item = '<section title="%(title)s" ref="%(ref)s">' % {
 | |
|                                                                 'title': title,
 | |
|                                                                 'ref': link}
 | |
|             parts.append(' '*4*indentlevel + item)
 | |
|             for subnode in node.children[1]:
 | |
|                 parts.extend(self.write_toc(subnode, indentlevel+1))
 | |
|             parts.append(' '*4*indentlevel + '</section>')
 | |
|         elif isinstance(node, nodes.list_item):
 | |
|             for subnode in node:
 | |
|                 parts.extend(self.write_toc(subnode, indentlevel))
 | |
|         elif isinstance(node, nodes.reference):
 | |
|             link = node['refuri']
 | |
|             title = cgi.escape(node.astext()).replace('"','"')
 | |
|             item = section_template % {'title': title, 'ref': link}
 | |
|             item = ' '*4*indentlevel + item.encode('ascii', 'xmlcharrefreplace')
 | |
|             parts.append(item.encode('ascii', 'xmlcharrefreplace'))
 | |
|         elif isinstance(node, nodes.bullet_list):
 | |
|             for subnode in node:
 | |
|                 parts.extend(self.write_toc(subnode, indentlevel))
 | |
|         elif isinstance(node, addnodes.compact_paragraph):
 | |
|             for subnode in node:
 | |
|                 parts.extend(self.write_toc(subnode, indentlevel))
 | |
| 
 | |
|         return parts
 | |
| 
 | |
|     def keyword_item(self, name, ref):
 | |
|         matchobj = _idpattern.match(name)
 | |
|         if matchobj:
 | |
|             groupdict = matchobj.groupdict()
 | |
|             shortname = groupdict['title']
 | |
|             id = groupdict.get('id')
 | |
| #            descr = groupdict.get('descr')
 | |
|             if shortname.endswith('()'):
 | |
|                 shortname = shortname[:-2]
 | |
|             id = '%s.%s' % (id, shortname)
 | |
|         else:
 | |
|             id = None
 | |
| 
 | |
|         if id:
 | |
|             item = ' '*12 + '<keyword name="%s" id="%s" ref="%s"/>' % (
 | |
|                                                                 name, id, ref)
 | |
|         else:
 | |
|             item = ' '*12 + '<keyword name="%s" ref="%s"/>' % (name, ref)
 | |
|         item.encode('ascii', 'xmlcharrefreplace')
 | |
|         return item
 | |
| 
 | |
|     def build_keywords(self, title, refs, subitems):
 | |
|         keywords = []
 | |
| 
 | |
|         title = cgi.escape(title)
 | |
| #        if len(refs) == 0: # XXX
 | |
| #            write_param('See Also', title)
 | |
|         if len(refs) == 1:
 | |
|             keywords.append(self.keyword_item(title, refs[0]))
 | |
|         elif len(refs) > 1:
 | |
|             for i, ref in enumerate(refs):  # XXX
 | |
| #                item = (' '*12 +
 | |
| #                        '<keyword name="%s [%d]" ref="%s"/>' % (
 | |
| #                                                        title, i, ref))
 | |
| #                item.encode('ascii', 'xmlcharrefreplace')
 | |
| #                keywords.append(item)
 | |
|                 keywords.append(self.keyword_item(title, ref))
 | |
| 
 | |
|         if subitems:
 | |
|             for subitem in subitems:
 | |
|                 keywords.extend(self.build_keywords(subitem[0], subitem[1], []))
 | |
| 
 | |
|         return keywords
 |