--- /dev/null
+# -*- coding: utf-8 -*-
+
+import book_base as BookBase
+import lilylib as ly
+global _;_=ly._
+import re
+import os
+import copy
+# TODO: We are using os.popen3, which has been deprecated since python 2.6. The
+# suggested replacement is the Popen function of the subprocess module.
+# Unfortunately, on windows this needs the msvcrt module, which doesn't seem
+# to be available in GUB?!?!?!
+# from subprocess import Popen, PIPE
+
+progress = ly.progress
+warning = ly.warning
+error = ly.error
+
+
+
+
+
+####################################################################
+# Snippet option handling
+####################################################################
+
+
+#
+# Is this pythonic? Personally, I find this rather #define-nesque. --hwn
+#
+# Global definitions:
+ADDVERSION = 'addversion'
+AFTER = 'after'
+ALT = 'alt'
+BEFORE = 'before'
+DOCTITLE = 'doctitle'
+EXAMPLEINDENT = 'exampleindent'
+FILENAME = 'filename'
+FILTER = 'filter'
+FRAGMENT = 'fragment'
+LANG = 'lang' ## TODO: This is handled nowhere!
+LAYOUT = 'layout'
+LILYQUOTE = 'lilyquote'
+LINE_WIDTH = 'line-width'
+NOFRAGMENT = 'nofragment'
+NOGETTEXT = 'nogettext'
+NOINDENT = 'noindent'
+NOQUOTE = 'noquote'
+INDENT = 'indent'
+NORAGGED_RIGHT = 'noragged-right'
+NOTES = 'body'
+NOTIME = 'notime'
+OUTPUT = 'output'
+OUTPUTIMAGE = 'outputimage'
+PAPER = 'paper'
+PAPERSIZE = 'papersize'
+PREAMBLE = 'preamble'
+PRINTFILENAME = 'printfilename'
+QUOTE = 'quote'
+RAGGED_RIGHT = 'ragged-right'
+RELATIVE = 'relative'
+STAFFSIZE = 'staffsize'
+TEXIDOC = 'texidoc'
+VERBATIM = 'verbatim'
+VERSION = 'lilypondversion'
+
+
+
+# NOTIME and NOGETTEXT have no opposite so they aren't part of this
+# dictionary.
+# NOQUOTE is used internally only.
+no_options = {
+ NOFRAGMENT: FRAGMENT,
+ NOINDENT: INDENT,
+}
+
+# Options that have no impact on processing by lilypond (or --process
+# argument)
+PROCESSING_INDEPENDENT_OPTIONS = (
+ ALT, NOGETTEXT, VERBATIM, ADDVERSION,
+ TEXIDOC, DOCTITLE, VERSION, PRINTFILENAME)
+
+
+
+# Options without a pattern in snippet_options.
+simple_options = [
+ EXAMPLEINDENT,
+ FRAGMENT,
+ NOFRAGMENT,
+ NOGETTEXT,
+ NOINDENT,
+ PRINTFILENAME,
+ DOCTITLE,
+ TEXIDOC,
+ LANG,
+ VERBATIM,
+ FILENAME,
+ ALT,
+ ADDVERSION
+]
+
+
+
+####################################################################
+# LilyPond templates for the snippets
+####################################################################
+
+snippet_options = {
+ ##
+ NOTES: {
+ RELATIVE: r'''\relative c%(relative_quotes)s''',
+ },
+
+ ##
+ PAPER: {
+ PAPERSIZE: r'''#(set-paper-size "%(papersize)s")''',
+ INDENT: r'''indent = %(indent)s''',
+ LINE_WIDTH: r'''line-width = %(line-width)s''',
+ QUOTE: r'''line-width = %(line-width)s - 2.0 * %(exampleindent)s''',
+ LILYQUOTE: r'''line-width = %(line-width)s - 2.0 * %(exampleindent)s''',
+ RAGGED_RIGHT: r'''ragged-right = ##t''',
+ NORAGGED_RIGHT: r'''ragged-right = ##f''',
+ },
+
+ ##
+ LAYOUT: {
+ NOTIME: r'''
+ \context {
+ \Score
+ timing = ##f
+ }
+ \context {
+ \Staff
+ \remove "Time_signature_engraver"
+ }''',
+ },
+
+ ##
+ PREAMBLE: {
+ STAFFSIZE: r'''#(set-global-staff-size %(staffsize)s)''',
+ },
+}
+
+
+
+
+
+FRAGMENT_LY = r'''
+%(notes_string)s
+{
+
+
+%% ****************************************************************
+%% ly snippet contents follows:
+%% ****************************************************************
+%(code)s
+
+
+%% ****************************************************************
+%% end ly snippet
+%% ****************************************************************
+}
+'''
+
+def classic_lilypond_book_compatibility (key, value):
+ if key == 'singleline' and value == None:
+ return (RAGGED_RIGHT, None)
+
+ m = re.search ('relative\s*([-0-9])', key)
+ if m:
+ return ('relative', m.group (1))
+
+ m = re.match ('([0-9]+)pt', key)
+ if m:
+ return ('staffsize', m.group (1))
+
+ if key == 'indent' or key == 'line-width':
+ m = re.match ('([-.0-9]+)(cm|in|mm|pt|staffspace)', value)
+ if m:
+ f = float (m.group (1))
+ return (key, '%f\\%s' % (f, m.group (2)))
+
+ return (None, None)
+
+
+PREAMBLE_LY = '''%%%% Generated by %(program_name)s
+%%%% Options: [%(option_string)s]
+\\include "lilypond-book-preamble.ly"
+
+
+%% ****************************************************************
+%% Start cut-&-pastable-section
+%% ****************************************************************
+
+%(preamble_string)s
+
+\paper {
+ %(paper_string)s
+ force-assignment = #""
+ line-width = #(- line-width (* mm %(padding_mm)f))
+}
+
+\layout {
+ %(layout_string)s
+}
+
+%(safe_mode_string)s
+'''
+
+
+FULL_LY = '''
+
+
+%% ****************************************************************
+%% ly snippet:
+%% ****************************************************************
+%(code)s
+
+
+%% ****************************************************************
+%% end ly snippet
+%% ****************************************************************
+'''
+
+
+
+
+
+
+
+
+####################################################################
+# Helper functions
+####################################################################
+
+def ps_page_count (ps_name):
+ header = file (ps_name).read (1024)
+ m = re.search ('\n%%Pages: ([0-9]+)', header)
+ if m:
+ return int (m.group (1))
+ return 0
+
+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))
+
+
+
+class CompileError(Exception):
+ pass
+
+
+
+####################################################################
+# Snippet classes
+####################################################################
+
+class Chunk:
+ def replacement_text (self):
+ return ''
+
+ def filter_text (self):
+ return self.replacement_text ()
+
+ 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):
+ self.source = source
+ self.start = start
+ self.end = end
+ self.line_number = line_number
+ self.override_text = None
+
+ def is_plain (self):
+ return True
+
+ def replacement_text (self):
+ if self.override_text:
+ return self.override_text
+ else:
+ return self.source[self.start:self.end]
+
+
+
+class Snippet (Chunk):
+ def __init__ (self, type, match, formatter, line_number, global_options):
+ self.type = type
+ self.match = match
+ self.checksum = 0
+ self.option_dict = {}
+ self.formatter = formatter
+ self.line_number = line_number
+ self.global_options = global_options
+ self.replacements = {'program_version': ly.program_version,
+ 'program_name': ly.program_name}
+
+ # return a shallow copy of the replacements, so the caller can modify
+ # it locally without interfering with other snippet operations
+ def get_replacements (self):
+ return copy.copy (self.replacements)
+
+ def replacement_text (self):
+ return self.match.group ('match')
+
+ def substring (self, s):
+ return self.match.group (s)
+
+ def __repr__ (self):
+ return `self.__class__` + ' type = ' + self.type
+
+
+
+class IncludeSnippet (Snippet):
+ def processed_filename (self):
+ f = self.substring ('filename')
+ return os.path.splitext (f)[0] + self.formatter.default_extension
+
+ def replacement_text (self):
+ s = self.match.group ('match')
+ f = self.substring ('filename')
+ return re.sub (f, self.processed_filename (), s)
+
+
+
+class LilypondSnippet (Snippet):
+ def __init__ (self, type, match, formatter, line_number, global_options):
+ Snippet.__init__ (self, type, match, formatter, line_number, global_options)
+ os = match.group ('options')
+ self.do_options (os, self.type)
+
+
+ def snippet_options (self):
+ return [];
+
+ def verb_ly_gettext (self, s):
+ lang = self.formatter.document_language
+ if not lang:
+ return s
+ try:
+ t = langdefs.translation[lang]
+ except:
+ return s
+
+ s = ly_comment_re.sub (lambda m: ly_comment_gettext (t, m), s)
+
+ if langdefs.LANGDICT[lang].enable_ly_identifier_l10n:
+ for v in ly_var_def_re.findall (s):
+ s = re.sub (r"(?m)(?<!\\clef)(^|[' \\#])%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
+
+ def verb_ly (self):
+ verb_text = self.substring ('code')
+ if not NOGETTEXT in self.option_dict:
+ verb_text = self.verb_ly_gettext (verb_text)
+ if not verb_text.endswith ('\n'):
+ verb_text += '\n'
+ return verb_text
+
+ def ly (self):
+ contents = self.substring ('code')
+ return ('\\sourcefileline %d\n%s'
+ % (self.line_number - 1, contents))
+
+ def full_ly (self):
+ s = self.ly ()
+ if s:
+ return self.compose_ly (s)
+ return ''
+
+ def split_options (self, option_string):
+ return self.formatter.split_snippet_options (option_string);
+
+ def do_options (self, option_string, type):
+ self.option_dict = {}
+
+ options = self.split_options (option_string)
+
+ for option in options:
+ if '=' in option:
+ (key, value) = re.split ('\s*=\s*', option)
+ self.option_dict[key] = value
+ else:
+ if option in no_options:
+ if no_options[option] in self.option_dict:
+ del self.option_dict[no_options[option]]
+ else:
+ self.option_dict[option] = None
+
+
+ # If LINE_WIDTH is used without parameter, set it to default.
+ has_line_width = self.option_dict.has_key (LINE_WIDTH)
+ if has_line_width and self.option_dict[LINE_WIDTH] == None:
+ has_line_width = False
+ del self.option_dict[LINE_WIDTH]
+
+ # TODO: Can't we do that more efficiently (built-in python func?)
+ for k in self.formatter.default_snippet_options:
+ if k not in self.option_dict:
+ self.option_dict[k] = self.formatter.default_snippet_options[k]
+
+ # RELATIVE does not work without FRAGMENT;
+ # make RELATIVE imply FRAGMENT
+ has_relative = self.option_dict.has_key (RELATIVE)
+ if has_relative and not self.option_dict.has_key (FRAGMENT):
+ self.option_dict[FRAGMENT] = None
+
+ if not has_line_width:
+ if type == 'lilypond' or FRAGMENT in self.option_dict:
+ self.option_dict[RAGGED_RIGHT] = None
+
+ if type == 'lilypond':
+ if LINE_WIDTH in self.option_dict:
+ del self.option_dict[LINE_WIDTH]
+ else:
+ if RAGGED_RIGHT in self.option_dict:
+ if LINE_WIDTH in self.option_dict:
+ del self.option_dict[LINE_WIDTH]
+
+ if QUOTE in self.option_dict or type == 'lilypond':
+ if LINE_WIDTH in self.option_dict:
+ del self.option_dict[LINE_WIDTH]
+
+ if not INDENT in self.option_dict:
+ self.option_dict[INDENT] = '0\\mm'
+
+ # Set a default line-width if there is none. We need this, because
+ # lilypond-book has set left-padding by default and therefore does
+ # #(define line-width (- line-width (* 3 mm)))
+ # TODO: Junk this ugly hack if the code gets rewritten to concatenate
+ # all settings before writing them in the \paper block.
+ if not LINE_WIDTH in self.option_dict:
+ if not QUOTE in self.option_dict:
+ if not LILYQUOTE in self.option_dict:
+ self.option_dict[LINE_WIDTH] = "#(- paper-width \
+left-margin-default right-margin-default)"
+
+ def get_option_list (self):
+ if not 'option_list' in self.__dict__:
+ option_list = []
+ for (key, value) in self.option_dict.items ():
+ if value == None:
+ option_list.append (key)
+ else:
+ option_list.append (key + '=' + value)
+ option_list.sort ()
+ self.option_list = option_list
+ return self.option_list
+
+ def compose_ly (self, code):
+ if FRAGMENT in self.option_dict:
+ body = FRAGMENT_LY
+ else:
+ body = FULL_LY
+
+ # Defaults.
+ relative = 1
+ override = {}
+ # The original concept of the `exampleindent' option is broken.
+ # It is not possible to get a sane value for @exampleindent at all
+ # without processing the document itself. Saying
+ #
+ # @exampleindent 0
+ # @example
+ # ...
+ # @end example
+ # @exampleindent 5
+ #
+ # causes ugly results with the DVI backend of texinfo since the
+ # default value for @exampleindent isn't 5em but 0.4in (or a smaller
+ # value). Executing the above code changes the environment
+ # indentation to an unknown value because we don't know the amount
+ # of 1em in advance since it is font-dependent. Modifying
+ # @exampleindent in the middle of a document is simply not
+ # supported within texinfo.
+ #
+ # As a consequence, the only function of @exampleindent is now to
+ # specify the amount of indentation for the `quote' option.
+ #
+ # To set @exampleindent locally to zero, we use the @format
+ # environment for non-quoted snippets.
+ override[EXAMPLEINDENT] = r'0.4\in'
+ override[LINE_WIDTH] = '5\\in' # = texinfo_line_widths['@smallbook']
+ override.update (self.formatter.default_snippet_options)
+
+ option_list = []
+ for option in self.get_option_list ():
+ for name in PROCESSING_INDEPENDENT_OPTIONS:
+ if option.startswith (name):
+ break
+ else:
+ option_list.append (option)
+ option_string = ','.join (option_list)
+ compose_dict = {}
+ compose_types = [NOTES, PREAMBLE, LAYOUT, PAPER]
+ for a in compose_types:
+ compose_dict[a] = []
+
+ option_names = self.option_dict.keys ()
+ option_names.sort ()
+ for key in option_names:
+ value = self.option_dict[key]
+ (c_key, c_value) = classic_lilypond_book_compatibility (key, value)
+ if c_key:
+ if c_value:
+ warning (
+ _ ("deprecated ly-option used: %s=%s") % (key, value))
+ warning (
+ _ ("compatibility mode translation: %s=%s") % (c_key, c_value))
+ else:
+ warning (
+ _ ("deprecated ly-option used: %s") % key)
+ warning (
+ _ ("compatibility mode translation: %s") % c_key)
+
+ (key, value) = (c_key, c_value)
+
+ if value:
+ override[key] = value
+ else:
+ if not override.has_key (key):
+ override[key] = None
+
+ found = 0
+ for typ in compose_types:
+ if snippet_options[typ].has_key (key):
+ compose_dict[typ].append (snippet_options[typ][key])
+ found = 1
+ break
+
+ if not found and key not in simple_options and key not in self.snippet_options ():
+ warning (_ ("ignoring unknown ly option: %s") % key)
+
+ # URGS
+ if RELATIVE in override and override[RELATIVE]:
+ relative = int (override[RELATIVE])
+
+ relative_quotes = ''
+
+ # 1 = central C
+ if relative < 0:
+ relative_quotes += ',' * (- relative)
+ elif relative > 0:
+ relative_quotes += "'" * relative
+
+ # put paper-size first, if it exists
+ for i,elem in enumerate(compose_dict[PAPER]):
+ if elem.startswith("#(set-paper-size"):
+ compose_dict[PAPER].insert(0, compose_dict[PAPER].pop(i))
+ break
+
+ paper_string = '\n '.join (compose_dict[PAPER]) % override
+ layout_string = '\n '.join (compose_dict[LAYOUT]) % override
+ notes_string = '\n '.join (compose_dict[NOTES]) % vars ()
+ preamble_string = '\n '.join (compose_dict[PREAMBLE]) % override
+ padding_mm = self.global_options.padding_mm
+ if self.global_options.safe_mode:
+ safe_mode_string = "#(ly:set-option 'safe #t)"
+ else:
+ safe_mode_string = ""
+
+ d = globals().copy()
+ d.update (locals())
+ d.update (self.global_options.information)
+ return (PREAMBLE_LY + body) % d
+
+ def get_checksum (self):
+ if not self.checksum:
+ # Work-around for md5 module deprecation warning in python 2.5+:
+ try:
+ from hashlib import md5
+ except ImportError:
+ from md5 import md5
+
+ # We only want to calculate the hash based on the snippet
+ # code plus fragment options relevant to processing by
+ # lilypond, not the snippet + preamble
+ hash = md5 (self.relevant_contents (self.ly ()))
+ for option in self.get_option_list ():
+ for name in PROCESSING_INDEPENDENT_OPTIONS:
+ if option.startswith (name):
+ break
+ else:
+ hash.update (option)
+
+ ## let's not create too long names.
+ self.checksum = hash.hexdigest ()[:10]
+
+ return self.checksum
+
+ def basename (self):
+ cs = self.get_checksum ()
+ name = '%s/lily-%s' % (cs[:2], cs[2:])
+ return name
+
+ final_basename = basename
+
+ def write_ly (self):
+ base = self.basename ()
+ path = os.path.join (self.global_options.lily_output_dir, base)
+ directory = os.path.split(path)[0]
+ if not os.path.isdir (directory):
+ os.makedirs (directory)
+ filename = path + '.ly'
+ if os.path.exists (filename):
+ existing = open (filename, 'r').read ()
+
+ if self.relevant_contents (existing) != self.relevant_contents (self.full_ly ()):
+ warning ("%s: duplicate filename but different contents of orginal file,\n\
+printing diff against existing file." % filename)
+ ly.stderr_write (self.filter_pipe (self.full_ly (), 'diff -u %s -' % filename))
+ else:
+ out = file (filename, 'w')
+ out.write (self.full_ly ())
+ file (path + '.txt', 'w').write ('image of music')
+
+ def relevant_contents (self, ly):
+ return re.sub (r'\\(version|sourcefileline|sourcefilename)[^\n]*\n', '', 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:
+ if (self.global_options.use_source_file_names
+ and isinstance (self, LilypondFileSnippet)):
+ base, ext = os.path.splitext (name)
+ components = base.split ('-')
+ # ugh, assume filenames with prefix with one dash (lily-xxxx)
+ if len (components) > 2:
+ base_suffix = '-' + components[-1]
+ else:
+ base_suffix = ''
+ final_name = self.final_basename () + base_suffix + ext
+ else:
+ final_name = name
+ try:
+ os.unlink (os.path.join (destination, final_name))
+ except OSError:
+ pass
+
+ src = os.path.join (output_dir, name)
+ dst = os.path.join (destination, final_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 self.global_options
+ skip_lily = self.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 self.global_options.process_cmd:
+ require_file (base + '.profile')
+ if 'dseparate-log-file' in self.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 self.formatter.document_language:
+ map (consider_file,
+ [base + '.texidoc' + self.formatter.document_language,
+ base + '.doctitle' + self.formatter.document_language])
+
+ required_files = self.formatter.required_files (self, base, full, result)
+ for f in required_files:
+ require_file (f)
+
+ 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 self.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_pipe (self, input, cmd):
+ """Pass input through cmd, and return the result."""
+
+ if self.global_options.verbose:
+ progress (_ ("Opening filter `%s'\n") % cmd)
+
+ # TODO: Use Popen once we resolve the problem with msvcrt in Windows:
+ (stdin, stdout, stderr) = os.popen3 (cmd)
+ # p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
+ # (stdin, stdout, stderr) = (p.stdin, p.stdout, p.stderr)
+ stdin.write (input)
+ status = stdin.close ()
+
+ if not status:
+ status = 0
+ output = stdout.read ()
+ status = stdout.close ()
+ error = stderr.read ()
+
+ if not status:
+ status = 0
+ signal = 0x0f & status
+ if status or (not output and error):
+ exit_status = status >> 8
+ ly.error (_ ("`%s' failed (%d)") % (cmd, exit_status))
+ ly.error (_ ("The error log is as follows:"))
+ ly.stderr_write (error)
+ ly.stderr_write (stderr.read ())
+ exit (status)
+
+ if self.global_options.verbose:
+ progress ('\n')
+
+ return output
+
+ def get_snippet_code (self):
+ return self.substring ('code');
+
+ 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.get_snippet_code ();
+ s = self.filter_pipe (code, self.global_options.filter_cmd)
+ d = {
+ 'code': s,
+ 'options': self.match.group ('options')
+ }
+ return self.formatter.output_simple_replacements (FILTER, d)
+
+ def replacement_text (self):
+ base = self.final_basename ()
+ return self.formatter.snippet_output (base, self)
+
+ def get_images (self):
+ rep = {'base': self.final_basename ()}
+
+ single = '%(base)s.png' % rep
+ multiple = '%(base)s-page1.png' % rep
+ images = (single,)
+ if (os.path.exists (multiple)
+ and (not os.path.exists (single)
+ or (os.stat (multiple)[stat.ST_MTIME]
+ > os.stat (single)[stat.ST_MTIME]))):
+ count = ps_page_count ('%(base)s.eps' % rep)
+ images = ['%s-page%d.png' % (rep['base'], page) for page in range (1, count+1)]
+ images = tuple (images)
+
+ return images
+
+
+
+re_begin_verbatim = re.compile (r'\s+%.*?begin verbatim.*\n*', re.M)
+re_end_verbatim = re.compile (r'\s+%.*?end verbatim.*$', re.M)
+
+class LilypondFileSnippet (LilypondSnippet):
+ def __init__ (self, type, match, formatter, line_number, global_options):
+ LilypondSnippet.__init__ (self, type, match, formatter, line_number, global_options)
+ self.contents = file (BookBase.find_file (self.substring ('filename'), global_options.include_path)).read ()
+
+ def get_snippet_code (self):
+ return self.contents;
+
+ def verb_ly (self):
+ s = self.contents
+ s = re_begin_verbatim.split (s)[-1]
+ s = re_end_verbatim.split (s)[0]
+ if not NOGETTEXT in self.option_dict:
+ s = self.verb_ly_gettext (s)
+ if not s.endswith ('\n'):
+ s += '\n'
+ return s
+
+ def ly (self):
+ name = self.substring ('filename')
+ return ('\\sourcefilename \"%s\"\n\\sourcefileline 0\n%s'
+ % (name, self.contents))
+
+ def final_basename (self):
+ if self.global_options.use_source_file_names:
+ base = os.path.splitext (os.path.basename (self.substring ('filename')))[0]
+ return base
+ else:
+ return self.basename ()
+
+
+class LilyPondVersionString (Snippet):
+ """A string that does not require extra memory."""
+ def __init__ (self, type, match, formatter, line_number, global_options):
+ Snippet.__init__ (self, type, match, formatter, line_number, global_options)
+
+ def replacement_text (self):
+ return self.formatter.output_simple (self.type, self)
+
+
+snippet_type_to_class = {
+ 'lilypond_file': LilypondFileSnippet,
+ 'lilypond_block': LilypondSnippet,
+ 'lilypond': LilypondSnippet,
+ 'include': IncludeSnippet,
+ 'lilypondversion': LilyPondVersionString,
+}