midi2ly: bump version in output to 2.7.38 (oldest supported).
[lilypond/patrick.git] / python / book_snippets.py
bloba1b1328c575b4be2af6afe92c07d09183b9b1870
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 PREAMBLE = 'preamble'
57 PRINTFILENAME = 'printfilename'
58 QUOTE = 'quote'
59 RAGGED_RIGHT = 'ragged-right'
60 RELATIVE = 'relative'
61 STAFFSIZE = 'staffsize'
62 TEXIDOC = 'texidoc'
63 VERBATIM = 'verbatim'
64 VERSION = 'lilypondversion'
68 # NOTIME and NOGETTEXT have no opposite so they aren't part of this
69 # dictionary.
70 # NOQUOTE is used internally only.
71 no_options = {
72 NOFRAGMENT: FRAGMENT,
73 NOINDENT: INDENT,
76 # Options that have no impact on processing by lilypond (or --process
77 # argument)
78 PROCESSING_INDEPENDENT_OPTIONS = (
79 ALT, NOGETTEXT, VERBATIM, ADDVERSION,
80 TEXIDOC, DOCTITLE, VERSION, PRINTFILENAME)
84 # Options without a pattern in snippet_options.
85 simple_options = [
86 EXAMPLEINDENT,
87 FRAGMENT,
88 NOFRAGMENT,
89 NOGETTEXT,
90 NOINDENT,
91 PRINTFILENAME,
92 DOCTITLE,
93 TEXIDOC,
94 LANG,
95 VERBATIM,
96 FILENAME,
97 ALT,
98 ADDVERSION
103 ####################################################################
104 # LilyPond templates for the snippets
105 ####################################################################
107 snippet_options = {
109 NOTES: {
110 RELATIVE: r'''\relative c%(relative_quotes)s''',
114 PAPER: {
115 INDENT: r'''indent = %(indent)s''',
116 LINE_WIDTH: r'''line-width = %(line-width)s''',
117 QUOTE: r'''line-width = %(line-width)s - 2.0 * %(exampleindent)s''',
118 LILYQUOTE: r'''line-width = %(line-width)s - 2.0 * %(exampleindent)s''',
119 RAGGED_RIGHT: r'''ragged-right = ##t''',
120 NORAGGED_RIGHT: r'''ragged-right = ##f''',
124 LAYOUT: {
125 NOTIME: r'''
126 \context {
127 \Score
128 timing = ##f
130 \context {
131 \Staff
132 \remove "Time_signature_engraver"
133 }''',
137 PREAMBLE: {
138 STAFFSIZE: r'''#(set-global-staff-size %(staffsize)s)''',
146 FRAGMENT_LY = r'''
147 %(notes_string)s
151 %% ****************************************************************
152 %% ly snippet contents follows:
153 %% ****************************************************************
154 %(code)s
157 %% ****************************************************************
158 %% end ly snippet
159 %% ****************************************************************
163 def classic_lilypond_book_compatibility (key, value):
164 if key == 'singleline' and value == None:
165 return (RAGGED_RIGHT, None)
167 m = re.search ('relative\s*([-0-9])', key)
168 if m:
169 return ('relative', m.group (1))
171 m = re.match ('([0-9]+)pt', key)
172 if m:
173 return ('staffsize', m.group (1))
175 if key == 'indent' or key == 'line-width':
176 m = re.match ('([-.0-9]+)(cm|in|mm|pt|staffspace)', value)
177 if m:
178 f = float (m.group (1))
179 return (key, '%f\\%s' % (f, m.group (2)))
181 return (None, None)
184 PREAMBLE_LY = '''%%%% Generated by %(program_name)s
185 %%%% Options: [%(option_string)s]
186 \\include "lilypond-book-preamble.ly"
189 %% ****************************************************************
190 %% Start cut-&-pastable-section
191 %% ****************************************************************
193 %(preamble_string)s
195 \paper {
196 %(paper_string)s
197 force-assignment = #""
198 line-width = #(- line-width (* mm %(padding_mm)f))
201 \layout {
202 %(layout_string)s
205 %(safe_mode_string)s
209 FULL_LY = '''
212 %% ****************************************************************
213 %% ly snippet:
214 %% ****************************************************************
215 %(code)s
218 %% ****************************************************************
219 %% end ly snippet
220 %% ****************************************************************
230 ####################################################################
231 # Helper functions
232 ####################################################################
234 def ps_page_count (ps_name):
235 header = file (ps_name).read (1024)
236 m = re.search ('\n%%Pages: ([0-9]+)', header)
237 if m:
238 return int (m.group (1))
239 return 0
241 ly_var_def_re = re.compile (r'^([a-zA-Z]+)[\t ]*=', re.M)
242 ly_comment_re = re.compile (r'(%+[\t ]*)(.*)$', re.M)
243 ly_context_id_re = re.compile ('\\\\(?:new|context)\\s+(?:[a-zA-Z]*?(?:Staff\
244 (?:Group)?|Voice|FiguredBass|FretBoards|Names|Devnull))\\s+=\\s+"?([a-zA-Z]+)"?\\s+')
246 def ly_comment_gettext (t, m):
247 return m.group (1) + t (m.group (2))
251 class CompileError(Exception):
252 pass
256 ####################################################################
257 # Snippet classes
258 ####################################################################
260 class Chunk:
261 def replacement_text (self):
262 return ''
264 def filter_text (self):
265 return self.replacement_text ()
267 def is_plain (self):
268 return False
270 class Substring (Chunk):
271 """A string that does not require extra memory."""
272 def __init__ (self, source, start, end, line_number):
273 self.source = source
274 self.start = start
275 self.end = end
276 self.line_number = line_number
277 self.override_text = None
279 def is_plain (self):
280 return True
282 def replacement_text (self):
283 if self.override_text:
284 return self.override_text
285 else:
286 return self.source[self.start:self.end]
290 class Snippet (Chunk):
291 def __init__ (self, type, match, formatter, line_number, global_options):
292 self.type = type
293 self.match = match
294 self.checksum = 0
295 self.option_dict = {}
296 self.formatter = formatter
297 self.line_number = line_number
298 self.global_options = global_options
299 self.replacements = {'program_version': ly.program_version,
300 'program_name': ly.program_name}
302 # return a shallow copy of the replacements, so the caller can modify
303 # it locally without interfering with other snippet operations
304 def get_replacements (self):
305 return copy.copy (self.replacements)
307 def replacement_text (self):
308 return self.match.group ('match')
310 def substring (self, s):
311 return self.match.group (s)
313 def __repr__ (self):
314 return `self.__class__` + ' type = ' + self.type
318 class IncludeSnippet (Snippet):
319 def processed_filename (self):
320 f = self.substring ('filename')
321 return os.path.splitext (f)[0] + self.formatter.default_extension
323 def replacement_text (self):
324 s = self.match.group ('match')
325 f = self.substring ('filename')
326 return re.sub (f, self.processed_filename (), s)
330 class LilypondSnippet (Snippet):
331 def __init__ (self, type, match, formatter, line_number, global_options):
332 Snippet.__init__ (self, type, match, formatter, line_number, global_options)
333 os = match.group ('options')
334 self.do_options (os, self.type)
337 def snippet_options (self):
338 return [];
340 def verb_ly_gettext (self, s):
341 lang = self.formatter.document_language
342 if not lang:
343 return s
344 try:
345 t = langdefs.translation[lang]
346 except:
347 return s
349 s = ly_comment_re.sub (lambda m: ly_comment_gettext (t, m), s)
351 if langdefs.LANGDICT[lang].enable_ly_identifier_l10n:
352 for v in ly_var_def_re.findall (s):
353 s = re.sub (r"(?m)(?<!\\clef)(^|[' \\#])%s([^a-zA-Z])" % v,
354 "\\1" + t (v) + "\\2",
356 for id in ly_context_id_re.findall (s):
357 s = re.sub (r'(\s+|")%s(\s+|")' % id,
358 "\\1" + t (id) + "\\2",
360 return s
362 def verb_ly (self):
363 verb_text = self.substring ('code')
364 if not NOGETTEXT in self.option_dict:
365 verb_text = self.verb_ly_gettext (verb_text)
366 if not verb_text.endswith ('\n'):
367 verb_text += '\n'
368 return verb_text
370 def ly (self):
371 contents = self.substring ('code')
372 return ('\\sourcefileline %d\n%s'
373 % (self.line_number - 1, contents))
375 def full_ly (self):
376 s = self.ly ()
377 if s:
378 return self.compose_ly (s)
379 return ''
381 def split_options (self, option_string):
382 return self.formatter.split_snippet_options (option_string);
384 def do_options (self, option_string, type):
385 self.option_dict = {}
387 options = self.split_options (option_string)
389 for option in options:
390 if '=' in option:
391 (key, value) = re.split ('\s*=\s*', option)
392 self.option_dict[key] = value
393 else:
394 if option in no_options:
395 if no_options[option] in self.option_dict:
396 del self.option_dict[no_options[option]]
397 else:
398 self.option_dict[option] = None
401 # If LINE_WIDTH is used without parameter, set it to default.
402 has_line_width = self.option_dict.has_key (LINE_WIDTH)
403 if has_line_width and self.option_dict[LINE_WIDTH] == None:
404 has_line_width = False
405 del self.option_dict[LINE_WIDTH]
407 # TODO: Can't we do that more efficiently (built-in python func?)
408 for k in self.formatter.default_snippet_options:
409 if k not in self.option_dict:
410 self.option_dict[k] = self.formatter.default_snippet_options[k]
412 # RELATIVE does not work without FRAGMENT;
413 # make RELATIVE imply FRAGMENT
414 has_relative = self.option_dict.has_key (RELATIVE)
415 if has_relative and not self.option_dict.has_key (FRAGMENT):
416 self.option_dict[FRAGMENT] = None
418 if not has_line_width:
419 if type == 'lilypond' or FRAGMENT in self.option_dict:
420 self.option_dict[RAGGED_RIGHT] = None
422 if type == 'lilypond':
423 if LINE_WIDTH in self.option_dict:
424 del self.option_dict[LINE_WIDTH]
425 else:
426 if RAGGED_RIGHT in self.option_dict:
427 if LINE_WIDTH in self.option_dict:
428 del self.option_dict[LINE_WIDTH]
430 if QUOTE in self.option_dict or type == 'lilypond':
431 if LINE_WIDTH in self.option_dict:
432 del self.option_dict[LINE_WIDTH]
434 if not INDENT in self.option_dict:
435 self.option_dict[INDENT] = '0\\mm'
437 # Set a default line-width if there is none. We need this, because
438 # lilypond-book has set left-padding by default and therefore does
439 # #(define line-width (- line-width (* 3 mm)))
440 # TODO: Junk this ugly hack if the code gets rewritten to concatenate
441 # all settings before writing them in the \paper block.
442 if not LINE_WIDTH in self.option_dict:
443 if not QUOTE in self.option_dict:
444 if not LILYQUOTE in self.option_dict:
445 self.option_dict[LINE_WIDTH] = "#(- paper-width \
446 left-margin-default right-margin-default)"
448 def get_option_list (self):
449 if not 'option_list' in self.__dict__:
450 option_list = []
451 for (key, value) in self.option_dict.items ():
452 if value == None:
453 option_list.append (key)
454 else:
455 option_list.append (key + '=' + value)
456 option_list.sort ()
457 self.option_list = option_list
458 return self.option_list
460 def compose_ly (self, code):
461 if FRAGMENT in self.option_dict:
462 body = FRAGMENT_LY
463 else:
464 body = FULL_LY
466 # Defaults.
467 relative = 1
468 override = {}
469 # The original concept of the `exampleindent' option is broken.
470 # It is not possible to get a sane value for @exampleindent at all
471 # without processing the document itself. Saying
473 # @exampleindent 0
474 # @example
475 # ...
476 # @end example
477 # @exampleindent 5
479 # causes ugly results with the DVI backend of texinfo since the
480 # default value for @exampleindent isn't 5em but 0.4in (or a smaller
481 # value). Executing the above code changes the environment
482 # indentation to an unknown value because we don't know the amount
483 # of 1em in advance since it is font-dependent. Modifying
484 # @exampleindent in the middle of a document is simply not
485 # supported within texinfo.
487 # As a consequence, the only function of @exampleindent is now to
488 # specify the amount of indentation for the `quote' option.
490 # To set @exampleindent locally to zero, we use the @format
491 # environment for non-quoted snippets.
492 override[EXAMPLEINDENT] = r'0.4\in'
493 override[LINE_WIDTH] = '5\\in' # = texinfo_line_widths['@smallbook']
494 override.update (self.formatter.default_snippet_options)
496 option_list = []
497 for option in self.get_option_list ():
498 for name in PROCESSING_INDEPENDENT_OPTIONS:
499 if option.startswith (name):
500 break
501 else:
502 option_list.append (option)
503 option_string = ','.join (option_list)
504 compose_dict = {}
505 compose_types = [NOTES, PREAMBLE, LAYOUT, PAPER]
506 for a in compose_types:
507 compose_dict[a] = []
509 option_names = self.option_dict.keys ()
510 option_names.sort ()
511 for key in option_names:
512 value = self.option_dict[key]
513 (c_key, c_value) = classic_lilypond_book_compatibility (key, value)
514 if c_key:
515 if c_value:
516 warning (
517 _ ("deprecated ly-option used: %s=%s") % (key, value))
518 warning (
519 _ ("compatibility mode translation: %s=%s") % (c_key, c_value))
520 else:
521 warning (
522 _ ("deprecated ly-option used: %s") % key)
523 warning (
524 _ ("compatibility mode translation: %s") % c_key)
526 (key, value) = (c_key, c_value)
528 if value:
529 override[key] = value
530 else:
531 if not override.has_key (key):
532 override[key] = None
534 found = 0
535 for typ in compose_types:
536 if snippet_options[typ].has_key (key):
537 compose_dict[typ].append (snippet_options[typ][key])
538 found = 1
539 break
541 if not found and key not in simple_options and key not in self.snippet_options ():
542 warning (_ ("ignoring unknown ly option: %s") % key)
544 # URGS
545 if RELATIVE in override and override[RELATIVE]:
546 relative = int (override[RELATIVE])
548 relative_quotes = ''
550 # 1 = central C
551 if relative < 0:
552 relative_quotes += ',' * (- relative)
553 elif relative > 0:
554 relative_quotes += "'" * relative
556 paper_string = '\n '.join (compose_dict[PAPER]) % override
557 layout_string = '\n '.join (compose_dict[LAYOUT]) % override
558 notes_string = '\n '.join (compose_dict[NOTES]) % vars ()
559 preamble_string = '\n '.join (compose_dict[PREAMBLE]) % override
560 padding_mm = self.global_options.padding_mm
561 if self.global_options.safe_mode:
562 safe_mode_string = "#(ly:set-option 'safe #t)"
563 else:
564 safe_mode_string = ""
566 d = globals().copy()
567 d.update (locals())
568 d.update (self.global_options.information)
569 return (PREAMBLE_LY + body) % d
571 def get_checksum (self):
572 if not self.checksum:
573 # Work-around for md5 module deprecation warning in python 2.5+:
574 try:
575 from hashlib import md5
576 except ImportError:
577 from md5 import md5
579 # We only want to calculate the hash based on the snippet
580 # code plus fragment options relevant to processing by
581 # lilypond, not the snippet + preamble
582 hash = md5 (self.relevant_contents (self.ly ()))
583 for option in self.get_option_list ():
584 for name in PROCESSING_INDEPENDENT_OPTIONS:
585 if option.startswith (name):
586 break
587 else:
588 hash.update (option)
590 ## let's not create too long names.
591 self.checksum = hash.hexdigest ()[:10]
593 return self.checksum
595 def basename (self):
596 cs = self.get_checksum ()
597 name = '%s/lily-%s' % (cs[:2], cs[2:])
598 return name
600 final_basename = basename
602 def write_ly (self):
603 base = self.basename ()
604 path = os.path.join (self.global_options.lily_output_dir, base)
605 directory = os.path.split(path)[0]
606 if not os.path.isdir (directory):
607 os.makedirs (directory)
608 filename = path + '.ly'
609 if os.path.exists (filename):
610 existing = open (filename, 'r').read ()
612 if self.relevant_contents (existing) != self.relevant_contents (self.full_ly ()):
613 warning ("%s: duplicate filename but different contents of orginal file,\n\
614 printing diff against existing file." % filename)
615 ly.stderr_write (self.filter_pipe (self.full_ly (), 'diff -u %s -' % filename))
616 else:
617 out = file (filename, 'w')
618 out.write (self.full_ly ())
619 file (path + '.txt', 'w').write ('image of music')
621 def relevant_contents (self, ly):
622 return re.sub (r'\\(version|sourcefileline|sourcefilename)[^\n]*\n', '', ly)
624 def link_all_output_files (self, output_dir, output_dir_files, destination):
625 existing, missing = self.all_output_files (output_dir, output_dir_files)
626 if missing:
627 print '\nMissing', missing
628 raise CompileError(self.basename())
629 for name in existing:
630 if (self.global_options.use_source_file_names
631 and isinstance (self, LilypondFileSnippet)):
632 base, ext = os.path.splitext (name)
633 components = base.split ('-')
634 # ugh, assume filenames with prefix with one dash (lily-xxxx)
635 if len (components) > 2:
636 base_suffix = '-' + components[-1]
637 else:
638 base_suffix = ''
639 final_name = self.final_basename () + base_suffix + ext
640 else:
641 final_name = name
642 try:
643 os.unlink (os.path.join (destination, final_name))
644 except OSError:
645 pass
647 src = os.path.join (output_dir, name)
648 dst = os.path.join (destination, final_name)
649 dst_path = os.path.split(dst)[0]
650 if not os.path.isdir (dst_path):
651 os.makedirs (dst_path)
652 os.link (src, dst)
655 def all_output_files (self, output_dir, output_dir_files):
656 """Return all files generated in lily_output_dir, a set.
658 output_dir_files is the list of files in the output directory.
660 result = set ()
661 missing = set ()
662 base = self.basename()
663 full = os.path.join (output_dir, base)
664 def consider_file (name):
665 if name in output_dir_files:
666 result.add (name)
668 def require_file (name):
669 if name in output_dir_files:
670 result.add (name)
671 else:
672 missing.add (name)
674 # UGH - junk self.global_options
675 skip_lily = self.global_options.skip_lilypond_run
676 for required in [base + '.ly',
677 base + '.txt']:
678 require_file (required)
679 if not skip_lily:
680 require_file (base + '-systems.count')
682 if 'ddump-profile' in self.global_options.process_cmd:
683 require_file (base + '.profile')
684 if 'dseparate-log-file' in self.global_options.process_cmd:
685 require_file (base + '.log')
687 map (consider_file, [base + '.tex',
688 base + '.eps',
689 base + '.texidoc',
690 base + '.doctitle',
691 base + '-systems.texi',
692 base + '-systems.tex',
693 base + '-systems.pdftexi'])
694 if self.formatter.document_language:
695 map (consider_file,
696 [base + '.texidoc' + self.formatter.document_language,
697 base + '.doctitle' + self.formatter.document_language])
699 required_files = self.formatter.required_files (self, base, full, result)
700 for f in required_files:
701 require_file (f)
703 system_count = 0
704 if not skip_lily and not missing:
705 system_count = int(file (full + '-systems.count').read())
707 for number in range(1, system_count + 1):
708 systemfile = '%s-%d' % (base, number)
709 require_file (systemfile + '.eps')
710 consider_file (systemfile + '.pdf')
712 # We can't require signatures, since books and toplevel
713 # markups do not output a signature.
714 if 'ddump-signature' in self.global_options.process_cmd:
715 consider_file (systemfile + '.signature')
718 return (result, missing)
720 def is_outdated (self, output_dir, current_files):
721 found, missing = self.all_output_files (output_dir, current_files)
722 return missing
724 def filter_pipe (self, input, cmd):
725 """Pass input through cmd, and return the result."""
727 if self.global_options.verbose:
728 progress (_ ("Opening filter `%s'\n") % cmd)
730 # TODO: Use Popen once we resolve the problem with msvcrt in Windows:
731 (stdin, stdout, stderr) = os.popen3 (cmd)
732 # p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
733 # (stdin, stdout, stderr) = (p.stdin, p.stdout, p.stderr)
734 stdin.write (input)
735 status = stdin.close ()
737 if not status:
738 status = 0
739 output = stdout.read ()
740 status = stdout.close ()
741 error = stderr.read ()
743 if not status:
744 status = 0
745 signal = 0x0f & status
746 if status or (not output and error):
747 exit_status = status >> 8
748 ly.error (_ ("`%s' failed (%d)") % (cmd, exit_status))
749 ly.error (_ ("The error log is as follows:"))
750 ly.stderr_write (error)
751 ly.stderr_write (stderr.read ())
752 exit (status)
754 if self.global_options.verbose:
755 progress ('\n')
757 return output
759 def get_snippet_code (self):
760 return self.substring ('code');
762 def filter_text (self):
763 """Run snippet bodies through a command (say: convert-ly).
765 This functionality is rarely used, and this code must have bitrot.
767 code = self.get_snippet_code ();
768 s = self.filter_pipe (code, self.global_options.filter_cmd)
769 d = {
770 'code': s,
771 'options': self.match.group ('options')
773 return self.formatter.output_simple_replacements (FILTER, d)
775 def replacement_text (self):
776 base = self.final_basename ()
777 return self.formatter.snippet_output (base, self)
779 def get_images (self):
780 rep = {'base': self.final_basename ()}
782 single = '%(base)s.png' % rep
783 multiple = '%(base)s-page1.png' % rep
784 images = (single,)
785 if (os.path.exists (multiple)
786 and (not os.path.exists (single)
787 or (os.stat (multiple)[stat.ST_MTIME]
788 > os.stat (single)[stat.ST_MTIME]))):
789 count = ps_page_count ('%(base)s.eps' % rep)
790 images = ['%s-page%d.png' % (rep['base'], page) for page in range (1, count+1)]
791 images = tuple (images)
793 return images
797 re_begin_verbatim = re.compile (r'\s+%.*?begin verbatim.*\n*', re.M)
798 re_end_verbatim = re.compile (r'\s+%.*?end verbatim.*$', re.M)
800 class LilypondFileSnippet (LilypondSnippet):
801 def __init__ (self, type, match, formatter, line_number, global_options):
802 LilypondSnippet.__init__ (self, type, match, formatter, line_number, global_options)
803 self.contents = file (BookBase.find_file (self.substring ('filename'), global_options.include_path)).read ()
805 def get_snippet_code (self):
806 return self.contents;
808 def verb_ly (self):
809 s = self.contents
810 s = re_begin_verbatim.split (s)[-1]
811 s = re_end_verbatim.split (s)[0]
812 if not NOGETTEXT in self.option_dict:
813 s = self.verb_ly_gettext (s)
814 if not s.endswith ('\n'):
815 s += '\n'
816 return s
818 def ly (self):
819 name = self.substring ('filename')
820 return ('\\sourcefilename \"%s\"\n\\sourcefileline 0\n%s'
821 % (name, self.contents))
823 def final_basename (self):
824 if self.global_options.use_source_file_names:
825 base = os.path.splitext (os.path.basename (self.substring ('filename')))[0]
826 return base
827 else:
828 return self.basename ()
831 class LilyPondVersionString (Snippet):
832 """A string that does not require extra memory."""
833 def __init__ (self, type, match, formatter, line_number, global_options):
834 Snippet.__init__ (self, type, match, formatter, line_number, global_options)
836 def replacement_text (self):
837 return self.formatter.output_simple (self.type, self)
840 snippet_type_to_class = {
841 'lilypond_file': LilypondFileSnippet,
842 'lilypond_block': LilypondSnippet,
843 'lilypond': LilypondSnippet,
844 'include': IncludeSnippet,
845 'lilypondversion': LilyPondVersionString,