X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Flilypond-book.py;h=09fecd15400b0b348fa05f4ceacfe57d9bf7d219;hb=9b5cef5890a9f545c6e9325a7efdb84f052cb4a3;hp=03ae87d43fe4d87a705adbf52ccac023b00e0337;hpb=ef93228f20f993afdfe7041b03123e1c8ddbec4e;p=lilypond.git diff --git a/scripts/lilypond-book.py b/scripts/lilypond-book.py index 03ae87d43f..09fecd1540 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,6 +42,7 @@ import tempfile import lilylib as ly import fontextract +import langdefs global _;_=ly._ @@ -56,9 +58,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 ', @@ -111,11 +113,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 +137,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", @@ -146,8 +155,20 @@ def get_option_parser (): action='store', dest='output_dir', default='') + p.add_option ('--skip-lily-check', + help=_ ("do not fail if no lilypond output is found"), + metavar=_ ("DIR"), + action='store_true', dest='skip_lilypond_run', + default=False) + + p.add_option ('--skip-png-check', + help=_ ("do not fail if no PNG images are found for EPS files"), + metavar=_ ("DIR"), + 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."), + help=_ ("write lily-XXX files to DIR, link into --output dir"), metavar=_ ("DIR"), action='store', dest='lily_output_dir', default=None) @@ -163,11 +184,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, @@ -181,7 +197,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')) @@ -189,6 +205,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' @@ -198,6 +220,8 @@ global_options = None default_ly_options = { 'alt': "[image of music]" } +document_language = '' + # # Is this pythonic? Personally, I find this rather #define-nesque. --hwn # @@ -218,6 +242,7 @@ LILYQUOTE = 'lilyquote' NOFRAGMENT = 'nofragment' NOINDENT = 'noindent' NOQUOTE = 'noquote' +NORAGGED_RIGHT = 'noragged-right' NOTES = 'body' NOTIME = 'notime' OUTPUT = 'output' @@ -230,6 +255,7 @@ QUOTE = 'quote' RAGGED_RIGHT = 'ragged-right' RELATIVE = 'relative' STAFFSIZE = 'staffsize' +DOCTITLE = 'doctitle' TEXIDOC = 'texidoc' TEXINFO = 'texinfo' VERBATIM = 'verbatim' @@ -516,6 +542,7 @@ simple_options = [ NOFRAGMENT, NOINDENT, PRINTFILENAME, + DOCTITLE, TEXIDOC, LANG, VERBATIM, @@ -543,6 +570,8 @@ ly_options = { RAGGED_RIGHT: r'''ragged-right = ##t''', + NORAGGED_RIGHT: r'''ragged-right = ##f''', + PACKED: r'''packed = ##t''', }, @@ -550,12 +579,12 @@ ly_options = { LAYOUT: { NOTIME: r''' \context { - \Score - timing = ##f + \Score + timing = ##f } \context { - \Staff - \remove Time_signature_engraver + \Staff + \remove "Time_signature_engraver" }''', }, @@ -808,9 +837,31 @@ 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) + +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) + 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) @@ -818,9 +869,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: @@ -841,9 +892,6 @@ class Chunk: def filter_text (self): return self.replacement_text () - def is_outdated (self): - return False - def is_plain (self): return False @@ -901,7 +949,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') @@ -955,6 +1003,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 @@ -1036,18 +1090,14 @@ class LilypondSnippet (Snippet): if c_key: if c_value: warning ( - _ ("deprecated ly-option used: %s=%s" - % (key, value))) + _ ("deprecated ly-option used: %s=%s") % (key, value)) warning ( - _ ("compatibility mode translation: %s=%s" - % (c_key, c_value))) + _ ("compatibility mode translation: %s=%s") % (c_key, c_value)) else: warning ( - _ ("deprecated ly-option used: %s" - % key)) + _ ("deprecated ly-option used: %s") % key) warning ( - _ ("compatibility mode translation: %s" - % c_key)) + _ ("compatibility mode translation: %s") % c_key) (key, value) = (c_key, c_value) @@ -1102,27 +1152,28 @@ 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] - if global_options.lily_output_dir: - name = os.path.join (global_options.lily_output_dir, name) + name = '%s/lily-%s' % (cs[:2], cs[2:10]) return name def write_ly (self): - out = file (self.basename () + '.ly', 'w') + 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 (self.basename () + '.txt', 'w').write ('image of music') + file (path + '.txt', 'w').write ('image of music') def relevant_contents (self, ly): return re.sub (r'\\(version|sourcefileline|sourcefilename)[^\n]*\n', '', ly) def link_all_output_files (self, output_dir, output_dir_files, destination): - existing = self.all_output_files (output_dir_files) + 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)) @@ -1131,67 +1182,92 @@ 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) - def all_output_files (self, output_dir_files): + 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. """ - class Missing(Exception): - pass - - result = set() - base = os.path.basename(self.basename()) + 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 not in output_dir_files: - raise Missing - result.add (name) - - try: - for required in [base + '.ly', - base + '.txt', - base + '-systems.count']: - require_file (required) - - map (consider_file, [base + '.tex', - base + '.eps', - base + '-systems.texi', - base + '-systems.tex', - base + '-systems.pdftexi']) - - if base + '.eps' in result and self.format in (HTML, TEXINFO): - page_count = ps_page_count (self.basename() + '.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 = int(file (self.basename () + '-systems.count').read()) - for number in range(1, system_count + 1): - systemfile = '%s-%d' % (base, number) - require_file (systemfile + '.eps') - consider_file (systemfile + '.pdf') - except Missing: - return None - - return result + 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, current_files): - return self.all_output_files (current_files) is None + 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 = { @@ -1304,9 +1380,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): @@ -1348,7 +1431,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') @@ -1382,7 +1465,7 @@ def find_linestarts (s): def find_toplevel_snippets (input_string, format, types): res = {} for t in types: - res[t] = ly.re.compile (snippet_res[format][t]) + res[t] = re.compile (snippet_res[format][t]) snippets = [] index = 0 @@ -1513,12 +1596,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) @@ -1554,7 +1637,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) @@ -1614,15 +1698,27 @@ def write_file_map (lys, name): #(define output-empty-score-list #f) #(ly:add-file-name-alist '(%s ))\n -""" % '\n'.join('("%s.ly" . "%s")\n' % (ly.basename (), name) - for ly in lys)) +""" % '\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)) - outdated = [c for c in snippets if c.is_outdated (output_files)] + 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) progress (_ ("Writing snippets...")) @@ -1640,7 +1736,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, @@ -1688,9 +1784,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') @@ -1709,7 +1810,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 @@ -1727,6 +1828,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]) @@ -1797,7 +1900,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), @@ -1849,8 +1952,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. @@ -1864,7 +1972,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)