X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Flilypond-book.py;h=5a6565bc58c6d9a4390e872b72d747e4110e8358;hb=5b4b0d6e9a197e8f9eb085b7c2ad78b8be3e5cfc;hp=86e78e2449427b2218bd99d6b7dd8c67a9973fad;hpb=bdf982efc1fc4b3350de0341f2f4a10e81080898;p=lilypond.git diff --git a/scripts/lilypond-book.py b/scripts/lilypond-book.py index 86e78e2449..5a6565bc58 100644 --- a/scripts/lilypond-book.py +++ b/scripts/lilypond-book.py @@ -28,6 +28,7 @@ TODO: ''' +import glob import md5 import os import re @@ -41,13 +42,26 @@ import tempfile import lilylib as ly import fontextract +import langdefs global _;_=ly._ +ly.require_python_version () # 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" + original_dir = os.getcwd () backend = 'ps' @@ -56,9 +70,9 @@ _ ("Process LilyPond snippets in hybrid HTML, LaTeX, texinfo or DocBook document + '\n\n' + _ ("Examples:") + ''' - lilypond-book --filter="tr '[a-z]' '[A-Z]'" %(BOOK)s - lilypond-book --filter="convert-ly --no-version --from=2.0.0 -" %(BOOK)s - lilypond-book --process='lilypond -I include' %(BOOK)s + $ lilypond-book --filter="tr '[a-z]' '[A-Z]'" %(BOOK)s + $ lilypond-book -F "convert-ly --no-version --from=2.0.0 -" %(BOOK)s + $ lilypond-book --process='lilypond -I include' %(BOOK)s ''' % {'BOOK': _ ("BOOK")}) authors = ('Jan Nieuwenhuizen ', @@ -98,7 +112,7 @@ def warranty (): %s %s -''' % ( _ ('Copyright (c) %s by') % '2001--2007', +''' % ( _ ('Copyright (c) %s by') % '2001--2008', ' '.join (authors), _ ("Distributed under terms of the GNU General Public License."), _ ("It comes with NO WARRANTY."))) @@ -111,11 +125,12 @@ def get_option_parser (): p.add_option ('-F', '--filter', metavar=_ ("FILTER"), action="store", dest="filter_cmd", - help=_ ("pipe snippets through FILTER [convert-ly -n -]"), + help=_ ("pipe snippets through FILTER [default: `convert-ly -n -']"), default=None) p.add_option ('-f', '--format', help=_ ("use output format FORMAT (texi [default], texi-html, latex, html, docbook)"), + metavar=_ ("FORMAT"), action='store') p.add_option("-h", "--help", @@ -134,6 +149,12 @@ 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", @@ -147,7 +168,7 @@ def get_option_parser (): default='') p.add_option ('--skip-lily-check', - help=_ ("do not fail if no lilypond output is found."), + help=_ ("do not fail if no lilypond output is found"), metavar=_ ("DIR"), action='store_true', dest='skip_lilypond_run', default=False) @@ -159,7 +180,7 @@ def get_option_parser (): default=False) p.add_option ('--lily-output-dir', - help=_ ("write lily-XXX files to DIR, link into --output dir."), + help=_ ("write lily-XXX files to DIR, link into --output dir"), metavar=_ ("DIR"), action='store', dest='lily_output_dir', default=None) @@ -175,11 +196,6 @@ def get_option_parser (): help=_ ("create PDF files for use with PDFTeX"), default=False) - p.add_option ('', '--psfonts', action="store_true", dest="psfonts", - help=_ ('''extract all PostScript fonts into INPUT.psfonts for LaTeX -must use this with dvips -h INPUT.psfonts'''), - default=None) - p.add_option ('-V', '--verbose', help=_ ("be verbose"), action="store_true", default=False, @@ -193,7 +209,7 @@ must use this with dvips -h INPUT.psfonts'''), p.add_option ('-w', '--warranty', help=_ ("show warranty and copyright"), action='store_true') - p.add_option_group (ly.display_encode (_ ('Bugs')), + p.add_option_group ('', description=( _ ("Report bugs via") + ' http://post.gmane.org/post.php?group=gmane.comp.gnu.lilypond.bugs\n')) @@ -201,6 +217,12 @@ must use this with dvips -h INPUT.psfonts'''), lilypond_binary = os.path.join ('@bindir@', 'lilypond') +# If we are called with full path, try to use lilypond binary +# installed in the same path; this is needed in GUB binaries, where +# @bindir is always different from the installed binary path. +if 'bindir' in globals () and bindir: + lilypond_binary = os.path.join (bindir, 'lilypond') + # Only use installed binary when we are installed too. if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary): lilypond_binary = 'lilypond' @@ -210,6 +232,8 @@ global_options = None default_ly_options = { 'alt': "[image of music]" } +document_language = '' + # # Is this pythonic? Personally, I find this rather #define-nesque. --hwn # @@ -230,6 +254,7 @@ LILYQUOTE = 'lilyquote' NOFRAGMENT = 'nofragment' NOINDENT = 'noindent' NOQUOTE = 'noquote' +NORAGGED_RIGHT = 'noragged-right' NOTES = 'body' NOTIME = 'notime' OUTPUT = 'output' @@ -242,9 +267,11 @@ QUOTE = 'quote' RAGGED_RIGHT = 'ragged-right' RELATIVE = 'relative' STAFFSIZE = 'staffsize' +DOCTITLE = 'doctitle' TEXIDOC = 'texidoc' TEXINFO = 'texinfo' VERBATIM = 'verbatim' +VERSION = 'lilypondversion' FONTLOAD = 'fontload' FILENAME = 'filename' ALT = 'alt' @@ -302,7 +329,9 @@ snippet_res = { 'verbatim': no_match, - + + 'lilypondversion': + no_match, }, ## HTML: { @@ -355,6 +384,11 @@ snippet_res = { (?s) (?P (?P
\s.*?
\s))''', + + 'lilypondversion': + r'''(?mx) + (?P + )''', }, ## @@ -428,6 +462,12 @@ snippet_res = { \\begin\s*{verbatim} .*? \\end\s*{verbatim}))''', + + 'lilypondversion': + r'''(?smx) + (?P + \\lilypondversion)[^a-zA-Z]''', + }, ## @@ -494,6 +534,12 @@ snippet_res = { @example \s.*? @end\s+example\s))''', + + 'lilypondversion': + r'''(?mx) + [^@](?P + @lilypondversion)[^a-zA-Z]''', + }, } @@ -528,6 +574,7 @@ simple_options = [ NOFRAGMENT, NOINDENT, PRINTFILENAME, + DOCTITLE, TEXIDOC, LANG, VERBATIM, @@ -555,6 +602,8 @@ ly_options = { RAGGED_RIGHT: r'''ragged-right = ##t''', + NORAGGED_RIGHT: r'''ragged-right = ##f''', + PACKED: r'''packed = ##t''', }, @@ -562,12 +611,12 @@ ly_options = { LAYOUT: { NOTIME: r''' \context { - \Score - timing = ##f + \Score + timing = ##f } \context { - \Staff - \remove Time_signature_engraver + \Staff + \remove "Time_signature_engraver" }''', }, @@ -590,6 +639,8 @@ output = { ''', VERBATIM: r'''%(verb)s''', + + VERSION: program_version, PRINTFILENAME: '%(filename)s' }, @@ -620,6 +671,8 @@ output = { VERBATIM: r'''
 %(verb)s
''', + + VERSION: program_version, }, ## @@ -649,6 +702,8 @@ output = { VERBATIM: r'''\noindent \begin{verbatim}%(verb)s\end{verbatim}''', + VERSION: program_version, + FILTER: r'''\begin{lilypond}[%(options)s] %(code)s \end{lilypond}''', @@ -703,6 +758,8 @@ output = { %(verb)s@end verbatim ''', + VERSION: program_version, + ADDVERSION: r'''@example \version @w{"@version{}"} @end example @@ -820,9 +877,37 @@ def verbatim_html (s): re.sub ('<', '<', re.sub ('&', '&', s))) +ly_var_def_re = re.compile (r'^([a-zA-Z]+)[\t ]*=', re.M) +ly_comment_re = re.compile (r'(%+[\t ]*)(.*)$', re.M) +ly_context_id_re = re.compile ('\\\\(?:new|context)\\s+(?:[a-zA-Z]*?(?:Staff\ +(?:Group)?|Voice|FiguredBass|FretBoards|Names|Devnull))\\s+=\\s+"?([a-zA-Z]+)"?\\s+') + +def ly_comment_gettext (t, m): + return m.group (1) + t (m.group (2)) + +def verb_ly_gettext (s): + if not document_language: + return s + try: + t = langdefs.translation[document_language] + except: + return s + + s = ly_comment_re.sub (lambda m: ly_comment_gettext (t, m), s) + + for v in ly_var_def_re.findall (s): + s = re.sub (r"(?m)(^|[' \\#])%s([^a-zA-Z])" % v, + "\\1" + t (v) + "\\2", + s) + for id in ly_context_id_re.findall (s): + s = re.sub (r'(\s+|")%s(\s+|")' % id, + "\\1" + t (id) + "\\2", + s) + return s texinfo_lang_re = re.compile ('(?m)^@documentlanguage (.*?)( |$)') def set_default_options (source, default_ly_options, format): + global document_language if LINE_WIDTH not in default_ly_options: if format == LATEX: textwidth = get_latex_textwidth (source) @@ -830,9 +915,9 @@ def set_default_options (source, default_ly_options, format): elif format == TEXINFO: m = texinfo_lang_re.search (source) if m and not m.group (1).startswith ('en'): - default_ly_options[LANG] = m.group (1) + document_language = m.group (1) else: - default_ly_options[LANG] = '' + document_language = '' for regex in texinfo_line_widths: # FIXME: @layout is usually not in # chunk #0: @@ -910,7 +995,7 @@ class LilypondSnippet (Snippet): self.do_options (os, self.type) def verb_ly (self): - return self.substring ('code') + return verb_ly_gettext (self.substring ('code')) def ly (self): contents = self.substring ('code') @@ -964,6 +1049,12 @@ class LilypondSnippet (Snippet): 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 @@ -1107,19 +1198,16 @@ class LilypondSnippet (Snippet): return self.checksum def basename (self): - if FILENAME in self.option_dict: - return self.option_dict[FILENAME] - cs = self.get_checksum () - - # TODO: use xx/xxxxx directory layout. - name = 'lily-%s' % cs[:10] + 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') @@ -1140,6 +1228,9 @@ class LilypondSnippet (Snippet): 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) @@ -1170,12 +1261,22 @@ class LilypondSnippet (Snippet): 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) @@ -1190,11 +1291,18 @@ class LilypondSnippet (Snippet): 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): @@ -1318,9 +1426,16 @@ class LilypondSnippet (Snippet): def output_texinfo (self): str = self.output_print_filename (TEXINFO) base = self.basename () + if DOCTITLE in self.option_dict: + doctitle = base + '.doctitle' + translated_doctitle = doctitle + document_language + if os.path.exists (translated_doctitle): + str += '@lydoctitle %s\n' % open (translated_doctitle).read () + elif os.path.exists (doctitle): + str += '@lydoctitle %s\n' % open (doctitle).read () if TEXIDOC in self.option_dict: texidoc = base + '.texidoc' - translated_texidoc = texidoc + default_ly_options[LANG] + translated_texidoc = texidoc + document_language if os.path.exists (translated_texidoc): str += '@include %(translated_texidoc)s\n\n' % vars () elif os.path.exists (texidoc): @@ -1362,7 +1477,7 @@ class LilypondFileSnippet (LilypondSnippet): s = self.contents s = re_begin_verbatim.split (s)[-1] s = re_end_verbatim.split (s)[0] - return s + return verb_ly_gettext (s) def ly (self): name = self.substring ('filename') @@ -1370,11 +1485,21 @@ class LilypondFileSnippet (LilypondSnippet): % (name, self.contents)) +class LilyPondVersionString (Snippet): + """A string that does not require extra memory.""" + def __init__ (self, type, match, format, line_number): + Snippet.__init__ (self, type, match, format, line_number) + + def replacement_text (self): + return output[self.format][self.type] + + snippet_type_to_class = { 'lilypond_file': LilypondFileSnippet, 'lilypond_block': LilypondSnippet, 'lilypond': LilypondSnippet, 'include': IncludeSnippet, + 'lilypondversion': LilyPondVersionString, } def find_linestarts (s): @@ -1527,12 +1652,12 @@ def process_snippets (cmd, snippets, checksum = snippet_list_checksum (snippets) contents = '\n'.join (['snippet-map-%d.ly' % checksum] - + [snip.basename() for snip in snippets]) + + [snip.basename() + '.ly' for snip in snippets]) name = os.path.join (lily_output_dir, - 'snippet-names-%d' % checksum) + 'snippet-names-%d.ly' % checksum) file (name, 'wb').write (contents) - system_in_directory (' '.join ([cmd, name]), + system_in_directory (' '.join ([cmd, ly.mkarg (name)]), lily_output_dir) @@ -1568,7 +1693,8 @@ def get_latex_textwidth (source): tmp_handle.write (latex_document) tmp_handle.close () - ly.system ('latex %s' % tmpfile, be_verbose=global_options.verbose) + ly.system ('%s %s' % (global_options.latex_program, tmpfile), + be_verbose=global_options.verbose) parameter_string = file (logfile).read() os.unlink (tmpfile) @@ -1631,11 +1757,23 @@ def write_file_map (lys, name): """ % '\n'.join(['("%s.ly" . "%s")\n' % (ly.basename (), name) for ly in lys])) +def split_output_files(directory): + """Returns directory entries in DIRECTORY/XX/ , where XX are hex digits. + + Return value is a set of strings. + """ + files = [] + for subdir in glob.glob (os.path.join (directory, '[a-f0-9][a-f0-9]')): + base_subdir = os.path.split (subdir)[1] + sub_files = [os.path.join (base_subdir, name) + for name in os.listdir (subdir)] + files += sub_files + return set (files) + def do_process_cmd (chunks, input_name, options): snippets = [c for c in chunks if isinstance (c, LilypondSnippet)] - - output_files = set(os.listdir(options.lily_output_dir)) + output_files = split_output_files (options.lily_output_dir) outdated = [c for c in snippets if c.is_outdated (options.lily_output_dir, output_files)] write_file_map (outdated, input_name) @@ -1654,7 +1792,7 @@ def do_process_cmd (chunks, input_name, options): progress (_ ("All snippets are up to date...")) if options.lily_output_dir != options.output_dir: - output_files = set(os.listdir(options.lily_output_dir)) + output_files = split_output_files (options.lily_output_dir) for snippet in snippets: snippet.link_all_output_files (options.lily_output_dir, output_files, @@ -1702,9 +1840,14 @@ def write_if_updated (file_name, lines): # this prevents make from always rerunning lilypond-book: # output file must be touched in order to be up to date os.utime (file_name, None) + return except: pass + output_dir = os.path.dirname (file_name) + if not os.path.exists (output_dir): + os.makedirs (output_dir) + progress (_ ("Writing `%s'...") % file_name) file (file_name, 'w').writelines (lines) progress ('\n') @@ -1723,7 +1866,7 @@ def samefile (f1, f2): f2 = re.sub ("//*", "/", f2) return f1 == f2 -def do_file (input_filename): +def do_file (input_filename, included=False): # Ugh. if not input_filename or input_filename == '-': in_handle = sys.stdin @@ -1741,6 +1884,8 @@ def do_file (input_filename): if input_filename == '-': input_base = 'stdin' + elif included: + input_base = os.path.splitext (input_filename)[0] else: input_base = os.path.basename ( os.path.splitext (input_filename)[0]) @@ -1783,6 +1928,7 @@ def do_file (input_filename): 'lilypond_file', 'include', 'lilypond', + 'lilypondversion', ) progress (_ ("Dissecting...")) chunks = find_toplevel_snippets (source, global_options.format, snippet_types) @@ -1811,7 +1957,7 @@ def do_file (input_filename): name = snippet.substring ('filename') progress (_ ("Processing include: %s") % name) progress ('\n') - return do_file (name) + return do_file (name, included=True) include_chunks = map (process_include, filter (lambda x: isinstance (x, IncludeSnippet), @@ -1863,8 +2009,13 @@ def main (): + ' --formats=%s -dbackend=eps ' % formats) if global_options.process_cmd: - global_options.process_cmd += ' '.join ([(' -I %s' % ly.mkarg (p)) - for p in global_options.include_path]) + includes = global_options.include_path + if global_options.lily_output_dir: + # This must be first, so lilypond prefers to read .ly + # files in the other lybookdb dir. + includes = [os.path.abspath(global_options.lily_output_dir)] + includes + global_options.process_cmd += ' '.join ([' -I %s' % ly.mkarg (p) + for p in includes]) if global_options.format in (TEXINFO, LATEX): ## prevent PDF from being switched on by default. @@ -1878,7 +2029,7 @@ def main (): if global_options.padding_mm: global_options.process_cmd += " -deps-box-padding=%f " % global_options.padding_mm - global_options.process_cmd += " -dread-file-list " + global_options.process_cmd += " -dread-file-list -dno-strip-output-dir" if global_options.lily_output_dir: global_options.lily_output_dir = os.path.abspath(global_options.lily_output_dir)