2 # -*- coding: utf-8 -*-
4 # This file is part of LilyPond, the GNU music typesetter.
6 # LilyPond is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # LilyPond is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
23 lilypond-book --filter="tr '[a-z]' '[A-Z]'" BOOK
26 lilypond-book --filter="convert-ly --no-version --from=1.6.11 -" BOOK
28 classic lilypond-book:
29 lilypond-book --process="lilypond" BOOK.tely
33 * ly-options: intertext?
35 * eps in latex / eps by lilypond -b ps?
36 * check latex parameters, twocolumn, multicolumn?
37 * use --png --ps --pdf for making images?
39 * Converting from lilypond-book source, substitute:
40 @mbinclude foo.itely -> @include foo.itely
46 # TODO: Better solve the global_options copying to the snippets...
55 from optparse
import OptionGroup
67 import book_base
as BookBase
68 import book_snippets
as BookSnippet
74 ly
.require_python_version ()
76 original_dir
= os
.getcwd ()
80 _ ("Process LilyPond snippets in hybrid HTML, LaTeX, texinfo or DocBook document.")
84 $ lilypond-book --filter="tr '[a-z]' '[A-Z]'" %(BOOK)s
85 $ lilypond-book -F "convert-ly --no-version --from=2.0.0 -" %(BOOK)s
86 $ lilypond-book --process='lilypond -I include' %(BOOK)s
87 ''' % {'BOOK': _ ("BOOK")})
89 authors
= ('Jan Nieuwenhuizen <janneke@gnu.org>',
90 'Han-Wen Nienhuys <hanwen@xs4all.nl>')
92 ################################################################
94 if global_options
.verbose
:
95 raise Exception (_ ('Exiting (%d)...') % i
)
100 ly
.encoded_write (sys
.stdout
, '%s (GNU LilyPond) %s\n' % (ly
.program_name
, ly
.program_version
))
102 progress
= ly
.progress
109 ly
.encoded_write (sys
.stdout
, '''
116 ''' % ( _ ('Copyright (c) %s by') % '2001--2011',
117 '\n '.join (authors
),
118 _ ("Distributed under terms of the GNU General Public License."),
119 _ ("It comes with NO WARRANTY.")))
121 def get_option_parser ():
122 p
= ly
.get_option_parser (usage
=_ ("%s [OPTION]... FILE") % 'lilypond-book',
123 description
=help_summary
,
124 conflict_handler
="resolve",
125 add_help_option
=False)
127 p
.add_option ('-F', '--filter', metavar
=_ ("FILTER"),
130 help=_ ("pipe snippets through FILTER [default: `convert-ly -n -']"),
133 p
.add_option ('-f', '--format',
134 help=_ ("use output format FORMAT (texi [default], texi-html, latex, html, docbook)"),
135 metavar
=_ ("FORMAT"),
138 p
.add_option("-h", "--help",
140 help=_ ("show this help and exit"))
142 p
.add_option ("-I", '--include', help=_ ("add DIR to include path"),
144 action
='append', dest
='include_path',
145 default
=[os
.path
.abspath (os
.getcwd ())])
147 p
.add_option ('--info-images-dir',
148 help=_ ("format Texinfo output so that Info will "
149 "look for images of music in DIR"),
151 action
='store', dest
='info_images_dir',
154 p
.add_option ('--left-padding',
157 help=_ ("pad left side of music to align music inspite of uneven bar numbers (in mm)"),
161 p
.add_option ('--lily-output-dir',
162 help=_ ("write lily-XXX files to DIR, link into --output dir"),
164 action
='store', dest
='lily_output_dir',
167 p
.add_option ('--load-custom-package', help=_ ("Load the additional python PACKAGE (containing e.g. a custom output format)"),
168 metavar
=_ ("PACKAGE"),
169 action
='append', dest
='custom_packages',
172 p
.add_option ("-o", '--output', help=_ ("write output to DIR"),
174 action
='store', dest
='output_dir',
177 p
.add_option ('-P', '--process', metavar
=_ ("COMMAND"),
178 help = _ ("process ly_files using COMMAND FILE..."),
180 dest
='process_cmd', default
='')
182 p
.add_option ('-s', '--safe', help=_ ("Compile snippets in safe mode"),
187 p
.add_option ('--skip-lily-check',
188 help=_ ("do not fail if no lilypond output is found"),
190 action
='store_true', dest
='skip_lilypond_run',
193 p
.add_option ('--skip-png-check',
194 help=_ ("do not fail if no PNG images are found for EPS files"),
196 action
='store_true', dest
='skip_png_check',
199 p
.add_option ('--use-source-file-names',
200 help=_ ("write snippet output files with the same base name as their source file"),
201 action
='store_true', dest
='use_source_file_names',
204 p
.add_option ('-V', '--verbose', help=_ ("be verbose"),
209 p
.version
= "@TOPLEVEL_VERSION@"
210 p
.add_option("--version",
212 help=_ ("show version number and exit"))
214 p
.add_option ('-w', '--warranty',
215 help=_ ("show warranty and copyright"),
218 group
= OptionGroup (p
, "Options only for the latex and texinfo backends")
219 group
.add_option ('--latex-program',
220 help=_ ("run executable PROG instead of latex, or in\n\
221 case --pdf option is set instead of pdflatex"),
223 action
='store', dest
='latex_program',
225 group
.add_option ('--pdf',
228 help=_ ("create PDF files for use with PDFTeX"),
230 p
.add_option_group (group
)
232 p
.add_option_group ('',
234 _ ("Report bugs via %s")
235 % ' http://post.gmane.org/post.php'
236 '?group=gmane.comp.gnu.lilypond.bugs') + '\n')
239 for formatter
in BookBase
.all_formats
:
240 formatter
.add_options (p
)
244 lilypond_binary
= os
.path
.join ('@bindir@', 'lilypond')
246 # If we are called with full path, try to use lilypond binary
247 # installed in the same path; this is needed in GUB binaries, where
248 # @bindir is always different from the installed binary path.
249 if 'bindir' in globals () and bindir
:
250 lilypond_binary
= os
.path
.join (bindir
, 'lilypond')
252 # Only use installed binary when we are installed too.
253 if '@bindir@' == ('@' + 'bindir@') or not os
.path
.exists (lilypond_binary
):
254 lilypond_binary
= 'lilypond'
256 global_options
= None
261 def find_linestarts (s
):
266 i
= s
.find ('\n', start
)
277 def find_toplevel_snippets (input_string
, formatter
):
279 types
= formatter
.supported_snippet_types ()
281 res
[t
] = re
.compile (formatter
.snippet_regexp (t
))
285 found
= dict ([(t
, None) for t
in types
])
287 line_starts
= find_linestarts (input_string
)
289 # We want to search for multiple regexes, without searching
290 # the string multiple times for one regex.
291 # Hence, we use earlier results to limit the string portion
293 # Since every part of the string is traversed at most once for
294 # every type of snippet, this is linear.
299 if not found
[type] or found
[type][0] < index
:
302 m
= res
[type].search (input_string
[index
:endex
])
306 klass
= global_options
.formatter
.snippet_class (type)
308 start
= index
+ m
.start ('match')
309 line_number
= line_start_idx
310 while (line_starts
[line_number
] < start
):
314 snip
= klass (type, m
, formatter
, line_number
, global_options
)
316 found
[type] = (start
, snip
)
320 or found
[type][0] < found
[first
][0])):
325 # Limiting the search space is a cute
326 # idea, but this *requires* to search
327 # for possible containing blocks
328 # first, at least as long as we do not
329 # search for the start of blocks, but
330 # always/directly for the entire
331 # @block ... @end block.
333 endex
= found
[first
][0]
336 snippets
.append (BookSnippet
.Substring (input_string
, index
, len (input_string
), line_start_idx
))
339 while (start
> line_starts
[line_start_idx
+1]):
342 (start
, snip
) = found
[first
]
343 snippets
.append (BookSnippet
.Substring (input_string
, index
, start
, line_start_idx
+ 1))
344 snippets
.append (snip
)
346 index
= start
+ len (snip
.match
.group ('match'))
350 def system_in_directory (cmd
, directory
):
351 """Execute a command in a different directory.
353 Because of win32 compatibility, we can't simply use subprocess.
356 current
= os
.getcwd()
358 ly
.system(cmd
, be_verbose
=global_options
.verbose
,
363 def process_snippets (cmd
, snippets
,
364 formatter
, lily_output_dir
):
365 """Run cmd on all of the .ly files from snippets."""
370 cmd
= formatter
.adjust_snippet_command (cmd
)
372 checksum
= snippet_list_checksum (snippets
)
373 contents
= '\n'.join (['snippet-map-%d.ly' % checksum
]
374 + list (set ([snip
.basename() + '.ly' for snip
in snippets
])))
375 name
= os
.path
.join (lily_output_dir
,
376 'snippet-names-%d.ly' % checksum
)
377 file (name
, 'wb').write (contents
)
379 system_in_directory (' '.join ([cmd
, ly
.mkarg (name
)]),
383 def snippet_list_checksum (snippets
):
384 return hash (' '.join([l
.basename() for l
in snippets
]))
386 def write_file_map (lys
, name
):
387 snippet_map
= file (os
.path
.join (
388 global_options
.lily_output_dir
,
389 'snippet-map-%d.ly' % snippet_list_checksum (lys
)), 'w')
391 snippet_map
.write ("""
392 #(define version-seen #t)
393 #(define output-empty-score-list #f)
394 #(ly:add-file-name-alist '(%s
396 """ % '\n'.join(['("%s.ly" . "%s")\n' % (ly
.basename (), name
)
399 def split_output_files(directory
):
400 """Returns directory entries in DIRECTORY/XX/ , where XX are hex digits.
402 Return value is a set of strings.
405 for subdir
in glob
.glob (os
.path
.join (directory
, '[a-f0-9][a-f0-9]')):
406 base_subdir
= os
.path
.split (subdir
)[1]
407 sub_files
= [os
.path
.join (base_subdir
, name
)
408 for name
in os
.listdir (subdir
)]
412 def do_process_cmd (chunks
, input_name
, options
):
413 snippets
= [c
for c
in chunks
if isinstance (c
, BookSnippet
.LilypondSnippet
)]
415 output_files
= split_output_files (options
.lily_output_dir
)
416 outdated
= [c
for c
in snippets
if c
.is_outdated (options
.lily_output_dir
, output_files
)]
418 write_file_map (outdated
, input_name
)
419 progress (_ ("Writing snippets..."))
420 for snippet
in outdated
:
425 progress (_ ("Processing..."))
427 process_snippets (options
.process_cmd
, outdated
,
428 options
.formatter
, options
.lily_output_dir
)
431 progress (_ ("All snippets are up to date..."))
433 if options
.lily_output_dir
!= options
.output_dir
:
434 output_files
= split_output_files (options
.lily_output_dir
)
435 for snippet
in snippets
:
436 snippet
.link_all_output_files (options
.lily_output_dir
,
444 # Format guessing data
446 def guess_format (input_filename
):
448 e
= os
.path
.splitext (input_filename
)[1]
449 for formatter
in BookBase
.all_formats
:
450 if formatter
.can_handle_extension (e
):
452 error (_ ("cannot determine format for: %s" % input_filename
))
455 def write_if_updated (file_name
, lines
):
459 new_str
= ''.join (lines
)
460 if oldstr
== new_str
:
461 progress (_ ("%s is up to date.") % file_name
)
464 # this prevents make from always rerunning lilypond-book:
465 # output file must be touched in order to be up to date
466 os
.utime (file_name
, None)
471 output_dir
= os
.path
.dirname (file_name
)
472 if not os
.path
.exists (output_dir
):
473 os
.makedirs (output_dir
)
475 progress (_ ("Writing `%s'...") % file_name
)
476 file (file_name
, 'w').writelines (lines
)
480 def note_input_file (name
, inputs
=[]):
481 ## hack: inputs is mutable!
485 def samefile (f1
, f2
):
487 return os
.path
.samefile (f1
, f2
)
488 except AttributeError: # Windoze
489 f1
= re
.sub ("//*", "/", f1
)
490 f2
= re
.sub ("//*", "/", f2
)
493 def do_file (input_filename
, included
=False):
495 input_absname
= input_filename
496 if not input_filename
or input_filename
== '-':
497 in_handle
= sys
.stdin
498 input_fullname
= '<stdin>'
500 if os
.path
.exists (input_filename
):
501 input_fullname
= input_filename
503 input_fullname
= global_options
.formatter
.input_fullname (input_filename
)
504 # Normalize path to absolute path, since we will change cwd to the output dir!
505 # Otherwise, "lilypond-book -o out test.tex" will complain that it is
506 # overwriting the input file (which it is actually not), since the
507 # input filename is relative to the CWD...
508 input_absname
= os
.path
.abspath (input_fullname
)
510 note_input_file (input_fullname
)
511 in_handle
= file (input_fullname
)
513 if input_filename
== '-':
516 input_base
= os
.path
.splitext (input_filename
)[0]
518 input_base
= os
.path
.basename (
519 os
.path
.splitext (input_filename
)[0])
521 # don't complain when global_options.output_dir is existing
522 if not global_options
.output_dir
:
523 global_options
.output_dir
= os
.getcwd()
525 global_options
.output_dir
= os
.path
.abspath(global_options
.output_dir
)
527 if not os
.path
.isdir (global_options
.output_dir
):
528 os
.mkdir (global_options
.output_dir
, 0777)
529 os
.chdir (global_options
.output_dir
)
531 output_filename
= os
.path
.join(global_options
.output_dir
,
532 input_base
+ global_options
.formatter
.default_extension
)
533 if (os
.path
.exists (input_filename
)
534 and os
.path
.exists (output_filename
)
535 and samefile (output_filename
, input_absname
)):
537 _ ("Output would overwrite input file; use --output."))
541 progress (_ ("Reading %s...") % input_fullname
)
542 source
= in_handle
.read ()
546 global_options
.formatter
.init_default_snippet_options (source
)
549 progress (_ ("Dissecting..."))
550 chunks
= find_toplevel_snippets (source
, global_options
.formatter
)
552 # Let the formatter modify the chunks before further processing
553 chunks
= global_options
.formatter
.process_chunks (chunks
)
556 if global_options
.filter_cmd
:
557 write_if_updated (output_filename
,
558 [c
.filter_text () for c
in chunks
])
559 elif global_options
.process_cmd
:
560 do_process_cmd (chunks
, input_fullname
, global_options
)
561 progress (_ ("Compiling %s...") % output_filename
)
563 write_if_updated (output_filename
,
564 [s
.replacement_text ()
567 def process_include (snippet
):
568 os
.chdir (original_dir
)
569 name
= snippet
.substring ('filename')
570 progress (_ ("Processing include: %s") % name
)
572 return do_file (name
, included
=True)
574 include_chunks
= map (process_include
,
575 filter (lambda x
: isinstance (x
, BookSnippet
.IncludeSnippet
),
578 return chunks
+ reduce (lambda x
, y
: x
+ y
, include_chunks
, [])
580 except BookSnippet
.CompileError
:
581 os
.chdir (original_dir
)
582 progress (_ ("Removing `%s'") % output_filename
)
584 raise BookSnippet
.CompileError
587 global global_options
589 opt_parser
= get_option_parser()
590 (global_options
, args
) = opt_parser
.parse_args ()
592 global_options
.information
= {'program_version': ly
.program_version
, 'program_name': ly
.program_name
}
594 global_options
.include_path
= map (os
.path
.abspath
, global_options
.include_path
)
596 # Load the python packages (containing e.g. custom formatter classes)
597 # passed on the command line
599 for i
in global_options
.custom_packages
:
601 print imp
.load_source ("book_custom_package%s" % nr
, i
)
604 if global_options
.warranty
:
607 if not args
or len (args
) > 1:
608 opt_parser
.print_help ()
614 # FIXME: 85 lines of `main' macramee??
615 files
= do_options ()
617 basename
= os
.path
.splitext (files
[0])[0]
618 basename
= os
.path
.split (basename
)[1]
620 if global_options
.format
:
621 # Retrieve the formatter for the given format
622 for formatter
in BookBase
.all_formats
:
623 if formatter
.can_handle_format (global_options
.format
):
624 global_options
.formatter
= formatter
626 global_options
.formatter
= guess_format (files
[0])
627 global_options
.format
= global_options
.formatter
.format
629 # make the global options available to the formatters:
630 global_options
.formatter
.global_options
= global_options
631 formats
= global_options
.formatter
.image_formats
633 if global_options
.process_cmd
== '':
634 global_options
.process_cmd
= (lilypond_binary
635 + ' --formats=%s -dbackend=eps ' % formats
)
637 if global_options
.process_cmd
:
638 includes
= global_options
.include_path
639 if global_options
.lily_output_dir
:
640 # This must be first, so lilypond prefers to read .ly
641 # files in the other lybookdb dir.
642 includes
= [os
.path
.abspath(global_options
.lily_output_dir
)] + includes
643 global_options
.process_cmd
+= ' '.join ([' -I %s' % ly
.mkarg (p
)
646 global_options
.formatter
.process_options (global_options
)
648 if global_options
.verbose
:
649 global_options
.process_cmd
+= " --verbose "
651 if global_options
.padding_mm
:
652 global_options
.process_cmd
+= " -deps-box-padding=%f " % global_options
.padding_mm
654 global_options
.process_cmd
+= " -dread-file-list -dno-strip-output-dir"
656 if global_options
.lily_output_dir
:
657 global_options
.lily_output_dir
= os
.path
.abspath(global_options
.lily_output_dir
)
658 if not os
.path
.isdir (global_options
.lily_output_dir
):
659 os
.makedirs (global_options
.lily_output_dir
)
661 global_options
.lily_output_dir
= os
.path
.abspath(global_options
.output_dir
)
666 chunks
= do_file (files
[0])
667 except BookSnippet
.CompileError
:
670 inputs
= note_input_file ('')
673 base_file_name
= os
.path
.splitext (os
.path
.basename (files
[0]))[0]
674 dep_file
= os
.path
.join (global_options
.output_dir
, base_file_name
+ '.dep')
675 final_output_file
= os
.path
.join (global_options
.output_dir
,
677 + '.%s' % global_options
.format
)
679 os
.chdir (original_dir
)
680 file (dep_file
, 'w').write ('%s: %s'
681 % (final_output_file
, ' '.join (inputs
)))
683 if __name__
== '__main__':