Fixes Issue 1504, allowing feather beam line breaking.
[lilypond/patrick.git] / python / book_snippets.py
blobb7c3ddf0d67d369acfec84b3db605a54860b4db9
1 # -*- coding: utf-8 -*-
3 import book_base as BookBase
4 import lilylib as ly
5 global _;_=ly._
6 import re
7 import os
8 import copy
9 # TODO: We are using os.popen3, which has been deprecated since python 2.6. The
10 # suggested replacement is the Popen function of the subprocess module.
11 # Unfortunately, on windows this needs the msvcrt module, which doesn't seem
12 # to be available in GUB?!?!?!
13 # from subprocess import Popen, PIPE
15 progress = ly.progress
16 warning = ly.warning
17 error = ly.error
23 ####################################################################
24 # Snippet option handling
25 ####################################################################
29 # Is this pythonic? Personally, I find this rather #define-nesque. --hwn
31 # Global definitions:
32 ADDVERSION = 'addversion'
33 AFTER = 'after'
34 ALT = 'alt'
35 BEFORE = 'before'
36 DOCTITLE = 'doctitle'
37 EXAMPLEINDENT = 'exampleindent'
38 FILENAME = 'filename'
39 FILTER = 'filter'
40 FRAGMENT = 'fragment'
41 LANG = 'lang' ## TODO: This is handled nowhere!
42 LAYOUT = 'layout'
43 LILYQUOTE = 'lilyquote'
44 LINE_WIDTH = 'line-width'
45 NOFRAGMENT = 'nofragment'
46 NOGETTEXT = 'nogettext'
47 NOINDENT = 'noindent'
48 NOQUOTE = 'noquote'
49 INDENT = 'indent'
50 NORAGGED_RIGHT = 'noragged-right'
51 NOTES = 'body'
52 NOTIME = 'notime'
53 OUTPUT = 'output'
54 OUTPUTIMAGE = 'outputimage'
55 PAPER = 'paper'
56 PAPERSIZE = 'papersize'
57 PREAMBLE = 'preamble'
58 PRINTFILENAME = 'printfilename'
59 QUOTE = 'quote'
60 RAGGED_RIGHT = 'ragged-right'
61 RELATIVE = 'relative'
62 STAFFSIZE = 'staffsize'
63 TEXIDOC = 'texidoc'
64 VERBATIM = 'verbatim'
65 VERSION = 'lilypondversion'
69 # NOTIME and NOGETTEXT have no opposite so they aren't part of this
70 # dictionary.
71 # NOQUOTE is used internally only.
72 no_options = {
73 NOFRAGMENT: FRAGMENT,
74 NOINDENT: INDENT,
77 # Options that have no impact on processing by lilypond (or --process
78 # argument)
79 PROCESSING_INDEPENDENT_OPTIONS = (
80 ALT, NOGETTEXT, VERBATIM, ADDVERSION,
81 TEXIDOC, DOCTITLE, VERSION, PRINTFILENAME)
85 # Options without a pattern in snippet_options.
86 simple_options = [
87 EXAMPLEINDENT,
88 FRAGMENT,
89 NOFRAGMENT,
90 NOGETTEXT,
91 NOINDENT,
92 PRINTFILENAME,
93 DOCTITLE,
94 TEXIDOC,
95 LANG,
96 VERBATIM,
97 FILENAME,
98 ALT,
99 ADDVERSION
104 ####################################################################
105 # LilyPond templates for the snippets
106 ####################################################################
108 snippet_options = {
110 NOTES: {
111 RELATIVE: r'''\relative c%(relative_quotes)s''',
115 PAPER: {
116 PAPERSIZE: r'''#(set-paper-size "%(papersize)s")''',
117 INDENT: r'''indent = %(indent)s''',
118 LINE_WIDTH: r'''line-width = %(line-width)s''',
119 QUOTE: r'''line-width = %(line-width)s - 2.0 * %(exampleindent)s''',
120 LILYQUOTE: r'''line-width = %(line-width)s - 2.0 * %(exampleindent)s''',
121 RAGGED_RIGHT: r'''ragged-right = ##t''',
122 NORAGGED_RIGHT: r'''ragged-right = ##f''',
126 LAYOUT: {
127 NOTIME: r'''
128 \context {
129 \Score
130 timing = ##f
132 \context {
133 \Staff
134 \remove "Time_signature_engraver"
135 }''',
139 PREAMBLE: {
140 STAFFSIZE: r'''#(set-global-staff-size %(staffsize)s)''',
148 FRAGMENT_LY = r'''
149 %(notes_string)s
153 %% ****************************************************************
154 %% ly snippet contents follows:
155 %% ****************************************************************
156 %(code)s
159 %% ****************************************************************
160 %% end ly snippet
161 %% ****************************************************************
165 def classic_lilypond_book_compatibility (key, value):
166 if key == 'singleline' and value == None:
167 return (RAGGED_RIGHT, None)
169 m = re.search ('relative\s*([-0-9])', key)
170 if m:
171 return ('relative', m.group (1))
173 m = re.match ('([0-9]+)pt', key)
174 if m:
175 return ('staffsize', m.group (1))
177 if key == 'indent' or key == 'line-width':
178 m = re.match ('([-.0-9]+)(cm|in|mm|pt|staffspace)', value)
179 if m:
180 f = float (m.group (1))
181 return (key, '%f\\%s' % (f, m.group (2)))
183 return (None, None)
186 PREAMBLE_LY = '''%%%% Generated by %(program_name)s
187 %%%% Options: [%(option_string)s]
188 \\include "lilypond-book-preamble.ly"
191 %% ****************************************************************
192 %% Start cut-&-pastable-section
193 %% ****************************************************************
195 %(preamble_string)s
197 \paper {
198 %(paper_string)s
199 force-assignment = #""
200 line-width = #(- line-width (* mm %(padding_mm)f))
203 \layout {
204 %(layout_string)s
207 %(safe_mode_string)s
211 FULL_LY = '''
214 %% ****************************************************************
215 %% ly snippet:
216 %% ****************************************************************
217 %(code)s
220 %% ****************************************************************
221 %% end ly snippet
222 %% ****************************************************************
232 ####################################################################
233 # Helper functions
234 ####################################################################
236 def ps_page_count (ps_name):
237 header = file (ps_name).read (1024)
238 m = re.search ('\n%%Pages: ([0-9]+)', header)
239 if m:
240 return int (m.group (1))
241 return 0
243 ly_var_def_re = re.compile (r'^([a-zA-Z]+)[\t ]*=', re.M)
244 ly_comment_re = re.compile (r'(%+[\t ]*)(.*)$', re.M)
245 ly_context_id_re = re.compile ('\\\\(?:new|context)\\s+(?:[a-zA-Z]*?(?:Staff\
246 (?:Group)?|Voice|FiguredBass|FretBoards|Names|Devnull))\\s+=\\s+"?([a-zA-Z]+)"?\\s+')
248 def ly_comment_gettext (t, m):
249 return m.group (1) + t (m.group (2))
253 class CompileError(Exception):
254 pass
258 ####################################################################
259 # Snippet classes
260 ####################################################################
262 class Chunk:
263 def replacement_text (self):
264 return ''
266 def filter_text (self):
267 return self.replacement_text ()
269 def is_plain (self):
270 return False
272 class Substring (Chunk):
273 """A string that does not require extra memory."""
274 def __init__ (self, source, start, end, line_number):
275 self.source = source
276 self.start = start
277 self.end = end
278 self.line_number = line_number
279 self.override_text = None
281 def is_plain (self):
282 return True
284 def replacement_text (self):
285 if self.override_text:
286 return self.override_text
287 else:
288 return self.source[self.start:self.end]
292 class Snippet (Chunk):
293 def __init__ (self, type, match, formatter, line_number, global_options):
294 self.type = type
295 self.match = match
296 self.checksum = 0
297 self.option_dict = {}
298 self.formatter = formatter
299 self.line_number = line_number
300 self.global_options = global_options
301 self.replacements = {'program_version': ly.program_version,
302 'program_name': ly.program_name}
304 # return a shallow copy of the replacements, so the caller can modify
305 # it locally without interfering with other snippet operations
306 def get_replacements (self):
307 return copy.copy (self.replacements)
309 def replacement_text (self):
310 return self.match.group ('match')
312 def substring (self, s):
313 return self.match.group (s)
315 def __repr__ (self):
316 return `self.__class__` + ' type = ' + self.type
320 class IncludeSnippet (Snippet):
321 def processed_filename (self):
322 f = self.substring ('filename')
323 return os.path.splitext (f)[0] + self.formatter.default_extension
325 def replacement_text (self):
326 s = self.match.group ('match')
327 f = self.substring ('filename')
328 return re.sub (f, self.processed_filename (), s)
332 class LilypondSnippet (Snippet):
333 def __init__ (self, type, match, formatter, line_number, global_options):
334 Snippet.__init__ (self, type, match, formatter, line_number, global_options)
335 os = match.group ('options')
336 self.do_options (os, self.type)
339 def snippet_options (self):
340 return [];
342 def verb_ly_gettext (self, s):
343 lang = self.formatter.document_language
344 if not lang:
345 return s
346 try:
347 t = langdefs.translation[lang]
348 except:
349 return s
351 s = ly_comment_re.sub (lambda m: ly_comment_gettext (t, m), s)
353 if langdefs.LANGDICT[lang].enable_ly_identifier_l10n:
354 for v in ly_var_def_re.findall (s):
355 s = re.sub (r"(?m)(?<!\\clef)(^|[' \\#])%s([^a-zA-Z])" % v,
356 "\\1" + t (v) + "\\2",
358 for id in ly_context_id_re.findall (s):
359 s = re.sub (r'(\s+|")%s(\s+|")' % id,
360 "\\1" + t (id) + "\\2",
362 return s
364 def verb_ly (self):
365 verb_text = self.substring ('code')
366 if not NOGETTEXT in self.option_dict:
367 verb_text = self.verb_ly_gettext (verb_text)
368 if not verb_text.endswith ('\n'):
369 verb_text += '\n'
370 return verb_text
372 def ly (self):
373 contents = self.substring ('code')
374 return ('\\sourcefileline %d\n%s'
375 % (self.line_number - 1, contents))
377 def full_ly (self):
378 s = self.ly ()
379 if s:
380 return self.compose_ly (s)
381 return ''
383 def split_options (self, option_string):
384 return self.formatter.split_snippet_options (option_string);
386 def do_options (self, option_string, type):
387 self.option_dict = {}
389 options = self.split_options (option_string)
391 for option in options:
392 if '=' in option:
393 (key, value) = re.split ('\s*=\s*', option)
394 self.option_dict[key] = value
395 else:
396 if option in no_options:
397 if no_options[option] in self.option_dict:
398 del self.option_dict[no_options[option]]
399 else:
400 self.option_dict[option] = None
403 # If LINE_WIDTH is used without parameter, set it to default.
404 has_line_width = self.option_dict.has_key (LINE_WIDTH)
405 if has_line_width and self.option_dict[LINE_WIDTH] == None:
406 has_line_width = False
407 del self.option_dict[LINE_WIDTH]
409 # TODO: Can't we do that more efficiently (built-in python func?)
410 for k in self.formatter.default_snippet_options:
411 if k not in self.option_dict:
412 self.option_dict[k] = self.formatter.default_snippet_options[k]
414 # RELATIVE does not work without FRAGMENT;
415 # make RELATIVE imply FRAGMENT
416 has_relative = self.option_dict.has_key (RELATIVE)
417 if has_relative and not self.option_dict.has_key (FRAGMENT):
418 self.option_dict[FRAGMENT] = None
420 if not has_line_width:
421 if type == 'lilypond' or FRAGMENT in self.option_dict:
422 self.option_dict[RAGGED_RIGHT] = None
424 if type == 'lilypond':
425 if LINE_WIDTH in self.option_dict:
426 del self.option_dict[LINE_WIDTH]
427 else:
428 if RAGGED_RIGHT in self.option_dict:
429 if LINE_WIDTH in self.option_dict:
430 del self.option_dict[LINE_WIDTH]
432 if QUOTE in self.option_dict or type == 'lilypond':
433 if LINE_WIDTH in self.option_dict:
434 del self.option_dict[LINE_WIDTH]
436 if not INDENT in self.option_dict:
437 self.option_dict[INDENT] = '0\\mm'
439 # Set a default line-width if there is none. We need this, because
440 # lilypond-book has set left-padding by default and therefore does
441 # #(define line-width (- line-width (* 3 mm)))
442 # TODO: Junk this ugly hack if the code gets rewritten to concatenate
443 # all settings before writing them in the \paper block.
444 if not LINE_WIDTH in self.option_dict:
445 if not QUOTE in self.option_dict:
446 if not LILYQUOTE in self.option_dict:
447 self.option_dict[LINE_WIDTH] = "#(- paper-width \
448 left-margin-default right-margin-default)"
450 def get_option_list (self):
451 if not 'option_list' in self.__dict__:
452 option_list = []
453 for (key, value) in self.option_dict.items ():
454 if value == None:
455 option_list.append (key)
456 else:
457 option_list.append (key + '=' + value)
458 option_list.sort ()
459 self.option_list = option_list
460 return self.option_list
462 def compose_ly (self, code):
463 if FRAGMENT in self.option_dict:
464 body = FRAGMENT_LY
465 else:
466 body = FULL_LY
468 # Defaults.
469 relative = 1
470 override = {}
471 # The original concept of the `exampleindent' option is broken.
472 # It is not possible to get a sane value for @exampleindent at all
473 # without processing the document itself. Saying
475 # @exampleindent 0
476 # @example
477 # ...
478 # @end example
479 # @exampleindent 5
481 # causes ugly results with the DVI backend of texinfo since the
482 # default value for @exampleindent isn't 5em but 0.4in (or a smaller
483 # value). Executing the above code changes the environment
484 # indentation to an unknown value because we don't know the amount
485 # of 1em in advance since it is font-dependent. Modifying
486 # @exampleindent in the middle of a document is simply not
487 # supported within texinfo.
489 # As a consequence, the only function of @exampleindent is now to
490 # specify the amount of indentation for the `quote' option.
492 # To set @exampleindent locally to zero, we use the @format
493 # environment for non-quoted snippets.
494 override[EXAMPLEINDENT] = r'0.4\in'
495 override[LINE_WIDTH] = '5\\in' # = texinfo_line_widths['@smallbook']
496 override.update (self.formatter.default_snippet_options)
498 option_list = []
499 for option in self.get_option_list ():
500 for name in PROCESSING_INDEPENDENT_OPTIONS:
501 if option.startswith (name):
502 break
503 else:
504 option_list.append (option)
505 option_string = ','.join (option_list)
506 compose_dict = {}
507 compose_types = [NOTES, PREAMBLE, LAYOUT, PAPER]
508 for a in compose_types:
509 compose_dict[a] = []
511 option_names = self.option_dict.keys ()
512 option_names.sort ()
513 for key in option_names:
514 value = self.option_dict[key]
515 (c_key, c_value) = classic_lilypond_book_compatibility (key, value)
516 if c_key:
517 if c_value:
518 warning (
519 _ ("deprecated ly-option used: %s=%s") % (key, value))
520 warning (
521 _ ("compatibility mode translation: %s=%s") % (c_key, c_value))
522 else:
523 warning (
524 _ ("deprecated ly-option used: %s") % key)
525 warning (
526 _ ("compatibility mode translation: %s") % c_key)
528 (key, value) = (c_key, c_value)
530 if value:
531 override[key] = value
532 else:
533 if not override.has_key (key):
534 override[key] = None
536 found = 0
537 for typ in compose_types:
538 if snippet_options[typ].has_key (key):
539 compose_dict[typ].append (snippet_options[typ][key])
540 found = 1
541 break
543 if not found and key not in simple_options and key not in self.snippet_options ():
544 warning (_ ("ignoring unknown ly option: %s") % key)
546 # URGS
547 if RELATIVE in override and override[RELATIVE]:
548 relative = int (override[RELATIVE])
550 relative_quotes = ''
552 # 1 = central C
553 if relative < 0:
554 relative_quotes += ',' * (- relative)
555 elif relative > 0:
556 relative_quotes += "'" * relative
558 # put paper-size first, if it exists
559 for i,elem in enumerate(compose_dict[PAPER]):
560 if elem.startswith("#(set-paper-size"):
561 compose_dict[PAPER].insert(0, compose_dict[PAPER].pop(i))
562 break
564 paper_string = '\n '.join (compose_dict[PAPER]) % override
565 layout_string = '\n '.join (compose_dict[LAYOUT]) % override
566 notes_string = '\n '.join (compose_dict[NOTES]) % vars ()
567 preamble_string = '\n '.join (compose_dict[PREAMBLE]) % override
568 padding_mm = self.global_options.padding_mm
569 if self.global_options.safe_mode:
570 safe_mode_string = "#(ly:set-option 'safe #t)"
571 else:
572 safe_mode_string = ""
574 d = globals().copy()
575 d.update (locals())
576 d.update (self.global_options.information)
577 return (PREAMBLE_LY + body) % d
579 def get_checksum (self):
580 if not self.checksum:
581 # Work-around for md5 module deprecation warning in python 2.5+:
582 try:
583 from hashlib import md5
584 except ImportError:
585 from md5 import md5
587 # We only want to calculate the hash based on the snippet
588 # code plus fragment options relevant to processing by
589 # lilypond, not the snippet + preamble
590 hash = md5 (self.relevant_contents (self.ly ()))
591 for option in self.get_option_list ():
592 for name in PROCESSING_INDEPENDENT_OPTIONS:
593 if option.startswith (name):
594 break
595 else:
596 hash.update (option)
598 ## let's not create too long names.
599 self.checksum = hash.hexdigest ()[:10]
601 return self.checksum
603 def basename (self):
604 cs = self.get_checksum ()
605 name = '%s/lily-%s' % (cs[:2], cs[2:])
606 return name
608 final_basename = basename
610 def write_ly (self):
611 base = self.basename ()
612 path = os.path.join (self.global_options.lily_output_dir, base)
613 directory = os.path.split(path)[0]
614 if not os.path.isdir (directory):
615 os.makedirs (directory)
616 filename = path + '.ly'
617 if os.path.exists (filename):
618 existing = open (filename, 'r').read ()
620 if self.relevant_contents (existing) != self.relevant_contents (self.full_ly ()):
621 warning ("%s: duplicate filename but different contents of orginal file,\n\
622 printing diff against existing file." % filename)
623 ly.stderr_write (self.filter_pipe (self.full_ly (), 'diff -u %s -' % filename))
624 else:
625 out = file (filename, 'w')
626 out.write (self.full_ly ())
627 file (path + '.txt', 'w').write ('image of music')
629 def relevant_contents (self, ly):
630 return re.sub (r'\\(version|sourcefileline|sourcefilename)[^\n]*\n', '', ly)
632 def link_all_output_files (self, output_dir, output_dir_files, destination):
633 existing, missing = self.all_output_files (output_dir, output_dir_files)
634 if missing:
635 print '\nMissing', missing
636 raise CompileError(self.basename())
637 for name in existing:
638 if (self.global_options.use_source_file_names
639 and isinstance (self, LilypondFileSnippet)):
640 base, ext = os.path.splitext (name)
641 components = base.split ('-')
642 # ugh, assume filenames with prefix with one dash (lily-xxxx)
643 if len (components) > 2:
644 base_suffix = '-' + components[-1]
645 else:
646 base_suffix = ''
647 final_name = self.final_basename () + base_suffix + ext
648 else:
649 final_name = name
650 try:
651 os.unlink (os.path.join (destination, final_name))
652 except OSError:
653 pass
655 src = os.path.join (output_dir, name)
656 dst = os.path.join (destination, final_name)
657 dst_path = os.path.split(dst)[0]
658 if not os.path.isdir (dst_path):
659 os.makedirs (dst_path)
660 os.link (src, dst)
663 def all_output_files (self, output_dir, output_dir_files):
664 """Return all files generated in lily_output_dir, a set.
666 output_dir_files is the list of files in the output directory.
668 result = set ()
669 missing = set ()
670 base = self.basename()
671 full = os.path.join (output_dir, base)
672 def consider_file (name):
673 if name in output_dir_files:
674 result.add (name)
676 def require_file (name):
677 if name in output_dir_files:
678 result.add (name)
679 else:
680 missing.add (name)
682 # UGH - junk self.global_options
683 skip_lily = self.global_options.skip_lilypond_run
684 for required in [base + '.ly',
685 base + '.txt']:
686 require_file (required)
687 if not skip_lily:
688 require_file (base + '-systems.count')
690 if 'ddump-profile' in self.global_options.process_cmd:
691 require_file (base + '.profile')
692 if 'dseparate-log-file' in self.global_options.process_cmd:
693 require_file (base + '.log')
695 map (consider_file, [base + '.tex',
696 base + '.eps',
697 base + '.texidoc',
698 base + '.doctitle',
699 base + '-systems.texi',
700 base + '-systems.tex',
701 base + '-systems.pdftexi'])
702 if self.formatter.document_language:
703 map (consider_file,
704 [base + '.texidoc' + self.formatter.document_language,
705 base + '.doctitle' + self.formatter.document_language])
707 required_files = self.formatter.required_files (self, base, full, result)
708 for f in required_files:
709 require_file (f)
711 system_count = 0
712 if not skip_lily and not missing:
713 system_count = int(file (full + '-systems.count').read())
715 for number in range(1, system_count + 1):
716 systemfile = '%s-%d' % (base, number)
717 require_file (systemfile + '.eps')
718 consider_file (systemfile + '.pdf')
720 # We can't require signatures, since books and toplevel
721 # markups do not output a signature.
722 if 'ddump-signature' in self.global_options.process_cmd:
723 consider_file (systemfile + '.signature')
726 return (result, missing)
728 def is_outdated (self, output_dir, current_files):
729 found, missing = self.all_output_files (output_dir, current_files)
730 return missing
732 def filter_pipe (self, input, cmd):
733 """Pass input through cmd, and return the result."""
735 if self.global_options.verbose:
736 progress (_ ("Opening filter `%s'\n") % cmd)
738 # TODO: Use Popen once we resolve the problem with msvcrt in Windows:
739 (stdin, stdout, stderr) = os.popen3 (cmd)
740 # p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
741 # (stdin, stdout, stderr) = (p.stdin, p.stdout, p.stderr)
742 stdin.write (input)
743 status = stdin.close ()
745 if not status:
746 status = 0
747 output = stdout.read ()
748 status = stdout.close ()
749 error = stderr.read ()
751 if not status:
752 status = 0
753 signal = 0x0f & status
754 if status or (not output and error):
755 exit_status = status >> 8
756 ly.error (_ ("`%s' failed (%d)") % (cmd, exit_status))
757 ly.error (_ ("The error log is as follows:"))
758 ly.stderr_write (error)
759 ly.stderr_write (stderr.read ())
760 exit (status)
762 if self.global_options.verbose:
763 progress ('\n')
765 return output
767 def get_snippet_code (self):
768 return self.substring ('code');
770 def filter_text (self):
771 """Run snippet bodies through a command (say: convert-ly).
773 This functionality is rarely used, and this code must have bitrot.
775 code = self.get_snippet_code ();
776 s = self.filter_pipe (code, self.global_options.filter_cmd)
777 d = {
778 'code': s,
779 'options': self.match.group ('options')
781 return self.formatter.output_simple_replacements (FILTER, d)
783 def replacement_text (self):
784 base = self.final_basename ()
785 return self.formatter.snippet_output (base, self)
787 def get_images (self):
788 rep = {'base': self.final_basename ()}
790 single = '%(base)s.png' % rep
791 multiple = '%(base)s-page1.png' % rep
792 images = (single,)
793 if (os.path.exists (multiple)
794 and (not os.path.exists (single)
795 or (os.stat (multiple)[stat.ST_MTIME]
796 > os.stat (single)[stat.ST_MTIME]))):
797 count = ps_page_count ('%(base)s.eps' % rep)
798 images = ['%s-page%d.png' % (rep['base'], page) for page in range (1, count+1)]
799 images = tuple (images)
801 return images
805 re_begin_verbatim = re.compile (r'\s+%.*?begin verbatim.*\n*', re.M)
806 re_end_verbatim = re.compile (r'\s+%.*?end verbatim.*$', re.M)
808 class LilypondFileSnippet (LilypondSnippet):
809 def __init__ (self, type, match, formatter, line_number, global_options):
810 LilypondSnippet.__init__ (self, type, match, formatter, line_number, global_options)
811 self.contents = file (BookBase.find_file (self.substring ('filename'), global_options.include_path)).read ()
813 def get_snippet_code (self):
814 return self.contents;
816 def verb_ly (self):
817 s = self.contents
818 s = re_begin_verbatim.split (s)[-1]
819 s = re_end_verbatim.split (s)[0]
820 if not NOGETTEXT in self.option_dict:
821 s = self.verb_ly_gettext (s)
822 if not s.endswith ('\n'):
823 s += '\n'
824 return s
826 def ly (self):
827 name = self.substring ('filename')
828 return ('\\sourcefilename \"%s\"\n\\sourcefileline 0\n%s'
829 % (name, self.contents))
831 def final_basename (self):
832 if self.global_options.use_source_file_names:
833 base = os.path.splitext (os.path.basename (self.substring ('filename')))[0]
834 return base
835 else:
836 return self.basename ()
839 class LilyPondVersionString (Snippet):
840 """A string that does not require extra memory."""
841 def __init__ (self, type, match, formatter, line_number, global_options):
842 Snippet.__init__ (self, type, match, formatter, line_number, global_options)
844 def replacement_text (self):
845 return self.formatter.output_simple (self.type, self)
848 snippet_type_to_class = {
849 'lilypond_file': LilypondFileSnippet,
850 'lilypond_block': LilypondSnippet,
851 'lilypond': LilypondSnippet,
852 'include': IncludeSnippet,
853 'lilypondversion': LilyPondVersionString,