X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Flilypond-book.py;h=79ec2ababbf3a4223478a238c19571d1dd013356;hb=4ea5963dd6b5289e57454bd2702b9527c06d6001;hp=392ddd061ebcbf3dabdda25cf7367fb787f32026;hpb=b7a0cffbf9d1069860368f289a5b50e9d1d90ba8;p=lilypond.git
diff --git a/scripts/lilypond-book.py b/scripts/lilypond-book.py
index 392ddd061e..79ec2ababb 100644
--- a/scripts/lilypond-book.py
+++ b/scripts/lilypond-book.py
@@ -1,4 +1,5 @@
#!@TARGET_PYTHON@
+# -*- coding: utf-8 -*-
# This file is part of LilyPond, the GNU music typesetter.
#
@@ -29,8 +30,6 @@ classic lilypond-book:
TODO:
- * this script is too complex. Modularize.
-
* ly-options: intertext?
* --line-width?
* eps in latex / eps by lilypond -b ps?
@@ -43,12 +42,18 @@ TODO:
'''
+
+# TODO: Better solve the global_options copying to the snippets...
+
import glob
import os
import re
import stat
import sys
import tempfile
+import imp
+from optparse import OptionGroup
+
"""
@relocate-preamble@
@@ -59,22 +64,14 @@ import fontextract
import langdefs
global _;_=ly._
-ly.require_python_version ()
+import book_base as BookBase
+import book_snippets as BookSnippet
+import book_html
+import book_docbook
+import book_texinfo
+import book_latex
-# Lilylib globals.
-program_version = '@TOPLEVEL_VERSION@'
-program_name = os.path.basename (sys.argv[0])
-
-# Check if program_version contains @ characters. This will be the case if
-# the .py file is called directly while building the lilypond documentation.
-# If so, try to check for the env var LILYPOND_VERSION, which is set by our
-# makefiles and use its value.
-at_re = re.compile (r'@')
-if at_re.match (program_version):
- if os.environ.has_key('LILYPOND_VERSION'):
- program_version = os.environ['LILYPOND_VERSION']
- else:
- program_version = "unknown"
+ly.require_python_version ()
original_dir = os.getcwd ()
backend = 'ps'
@@ -100,22 +97,12 @@ def exit (i):
sys.exit (i)
def identify ():
- ly.encoded_write (sys.stdout, '%s (GNU LilyPond) %s\n' % (program_name, program_version))
+ ly.encoded_write (sys.stdout, '%s (GNU LilyPond) %s\n' % (ly.program_name, ly.program_version))
progress = ly.progress
+warning = ly.warning
+error = ly.error
-def warning (s):
- ly.stderr_write (program_name + ": " + _ ("warning: %s") % s + '\n')
-
-def error (s):
- ly.stderr_write (program_name + ": " + _ ("error: %s") % s + '\n')
-
-def ps_page_count (ps_name):
- header = file (ps_name).read (1024)
- m = re.search ('\n%%Pages: ([0-9]+)', header)
- if m:
- return int (m.group (1))
- return 0
def warranty ():
identify ()
@@ -126,7 +113,7 @@ def warranty ():
%s
%s
-''' % ( _ ('Copyright (c) %s by') % '2001--2009',
+''' % ( _ ('Copyright (c) %s by') % '2001--2010',
'\n '.join (authors),
_ ("Distributed under terms of the GNU General Public License."),
_ ("It comes with NO WARRANTY.")))
@@ -134,6 +121,7 @@ def warranty ():
def get_option_parser ():
p = ly.get_option_parser (usage=_ ("%s [OPTION]... FILE") % 'lilypond-book',
description=help_summary,
+ conflict_handler="resolve",
add_help_option=False)
p.add_option ('-F', '--filter', metavar=_ ("FILTER"),
@@ -163,12 +151,6 @@ def get_option_parser ():
action='store', dest='info_images_dir',
default='')
- p.add_option ('--latex-program',
- help=_ ("run executable PROG instead of latex"),
- metavar=_ ("PROG"),
- action='store', dest='latex_program',
- default='latex')
-
p.add_option ('--left-padding',
metavar=_ ("PAD"),
dest="padding_mm",
@@ -176,11 +158,27 @@ def get_option_parser ():
type="float",
default=3.0)
+ p.add_option ('--lily-output-dir',
+ help=_ ("write lily-XXX files to DIR, link into --output dir"),
+ metavar=_ ("DIR"),
+ action='store', dest='lily_output_dir',
+ default=None)
+
+ p.add_option ('--load-custom-package', help=_ ("Load the additional python PACKAGE (containing e.g. a custom output format)"),
+ metavar=_ ("PACKAGE"),
+ action='append', dest='custom_packages',
+ default=[])
+
p.add_option ("-o", '--output', help=_ ("write output to DIR"),
metavar=_ ("DIR"),
action='store', dest='output_dir',
default='')
+ p.add_option ('-P', '--process', metavar=_ ("COMMAND"),
+ help = _ ("process ly_files using COMMAND FILE..."),
+ action='store',
+ dest='process_cmd', default='')
+
p.add_option ('--skip-lily-check',
help=_ ("do not fail if no lilypond output is found"),
metavar=_ ("DIR"),
@@ -193,21 +191,9 @@ def get_option_parser ():
action='store_true', dest='skip_png_check',
default=False)
- p.add_option ('--lily-output-dir',
- help=_ ("write lily-XXX files to DIR, link into --output dir"),
- metavar=_ ("DIR"),
- action='store', dest='lily_output_dir',
- default=None)
-
- p.add_option ('-P', '--process', metavar=_ ("COMMAND"),
- help = _ ("process ly_files using COMMAND FILE..."),
- action='store',
- dest='process_cmd', default='')
-
- p.add_option ('--pdf',
- action="store_true",
- dest="create_pdf",
- help=_ ("create PDF files for use with PDFTeX"),
+ p.add_option ('--use-source-file-names',
+ help=_ ("write snippet output files with the same base name as their source file"),
+ action='store_true', dest='use_source_file_names',
default=False)
p.add_option ('-V', '--verbose', help=_ ("be verbose"),
@@ -223,11 +209,31 @@ def get_option_parser ():
p.add_option ('-w', '--warranty',
help=_ ("show warranty and copyright"),
action='store_true')
+
+ group = OptionGroup (p, "Options only for the latex and texinfo backends")
+ group.add_option ('--latex-program',
+ help=_ ("run executable PROG instead of latex, or in\n\
+case --pdf option is set instead of pdflatex"),
+ metavar=_ ("PROG"),
+ action='store', dest='latex_program',
+ default='latex')
+ group.add_option ('--pdf',
+ action="store_true",
+ dest="create_pdf",
+ help=_ ("create PDF files for use with PDFTeX"),
+ default=False)
+ p.add_option_group (group)
+
p.add_option_group ('',
description=(
_ ("Report bugs via %s")
% ' http://post.gmane.org/post.php'
'?group=gmane.comp.gnu.lilypond.bugs') + '\n')
+
+
+ for formatter in BookBase.all_formats:
+ formatter.add_options (p)
+
return p
lilypond_binary = os.path.join ('@bindir@', 'lilypond')
@@ -245,1332 +251,7 @@ if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary):
global_options = None
-default_ly_options = { 'alt': "[image of music]" }
-
-document_language = ''
-
-#
-# Is this pythonic? Personally, I find this rather #define-nesque. --hwn
-#
-ADDVERSION = 'addversion'
-AFTER = 'after'
-BEFORE = 'before'
-DOCBOOK = 'docbook'
-EXAMPLEINDENT = 'exampleindent'
-FILTER = 'filter'
-FRAGMENT = 'fragment'
-HTML = 'html'
-INDENT = 'indent'
-LANG = 'lang'
-LATEX = 'latex'
-LAYOUT = 'layout'
-LINE_WIDTH = 'line-width'
-LILYQUOTE = 'lilyquote'
-NOFRAGMENT = 'nofragment'
-NOGETTEXT = 'nogettext'
-NOINDENT = 'noindent'
-NOQUOTE = 'noquote'
-NORAGGED_RIGHT = 'noragged-right'
-NOTES = 'body'
-NOTIME = 'notime'
-OUTPUT = 'output'
-OUTPUTIMAGE = 'outputimage'
-PAPER = 'paper'
-PREAMBLE = 'preamble'
-PRINTFILENAME = 'printfilename'
-QUOTE = 'quote'
-RAGGED_RIGHT = 'ragged-right'
-RELATIVE = 'relative'
-STAFFSIZE = 'staffsize'
-DOCTITLE = 'doctitle'
-TEXIDOC = 'texidoc'
-TEXINFO = 'texinfo'
-VERBATIM = 'verbatim'
-VERSION = 'lilypondversion'
-FILENAME = 'filename'
-ALT = 'alt'
-
-
-# NOTIME has no opposite so it isn't part of this dictionary.
-# NOQUOTE is used internally only.
-no_options = {
- NOFRAGMENT: FRAGMENT,
- NOINDENT: INDENT,
-}
-
-
-# Recognize special sequences in the input.
-#
-# (?P.*?)
-
.*?)
-
)
- \s)''',
-
- 'singleline_comment':
- no_match,
-
- 'verb':
- no_match,
-
- 'verbatim':
- no_match,
-
- 'lilypondversion':
- no_match,
- },
- ##
- HTML: {
- 'include':
- no_match,
-
- 'lilypond':
- r'''(?mx)
- (?P
.*?)
- />)''',
-
- 'lilypond_block':
- r'''(?msx)
- (?P
.*?)
-
)
- \s)''',
-
- 'singleline_comment':
- no_match,
-
- 'verb':
- r'''(?x)
- (?P
.*?
))''',
-
- 'verbatim':
- r'''(?x)
- (?s)
- (?P\s.*?
\s))''',
-
- 'lilypondversion':
- r'''(?mx)
- (?P.*?)
- })''',
-
- 'lilypond_block':
- r'''(?smx)
- ^[^%\n]*?
- (?P
.*?)
- ^[^%\n]*?
- \\end\s*{lilypond})''',
-
- 'lilypond_file':
- r'''(?smx)
- ^[^%\n]*?
- (?P
- %.*$\n+))''',
-
- 'verb':
- r'''(?mx)
- ^[^%\n]*?
- (?P
- \\verb(?P
.)
- .*?
- (?P=del)))''',
-
- 'verbatim':
- r'''(?msx)
- ^[^%\n]*?
- (?P
- \\begin\s*{verbatim}
- .*?
- \\end\s*{verbatim}))''',
-
- 'lilypondversion':
- r'''(?smx)
- (?P
.*?)
- })''',
-
- 'lilypond_block':
- r'''(?msx)
- ^(?P
.*?)
- ^@end\s+lilypond)\s''',
-
- 'lilypond_file':
- r'''(?mx)
- ^(?P
- @ignore\s
- .*?
- @end\s+ignore))\s''',
-
- 'singleline_comment':
- r'''(?mx)
- ^.*
- (?P
- @c([ \t][^\n]*|)\n))''',
-
- # Don't do this: It interferes with @code{@{}.
- # 'verb': r'''(?P
@code{.*?})''',
-
- 'verbatim':
- r'''(?sx)
- (?P
- @example
- \s.*?
- @end\s+example\s))''',
-
- 'lilypondversion':
- r'''(?mx)
- [^@](?P
- ''',
-
- OUTPUT: r'''
- ''',
-
- PRINTFILENAME: '',
-
- QUOTE: r'''
-
-
-
-
-%(str)s
-
-''',
-
- VERBATIM: r'''
-%(verb)s
''',
-
- VERSION: program_version,
- },
-
- ##
- LATEX: {
- OUTPUT: r'''{%%
-\parindent 0pt
-\ifx\preLilyPondExample \undefined
-\else
- \expandafter\preLilyPondExample
-\fi
-\def\lilypondbook{}%%
-\input %(base)s-systems.tex
-\ifx\postLilyPondExample \undefined
-\else
- \expandafter\postLilyPondExample
-\fi
-}''',
-
- PRINTFILENAME: '''\\texttt{%(filename)s}
-''',
-
- QUOTE: r'''\begin{quotation}
-%(str)s
-\end{quotation}''',
-
- VERBATIM: r'''\noindent
-\begin{verbatim}%(verb)s\end{verbatim}
-''',
-
- VERSION: program_version,
-
- FILTER: r'''\begin{lilypond}[%(options)s]
-%(code)s
-\end{lilypond}''',
- },
-
- ##
- TEXINFO: {
- FILTER: r'''@lilypond[%(options)s]
-%(code)s
-@lilypond''',
-
- OUTPUT: r'''
-@iftex
-@include %(base)s-systems.texi
-@end iftex
-''',
-
- OUTPUTIMAGE: r'''@noindent
-@ifinfo
-@image{%(info_image_path)s,,,%(alt)s,%(ext)s}
-@end ifinfo
-@html
-["\'])(.*)(?P=q)', '\g<1>\g<3>', opt)
- for opt in options]
- return options
- else:
- return re.split (format_res[self.format]['option_sep'],
- option_string)
- return []
-
- def do_options (self, option_string, type):
- self.option_dict = {}
-
- options = self.split_options (option_string)
-
- for option in options:
- if '=' in option:
- (key, value) = re.split ('\s*=\s*', option)
- self.option_dict[key] = value
- else:
- if option in no_options:
- if no_options[option] in self.option_dict:
- del self.option_dict[no_options[option]]
- else:
- self.option_dict[option] = None
-
- has_line_width = self.option_dict.has_key (LINE_WIDTH)
- no_line_width_value = 0
-
- # If LINE_WIDTH is used without parameter, set it to default.
- if has_line_width and self.option_dict[LINE_WIDTH] == None:
- no_line_width_value = 1
- del self.option_dict[LINE_WIDTH]
-
- for k in default_ly_options:
- if k not in self.option_dict:
- self.option_dict[k] = default_ly_options[k]
-
- # RELATIVE does not work without FRAGMENT;
- # make RELATIVE imply FRAGMENT
- has_relative = self.option_dict.has_key (RELATIVE)
- if has_relative and not self.option_dict.has_key (FRAGMENT):
- self.option_dict[FRAGMENT] = None
-
- if not has_line_width:
- if type == 'lilypond' or FRAGMENT in self.option_dict:
- self.option_dict[RAGGED_RIGHT] = None
-
- if type == 'lilypond':
- if LINE_WIDTH in self.option_dict:
- del self.option_dict[LINE_WIDTH]
- else:
- if RAGGED_RIGHT in self.option_dict:
- if LINE_WIDTH in self.option_dict:
- del self.option_dict[LINE_WIDTH]
-
- if QUOTE in self.option_dict or type == 'lilypond':
- if LINE_WIDTH in self.option_dict:
- del self.option_dict[LINE_WIDTH]
-
- if not INDENT in self.option_dict:
- self.option_dict[INDENT] = '0\\mm'
-
- # Set a default line-width if there is none. We need this, because
- # lilypond-book has set left-padding by default and therefore does
- # #(define line-width (- line-width (* 3 mm)))
- # TODO: Junk this ugly hack if the code gets rewritten to concatenate
- # all settings before writing them in the \paper block.
- if not LINE_WIDTH in self.option_dict:
- if not QUOTE in self.option_dict:
- if not LILYQUOTE in self.option_dict:
- self.option_dict[LINE_WIDTH] = "#(- paper-width \
-left-margin-default right-margin-default)"
-
- def compose_ly (self, code):
- if FRAGMENT in self.option_dict:
- body = FRAGMENT_LY
- else:
- body = FULL_LY
-
- # Defaults.
- relative = 1
- override = {}
- # The original concept of the `exampleindent' option is broken.
- # It is not possible to get a sane value for @exampleindent at all
- # without processing the document itself. Saying
- #
- # @exampleindent 0
- # @example
- # ...
- # @end example
- # @exampleindent 5
- #
- # causes ugly results with the DVI backend of texinfo since the
- # default value for @exampleindent isn't 5em but 0.4in (or a smaller
- # value). Executing the above code changes the environment
- # indentation to an unknown value because we don't know the amount
- # of 1em in advance since it is font-dependent. Modifying
- # @exampleindent in the middle of a document is simply not
- # supported within texinfo.
- #
- # As a consequence, the only function of @exampleindent is now to
- # specify the amount of indentation for the `quote' option.
- #
- # To set @exampleindent locally to zero, we use the @format
- # environment for non-quoted snippets.
- override[EXAMPLEINDENT] = r'0.4\in'
- override[LINE_WIDTH] = texinfo_line_widths['@smallbook']
- override.update (default_ly_options)
-
- option_list = []
- for (key, value) in self.option_dict.items ():
- if value == None:
- option_list.append (key)
- else:
- option_list.append (key + '=' + value)
- option_string = ','.join (option_list)
-
- compose_dict = {}
- compose_types = [NOTES, PREAMBLE, LAYOUT, PAPER]
- for a in compose_types:
- compose_dict[a] = []
-
- for (key, value) in self.option_dict.items ():
- (c_key, c_value) = classic_lilypond_book_compatibility (key, value)
- if c_key:
- if c_value:
- warning (
- _ ("deprecated ly-option used: %s=%s") % (key, value))
- warning (
- _ ("compatibility mode translation: %s=%s") % (c_key, c_value))
- else:
- warning (
- _ ("deprecated ly-option used: %s") % key)
- warning (
- _ ("compatibility mode translation: %s") % c_key)
-
- (key, value) = (c_key, c_value)
-
- if value:
- override[key] = value
- else:
- if not override.has_key (key):
- override[key] = None
-
- found = 0
- for type in compose_types:
- if ly_options[type].has_key (key):
- compose_dict[type].append (ly_options[type][key])
- found = 1
- break
-
- if not found and key not in simple_options:
- warning (_ ("ignoring unknown ly option: %s") % key)
-
- # URGS
- if RELATIVE in override and override[RELATIVE]:
- relative = int (override[RELATIVE])
-
- relative_quotes = ''
-
- # 1 = central C
- if relative < 0:
- relative_quotes += ',' * (- relative)
- elif relative > 0:
- relative_quotes += "'" * relative
-
- paper_string = '\n '.join (compose_dict[PAPER]) % override
- layout_string = '\n '.join (compose_dict[LAYOUT]) % override
- notes_string = '\n '.join (compose_dict[NOTES]) % vars ()
- preamble_string = '\n '.join (compose_dict[PREAMBLE]) % override
- padding_mm = global_options.padding_mm
-
- d = globals().copy()
- d.update (locals())
- return (PREAMBLE_LY + body) % d
-
- def get_checksum (self):
- if not self.checksum:
- # Work-around for md5 module deprecation warning in python 2.5+:
- try:
- from hashlib import md5
- except ImportError:
- from md5 import md5
-
- # We only want to calculate the hash based
- # on the snippet code, not the snippet + preamble
- hash = md5 (self.relevant_contents (self.ly ()))
-
- ## let's not create too long names.
- self.checksum = hash.hexdigest ()[:10]
-
- return self.checksum
-
- def basename (self):
- cs = self.get_checksum ()
- name = '%s/lily-%s' % (cs[:2], cs[2:10])
- return name
-
- def write_ly (self):
- base = self.basename ()
- path = os.path.join (global_options.lily_output_dir, base)
- directory = os.path.split(path)[0]
- if not os.path.isdir (directory):
- os.makedirs (directory)
- out = file (path + '.ly', 'w')
- out.write (self.full_ly ())
- file (path + '.txt', 'w').write ('image of music')
-
- def relevant_contents (self, ly):
- return re.sub (r'\\(version|sourcefileline|sourcefilename)[^\n]*\n|' +
- NOGETTEXT + '[,\]]', '', ly)
-
- def link_all_output_files (self, output_dir, output_dir_files, destination):
- existing, missing = self.all_output_files (output_dir, output_dir_files)
- if missing:
- print '\nMissing', missing
- raise CompileError(self.basename())
- for name in existing:
- try:
- os.unlink (os.path.join (destination, name))
- except OSError:
- pass
-
- src = os.path.join (output_dir, name)
- dst = os.path.join (destination, name)
- dst_path = os.path.split(dst)[0]
- if not os.path.isdir (dst_path):
- os.makedirs (dst_path)
- os.link (src, dst)
-
-
- def all_output_files (self, output_dir, output_dir_files):
- """Return all files generated in lily_output_dir, a set.
-
- output_dir_files is the list of files in the output directory.
- """
- result = set ()
- missing = set ()
- base = self.basename()
- full = os.path.join (output_dir, base)
- def consider_file (name):
- if name in output_dir_files:
- result.add (name)
-
- def require_file (name):
- if name in output_dir_files:
- result.add (name)
- else:
- missing.add (name)
-
- # UGH - junk global_options
- skip_lily = global_options.skip_lilypond_run
- for required in [base + '.ly',
- base + '.txt']:
- require_file (required)
- if not skip_lily:
- require_file (base + '-systems.count')
-
- if 'ddump-profile' in global_options.process_cmd:
- require_file (base + '.profile')
- if 'dseparate-log-file' in global_options.process_cmd:
- require_file (base + '.log')
-
- map (consider_file, [base + '.tex',
- base + '.eps',
- base + '.texidoc',
- base + '.doctitle',
- base + '-systems.texi',
- base + '-systems.tex',
- base + '-systems.pdftexi'])
- if document_language:
- map (consider_file,
- [base + '.texidoc' + document_language,
- base + '.doctitle' + document_language])
-
- # UGH - junk global_options
- if (base + '.eps' in result and self.format in (HTML, TEXINFO)
- and not global_options.skip_png_check):
- page_count = ps_page_count (full + '.eps')
- if page_count <= 1:
- require_file (base + '.png')
- else:
- for page in range (1, page_count + 1):
- require_file (base + '-page%d.png' % page)
-
- system_count = 0
- if not skip_lily and not missing:
- system_count = int(file (full + '-systems.count').read())
-
- for number in range(1, system_count + 1):
- systemfile = '%s-%d' % (base, number)
- require_file (systemfile + '.eps')
- consider_file (systemfile + '.pdf')
-
- # We can't require signatures, since books and toplevel
- # markups do not output a signature.
- if 'ddump-signature' in global_options.process_cmd:
- consider_file (systemfile + '.signature')
-
-
- return (result, missing)
-
- def is_outdated (self, output_dir, current_files):
- found, missing = self.all_output_files (output_dir, current_files)
- return missing
-
- def filter_text (self):
- """Run snippet bodies through a command (say: convert-ly).
-
- This functionality is rarely used, and this code must have bitrot.
- """
- code = self.substring ('code')
- s = filter_pipe (code, global_options.filter_cmd)
- d = {
- 'code': s,
- 'options': self.match.group ('options')
- }
- # TODO
- return output[self.format][FILTER] % d
-
- def replacement_text (self):
- func = LilypondSnippet.__dict__['output_' + self.format]
- return func (self)
-
- def get_images (self):
- base = self.basename ()
-
- single = '%(base)s.png' % vars ()
- multiple = '%(base)s-page1.png' % vars ()
- images = (single,)
- if (os.path.exists (multiple)
- and (not os.path.exists (single)
- or (os.stat (multiple)[stat.ST_MTIME]
- > os.stat (single)[stat.ST_MTIME]))):
- count = ps_page_count ('%(base)s.eps' % vars ())
- images = ['%s-page%d.png' % (base, page) for page in range (1, count+1)]
- images = tuple (images)
-
- return images
-
- def output_docbook (self):
- str = ''
- base = self.basename ()
- for image in self.get_images ():
- (base, ext) = os.path.splitext (image)
- str += output[DOCBOOK][OUTPUT] % vars ()
- str += self.output_print_filename (DOCBOOK)
- if (self.substring('inline') == 'inline'):
- str = '