]> git.donarmstrong.com Git - lilypond.git/blob - scripts/lilypond-book.py
lilypond-book: Textwidth detection with included file (issue 3136).
[lilypond.git] / scripts / lilypond-book.py
1 #!@TARGET_PYTHON@
2 # -*- coding: utf-8 -*-
3
4 # This file is part of LilyPond, the GNU music typesetter.
5 #
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.
10 #
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.
15 #
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/>.
18
19 '''
20 Example usage:
21
22 test:
23   lilypond-book --filter="tr '[a-z]' '[A-Z]'" BOOK
24
25 convert-ly on book:
26   lilypond-book --filter="convert-ly --no-version --from=1.6.11 -" BOOK
27
28 classic lilypond-book:
29   lilypond-book --process="lilypond" BOOK.tely
30
31 TODO:
32
33   *  ly-options: intertext?
34   *  --line-width?
35   *  eps in latex / eps by lilypond -b ps?
36   *  check latex parameters, twocolumn, multicolumn?
37   *  use --png --ps --pdf for making images?
38
39   *  Converting from lilypond-book source, substitute:
40    @mbinclude foo.itely -> @include foo.itely
41    \mbinput -> \input
42
43 '''
44
45
46 # TODO: Better solve the global_options copying to the snippets...
47
48 import glob
49 import os
50 import re
51 import stat
52 import sys
53 import tempfile
54 import imp
55 from optparse import OptionGroup
56
57
58 """
59 @relocate-preamble@
60 """
61
62 import lilylib as ly
63 import fontextract
64 import langdefs
65 global _;_=ly._
66
67 import book_base as BookBase
68 import book_snippets as BookSnippet
69 import book_html
70 import book_docbook
71 import book_texinfo
72 import book_latex
73
74 ly.require_python_version ()
75
76 original_dir = os.getcwd ()
77 backend = 'ps'
78
79 help_summary = (
80 _ ("Process LilyPond snippets in hybrid HTML, LaTeX, texinfo or DocBook document.")
81 + '\n\n'
82 + _ ("Examples:")
83 + '''
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")})
88
89 authors = ('Jan Nieuwenhuizen <janneke@gnu.org>',
90            'Han-Wen Nienhuys <hanwen@xs4all.nl>')
91
92 ################################################################
93 def exit (i):
94     if ly.is_verbose ():
95         raise Exception (_ ('Exiting (%d)...') % i)
96     else:
97         sys.exit (i)
98
99 progress = ly.progress
100 warning = ly.warning
101 error = ly.error
102
103 def identify ():
104     progress('%s (GNU LilyPond) %s' % (ly.program_name, ly.program_version))
105
106 def warranty ():
107     identify ()
108     ly.encoded_write (sys.stdout, '''
109 %s
110
111   %s
112
113 %s
114 %s
115 ''' % ( _ ('Copyright (c) %s by') % '2001--2012',
116         '\n  '.join (authors),
117         _ ("Distributed under terms of the GNU General Public License."),
118         _ ("It comes with NO WARRANTY.")))
119
120
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)
126
127     p.add_option ('-F', '--filter', metavar=_ ("FILTER"),
128                   action="store",
129                   dest="filter_cmd",
130                   help=_ ("pipe snippets through FILTER [default: `convert-ly -n -']"),
131                   default=None)
132
133     p.add_option ('-f', '--format',
134                   help=_ ("use output format FORMAT (texi [default], texi-html, latex, html, docbook)"),
135                   metavar=_ ("FORMAT"),
136                   action='store')
137
138     p.add_option("-h", "--help",
139                  action="help",
140                  help=_ ("show this help and exit"))
141
142     p.add_option ("-I", '--include', help=_ ("add DIR to include path"),
143                   metavar=_ ("DIR"),
144                   action='append', dest='include_path',
145                   default=[])
146
147     p.add_option ('--info-images-dir',
148                   help=_ ("format Texinfo output so that Info will "
149                           "look for images of music in DIR"),
150                   metavar=_ ("DIR"),
151                   action='store', dest='info_images_dir',
152                   default='')
153
154     p.add_option ('--left-padding',
155                   metavar=_ ("PAD"),
156                   dest="padding_mm",
157                   help=_ ("pad left side of music to align music inspite of uneven bar numbers (in mm)"),
158                   type="float",
159                   default=3.0)
160
161     p.add_option ('--lily-loglevel',
162                   help=_ ("Print lilypond log messages according to LOGLEVEL"),
163                   metavar=_ ("LOGLEVEL"),
164                   action='store', dest='lily_loglevel',
165                   default=os.environ.get ("LILYPOND_LOGLEVEL", None))
166
167     p.add_option ('--lily-output-dir',
168                   help=_ ("write lily-XXX files to DIR, link into --output dir"),
169                   metavar=_ ("DIR"),
170                   action='store', dest='lily_output_dir',
171                   default=None)
172
173     p.add_option ('--load-custom-package', help=_ ("Load the additional python PACKAGE (containing e.g. a custom output format)"),
174                   metavar=_ ("PACKAGE"),
175                   action='append', dest='custom_packages',
176                   default=[])
177
178     p.add_option ("-l", "--loglevel",
179                   help=_ ("Print log messages according to LOGLEVEL "
180                           "(NONE, ERROR, WARNING, PROGRESS (default), DEBUG)"),
181                   metavar=_ ("LOGLEVEL"),
182                   action='callback',
183                   callback=ly.handle_loglevel_option,
184                   type='string')
185
186     p.add_option ("-o", '--output', help=_ ("write output to DIR"),
187                   metavar=_ ("DIR"),
188                   action='store', dest='output_dir',
189                   default='')
190
191     p.add_option ('-P', '--process', metavar=_ ("COMMAND"),
192                   help = _ ("process ly_files using COMMAND FILE..."),
193                   action='store',
194                   dest='process_cmd', default='')
195
196     p.add_option ('--redirect-lilypond-output',
197                   help = _ ("Redirect the lilypond output"),
198                   action='store_true',
199                   dest='redirect_output', default=False)
200
201     p.add_option ('-s', '--safe', help=_ ("Compile snippets in safe mode"),
202                   action="store_true",
203                   default=False,
204                   dest="safe_mode")
205
206     p.add_option ('--skip-lily-check',
207                   help=_ ("do not fail if no lilypond output is found"),
208                   metavar=_ ("DIR"),
209                   action='store_true', dest='skip_lilypond_run',
210                   default=False)
211
212     p.add_option ('--skip-png-check',
213                   help=_ ("do not fail if no PNG images are found for EPS files"),
214                   metavar=_ ("DIR"),
215                   action='store_true', dest='skip_png_check',
216                   default=False)
217
218     p.add_option ('--use-source-file-names',
219                   help=_ ("write snippet output files with the same base name as their source file"),
220                   action='store_true', dest='use_source_file_names',
221                   default=False)
222
223     p.add_option ('-V', '--verbose', help=_ ("be verbose"),
224                   action="callback",
225                   callback=ly.handle_loglevel_option,
226                   callback_args=("DEBUG",))
227
228     p.version = "@TOPLEVEL_VERSION@"
229     p.add_option("--version",
230                  action="version",
231                  help=_ ("show version number and exit"))
232
233     p.add_option ('-w', '--warranty',
234                   help=_ ("show warranty and copyright"),
235                   action='store_true')
236
237     group = OptionGroup (p, "Options only for the latex and texinfo backends")
238     group.add_option ('--latex-program',
239               help=_ ("run executable PROG instead of latex, or in\n\
240 case --pdf option is set instead of pdflatex"),
241               metavar=_ ("PROG"),
242               action='store', dest='latex_program',
243               default='latex')
244     group.add_option ('--texinfo-program',
245               help=_ ("run executable PROG instead of texi2pdf"),
246               metavar=_ ("PROG"),
247               action='store', dest='texinfo_program',
248               default='texi2pdf')
249     group.add_option ('--pdf',
250               action="store_true",
251               dest="create_pdf",
252               help=_ ("create PDF files for use with PDFTeX"),
253               default=False)
254     p.add_option_group (group)
255
256     p.add_option_group ('',
257                         description=(
258         _ ("Report bugs via %s")
259         % ' http://post.gmane.org/post.php'
260         '?group=gmane.comp.gnu.lilypond.bugs') + '\n')
261
262
263     for formatter in BookBase.all_formats:
264       formatter.add_options (p)
265
266     return p
267
268 lilypond_binary = os.path.join ('@bindir@', 'lilypond')
269
270 # If we are called with full path, try to use lilypond binary
271 # installed in the same path; this is needed in GUB binaries, where
272 # @bindir is always different from the installed binary path.
273 if 'bindir' in globals () and bindir:
274     lilypond_binary = os.path.join (bindir, 'lilypond')
275
276 # Only use installed binary when we are installed too.
277 if '@bindir@' == ('@' + 'bindir@') or not os.path.exists (lilypond_binary):
278     lilypond_binary = 'lilypond'
279
280 global_options = None
281
282
283
284
285 def find_linestarts (s):
286     nls = [0]
287     start = 0
288     end = len (s)
289     while 1:
290         i = s.find ('\n', start)
291         if i < 0:
292             break
293
294         i = i + 1
295         nls.append (i)
296         start = i
297
298     nls.append (len (s))
299     return nls
300
301 def find_toplevel_snippets (input_string, formatter):
302     res = {}
303     types = formatter.supported_snippet_types ()
304     for t in types:
305         res[t] = re.compile (formatter.snippet_regexp (t))
306
307     snippets = []
308     index = 0
309     found = dict ([(t, None) for t in types])
310
311     line_starts = find_linestarts (input_string)
312     line_start_idx = 0
313     # We want to search for multiple regexes, without searching
314     # the string multiple times for one regex.
315     # Hence, we use earlier results to limit the string portion
316     # where we search.
317     # Since every part of the string is traversed at most once for
318     # every type of snippet, this is linear.
319     while 1:
320         first = None
321         endex = 1 << 30
322         for type in types:
323             if not found[type] or found[type][0] < index:
324                 found[type] = None
325
326                 m = res[type].search (input_string[index:endex])
327                 if not m:
328                     continue
329
330                 klass = global_options.formatter.snippet_class (type)
331
332                 start = index + m.start ('match')
333                 line_number = line_start_idx
334                 while (line_starts[line_number] < start):
335                     line_number += 1
336
337                 line_number += 1
338                 snip = klass (type, m, formatter, line_number, global_options)
339
340                 found[type] = (start, snip)
341
342             if (found[type]
343                 and (not first
344                      or found[type][0] < found[first][0])):
345                 first = type
346
347                 # FIXME.
348
349                 # Limiting the search space is a cute
350                 # idea, but this *requires* to search
351                 # for possible containing blocks
352                 # first, at least as long as we do not
353                 # search for the start of blocks, but
354                 # always/directly for the entire
355                 # @block ... @end block.
356
357                 endex = found[first][0]
358
359         if not first:
360             snippets.append (BookSnippet.Substring (input_string, index, len (input_string), line_start_idx))
361             break
362
363         while (start > line_starts[line_start_idx+1]):
364             line_start_idx += 1
365
366         (start, snip) = found[first]
367         snippets.append (BookSnippet.Substring (input_string, index, start, line_start_idx + 1))
368         snippets.append (snip)
369         found[first] = None
370         index = start + len (snip.match.group ('match'))
371
372     return snippets
373
374 def system_in_directory (cmd, directory, logfile):
375     """Execute a command in a different directory.
376
377     Because of win32 compatibility, we can't simply use subprocess.
378     """
379
380     current = os.getcwd()
381     os.chdir (directory)
382     """NB - ignore_error is deliberately set to the same value
383     as redirect_output - this is not a typo."""
384     retval = ly.system(cmd,
385               be_verbose=ly.is_verbose (),
386               redirect_output=global_options.redirect_output,
387               log_file=logfile,
388               progress_p=1,
389               ignore_error=global_options.redirect_output)
390     if retval != 0:
391         print ("Error trapped by lilypond-book")
392         print ("\nPlease see " + logfile + ".log\n")
393         sys.exit(1)
394
395     os.chdir (current)
396
397
398 def process_snippets (cmd, snippets,
399                       formatter, lily_output_dir):
400     """Run cmd on all of the .ly files from snippets."""
401
402     if not snippets:
403         return
404
405     cmd = formatter.adjust_snippet_command (cmd)
406
407     checksum = snippet_list_checksum (snippets)
408     contents = '\n'.join (['snippet-map-%d.ly' % checksum]
409                           + list (set ([snip.basename() + '.ly' for snip in snippets])))
410     name = os.path.join (lily_output_dir,
411                          'snippet-names-%d.ly' % checksum)
412     logfile = name.replace('.ly', '')
413     file (name, 'wb').write (contents)
414
415     system_in_directory (' '.join ([cmd, ly.mkarg (name)]),
416                          lily_output_dir,
417                          logfile)
418
419 def snippet_list_checksum (snippets):
420     return hash (' '.join([l.basename() for l in snippets]))
421
422 def write_file_map (lys, name):
423     snippet_map = file (os.path.join (
424         global_options.lily_output_dir,
425         'snippet-map-%d.ly' % snippet_list_checksum (lys)), 'w')
426
427     snippet_map.write ("""
428 #(define version-seen #t)
429 #(define output-empty-score-list #f)
430 #(ly:add-file-name-alist '(%s
431     ))\n
432 """ % '\n'.join(['("%s.ly" . "%s")\n' % (ly.basename ().replace('\\','/'), name)
433                  for ly in lys]))
434
435 def split_output_files(directory):
436     """Returns directory entries in DIRECTORY/XX/ , where XX are hex digits.
437
438     Return value is a set of strings.
439     """
440     files = []
441     for subdir in glob.glob (os.path.join (directory, '[a-f0-9][a-f0-9]')):
442         base_subdir = os.path.split (subdir)[1]
443         sub_files = [os.path.join (base_subdir, name)
444                      for name in os.listdir (subdir)]
445         files += sub_files
446     return set (files)
447
448 def do_process_cmd (chunks, input_name, options):
449     snippets = [c for c in chunks if isinstance (c, BookSnippet.LilypondSnippet)]
450
451     output_files = split_output_files (options.lily_output_dir)
452     outdated = [c for c in snippets if c.is_outdated (options.lily_output_dir, output_files)]
453
454     write_file_map (outdated, input_name)
455     progress (_ ("Writing snippets..."))
456     for snippet in outdated:
457         snippet.write_ly()
458
459     if outdated:
460         progress (_ ("Processing..."))
461         process_snippets (options.process_cmd, outdated,
462                           options.formatter, options.lily_output_dir)
463
464     else:
465         progress (_ ("All snippets are up to date..."))
466
467     progress (_ ("Linking files..."))
468     abs_lily_output_dir = os.path.join (options.original_dir, options.lily_output_dir)
469     abs_output_dir = os.path.join (options.original_dir, options.output_dir)
470     if abs_lily_output_dir != abs_output_dir:
471         output_files = split_output_files (abs_lily_output_dir)
472         for snippet in snippets:
473             snippet.link_all_output_files (abs_lily_output_dir,
474                                            output_files,
475                                            abs_output_dir)
476
477
478 ###
479 # Format guessing data
480
481 def guess_format (input_filename):
482     format = None
483     e = os.path.splitext (input_filename)[1]
484     for formatter in BookBase.all_formats:
485       if formatter.can_handle_extension (e):
486         return formatter
487     error (_ ("cannot determine format for: %s" % input_filename))
488     exit (1)
489
490 def write_if_updated (file_name, lines):
491     try:
492         f = file (file_name)
493         oldstr = f.read ()
494         new_str = ''.join (lines)
495         if oldstr == new_str:
496             progress (_ ("%s is up to date.") % file_name)
497
498             # this prevents make from always rerunning lilypond-book:
499             # output file must be touched in order to be up to date
500             os.utime (file_name, None)
501             return
502     except:
503         pass
504
505     output_dir = os.path.dirname (file_name)
506     if not os.path.exists (output_dir):
507         os.makedirs (output_dir)
508
509     progress (_ ("Writing `%s'...") % file_name)
510     file (file_name, 'w').writelines (lines)
511
512
513 def note_input_file (name, inputs=[]):
514     ## hack: inputs is mutable!
515     inputs.append (name)
516     return inputs
517
518 def samefile (f1, f2):
519     try:
520         return os.path.samefile (f1, f2)
521     except AttributeError:                # Windoze
522         f1 = re.sub ("//*", "/", f1)
523         f2 = re.sub ("//*", "/", f2)
524         return f1 == f2
525
526 def do_file (input_filename, included=False):
527     # Ugh.
528     input_absname = input_filename
529     if not input_filename or input_filename == '-':
530         in_handle = sys.stdin
531         input_fullname = '<stdin>'
532     else:
533         if os.path.exists (input_filename):
534             input_fullname = input_filename
535         else:
536             input_fullname = global_options.formatter.input_fullname (input_filename)
537         # Normalize path to absolute path, since we will change cwd to the output dir!
538         # Otherwise, "lilypond-book -o out test.tex" will complain that it is
539         # overwriting the input file (which it is actually not), since the
540         # input filename is relative to the CWD...
541         input_absname = os.path.abspath (input_fullname)
542
543         note_input_file (input_fullname)
544         in_handle = file (input_fullname)
545
546     if input_filename == '-':
547         global_options.input_dir = os.getcwd ()
548         input_base = 'stdin'
549     elif included:
550         input_base = os.path.splitext (input_filename)[0]
551     else:
552         global_options.input_dir = os.path.split (input_absname)[0]
553         input_base = os.path.basename (
554             os.path.splitext (input_filename)[0])
555
556     # don't complain when global_options.output_dir is existing
557     if not global_options.output_dir:
558         global_options.output_dir = os.getcwd()
559     else:
560         global_options.output_dir = os.path.abspath(global_options.output_dir)
561
562         if not os.path.isdir (global_options.output_dir):
563             os.mkdir (global_options.output_dir, 0777)
564         os.chdir (global_options.output_dir)
565
566     output_filename = os.path.join(global_options.output_dir,
567                                    input_base + global_options.formatter.default_extension)
568     if (os.path.exists (input_filename)
569         and os.path.exists (output_filename)
570         and samefile (output_filename, input_absname)):
571      error (
572      _ ("Output would overwrite input file; use --output."))
573      exit (2)
574
575     try:
576         progress (_ ("Reading %s...") % input_fullname)
577         source = in_handle.read ()
578
579         if not included:
580             global_options.formatter.init_default_snippet_options (source)
581
582
583         progress (_ ("Dissecting..."))
584         chunks = find_toplevel_snippets (source, global_options.formatter)
585
586         # Let the formatter modify the chunks before further processing
587         chunks = global_options.formatter.process_chunks (chunks)
588
589         if global_options.filter_cmd:
590             write_if_updated (output_filename,
591                      [c.filter_text () for c in chunks])
592         elif global_options.process_cmd:
593             do_process_cmd (chunks, input_fullname, global_options)
594             progress (_ ("Compiling %s...") % output_filename)
595             write_if_updated (output_filename,
596                      [s.replacement_text ()
597                      for s in chunks])
598
599         def process_include (snippet):
600             os.chdir (original_dir)
601             name = snippet.substring ('filename')
602             progress (_ ("Processing include: %s") % name)
603             return do_file (name, included=True)
604
605         include_chunks = map (process_include,
606                               filter (lambda x: isinstance (x, BookSnippet.IncludeSnippet),
607                                       chunks))
608
609         return chunks + reduce (lambda x, y: x + y, include_chunks, [])
610
611     except BookSnippet.CompileError:
612         os.chdir (original_dir)
613         progress (_ ("Removing `%s'") % output_filename)
614         raise BookSnippet.CompileError
615
616 def inverse_relpath (path, relpath):
617     """Given two paths, the second relative to the first,
618     return the first path relative to the second."""
619     if os.path.isabs (relpath):
620         return os.path.abspath (path)
621     relparts = []
622     parts = os.path.normpath (path).split (os.path.sep)
623     for part in os.path.normpath (relpath).split (os.path.sep):
624         if part == '..':
625             relparts.append (parts[-1])
626             parts.pop ()
627         else:
628             relparts.append ('..')
629             parts.append (part)
630     return os.path.sep.join (relparts[::-1])
631
632 def do_options ():
633     global global_options
634
635     opt_parser = get_option_parser()
636     (global_options, args) = opt_parser.parse_args ()
637
638     global_options.information = {'program_version': ly.program_version, 'program_name': ly.program_name }
639     global_options.original_dir = original_dir
640
641     if global_options.lily_output_dir:
642         global_options.lily_output_dir = os.path.expanduser (global_options.lily_output_dir)
643         global_options.include_path.insert (0, inverse_relpath (original_dir, global_options.lily_output_dir))
644
645     if global_options.output_dir:
646         global_options.output_dir = os.path.expanduser (global_options.output_dir)
647         global_options.include_path.insert (0, inverse_relpath (original_dir, global_options.output_dir))
648
649     global_options.include_path.insert (0, ".")
650
651     # Load the python packages (containing e.g. custom formatter classes)
652     # passed on the command line
653     nr = 0
654     for i in global_options.custom_packages:
655         nr += 1
656         progress (str(imp.load_source ("book_custom_package%s" % nr, i)))
657
658
659     if global_options.warranty:
660         warranty ()
661         exit (0)
662     if not args or len (args) > 1:
663         opt_parser.print_help ()
664         exit (2)
665
666     return args
667
668 def main ():
669     # FIXME: 85 lines of `main' macramee??
670     if (os.environ.has_key ("LILYPOND_BOOK_LOGLEVEL")):
671         ly.set_loglevel (os.environ["LILYPOND_BOOK_LOGLEVEL"])
672     files = do_options ()
673
674     basename = os.path.splitext (files[0])[0]
675     basename = os.path.split (basename)[1]
676
677     if global_options.format:
678       # Retrieve the formatter for the given format
679       for formatter in BookBase.all_formats:
680         if formatter.can_handle_format (global_options.format):
681           global_options.formatter = formatter
682     else:
683         global_options.formatter = guess_format (files[0])
684         global_options.format = global_options.formatter.format
685
686     # make the global options available to the formatters:
687     global_options.formatter.global_options = global_options
688     formats = global_options.formatter.image_formats
689
690     if global_options.process_cmd == '':
691         global_options.process_cmd = (lilypond_binary
692                                       + ' --formats=%s -dbackend=eps ' % formats)
693
694     if global_options.process_cmd:
695         includes = global_options.include_path
696         if global_options.lily_output_dir:
697             # This must be first, so lilypond prefers to read .ly
698             # files in the other lybookdb dir.
699             includes = [global_options.lily_output_dir] + includes
700         global_options.process_cmd += ' '.join ([' -I %s' % ly.mkarg (p)
701                                                  for p in includes])
702
703     global_options.formatter.process_options (global_options)
704
705     if global_options.lily_loglevel:
706         ly.debug_output (_ ("Setting LilyPond's loglevel to %s") % global_options.lily_loglevel, True)
707         global_options.process_cmd += " --loglevel=%s" % global_options.lily_loglevel
708     elif ly.is_verbose ():
709         if os.environ.get ("LILYPOND_LOGLEVEL", None):
710             ly.debug_output (_ ("Setting LilyPond's loglevel to %s (from environment variable LILYPOND_LOGLEVEL)") % os.environ.get ("LILYPOND_LOGLEVEL", None), True)
711             global_options.process_cmd += " --loglevel=%s" % os.environ.get ("LILYPOND_LOGLEVEL", None)
712         else:
713             ly.debug_output (_ ("Setting LilyPond's output to --verbose, implied by lilypond-book's setting"), True)
714             global_options.process_cmd += " --verbose"
715
716     if global_options.padding_mm:
717         global_options.process_cmd += " -deps-box-padding=%f " % global_options.padding_mm
718
719     global_options.process_cmd += " -dread-file-list -dno-strip-output-dir"
720
721     if global_options.lily_output_dir:
722         global_options.lily_output_dir = os.path.abspath(global_options.lily_output_dir)
723         if not os.path.isdir (global_options.lily_output_dir):
724             os.makedirs (global_options.lily_output_dir)
725     else:
726         global_options.lily_output_dir = os.path.abspath(global_options.output_dir)
727
728     relative_output_dir = global_options.output_dir
729
730     identify ()
731     try:
732         chunks = do_file (files[0])
733     except BookSnippet.CompileError:
734         exit (1)
735
736     inputs = note_input_file ('')
737     inputs.pop ()
738
739     base_file_name = os.path.splitext (os.path.basename (files[0]))[0]
740     dep_file = os.path.join (global_options.output_dir, base_file_name + '.dep')
741     final_output_file = os.path.join (relative_output_dir,
742                      base_file_name + global_options.formatter.default_extension)
743
744     os.chdir (original_dir)
745     file (dep_file, 'w').write ('%s: %s\n'
746                                 % (final_output_file, ' '.join (inputs)))
747
748 if __name__ == '__main__':
749     main ()