summaryrefslogtreecommitdiff
path: root/tools/yelp-build.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/yelp-build.py')
-rw-r--r--tools/yelp-build.py834
1 files changed, 0 insertions, 834 deletions
diff --git a/tools/yelp-build.py b/tools/yelp-build.py
deleted file mode 100644
index 1c52335..0000000
--- a/tools/yelp-build.py
+++ /dev/null
@@ -1,834 +0,0 @@
-#!/bin/python3
-#
-# yelp-build
-# Copyright (C) 2010-2020 Shaun McCance <shaunm@gnome.org>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-
-import configparser
-import os
-import sys
-import shutil
-import subprocess
-import tempfile
-import urllib.parse
-import uuid
-
-import lxml.etree
-import lxml.ElementInclude
-
-
-# FIXME: don't hardcode these
-XSL_DB2HTML = '/usr/share/yelp-xsl/xslt/docbook/html/db2html.xsl'
-XSL_DB2XHTML = '/usr/share/yelp-xsl/xslt/docbook/html/db2xhtml.xsl'
-XSL_MALCACHE = '/usr/share/yelp-xsl/xslt/mallard/cache/mal-cache.xsl'
-XSL_MAL2HTML = '/usr/share/yelp-xsl/xslt/mallard/html/mal2html.xsl'
-XSL_MAL2XHTML = '/usr/share/yelp-xsl/xslt/mallard/html/mal2xhtml.xsl'
-XSL_MAL_OPF='/usr/share/yelp-tools/xslt/mal-opf.xsl'
-XSL_MAL_NCX='/usr/share/yelp-tools/xslt/mal-ncx.xsl'
-DATADIR = '/usr/share/yelp-tools'
-YELP_JSDIR = '/usr/share/yelp-xsl/js'
-
-XSLCOMMON = ('''
-<xsl:variable name="yelp.internal.datadir" select="'{intdatadir}'"/>
-<xsl:param name="html.css.root" select="$yelp.internal.datadir"/>
-<xsl:param name="html.js.root" select="$yelp.internal.datadir"/>
-{includes}
-<xsl:template name="html.css">
- <xsl:param name="node" select="."/>
- <xsl:variable name="yelp.locale">
- <xsl:choose>
- <xsl:when test="$node/@xml:lang != ''">
- <xsl:value-of select="$node/@xml:lang"/>
- </xsl:when>
- <xsl:when test="$node/@lang != ''">
- <xsl:value-of select="$node/@lang"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:text>C</xsl:text>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
- <exsl:document href="{{$yelp.internal.datadir}}{{$yelp.locale}}.css" method="text">
- <xsl:call-template name="html.css.content">
- <xsl:with-param name="node" select="$node"/>
- <xsl:with-param name="direction">
- <xsl:call-template name="l10n.direction">
- <xsl:with-param name="lang" select="$yelp.locale"/>
- </xsl:call-template>
- </xsl:with-param>
- </xsl:call-template>
- </exsl:document>
- <link rel="stylesheet" type="text/css" href="{{$html.css.root}}{{$yelp.locale}}.css"/>
-</xsl:template>
-<xsl:template name="html.js.script">
- <xsl:param name="node" select="."/>
- <exsl:document href="{{$yelp.internal.datadir}}yelp.js" method="text">
- <xsl:call-template name="html.js.content">
- <xsl:with-param name="node" select="$node"/>
- </xsl:call-template>
- </exsl:document>
- <script type="text/javascript" src="{{$html.js.root}}yelp.js"/>
-</xsl:template>
-''')
-
-DB2HTML = ('''
-<xsl:stylesheet
- xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
- xmlns:exsl="http://exslt.org/common"
- xmlns="http://www.w3.org/1999/xhtml"
- extension-element-prefixes="exsl"
- version="1.0">
-<xsl:import href="file://{xslfile}"/>
-'''
-+ XSLCOMMON +
-'''
-</xsl:stylesheet>
-''')
-
-
-MAL2HTML = ('''
-<xsl:stylesheet
- xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
- xmlns:mal="http://projectmallard.org/1.0/"
- xmlns:cache="http://projectmallard.org/cache/1.0/"
- xmlns:exsl="http://exslt.org/common"
- xmlns="http://www.w3.org/1999/xhtml"
- exclude-result-prefixes="mal cache"
- extension-element-prefixes="exsl"
- version="1.0">
-<xsl:import href="file://{xslfile}"/>
-<xsl:param name="mal.cache.file" select="'file://{cachefile}'"/>
-'''
-+ XSLCOMMON +
-'''
-<xsl:template match="/">
- <xsl:for-each select="cache:cache/mal:page | cache:cache/mal:stack">
- <xsl:variable name="href" select="@cache:href"/>
- <xsl:for-each select="document(@cache:href)">
- <xsl:for-each select="mal:page | mal:stack/mal:page">
- <xsl:call-template name="html.output"/>
- </xsl:for-each>
- </xsl:for-each>
- </xsl:for-each>
-</xsl:template>
-</xsl:stylesheet>
-''')
-
-
-
-class InputFile:
- def __init__(self, filepath, filename, sitedir=None):
- self.filepath = filepath
- self.filename = filename
- self.absfile = os.path.join(filepath, filename)
- self.absdir = os.path.dirname(self.absfile)
- self.sitedir = sitedir or ''
- self.sitefilename = self.sitedir + self.filename
-
-
-class PathResolver(lxml.etree.Resolver):
- def __init__(self, srcdir, path):
- if srcdir.endswith('/'):
- self.srcdir = srcdir
- else:
- self.srcdir = srcdir + '/'
- self.path = path
-
- def resolve(self, uri, id, context):
- if os.path.exists(uri):
- return self.resolve_filename(uri, context)
- if uri.startswith(self.srcdir):
- ref = uri[len(self.srcdir):]
- else:
- ref = uri
- for p in self.path:
- tryfile = os.path.join(p, ref)
- if os.path.exists(tryfile):
- return self.resolve_filename(tryfile, context)
- return None
-
-
-class Builder:
- name = None
- desc = None
- blurb = None
- formats = []
- arguments = []
- postblurb = None
- config = None
-
- def __init__(self, yelpbuild):
- self.yelpbuild = yelpbuild
- self.options = {}
- self.fileargs = []
- self.tmpdir = None
-
- def __del__(self):
- if self.tmpdir is not None:
- shutil.rmtree(self.tmpdir)
- self.tmpdir = None
-
- def parse_args(self, args):
- while len(args) > 0:
- argdef = None
- if args[0].startswith('--'):
- for arg_ in self.arguments:
- if args[0] == '--' + arg_[0]:
- argdef = arg_
- break
- if argdef is None:
- self.print_help()
- return 1
- elif args[0].startswith('-'):
- for arg_ in self.arguments:
- if args[0] == arg_[1]:
- argdef = arg_
- break
- if argdef is None:
- self.print_help()
- return 1
- if argdef is not None:
- takesarg = (argdef[2] is not None)
- if takesarg:
- if len(args) < 2:
- self.print_help()
- return 1
- self.options.setdefault(argdef[0], [])
- self.options[argdef[0]].append(args[1])
- args = args[2:]
- else:
- self.options[argdef[0]] = True
- args = args[1:]
- else:
- self.fileargs.append(args[0])
- args = args[1:]
- cfgfile = None
- if len(self.fileargs) > 0:
- cfgfile = os.path.join(os.path.dirname(self.fileargs[0]), '.yelp-tools.cfg')
- if not os.path.exists(cfgfile):
- cfgfile = None
- if cfgfile is None:
- cfgfile = os.path.join(os.getcwd(), '.yelp-tools.cfg')
- if os.path.exists(cfgfile):
- self.config = configparser.ConfigParser()
- try:
- self.config.read(cfgfile)
- except Exception as e:
- print(e, file=sys.stderr)
- sys.exit(1)
- return 0
-
- def get_option_bool(self, arg):
- if arg in self.options:
- return self.options[arg] == True
- if self.config is not None:
- val = self.config.get('build:' + self.name, arg, fallback=None)
- if val is not None:
- return (val == 'true')
- val = self.config.get('build', arg, fallback=None)
- if val is not None:
- return (val == 'true')
- val = self.config.get('default', arg, fallback=None)
- if val is not None:
- return (val == 'true')
- return False
-
- def get_option_str(self, arg):
- if arg in self.options:
- if isinstance(self.options[arg], list):
- return self.options[arg][-1]
- if self.config is not None:
- val = self.config.get('build:' + self.name, arg, fallback=None)
- if val is not None:
- return val
- val = self.config.get('build', arg, fallback=None)
- if val is not None:
- return val
- val = self.config.get('default', arg, fallback=None)
- if val is not None:
- return val
- return None
-
- def get_option_list(self, arg):
- if arg in self.options:
- if isinstance(self.options[arg], list):
- ret = []
- for opt in self.options[arg]:
- ret.extend(opt.replace(',', ' ').split())
- return ret
- if self.config is not None:
- val = self.config.get('build:' + self.name, arg, fallback=None)
- if val is not None:
- return val.replace(',', ' ').split()
- val = self.config.get('build', arg, fallback=None)
- if val is not None:
- return val.replace(',', ' ').split()
- val = self.config.get('default', arg, fallback=None)
- if val is not None:
- return val.replace(',', ' ').split()
- return None
-
- def get_xml(self, infile, path):
- parser = lxml.etree.XMLParser()
- parser.resolvers.add(PathResolver(os.path.realpath(infile.absdir), path))
- tree = lxml.etree.parse(infile.absfile, parser=parser)
- def pathloader(href, parse, encoding=None):
- usefile = os.path.join(infile.absdir, href)
- if not os.path.exists(href):
- usefile = None
- if usefile is None:
- absdir = infile.absdir
- if not absdir.endswith('/'):
- absdir = absdir + '/'
- ref = href
- if ref.startswith(absdir):
- ref = ref[len(absdir):]
- for p in path:
- tryfile = os.path.join(p, ref)
- if os.path.exists(tryfile):
- usefile = tryfile
- break
- if usefile is not None:
- if parse == 'xml':
- return lxml.etree.parse(usefile, parser=parser).getroot()
- elif parse == 'text':
- return open(usefile).read()
- return None
- lxml.ElementInclude.include(tree, loader=pathloader)
- return tree
-
- def iter_files(self, sitedir=None):
- issite = self.get_option_bool('site')
- if len(self.fileargs) == 0:
- self.fileargs.append('.')
- for filearg in self.fileargs:
- if os.path.isdir(filearg):
- if issite:
- for infile in self.iter_site(filearg, '/'):
- yield infile
- else:
- for fname in os.listdir(filearg):
- if fname.endswith('.page'):
- yield InputFile(filearg, fname)
- else:
- if issite:
- # FIXME: should do some normalization here, I guess.
- # It's hard to get this perfect without a defined start dir
- yield InputFile(os.getcwd(), filearg, '/' + os.path.dirname(filearg))
- else:
- yield InputFile(os.getcwd(), filearg)
-
- def iter_site(self, filepath, sitedir):
- for fname in os.listdir(filepath):
- newpath = os.path.join(filepath, fname)
- if os.path.isdir(newpath):
- # FIXME https://github.com/projectmallard/pintail/issues/36
- if fname == '__pintail__':
- continue
- for infile in self.iter_site(newpath, sitedir + fname + '/'):
- yield infile
- elif fname.endswith('.page'):
- yield InputFile(filepath, fname, sitedir)
-
- def create_tmpdir(self):
- if self.tmpdir is None:
- self.tmpdir = tempfile.mkdtemp()
-
- def print_help(self):
- print('Usage: yelp-build ' + self.name + ' [OPTIONS] [FILES]')
- print('Formats: ' + ' '.join(self.formats) + '\n')
- #FIXME: prettify names of formats
- if self.blurb is not None:
- print(self.blurb + '\n')
- print('Options:')
- maxarglen = 2
- args = []
- for arg in self.arguments:
- argkey = '--' + arg[0]
- if arg[1] is not None:
- argkey = arg[1] + ', ' + argkey
- if arg[2] is not None:
- argkey = argkey + ' ' + arg[2]
- args.append((argkey, arg[3]))
- for arg in args:
- maxarglen = max(maxarglen, len(arg[0]) + 1)
- for arg in args:
- print(' ' + (arg[0]).ljust(maxarglen) + ' ' + arg[1])
- if self.postblurb is not None:
- print(self.postblurb)
-
- def main(self, args):
- pass
-
-
-class CacheBuilder (Builder):
- name = 'cache'
- desc = 'Convert a Mallard cache file'
- blurb = ('Create a Mallard cache file from the page files FILES.\n' +
- 'If FILES contains directories, all .page files in those\n' +
- 'directories will be used.')
- formats = ['mallard']
- arguments = [
- ('help', '-h', None, 'Show this help and exit'),
- ('output', '-o', 'OUT', 'Output files in the directory OUT'),
- ('path', '-p', 'PATH', 'Extra directories to search for files'),
- ('site', '-s', None, 'Treat pages as belonging to a Mallard site')
- ]
-
- def build_cache_in(self, filename):
- with open(filename, 'w') as cachein:
- print('<cache:cache xmlns:cache="http://projectmallard.org/cache/1.0/"' +
- ' xmlns:site="http://projectmallard.org/site/1.0/"'
- ' xmlns="http://projectmallard.org/1.0/">',
- file=cachein)
- for infile in self.iter_files():
- if infile.filename.endswith('.page'):
- page = '<page'
- elif infile.filename.endswith('.stack'):
- page = '<stack'
- else:
- continue
- page += ' cache:href="file://' + urllib.parse.quote(os.path.realpath(infile.absfile)) + '"'
- if self.get_option_bool('site'):
- page += ' site:dir="' + infile.sitedir + '"'
- page += '/>'
- print(page, file=cachein)
- print('</cache:cache>', file=cachein)
-
- def main(self, args, output=None, path=None):
- if self.parse_args(args) != 0:
- return 1
- if 'help' in self.options:
- self.print_help()
- return 0
-
- retcode = 0
- self.create_tmpdir()
- cacheinfile = os.path.join(self.tmpdir, 'index.cache.in')
- self.build_cache_in(cacheinfile)
- if output is None:
- output = self.get_option_str('output')
- if output is None:
- output = 'index.cache'
- if path is None:
- path = self.get_option_list('path')
- if path is None:
- path = ':'
- else:
- path = ':'.join(path)
- retcode = subprocess.call(['xsltproc', '--xinclude', '-o', output,
- '--path', path,
- XSL_MALCACHE, cacheinfile])
- return retcode
-
-
-class XhtmlBuilder (Builder):
- name = 'xhtml'
- desc = 'Convert input files to XHTML'
- blurb = ('Create XHTML output from the input files FILES.\n' +
- 'FILES can be DocBook files, Mallard page files,\n' +
- 'or directories containing Mallard page files.')
- formats = ['docbook4', 'docbook5', 'mallard']
- arguments = [
- ('help', '-h', None, 'Show this help and exit'),
- ('cache', '-c', 'CACHE', 'Use the existing Mallard cache CACHE'),
- ('output', '-o', 'OUT', 'Output files in the directory OUT'),
- ('xsl', '-x', 'CUSTOM', 'Import the custom XSLT file CUSTOM'),
- ('path', '-p', 'PATH', 'Extra directories to search for files'),
- ('ignore', '-i', None, 'Ignore missing media files')
- ]
-
- def __init__(self, yelpbuild, xhtml=True, epub=False):
- super().__init__(yelpbuild)
- self.mal2html = None
- self.db2html = None
- self.xhtml = xhtml
- self.epub = epub
- if self.epub:
- self.intdatadir = 'yelp'
- else:
- self.intdatadir = ''
- self.cacheinfile = None
-
-
- def build_mallard_all(self, cache=None, output=None, xsl=None, path=None):
- if self.mal2html is not None:
- # We build all the pages on the first call, because it's faster
- return 0
- if path is None:
- path = self.get_option_list('path')
- self.create_tmpdir()
- if cache is None:
- cachefile = self.get_option_str('cache')
- else:
- cachefile = cache
- cachebuilder = CacheBuilder(self.yelpbuild)
- if cachefile is None:
- cachefile = os.path.join(self.tmpdir, 'index.cache')
- retcode = cachebuilder.main(self.fileargs, output=cachefile, path=path)
- if retcode != 0:
- return retcode
- self.cacheinfile = cachefile
- else:
- cachefile = os.path.realpath(cachefile)
- self.cacheinfile = os.path.join(self.tmpdir, 'index.cache.in')
- cachebuilder.parse_args(self.fileargs)
- cachebuilder.build_cache_in(self.cacheinfile)
- self.mal2html = os.path.join(self.tmpdir, 'mal2html.xsl')
- with open(self.mal2html, 'w') as xslout:
- if self.xhtml:
- xslfile = XSL_MAL2XHTML
- else:
- xslfile = XSL_MAL2HTML
- includes = ''
- if xsl is None:
- customxsl = self.get_option_str('xsl')
- else:
- customxsl = xsl
- if customxsl is not None:
- customxsl = urllib.parse.quote(os.path.realpath(customxsl))
- includes += '<xsl:include href="file://' + customxsl + '"/>'
- if self.epub:
- includes += '''<xsl:param name="mal.if.target" select="'target:epub target:html target:xhtml'"/>'''
- includes += '''<xsl:template mode="html.header.mode" match="mal:page"/>'''
- includes += '''<xsl:template mode="html.footer.mode" match="mal:page"/>'''
- xslout.write(MAL2HTML.format(xslfile=xslfile,
- cachefile=cachefile,
- includes=includes,
- intdatadir=self.intdatadir))
- if output is None:
- output = self.get_option_str('output')
- if output is None:
- output = os.getcwd()
- else:
- if not os.path.isdir(output):
- print('Output must be a directory', file=sys.stderr)
- return 1
- if not output.endswith('/'):
- # xsltproc is picky about this
- output = output + '/'
- if path is None:
- pathstr = ':'
- else:
- pathstr = ':'.join(path)
- retcode = subprocess.call(['xsltproc', '--xinclude', '-o', output,
- '--path', pathstr,
- '--stringparam', 'mal.cache.file', cachefile,
- self.mal2html, self.cacheinfile])
- return retcode
-
-
- def build_docbook(self, infile, output=None, xsl=None, path=None):
- if self.db2html is None:
- self.create_tmpdir()
- self.db2html = os.path.join(self.tmpdir, 'db2html.xsl')
- with open(self.db2html, 'w') as xslout:
- if self.xhtml:
- xslfile = XSL_DB2XHTML
- else:
- xslfile = XSL_DB2HTML
- includes = ''
- if xsl is not None:
- customxsl = xsl
- else:
- customxsl = self.get_option_str('xsl')
- if customxsl is not None:
- customxsl = urllib.parse.quote(os.path.realpath(customxsl))
- includes += '<xsl:include href="file://' + customxsl + '"/>'
- xslout.write(DB2HTML.format(xslfile=xslfile,
- includes=includes,
- intdatadir=self.intdatadir))
- if output is None:
- output = self.get_option_str('output')
- if output is None:
- output = os.getcwd()
- else:
- if not os.path.isdir(output):
- print('Output must be a directory', file=sys.stderr)
- return 1
- if path is None:
- path = self.get_option_list('path')
- if path is None:
- pathstr = ':'
- else:
- pathstr = ':'.join(path)
- retcode = subprocess.call(['xsltproc', '--xinclude', '-o', output,
- '--path', pathstr,
- self.db2html, infile.absfile])
- return retcode
-
-
- def main(self, args, cache=None, output=None, xsl=None, path=None, ignore=None):
- if self.parse_args(args) != 0:
- return 1
- if 'help' in self.options:
- self.print_help()
- return 0
-
- if path is None:
- pathopt = self.get_option_list('path')
- else:
- pathopt = path
- path = []
- if pathopt is not None:
- for p in pathopt:
- path.extend(p.split(':'))
- if output is None:
- output = self.get_option_str('output')
- srcs = {}
- for infile in self.iter_files():
- if infile.filename.endswith('.page') or infile.filename.endswith('.stack'):
- retcode = self.build_mallard_all(cache=cache, output=output, xsl=xsl, path=path)
- if retcode != 0:
- return retcode
- if output is not None:
- tree = self.get_xml(infile, path)
- if tree is None:
- return 1
- for el in tree.xpath('//*[@src]'):
- src = el.get('src')
- srcs.setdefault(src, [])
- orig = os.path.join(os.path.realpath(infile.absdir), src)
- if orig not in srcs[src]:
- srcs[src].append(orig)
- elif infile.filename.endswith('.docbook') or infile.filename.endswith('.xml'):
- retcode = self.build_docbook(infile, output=output, xsl=xsl, path=path)
- if retcode != 0:
- return retcode
- if output is not None:
- tree = self.get_xml(infile, path)
- if tree is None:
- return 1
- for el in tree.xpath('//*[@fileref]'):
- src = el.get('fileref')
- srcs.setdefault(src, [])
- orig = os.path.join(os.path.realpath(infile.absdir), src)
- if orig not in srcs[src]:
- srcs[src].append(orig)
- else:
- print('Error: No builder for ' + infile.filename)
- return 1
-
- if ignore is None:
- ignore = self.get_option_bool('ignore')
- tocopy = {}
- for src in srcs:
- useorig = None
- for orig in srcs[src]:
- if os.path.exists(orig):
- if useorig is None:
- useorig = orig
- else:
- print('Warning: Multiple sources for ' + src + '. Using first.',
- file=sys.stderr)
- if useorig is None:
- for p in path:
- tryorig = os.path.join(p, src)
- if os.path.exists(tryorig):
- useorig = tryorig
- break
- if useorig is None:
- if ignore:
- print('Warning: No source found for ' + src, file=sys.stderr)
- else:
- print('Error: No source found for ' + src, file=sys.stderr)
- return 1
- if useorig is not None:
- destfile = os.path.join(output, src)
- destdir = os.path.dirname(destfile)
- os.makedirs(destdir, exist_ok=True)
- shutil.copyfile(useorig, destfile)
-
- if output is None:
- shutil.copyfile(os.path.join(YELP_JSDIR, 'highlight.pack.js'),
- os.path.join(self.intdatadir, 'highlight.pack.js'))
- else:
- shutil.copyfile(os.path.join(YELP_JSDIR, 'highlight.pack.js'),
- os.path.join(output, self.intdatadir, 'highlight.pack.js'))
-
- return 0
-
-
-class HtmlBuilder (Builder):
- name = 'html'
- desc = 'Convert input files to HTML'
- blurb = ('Create HTML output from the input files FILES.\n' +
- 'FILES can be DocBook files, Mallard page files,\n' +
- 'or directories containing Mallard page files.')
- formats = ['docbook4', 'docbook5', 'mallard']
- arguments = [
- ('help', '-h', None, 'Show this help and exit'),
- ('cache', '-c', 'CACHE', 'Use the existing Mallard cache CACHE'),
- ('output', '-o', 'OUT', 'Output files in the directory OUT'),
- ('xsl', '-x', 'CUSTOM', 'Import the custom XSLT file CUSTOM'),
- ('path', '-p', 'PATH', 'Extra directories to search for files'),
- ('ignore', '-i', None, 'Ignore missing media files')
- ]
-
- def __init__(self, yelpbuild):
- super().__init__(yelpbuild)
- self.xhtmlbuilder = XhtmlBuilder(yelpbuild, xhtml=False)
-
- def main(self, args):
- if self.parse_args(args) != 0:
- return 1
- if 'help' in self.options:
- self.print_help()
- return 0
-
- return self.xhtmlbuilder.main(args)
-
-
-class EpubBuilder (Builder):
- name = 'epub'
- desc = 'Create an EPUB file for Mallard'
- blurb = ('Create an EPUB file from the Mallard page files FILES')
- formats = ['mallard']
- arguments = [
- ('help', '-h', None, 'Show this help and exit'),
- ('cache', '-c', 'CACHE', 'Use the existing Mallard cache CACHE'),
- ('output', '-o', 'OUT', 'Output files in the directory OUT'),
- ('xsl', '-x', 'CUSTOM', 'Import the custom XSLT file CUSTOM'),
- ('path', '-p', 'PATH', 'Extra directories to search for files'),
- ('ignore', '-i', None, 'Ignore missing media files'),
- ('nozip', None, None, 'Do not zip the output directory')
- ]
-
- def __init__(self, yelpbuild):
- super().__init__(yelpbuild)
-
- def main(self, args):
- if self.parse_args(args) != 0:
- return 1
- if 'help' in self.options:
- self.print_help()
- return 0
-
- output = self.get_option_str('output')
- nozip = self.get_option_bool('nozip')
- if nozip:
- if output is None:
- output = 'EPUB'
- if os.path.isfile(output):
- print('Error: Output must be a directory', file=sys.stderr)
- sys.exit(1)
- epubdir = output
- else:
- self.create_tmpdir()
- if output is None:
- output = 'index.epub'
- if os.path.isdir(output):
- print('Error: Output must be a file', file=sys.stderr)
- sys.exit(1)
- epubdir = os.path.join(self.tmpdir, 'EPUB')
- os.makedirs(epubdir, exist_ok=True)
- os.makedirs(os.path.join(epubdir, 'OPS', 'yelp'), exist_ok=True)
-
- xhtmlbuilder = XhtmlBuilder(self.yelpbuild, epub=True)
- retcode = xhtmlbuilder.main(self.fileargs,
- cache=self.get_option_str('cache'),
- output=os.path.join(epubdir, 'OPS'),
- xsl=self.get_option_str('xsl'),
- path=self.get_option_list('path'),
- ignore=self.get_option_bool('ignore'))
- if retcode != 0:
- return retcode
-
- with open(os.path.join(epubdir, 'mimetype'), 'w') as fd:
- fd.write('application/epub+zip\n')
-
- os.makedirs(os.path.join(epubdir, 'META-INF'), exist_ok=True)
-
- with open(os.path.join(epubdir, 'META-INF', 'container.xml'), 'w') as fd:
- fd.write('<?xml version="1.0" encoding="UTF-8"?>')
- fd.write('<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">')
- fd.write('<rootfiles>')
- fd.write('<rootfile full-path="OPS/opf.opf" media-type="application/oebps-package+xml"/>')
- fd.write('</rootfiles>')
- fd.write('</container>\n')
-
- path = self.get_option_list('path')
- if path is None:
- pathstr = ':'
- else:
- pathstr = ':'.join(path)
- epubid = str(uuid.uuid4())
- opfdata = ''
- for fname in os.listdir(os.path.join(epubdir, 'OPS', 'yelp')):
- opfdata += ' OPS/yelp/' + urllib.parse.quote(fname)
- retcode = subprocess.call(['xsltproc', '--xinclude',
- '-o', os.path.join(epubdir, 'OPS', 'opf.opf'),
- '--path', pathstr,
- '--stringparam', 'opf.id', epubid,
- '--stringparam', 'opf.data', opfdata,
- XSL_MAL_OPF, xhtmlbuilder.cacheinfile])
- if retcode != 0:
- return retcode
- retcode = subprocess.call(['xsltproc', '--xinclude',
- '-o', os.path.join(epubdir, 'OPS', 'ncx.ncx'),
- '--path', pathstr,
- '--stringparam', 'ncx.id', epubid,
- XSL_MAL_NCX, xhtmlbuilder.cacheinfile])
- if retcode != 0:
- return retcode
-
- if not nozip:
- retcode = subprocess.call(['zip', '-q', '-r', os.path.realpath(output),
- 'mimetype', 'META-INF', 'OPS'],
- cwd=os.path.realpath(epubdir))
- if retcode != 0:
- return retcode
- return 0
-
-
-class YelpBuild:
- def __init__(self):
- pass
-
- def main(self):
- if len(sys.argv) < 2:
- self.print_usage()
- return 1
-
- builder = None
- for cls in Builder.__subclasses__():
- if sys.argv[1] == cls.name:
- builder = cls(self)
-
- if builder is None:
- print('Unrecognized command: ' + sys.argv[1], file=sys.stderr)
- return 1
-
- return builder.main(sys.argv[2:])
-
- def print_usage(self):
- print('Usage: yelp-builder <COMMAND> [OPTIONS] [FILES]')
- namelen = 2
- builders = []
- for cls in sorted(Builder.__subclasses__(), key=(lambda cls: cls.name or '')):
- namelen = max(namelen, len(cls.name) + 2)
- builders.append(cls)
-
- print('\nCommands:')
- for cls in builders:
- print(' ' + cls.name.ljust(namelen) + cls.desc)
-
-
-if __name__ == '__main__':
- try:
- sys.exit(YelpBuild().main())
- except KeyboardInterrupt:
- sys.exit(1)