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--2010',
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 ('--skip-lily-check',
183 help=_ ("do not fail if no lilypond output is found"),
185 action
='store_true', dest
='skip_lilypond_run',
188 p
.add_option ('--skip-png-check',
189 help=_ ("do not fail if no PNG images are found for EPS files"),
191 action
='store_true', dest
='skip_png_check',
194 p
.add_option ('--use-source-file-names',
195 help=_ ("write snippet output files with the same base name as their source file"),
196 action
='store_true', dest
='use_source_file_names',
199 p
.add_option ('-V', '--verbose', help=_ ("be verbose"),
204 p
.version
= "@TOPLEVEL_VERSION@"
205 p
.add_option("--version",
207 help=_ ("show version number and exit"))
209 p
.add_option ('-w', '--warranty',
210 help=_ ("show warranty and copyright"),
213 group
= OptionGroup (p
, "Options only for the latex and texinfo backends")
214 group
.add_option ('--latex-program',
215 help=_ ("run executable PROG instead of latex, or in\n\
216 case --pdf option is set instead of pdflatex"),
218 action
='store', dest
='latex_program',
220 group
.add_option ('--pdf',
223 help=_ ("create PDF files for use with PDFTeX"),
225 p
.add_option_group (group
)
227 p
.add_option_group ('',
229 _ ("Report bugs via %s")
230 % ' http://post.gmane.org/post.php'
231 '?group=gmane.comp.gnu.lilypond.bugs') + '\n')
234 for formatter
in BookBase
.all_formats
:
235 formatter
.add_options (p
)
239 lilypond_binary
= os
.path
.join ('@bindir@', 'lilypond')
241 # If we are called with full path, try to use lilypond binary
242 # installed in the same path; this is needed in GUB binaries, where
243 # @bindir is always different from the installed binary path.
244 if 'bindir' in globals () and bindir
:
245 lilypond_binary
= os
.path
.join (bindir
, 'lilypond')
247 # Only use installed binary when we are installed too.
248 if '@bindir@' == ('@' + 'bindir@') or not os
.path
.exists (lilypond_binary
):
249 lilypond_binary
= 'lilypond'
251 global_options
= None
256 def find_linestarts (s
):
261 i
= s
.find ('\n', start
)
272 def find_toplevel_snippets (input_string
, formatter
):
274 types
= formatter
.supported_snippet_types ()
276 res
[t
] = re
.compile (formatter
.snippet_regexp (t
))
280 found
= dict ([(t
, None) for t
in types
])
282 line_starts
= find_linestarts (input_string
)
284 # We want to search for multiple regexes, without searching
285 # the string multiple times for one regex.
286 # Hence, we use earlier results to limit the string portion
288 # Since every part of the string is traversed at most once for
289 # every type of snippet, this is linear.
294 if not found
[type] or found
[type][0] < index
:
297 m
= res
[type].search (input_string
[index
:endex
])
301 klass
= global_options
.formatter
.snippet_class (type)
303 start
= index
+ m
.start ('match')
304 line_number
= line_start_idx
305 while (line_starts
[line_number
] < start
):
309 snip
= klass (type, m
, formatter
, line_number
, global_options
)
311 found
[type] = (start
, snip
)
315 or found
[type][0] < found
[first
][0])):
320 # Limiting the search space is a cute
321 # idea, but this *requires* to search
322 # for possible containing blocks
323 # first, at least as long as we do not
324 # search for the start of blocks, but
325 # always/directly for the entire
326 # @block ... @end block.
328 endex
= found
[first
][0]
331 snippets
.append (BookSnippet
.Substring (input_string
, index
, len (input_string
), line_start_idx
))
334 while (start
> line_starts
[line_start_idx
+1]):
337 (start
, snip
) = found
[first
]
338 snippets
.append (BookSnippet
.Substring (input_string
, index
, start
, line_start_idx
+ 1))
339 snippets
.append (snip
)
341 index
= start
+ len (snip
.match
.group ('match'))
345 def system_in_directory (cmd
, directory
):
346 """Execute a command in a different directory.
348 Because of win32 compatibility, we can't simply use subprocess.
351 current
= os
.getcwd()
353 ly
.system(cmd
, be_verbose
=global_options
.verbose
,
358 def process_snippets (cmd
, snippets
,
359 formatter
, lily_output_dir
):
360 """Run cmd on all of the .ly files from snippets."""
365 cmd
= formatter
.adjust_snippet_command (cmd
)
367 checksum
= snippet_list_checksum (snippets
)
368 contents
= '\n'.join (['snippet-map-%d.ly' % checksum
]
369 + list (set ([snip
.basename() + '.ly' for snip
in snippets
])))
370 name
= os
.path
.join (lily_output_dir
,
371 'snippet-names-%d.ly' % checksum
)
372 file (name
, 'wb').write (contents
)
374 system_in_directory (' '.join ([cmd
, ly
.mkarg (name
)]),
378 def snippet_list_checksum (snippets
):
379 return hash (' '.join([l
.basename() for l
in snippets
]))
381 def write_file_map (lys
, name
):
382 snippet_map
= file (os
.path
.join (
383 global_options
.lily_output_dir
,
384 'snippet-map-%d.ly' % snippet_list_checksum (lys
)), 'w')
386 snippet_map
.write ("""
387 #(define version-seen #t)
388 #(define output-empty-score-list #f)
389 #(ly:add-file-name-alist '(%s
391 """ % '\n'.join(['("%s.ly" . "%s")\n' % (ly
.basename (), name
)
394 def split_output_files(directory
):
395 """Returns directory entries in DIRECTORY/XX/ , where XX are hex digits.
397 Return value is a set of strings.
400 for subdir
in glob
.glob (os
.path
.join (directory
, '[a-f0-9][a-f0-9]')):
401 base_subdir
= os
.path
.split (subdir
)[1]
402 sub_files
= [os
.path
.join (base_subdir
, name
)
403 for name
in os
.listdir (subdir
)]
407 def do_process_cmd (chunks
, input_name
, options
):
408 snippets
= [c
for c
in chunks
if isinstance (c
, BookSnippet
.LilypondSnippet
)]
410 output_files
= split_output_files (options
.lily_output_dir
)
411 outdated
= [c
for c
in snippets
if c
.is_outdated (options
.lily_output_dir
, output_files
)]
413 write_file_map (outdated
, input_name
)
414 progress (_ ("Writing snippets..."))
415 for snippet
in outdated
:
420 progress (_ ("Processing..."))
422 process_snippets (options
.process_cmd
, outdated
,
423 options
.formatter
, options
.lily_output_dir
)
426 progress (_ ("All snippets are up to date..."))
428 if options
.lily_output_dir
!= options
.output_dir
:
429 output_files
= split_output_files (options
.lily_output_dir
)
430 for snippet
in snippets
:
431 snippet
.link_all_output_files (options
.lily_output_dir
,
439 # Format guessing data
441 def guess_format (input_filename
):
443 e
= os
.path
.splitext (input_filename
)[1]
444 for formatter
in BookBase
.all_formats
:
445 if formatter
.can_handle_extension (e
):
447 error (_ ("cannot determine format for: %s" % input_filename
))
450 def write_if_updated (file_name
, lines
):
454 new_str
= ''.join (lines
)
455 if oldstr
== new_str
:
456 progress (_ ("%s is up to date.") % file_name
)
459 # this prevents make from always rerunning lilypond-book:
460 # output file must be touched in order to be up to date
461 os
.utime (file_name
, None)
466 output_dir
= os
.path
.dirname (file_name
)
467 if not os
.path
.exists (output_dir
):
468 os
.makedirs (output_dir
)
470 progress (_ ("Writing `%s'...") % file_name
)
471 file (file_name
, 'w').writelines (lines
)
475 def note_input_file (name
, inputs
=[]):
476 ## hack: inputs is mutable!
480 def samefile (f1
, f2
):
482 return os
.path
.samefile (f1
, f2
)
483 except AttributeError: # Windoze
484 f1
= re
.sub ("//*", "/", f1
)
485 f2
= re
.sub ("//*", "/", f2
)
488 def do_file (input_filename
, included
=False):
490 if not input_filename
or input_filename
== '-':
491 in_handle
= sys
.stdin
492 input_fullname
= '<stdin>'
494 if os
.path
.exists (input_filename
):
495 input_fullname
= input_filename
497 input_fullname
= global_options
.formatter
.input_fullname (input_filename
)
498 # Normalize path to absolute path, since we will change cwd to the output dir!
499 input_fullname
= os
.path
.abspath (input_fullname
)
501 note_input_file (input_fullname
)
502 in_handle
= file (input_fullname
)
504 if input_filename
== '-':
507 input_base
= os
.path
.splitext (input_filename
)[0]
509 input_base
= os
.path
.basename (
510 os
.path
.splitext (input_filename
)[0])
512 # don't complain when global_options.output_dir is existing
513 if not global_options
.output_dir
:
514 global_options
.output_dir
= os
.getcwd()
516 global_options
.output_dir
= os
.path
.abspath(global_options
.output_dir
)
518 if not os
.path
.isdir (global_options
.output_dir
):
519 os
.mkdir (global_options
.output_dir
, 0777)
520 os
.chdir (global_options
.output_dir
)
522 output_filename
= os
.path
.join(global_options
.output_dir
,
523 input_base
+ global_options
.formatter
.default_extension
)
524 if (os
.path
.exists (input_filename
)
525 and os
.path
.exists (output_filename
)
526 and samefile (output_filename
, input_fullname
)):
528 _ ("Output would overwrite input file; use --output."))
532 progress (_ ("Reading %s...") % input_fullname
)
533 source
= in_handle
.read ()
537 global_options
.formatter
.init_default_snippet_options (source
)
540 progress (_ ("Dissecting..."))
541 chunks
= find_toplevel_snippets (source
, global_options
.formatter
)
543 # Let the formatter modify the chunks before further processing
544 chunks
= global_options
.formatter
.process_chunks (chunks
)
547 if global_options
.filter_cmd
:
548 write_if_updated (output_filename
,
549 [c
.filter_text () for c
in chunks
])
550 elif global_options
.process_cmd
:
551 do_process_cmd (chunks
, input_fullname
, global_options
)
552 progress (_ ("Compiling %s...") % output_filename
)
554 write_if_updated (output_filename
,
555 [s
.replacement_text ()
558 def process_include (snippet
):
559 os
.chdir (original_dir
)
560 name
= snippet
.substring ('filename')
561 progress (_ ("Processing include: %s") % name
)
563 return do_file (name
, included
=True)
565 include_chunks
= map (process_include
,
566 filter (lambda x
: isinstance (x
, BookSnippet
.IncludeSnippet
),
569 return chunks
+ reduce (lambda x
, y
: x
+ y
, include_chunks
, [])
571 except BookSnippet
.CompileError
:
572 os
.chdir (original_dir
)
573 progress (_ ("Removing `%s'") % output_filename
)
575 raise BookSnippet
.CompileError
578 global global_options
580 opt_parser
= get_option_parser()
581 (global_options
, args
) = opt_parser
.parse_args ()
583 global_options
.information
= {'program_version': ly
.program_version
, 'program_name': ly
.program_name
}
585 global_options
.include_path
= map (os
.path
.abspath
, global_options
.include_path
)
587 # Load the python packages (containing e.g. custom formatter classes)
588 # passed on the command line
590 for i
in global_options
.custom_packages
:
592 print imp
.load_source ("book_custom_package%s" % nr
, i
)
595 if global_options
.warranty
:
598 if not args
or len (args
) > 1:
599 opt_parser
.print_help ()
605 # FIXME: 85 lines of `main' macramee??
606 files
= do_options ()
608 basename
= os
.path
.splitext (files
[0])[0]
609 basename
= os
.path
.split (basename
)[1]
611 if global_options
.format
:
612 # Retrieve the formatter for the given format
613 for formatter
in BookBase
.all_formats
:
614 if formatter
.can_handle_format (global_options
.format
):
615 global_options
.formatter
= formatter
617 global_options
.formatter
= guess_format (files
[0])
618 global_options
.format
= global_options
.formatter
.format
620 # make the global options available to the formatters:
621 global_options
.formatter
.global_options
= global_options
622 formats
= global_options
.formatter
.image_formats
624 if global_options
.process_cmd
== '':
625 global_options
.process_cmd
= (lilypond_binary
626 + ' --formats=%s -dbackend=eps ' % formats
)
628 if global_options
.process_cmd
:
629 includes
= global_options
.include_path
630 if global_options
.lily_output_dir
:
631 # This must be first, so lilypond prefers to read .ly
632 # files in the other lybookdb dir.
633 includes
= [os
.path
.abspath(global_options
.lily_output_dir
)] + includes
634 global_options
.process_cmd
+= ' '.join ([' -I %s' % ly
.mkarg (p
)
637 global_options
.formatter
.process_options (global_options
)
639 if global_options
.verbose
:
640 global_options
.process_cmd
+= " --verbose "
642 if global_options
.padding_mm
:
643 global_options
.process_cmd
+= " -deps-box-padding=%f " % global_options
.padding_mm
645 global_options
.process_cmd
+= " -dread-file-list -dno-strip-output-dir"
647 if global_options
.lily_output_dir
:
648 global_options
.lily_output_dir
= os
.path
.abspath(global_options
.lily_output_dir
)
649 if not os
.path
.isdir (global_options
.lily_output_dir
):
650 os
.makedirs (global_options
.lily_output_dir
)
652 global_options
.lily_output_dir
= os
.path
.abspath(global_options
.output_dir
)
657 chunks
= do_file (files
[0])
658 except BookSnippet
.CompileError
:
661 inputs
= note_input_file ('')
664 base_file_name
= os
.path
.splitext (os
.path
.basename (files
[0]))[0]
665 dep_file
= os
.path
.join (global_options
.output_dir
, base_file_name
+ '.dep')
666 final_output_file
= os
.path
.join (global_options
.output_dir
,
668 + '.%s' % global_options
.format
)
670 os
.chdir (original_dir
)
671 file (dep_file
, 'w').write ('%s: %s'
672 % (final_output_file
, ' '.join (inputs
)))
674 if __name__
== '__main__':