X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Flilypond-book.py;h=e1d1824d75b013f51d6c0736c579ed469c7d4722;hb=a8bf97b6b7f724102ecef1d220c71f090bc45657;hp=495c3683a7685aa4751f01c3c3811dea4bb61545;hpb=722aaff887f824e0c5f731f2c7e45dca39274ca7;p=lilypond.git diff --git a/scripts/lilypond-book.py b/scripts/lilypond-book.py index 495c3683a7..e1d1824d75 100644 --- a/scripts/lilypond-book.py +++ b/scripts/lilypond-book.py @@ -15,7 +15,7 @@ classic lilypond-book: TODO: * this script is too complex. Modularize. - + * ly-options: intertext? * --line-width? * eps in latex / eps by lilypond -b ps? @@ -28,8 +28,7 @@ TODO: ''' -import commands -import md5 +import glob import os import re import stat @@ -42,13 +41,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' @@ -57,13 +69,13 @@ _ ("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 ', - 'Han-Wen Nienhuys ') + 'Han-Wen Nienhuys ') ################################################################ def exit (i): @@ -95,14 +107,14 @@ def warranty (): ly.encoded_write (sys.stdout, ''' %s -%s + %s %s %s -''' % ( _ ('Copyright (c) %s by') % '2001--2007', - ' '.join (authors), - _ ("Distributed under terms of the GNU General Public License."), - _ ("It comes with NO WARRANTY."))) +''' % ( _ ('Copyright (c) %s by') % '2001--2009', + '\n '.join (authors), + _ ("Distributed under terms of the GNU General Public License."), + _ ("It comes with NO WARRANTY."))) def get_option_parser (): p = ly.get_option_parser (usage=_ ("%s [OPTION]... FILE") % 'lilypond-book', @@ -112,11 +124,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", @@ -135,27 +148,45 @@ def get_option_parser (): action='store', dest='info_images_dir', default='') - p.add_option ('--left-padding', + 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", help=_ ("pad left side of music to align music inspite of uneven bar numbers (in mm)"), type="float", default=3.0) - + p.add_option ("-o", '--output', help=_ ("write output to DIR"), metavar=_ ("DIR"), - action='store', dest='output_name', + 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) - + p.add_option ('-P', '--process', metavar=_ ("COMMAND"), help = _ ("process ly_files using COMMAND FILE..."), - action='store', + action='store', dest='process_cmd', default='') p.add_option ('--pdf', @@ -164,11 +195,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, @@ -182,14 +208,21 @@ 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')), - description=(_ ("Report bugs via") - + ''' http://post.gmane.org/post.php''' - '''?group=gmane.comp.gnu.lilypond.bugs\n''')) + p.add_option_group ('', + description=( + _ ("Report bugs via %s") + % ' http://post.gmane.org/post.php' + '?group=gmane.comp.gnu.lilypond.bugs') + '\n') return p 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' @@ -199,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 # @@ -211,18 +246,20 @@ 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' -PACKED = 'packed' PAPER = 'paper' PREAMBLE = 'preamble' PRINTFILENAME = 'printfilename' @@ -230,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' @@ -250,6 +289,9 @@ no_options = { # # (?Pregex) -- Assign result of REGEX to NAME. # *? -- Match non-greedily. +# (?!...) -- Match if `...' doesn't match next (without consuming +# the string). +# # (?m) -- Multiline regex: Make ^ and $ match at each line. # (?s) -- Make the dot match all characters including newline. # (?x) -- Ignore whitespace in patterns. @@ -263,17 +305,36 @@ snippet_res = { 'lilypond': r'''(?smx) (?P - <(?P(inline)?)mediaobject>\s*\s*.*?)")?>(?P.*?)\s*\s*)''', + <(?P(inline)?)mediaobject>\s* + \s* + .*?)")?> + (?P.*?) + \s* + \s* + )''', 'lilypond_block': r'''(?smx) (?P - <(?P(inline)?)mediaobject>\s*\s*.*?)")?>(?P.*?)\s*\s*)''', + <(?P(inline)?)mediaobject>\s* + \s* + .*?)")?> + (?P.*?) + \s* + \s* + )''', 'lilypond_file': r'''(?smx) (?P - <(?P(inline)?)mediaobject>\s*\s*.*?\.ly)"\s*(role="(?P.*?)")?\s*(/>|>\s*)\s*\s*)''', + <(?P(inline)?)mediaobject>\s* + \s* + |>\s*)\s* + \s* + )''', 'multiline_comment': r'''(?smx) @@ -289,9 +350,11 @@ snippet_res = { no_match, 'verbatim': - no_match, - - }, + no_match, + + 'lilypondversion': + no_match, + }, ## HTML: { 'include': @@ -343,6 +406,11 @@ snippet_res = { (?s) (?P (?P
\s.*?
\s))''', + + 'lilypondversion': + r'''(?mx) + (?P + )''', }, ## @@ -416,6 +484,12 @@ snippet_res = { \\begin\s*{verbatim} .*? \\end\s*{verbatim}))''', + + 'lilypondversion': + r'''(?smx) + (?P + \\lilypondversion)[^a-zA-Z]''', + }, ## @@ -482,17 +556,21 @@ snippet_res = { @example \s.*? @end\s+example\s))''', - }, -} + 'lilypondversion': + r'''(?mx) + [^@](?P + @lilypondversion)[^a-zA-Z]''', + }, +} format_res = { - DOCBOOK: { - 'intertext': r',?\s*intertext=\".*?\"', + DOCBOOK: { + 'intertext': r',?\s*intertext=\".*?\"', 'option_sep': '\s*', - }, + }, HTML: { 'intertext': r',?\s*intertext=\".*?\"', 'option_sep': '\s*', @@ -509,14 +587,18 @@ format_res = { }, } + # Options without a pattern in ly_options. simple_options = [ EXAMPLEINDENT, FRAGMENT, NOFRAGMENT, + NOGETTEXT, NOINDENT, PRINTFILENAME, + DOCTITLE, TEXIDOC, + LANG, VERBATIM, FONTLOAD, FILENAME, @@ -542,19 +624,19 @@ ly_options = { RAGGED_RIGHT: r'''ragged-right = ##t''', - PACKED: r'''packed = ##t''', + NORAGGED_RIGHT: r'''ragged-right = ##f''', }, ## LAYOUT: { NOTIME: r''' \context { - \Score - timing = ##f + \Score + timing = ##f } \context { - \Staff - \remove Time_signature_engraver + \Staff + \remove "Time_signature_engraver" }''', }, @@ -566,19 +648,37 @@ ly_options = { output = { ## - DOCBOOK: { - FILTER: r'''%(code)s''', - - OUTPUT: r''' - - - - - ''', - - VERBATIM: r'''%(verb)s''', - - PRINTFILENAME: '%(filename)s' + DOCBOOK: { + FILTER: r''' + + +%(code)s + + +''', + + OUTPUT: r''' + + + + +''', + + VERBATIM: r''' +%(verb)s''', + + VERSION: program_version, + + PRINTFILENAME: r''' + + + + %(filename)s + + + +''' }, ## HTML: { @@ -595,8 +695,10 @@ output = { ''', OUTPUT: r''' - %(alt)s''', + %(alt)s''', PRINTFILENAME: '

%(filename)s

', @@ -607,34 +709,38 @@ output = { VERBATIM: r'''
 %(verb)s
''', + + VERSION: program_version, }, ## LATEX: { OUTPUT: r'''{%% -\parindent 0pt%% -\ifx\preLilyPondExample \undefined%% - \relax%% -\else%% - \preLilyPondExample%% -\fi%% +\parindent 0pt +\ifx\preLilyPondExample \undefined +\else + \expandafter\preLilyPondExample +\fi \def\lilypondbook{}%% -\input %(base)s-systems.tex%% -\ifx\postLilyPondExample \undefined%% - \relax%% -\else%% - \postLilyPondExample%% -\fi%% +\input %(base)s-systems.tex +\ifx\postLilyPondExample \undefined +\else + \expandafter\postLilyPondExample +\fi }''', PRINTFILENAME: '''\\texttt{%(filename)s} - ''', +''', - QUOTE: r'''\begin{quotation}%(str)s + QUOTE: r'''\begin{quotation} +%(str)s \end{quotation}''', VERBATIM: r'''\noindent -\begin{verbatim}%(verb)s\end{verbatim}''', +\begin{verbatim}%(verb)s\end{verbatim} +''', + + VERSION: program_version, FILTER: r'''\begin{lilypond}[%(options)s] %(code)s @@ -661,7 +767,9 @@ output = {

%(alt)s + border="0" + src="%(image)s" + alt="%(alt)s">

@end html @@ -690,6 +798,8 @@ output = { %(verb)s@end verbatim ''', + VERSION: program_version, + ADDVERSION: r'''@example \version @w{"@version{}"} @end example @@ -714,7 +824,7 @@ PREAMBLE_LY = '''%%%% Generated by %(program_name)s %% **************************************************************** -%% Start cut-&-pastable-section +%% Start cut-&-pastable-section %% **************************************************************** %(preamble_string)s @@ -796,7 +906,7 @@ def find_file (name, raise_error=True): full = os.path.join (i, name) if os.path.exists (full): return full - + if raise_error: error (_ ("file not found: %s") % name + '\n') exit (1) @@ -807,26 +917,48 @@ def verbatim_html (s): re.sub ('<', '<', re.sub ('&', '&', s))) -def split_options (option_string): - if option_string: - if global_options.format == HTML: - options = re.findall('[\w\.-:]+(?:\s*=\s*(?:"[^"]*"|\'[^\']*\'|\S+))?',option_string) - for i in range(len(options)): - options[i] = re.sub('^([^=]+=\s*)(?P["\'])(.*)(?P=q)','\g<1>\g<3>',options[i]) - return options - else: - return re.split (format_res[global_options.format]['option_sep'], - option_string) - return [] +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 set_default_options (source): - global default_ly_options - if not default_ly_options.has_key (LINE_WIDTH): - if global_options.format == LATEX: +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) default_ly_options[LINE_WIDTH] = '%.0f\\pt' % textwidth - elif global_options.format == TEXINFO: - for (k, v) in texinfo_line_widths.items (): + elif format == TEXINFO: + m = texinfo_lang_re.search (source) + if m and not m.group (1).startswith ('en'): + document_language = m.group (1) + else: + document_language = '' + for regex in texinfo_line_widths: # FIXME: @layout is usually not in # chunk #0: # @@ -835,8 +967,8 @@ def set_default_options (source): # Bluntly search first K items of # source. # s = chunks[0].replacement_text () - if re.search (k, source[:1024]): - default_ly_options[LINE_WIDTH] = v + if re.search (regex, source[:1024]): + default_ly_options[LINE_WIDTH] = texinfo_line_widths[regex] break class Chunk: @@ -846,15 +978,9 @@ class Chunk: def filter_text (self): return self.replacement_text () - def ly_is_outdated (self): - return False - - def png_is_outdated (self): - return False - def is_plain (self): return False - + class Substring (Chunk): """A string that does not require extra memory.""" def __init__ (self, source, start, end, line_number): @@ -863,7 +989,7 @@ class Substring (Chunk): self.end = end self.line_number = line_number self.override_text = None - + def is_plain (self): return True @@ -894,7 +1020,7 @@ class Snippet (Chunk): class IncludeSnippet (Snippet): def processed_filename (self): f = self.substring ('filename') - return os.path.splitext (f)[0] + format2ext[global_options.format] + return os.path.splitext (f)[0] + format2ext[self.format] def replacement_text (self): s = self.match.group ('match') @@ -909,7 +1035,10 @@ class LilypondSnippet (Snippet): self.do_options (os, self.type) def verb_ly (self): - return self.substring ('code') + if NOGETTEXT in self.option_dict: + return self.substring ('code') + else: + return verb_ly_gettext (self.substring ('code')) def ly (self): contents = self.substring ('code') @@ -922,21 +1051,34 @@ class LilypondSnippet (Snippet): return self.compose_ly (s) return '' + def split_options (self, option_string): + if option_string: + if self.format == HTML: + options = re.findall('[\w\.-:]+(?:\s*=\s*(?:"[^"]*"|\'[^\']*\'|\S+))?', + option_string) + options = [re.sub('^([^=]+=\s*)(?P["\'])(.*)(?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 = split_options (option_string) + options = self.split_options (option_string) - for i in options: - if '=' in i: - (key, value) = re.split ('\s*=\s*', i) + for option in options: + if '=' in option: + (key, value) = re.split ('\s*=\s*', option) self.option_dict[key] = value else: - if i in no_options.keys (): - if no_options[i] in self.option_dict.keys (): - del self.option_dict[no_options[i]] + if option in no_options: + if no_options[option] in self.option_dict: + del self.option_dict[no_options[option]] else: - self.option_dict[i] = None + self.option_dict[option] = None has_line_width = self.option_dict.has_key (LINE_WIDTH) no_line_width_value = 0 @@ -946,39 +1088,45 @@ class LilypondSnippet (Snippet): no_line_width_value = 1 del self.option_dict[LINE_WIDTH] - for i in default_ly_options.keys (): - if i not in self.option_dict.keys (): - self.option_dict[i] = default_ly_options[i] + 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.keys (): + 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.keys (): + if LINE_WIDTH in self.option_dict: del self.option_dict[LINE_WIDTH] else: - if RAGGED_RIGHT in self.option_dict.keys (): - if LINE_WIDTH in self.option_dict.keys (): + 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.keys () or type == 'lilypond': - if LINE_WIDTH in self.option_dict.keys (): + 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.keys (): + if not INDENT in self.option_dict: self.option_dict[INDENT] = '0\\mm' # The QUOTE pattern from ly_options only emits the `line-width' # keyword. - if has_line_width and QUOTE in self.option_dict.keys (): + if has_line_width and QUOTE in self.option_dict: if no_line_width_value: del self.option_dict[LINE_WIDTH] else: del self.option_dict[QUOTE] def compose_ly (self, code): - if FRAGMENT in self.option_dict.keys (): + if FRAGMENT in self.option_dict: body = FRAGMENT_LY else: body = FULL_LY @@ -1031,18 +1179,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) @@ -1063,7 +1207,7 @@ class LilypondSnippet (Snippet): warning (_ ("ignoring unknown ly option: %s") % key) # URGS - if RELATIVE in override.keys () and override[RELATIVE]: + if RELATIVE in override and override[RELATIVE]: relative = int (override[RELATIVE]) relative_quotes = '' @@ -1089,85 +1233,137 @@ class LilypondSnippet (Snippet): def get_checksum (self): if not self.checksum: - hash = md5.md5 (self.relevant_contents (self.full_ly ())) + # Work-around for md5 module deprecation warning in python 2.5+: + try: + from hashlib import md5 + except ImportError: + from md5 import md5 + + hash = md5 (self.relevant_contents (self.full_ly ())) ## let's not create too long names. self.checksum = hash.hexdigest ()[:10] - + 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[:2], cs[2:]) - 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 ly_is_outdated (self): - base = self.basename () - ly_file = find_file (base + '.ly', raise_error=False) - tex_file = find_file (base + '.tex', raise_error=False) - systems_file = find_file (base + '-systems.tex', raise_error=False) - - if (not os.path.exists (ly_file) - or not os.path.exists (systems_file)): - return True - - lines = file (systems_file).readlines () - if not lines: - return True - - if not re.match ('% eof', lines[-1]): - return true - - # and FILENAME in self.option_dict - #huh? - - if (self.relevant_contents (self.full_ly ()) - != self.relevant_contents (file (ly_file).read ())): - return True - - return False - - def png_is_outdated (self): - base = self.basename () - eps_file = find_file (base + '.eps', raise_error=False) - png_file = find_file (base + '.png', raise_error=False) - if self.ly_is_outdated (): - return True - - if (global_options.format in (HTML, TEXINFO) - and os.path.exists (eps_file)): - page_count = ps_page_count (eps_file) + 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: - return not os.path.exists (png_file) + require_file (base + '.png') else: - for a in range (1, page_count + 1): - if not find_file (base + '-page%d.png' % a, - raise_error=False): - return True - - return False - + 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 = { @@ -1187,13 +1383,14 @@ class LilypondSnippet (Snippet): single = '%(base)s.png' % vars () multiple = '%(base)s-page1.png' % vars () images = (single,) - if (os.path.exists (multiple) + 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, a) for a in range (1, count+1)] + images = ['%s-page%d.png' % (base, page) for page in range (1, count+1)] images = tuple (images) + return images def output_docbook (self): @@ -1202,8 +1399,8 @@ class LilypondSnippet (Snippet): 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 += self.output_print_filename (DOCBOOK) + if (self.substring('inline') == 'inline'): str = '' + str + '' else: str = '' + str + '' @@ -1211,11 +1408,11 @@ class LilypondSnippet (Snippet): verb = verbatim_html (self.verb_ly ()) str = output[DOCBOOK][VERBATIM] % vars () + str return str - + def output_html (self): str = '' base = self.basename () - if global_options.format == HTML: + if self.format == HTML: str += self.output_print_filename (HTML) if VERBATIM in self.option_dict: verb = verbatim_html (self.verb_ly ()) @@ -1244,13 +1441,13 @@ class LilypondSnippet (Snippet): str += output[TEXINFO][OUTPUTIMAGE] % vars () base = self.basename () - str += output[global_options.format][OUTPUT] % vars () + str += output[self.format][OUTPUT] % vars () return str def output_latex (self): str = '' base = self.basename () - if global_options.format == LATEX: + if self.format == LATEX: str += self.output_print_filename (LATEX) if VERBATIM in self.option_dict: verb = self.verb_ly () @@ -1262,7 +1459,7 @@ class LilypondSnippet (Snippet): if 0: breaks = self.ly ().count ("\n") str += "".ljust (breaks, "\n").replace ("\n","%\n") - + if QUOTE in self.option_dict: str = output[LATEX][QUOTE] % vars () return str @@ -1279,9 +1476,19 @@ 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\n' % open (translated_doctitle).read () + elif os.path.exists (doctitle): + str += '@lydoctitle %s\n\n' % open (doctitle).read () if TEXIDOC in self.option_dict: texidoc = base + '.texidoc' - if os.path.exists (texidoc): + translated_texidoc = texidoc + document_language + if os.path.exists (translated_texidoc): + str += '@include %(translated_texidoc)s\n\n' % vars () + elif os.path.exists (texidoc): str += '@include %(texidoc)s\n\n' % vars () substr = '' @@ -1320,7 +1527,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') @@ -1328,11 +1535,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): @@ -1351,16 +1568,16 @@ def find_linestarts (s): nls.append (len (s)) return nls -def find_toplevel_snippets (s, types): +def find_toplevel_snippets (input_string, format, types): res = {} - for i in types: - res[i] = ly.re.compile (snippet_res[global_options.format][i]) + for t in types: + res[t] = re.compile (snippet_res[format][t]) snippets = [] index = 0 found = dict ([(t, None) for t in types]) - line_starts = find_linestarts (s) + line_starts = find_linestarts (input_string) line_start_idx = 0 # We want to search for multiple regexes, without searching # the string multiple times for one regex. @@ -1375,15 +1592,14 @@ def find_toplevel_snippets (s, types): for type in types: if not found[type] or found[type][0] < index: found[type] = None - - m = res[type].search (s[index:endex]) + + m = res[type].search (input_string[index:endex]) if not m: continue - cl = Snippet - if snippet_type_to_class.has_key (type): - cl = snippet_type_to_class[type] - + klass = Snippet + if type in snippet_type_to_class: + klass = snippet_type_to_class[type] start = index + m.start ('match') line_number = line_start_idx @@ -1391,12 +1607,12 @@ def find_toplevel_snippets (s, types): line_number += 1 line_number += 1 - snip = cl (type, m, global_options.format, line_number) + snip = klass (type, m, format, line_number) found[type] = (start, snip) - if (found[type] - and (not first + if (found[type] + and (not first or found[type][0] < found[first][0])): first = type @@ -1413,14 +1629,14 @@ def find_toplevel_snippets (s, types): endex = found[first][0] if not first: - snippets.append (Substring (s, index, len (s), line_start_idx)) + snippets.append (Substring (input_string, index, len (input_string), line_start_idx)) break while (start > line_starts[line_start_idx+1]): line_start_idx += 1 (start, snip) = found[first] - snippets.append (Substring (s, index, start, line_start_idx + 1)) + snippets.append (Substring (input_string, index, start, line_start_idx + 1)) snippets.append (snip) found[first] = None index = start + len (snip.match.group ('match')) @@ -1429,7 +1645,7 @@ def find_toplevel_snippets (s, types): def filter_pipe (input, cmd): """Pass input through cmd, and return the result.""" - + if global_options.verbose: progress (_ ("Opening filter `%s'") % cmd) @@ -1464,33 +1680,36 @@ def system_in_directory (cmd, directory): Because of win32 compatibility, we can't simply use subprocess. """ - + current = os.getcwd() os.chdir (directory) - ly.system(cmd, be_verbose=global_options.verbose, + ly.system(cmd, be_verbose=global_options.verbose, progress_p=1) os.chdir (current) - -def process_snippets (cmd, snippets): + +def process_snippets (cmd, snippets, + format, lily_output_dir): """Run cmd on all of the .ly files from snippets.""" + if not snippets: return - - if global_options.format in (HTML, TEXINFO) and '--formats' not in cmd: + + if format in (HTML, TEXINFO) and '--formats' not in cmd: cmd += ' --formats=png ' - elif global_options.format in (DOCBOOK) and '--formats' not in cmd: + elif format in (DOCBOOK) and '--formats' not in cmd: cmd += ' --formats=png,pdf ' checksum = snippet_list_checksum (snippets) - contents = '\n'.join (['snippet-map-%d.ly' % checksum] - + [snip.basename() for snip in snippets]) - name = os.path.join (global_options.lily_output_dir, - 'snippet-names-%d' % checksum) + contents = '\n'.join (['snippet-map-%d.ly' % checksum] + + [snip.basename() + '.ly' for snip in snippets]) + name = os.path.join (lily_output_dir, + 'snippet-names-%d.ly' % checksum) file (name, 'wb').write (contents) - system_in_directory (' '.join ([cmd, name]), - global_options.lily_output_dir) + system_in_directory (' '.join ([cmd, ly.mkarg (name)]), + lily_output_dir) + ### # Retrieve dimensions from LaTeX @@ -1509,13 +1728,13 @@ def get_latex_textwidth (source): m = re.search (r'''(?P\\begin\s*{document})''', source) if m == None: warning (_ ("cannot find \\begin{document} in LaTeX document")) - + ## what's a sensible default? return 550.0 - + preamble = source[:m.start (0)] latex_document = LATEX_INSPECTION_DOCUMENT % vars () - + (handle, tmpfile) = tempfile.mkstemp('.tex') logfile = os.path.splitext (tmpfile)[0] + '.log' logfile = os.path.split (logfile)[1] @@ -1523,10 +1742,11 @@ def get_latex_textwidth (source): tmp_handle = os.fdopen (handle,'w') 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) os.unlink (logfile) @@ -1557,7 +1777,7 @@ def modify_preamble (chunk): r"\\usepackage{graphics}" + '\n' + r"\\begin{document}", str) - chunk.override_text = str + chunk.override_text = str format2ext = { @@ -1578,23 +1798,35 @@ def write_file_map (lys, name): snippet_map = file (os.path.join ( global_options.lily_output_dir, 'snippet-map-%d.ly' % snippet_list_checksum (lys)), 'w') + snippet_map.write (""" #(define version-seen #t) #(define output-empty-score-list #f) -#(ly:add-file-name-alist '( -""") - - for ly in lys: - snippet_map.write ('("%s.ly" . "%s")\n' - % (ly.basename (), name)) - - snippet_map.write ('))\n') - -def do_process_cmd (chunks, input_name): - outdated = [c for c in chunks - if (isinstance (c, LilypondSnippet) - and (c.ly_is_outdated () or c.png_is_outdated ()))] - write_file_map (outdated, input_name) +#(ly:add-file-name-alist '(%s + ))\n +""" % '\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 = 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...")) for snippet in outdated: snippet.write_ly() @@ -1603,9 +1835,19 @@ def do_process_cmd (chunks, input_name): if outdated: progress (_ ("Processing...")) progress ('\n') - process_snippets (global_options.process_cmd, outdated) + process_snippets (options.process_cmd, outdated, + options.format, options.lily_output_dir) + else: progress (_ ("All snippets are up to date...")) + + if options.lily_output_dir != options.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, + options.output_dir) + progress ('\n') @@ -1627,7 +1869,7 @@ ext2format = { def guess_format (input_filename): format = None e = os.path.splitext (input_filename)[1] - if e in ext2format.keys (): + if e in ext2format: # FIXME format = ext2format[e] else: @@ -1648,9 +1890,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') @@ -1669,7 +1916,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 @@ -1687,35 +1934,37 @@ 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]) - # Only default to stdout when filtering. - if global_options.output_name == '-' or (not global_options.output_name and global_options.filter_cmd): - output_filename = '-' - output_file = sys.stdout + # don't complain when global_options.output_dir is existing + if not global_options.output_dir: + global_options.output_dir = os.getcwd() else: - # don't complain when global_options.output_name is existing - output_filename = input_base + format2ext[global_options.format] - if global_options.output_name: - if not os.path.isdir (global_options.output_name): - os.mkdir (global_options.output_name, 0777) - os.chdir (global_options.output_name) - else: - if (os.path.exists (input_filename) - and os.path.exists (output_filename) - and samefile (output_filename, input_fullname)): - error ( - _ ("Output would overwrite input file; use --output.")) - exit (2) + global_options.output_dir = os.path.abspath(global_options.output_dir) + + if not os.path.isdir (global_options.output_dir): + os.mkdir (global_options.output_dir, 0777) + os.chdir (global_options.output_dir) + + output_filename = os.path.join(global_options.output_dir, + input_base + format2ext[global_options.format]) + if (os.path.exists (input_filename) + and os.path.exists (output_filename) + and samefile (output_filename, input_fullname)): + error ( + _ ("Output would overwrite input file; use --output.")) + exit (2) try: progress (_ ("Reading %s...") % input_fullname) source = in_handle.read () progress ('\n') - set_default_options (source) + set_default_options (source, default_ly_options, global_options.format) # FIXME: Containing blocks must be first, see @@ -1729,9 +1978,10 @@ def do_file (input_filename): 'lilypond_file', 'include', 'lilypond', + 'lilypondversion', ) progress (_ ("Dissecting...")) - chunks = find_toplevel_snippets (source, snippet_types) + chunks = find_toplevel_snippets (source, global_options.format, snippet_types) if global_options.format == LATEX: for c in chunks: @@ -1745,26 +1995,26 @@ def do_file (input_filename): write_if_updated (output_filename, [c.filter_text () for c in chunks]) elif global_options.process_cmd: - do_process_cmd (chunks, input_fullname) + do_process_cmd (chunks, input_fullname, global_options) progress (_ ("Compiling %s...") % output_filename) progress ('\n') write_if_updated (output_filename, [s.replacement_text () for s in chunks]) - + def process_include (snippet): os.chdir (original_dir) 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), - chunks)) + filter (lambda x: isinstance (x, IncludeSnippet), + chunks)) return chunks + reduce (lambda x, y: x + y, include_chunks, []) - + except CompileError: os.chdir (original_dir) progress (_ ("Removing `%s'") % output_filename) @@ -1776,36 +2026,19 @@ def do_options (): opt_parser = get_option_parser() (global_options, args) = opt_parser.parse_args () - if global_options.format in ('texi-html', 'texi'): global_options.format = TEXINFO global_options.include_path = map (os.path.abspath, global_options.include_path) - + if global_options.warranty: warranty () exit (0) if not args or len (args) > 1: opt_parser.print_help () exit (2) - - return args - -def psfonts_warning (options, basename): - if options.format in (TEXINFO, LATEX): - psfonts_file = os.path.join (options.output_name, basename + '.psfonts') - output = os.path.join (options.output_name, basename + '.dvi' ) - if not options.create_pdf: - if not options.psfonts: - warning (_ ("option --psfonts not used")) - warning (_ ("processing with dvips will have no fonts")) - else: - progress ('\n') - progress (_ ("DVIPS usage:")) - progress ('\n') - progress (" dvips -h %(psfonts_file)s %(output)s" % vars ()) - progress ('\n') + return args def main (): # FIXME: 85 lines of `main' macramee?? @@ -1813,7 +2046,7 @@ def main (): basename = os.path.splitext (files[0])[0] basename = os.path.split (basename)[1] - + if not global_options.format: global_options.format = guess_format (files[0]) @@ -1822,67 +2055,55 @@ def main (): formats += ',png' if global_options.process_cmd == '': - global_options.process_cmd = (lilypond_binary + global_options.process_cmd = (lilypond_binary + ' --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. global_options.process_cmd += ' --formats=eps ' if global_options.create_pdf: global_options.process_cmd += "--pdf -dinclude-eps-fonts -dgs-load-fonts " - + if global_options.verbose: global_options.process_cmd += " --verbose " 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) if not os.path.isdir (global_options.lily_output_dir): os.makedirs (global_options.lily_output_dir) else: - global_options.lily_output_dir = os.path.abspath(global_options.output_name) - + global_options.lily_output_dir = os.path.abspath(global_options.output_dir) + identify () try: chunks = do_file (files[0]) - if global_options.psfonts: - fontextract.verbose = global_options.verbose - snippet_chunks = filter ( - lambda x: isinstance (x, LilypondSnippet), - chunks) - - psfonts_file = basename + '.psfonts' - if not global_options.verbose: - progress (_ ("Writing fonts to %s...") % psfonts_file) - fontextract.extract_fonts ( - psfonts_file, - [x.basename() + '.eps' for x in snippet_chunks]) - if not global_options.verbose: - progress ('\n') - except CompileError: exit (1) - psfonts_warning (global_options, basename) - inputs = note_input_file ('') inputs.pop () base_file_name = os.path.splitext (os.path.basename (files[0]))[0] - dep_file = os.path.join (global_options.output_name, base_file_name + '.dep') - final_output_file = os.path.join (global_options.output_name, + dep_file = os.path.join (global_options.output_dir, base_file_name + '.dep') + final_output_file = os.path.join (global_options.output_dir, base_file_name + '.%s' % global_options.format) - + os.chdir (original_dir) file (dep_file, 'w').write ('%s: %s' % (final_output_file, ' '.join (inputs)))