X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Flilypond-book.py;h=1f925c13bde7094a954d579f3e64c35cc4bc0c08;hb=e72fc73821781e72d20730f0f79c2a16f52cc947;hp=5e16f6b3971db50e4ebfd8133a6160676c6bef0f;hpb=6df8b9af705dfe7cb12dcea625a4e9ab00cc6cae;p=lilypond.git diff --git a/scripts/lilypond-book.py b/scripts/lilypond-book.py index 5e16f6b397..1f925c13bd 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,14 +155,20 @@ def get_option_parser (): action='store', dest='output_dir', default='') - p.add_option ('--no-lily-run', - help=_ ("do not fail if no lilypond output is found."), + 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) @@ -169,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, @@ -187,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')) @@ -204,6 +214,8 @@ global_options = None default_ly_options = { 'alt': "[image of music]" } +document_language = '' + # # Is this pythonic? Personally, I find this rather #define-nesque. --hwn # @@ -224,6 +236,7 @@ LILYQUOTE = 'lilyquote' NOFRAGMENT = 'nofragment' NOINDENT = 'noindent' NOQUOTE = 'noquote' +NORAGGED_RIGHT = 'noragged-right' NOTES = 'body' NOTIME = 'notime' OUTPUT = 'output' @@ -236,6 +249,7 @@ QUOTE = 'quote' RAGGED_RIGHT = 'ragged-right' RELATIVE = 'relative' STAFFSIZE = 'staffsize' +DOCTITLE = 'doctitle' TEXIDOC = 'texidoc' TEXINFO = 'texinfo' VERBATIM = 'verbatim' @@ -522,6 +536,7 @@ simple_options = [ NOFRAGMENT, NOINDENT, PRINTFILENAME, + DOCTITLE, TEXIDOC, LANG, VERBATIM, @@ -549,6 +564,8 @@ ly_options = { RAGGED_RIGHT: r'''ragged-right = ##t''', + NORAGGED_RIGHT: r'''ragged-right = ##f''', + PACKED: r'''packed = ##t''', }, @@ -556,12 +573,12 @@ ly_options = { LAYOUT: { NOTIME: r''' \context { - \Score - timing = ##f + \Score + timing = ##f } \context { - \Staff - \remove Time_signature_engraver + \Staff + \remove "Time_signature_engraver" }''', }, @@ -814,9 +831,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) @@ -824,9 +863,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: @@ -904,7 +943,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') @@ -958,6 +997,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 @@ -1101,19 +1146,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') @@ -1122,9 +1164,9 @@ class LilypondSnippet (Snippet): 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, output_dir_files) - if existing is None: - print '\nMissing', self.basename() + 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: @@ -1134,6 +1176,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) @@ -1142,65 +1187,81 @@ class LilypondSnippet (Snippet): output_dir_files is the list of files in the output directory. """ - class Missing(Exception): - pass - - result = set() + 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) + if name in output_dir_files: + result.add (name) + else: + missing.add (name) + # UGH - junk global_options skip_lily = global_options.skip_lilypond_run - try: - for required in [base + '.ly', - base + '.txt']: - require_file (required) - if not skip_lily: - require_file (base + '-systems.count') - - map (consider_file, [base + '.tex', - base + '.eps', - base + '.texidoc', - 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 (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) + 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') - system_count = 0 - if not skip_lily: - 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') - except Missing: - return None - - return result + + return (result, missing) def is_outdated (self, output_dir, current_files): - return self.all_output_files (output_dir, current_files) is None + 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 = { @@ -1313,9 +1374,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): @@ -1357,7 +1425,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') @@ -1522,9 +1590,9 @@ 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]), @@ -1563,7 +1631,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) @@ -1623,14 +1692,26 @@ 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)) + 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) @@ -1649,7 +1730,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, @@ -1697,9 +1778,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') @@ -1718,7 +1804,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 @@ -1736,6 +1822,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]) @@ -1806,7 +1894,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), @@ -1858,8 +1946,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. @@ -1873,7 +1966,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)