]> git.donarmstrong.com Git - lilypond.git/blob - scripts/ly2dvi.py
(ChordNamesContext): minimumverticalExtent
[lilypond.git] / scripts / ly2dvi.py
1 #!@PYTHON@
2
3 # Run lilypond, latex, dvips.
4 #
5 # This is the third incarnation of ly2dvi.
6 #
7 # Earlier incarnations of ly2dvi were written by
8 # Jeffrey B. Reed<daboys@austin.rr.com> (Python version)
9 # Jan Arne Fagertun <Jan.A.Fagertun@@energy.sintef.no> (Bourne shell script)
10 #
11
12 #
13 # Note: gettext work best if we use ' for docstrings and "
14 #       for gettextable strings.
15 #       --> DO NOT USE """ for docstrings.
16
17 '''
18 TODO:
19
20   * figure out which set of command line options should make ly2dvi:
21
22       na: create tex only?  
23       na: create latex only? 
24       na: create tex and latex
25       default: create dvi only
26       na: create tex, latex and dvi
27       -P: create dvi and ps
28       na: * create ps only
29
30      etc.
31
32      for foo.ly, rename ly2dvi.dir to out-ly2dvi, foo.ly2dvi, foo.dir ?
33      
34   * move versatile taglines, 
35   
36      \header {
37         beginfooter=\mutopiaPD
38         endfooter=\tagline  -> 'lily was here <version>'
39      }
40
41      lilytagline (->lily was here), usertagline, copyright etc.
42
43   * head/header tagline/endfooter
44
45   * dvi from lilypond .tex output?  This is hairy, because we create dvi
46     from lilypond .tex *and* header output.
47
48   * multiple \score blocks?
49   
50 '''
51
52
53 import os
54 import stat
55 import string
56 import re
57 import getopt
58 import sys
59 import shutil
60 import __main__
61 import operator
62 import tempfile
63 import traceback
64
65
66 ################################################################
67 # lilylib.py -- options and stuff
68
69 # source file of the GNU LilyPond music typesetter
70
71 # Handle bug in Python 1.6-2.1
72 #
73 # there are recursion limits for some patterns in Python 1.6 til 2.1. 
74 # fix this by importing pre instead. Fix by Mats.
75
76 # todo: should check Python version first.
77 try:
78         import pre
79         re = pre
80         del pre
81 except ImportError:
82         import re
83
84 # Attempt to fix problems with limited stack size set by Python!
85 # Sets unlimited stack size. Note that the resource module only
86 # is available on UNIX.
87 try:
88        import resource
89        resource.setrlimit (resource.RLIMIT_STACK, (-1, -1))
90 except:
91        pass
92
93 try:
94         import gettext
95         gettext.bindtextdomain ('lilypond', localedir)
96         gettext.textdomain ('lilypond')
97         _ = gettext.gettext
98 except:
99         def _ (s):
100                 return s
101
102 program_version = '@TOPLEVEL_VERSION@'
103 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
104         program_version = '1.5.54'
105
106 def identify ():
107         sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
108
109 def warranty ():
110         identify ()
111         sys.stdout.write ('\n')
112         sys.stdout.write (_ ('Copyright (c) %s by' % ' 2001--2002'))
113         sys.stdout.write ('\n')
114         sys.stdout.write ('  Han-Wen Nienhuys')
115         sys.stdout.write ('  Jan Nieuwenhuizen')
116         sys.stdout.write ('\n')
117         sys.stdout.write (_ (r'''
118 Distributed under terms of the GNU General Public License. It comes with
119 NO WARRANTY.'''))
120         sys.stdout.write ('\n')
121
122 def progress (s):
123         errorport.write (s + '\n')
124
125 def warning (s):
126         progress (_ ("warning: ") + s)
127
128 def user_error (s, e=1):
129         errorport.write (program_name + ":" + _ ("error: ") + s + '\n')
130         sys.exit (e)
131         
132 def error (s):
133         '''Report the error S.  Exit by raising an exception. Please
134         do not abuse by trying to catch this error. If you do not want
135         a stack trace, write to the output directly.
136
137         RETURN VALUE
138
139         None
140         
141         '''
142         
143         progress (_ ("error: ") + s)
144         raise _ ("Exiting ... ")
145
146 def getopt_args (opts):
147         '''Construct arguments (LONG, SHORT) for getopt from  list of options.'''
148         short = ''
149         long = []
150         for o in opts:
151                 if o[1]:
152                         short = short + o[1]
153                         if o[0]:
154                                 short = short + ':'
155                 if o[2]:
156                         l = o[2]
157                         if o[0]:
158                                 l = l + '='
159                         long.append (l)
160         return (short, long)
161
162 def option_help_str (o):
163         '''Transform one option description (4-tuple ) into neatly formatted string'''
164         sh = '  '       
165         if o[1]:
166                 sh = '-%s' % o[1]
167
168         sep = ' '
169         if o[1] and o[2]:
170                 sep = ','
171                 
172         long = ''
173         if o[2]:
174                 long= '--%s' % o[2]
175
176         arg = ''
177         if o[0]:
178                 if o[2]:
179                         arg = '='
180                 arg = arg + o[0]
181         return '  ' + sh + sep + long + arg
182
183
184 def options_help_str (opts):
185         '''Convert a list of options into a neatly formatted string'''
186         w = 0
187         strs =[]
188         helps = []
189
190         for o in opts:
191                 s = option_help_str (o)
192                 strs.append ((s, o[3]))
193                 if len (s) > w:
194                         w = len (s)
195
196         str = ''
197         for s in strs:
198                 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0])  + 3), s[1])
199         return str
200
201 def help ():
202         ls = [(_ ("Usage: %s [OPTION]... FILE") % program_name),
203                 ('\n\n'),
204                 (help_summary),
205                 ('\n\n'),
206                 (_ ("Options:")),
207                 ('\n'),
208                 (options_help_str (option_definitions)),
209                 ('\n\n'),
210                 (_ ("Report bugs to %s") % 'bug-lilypond@gnu.org'),
211                 ('\n')]
212         map (sys.stdout.write, ls)
213         
214 def setup_temp ():
215         """
216         Create a temporary directory, and return its name. 
217         """
218         global temp_dir
219         if not keep_temp_dir_p:
220                 temp_dir = tempfile.mktemp (program_name)
221         try:
222                 os.mkdir (temp_dir, 0777)
223         except OSError:
224                 pass
225
226         return temp_dir
227
228
229 def system (cmd, ignore_error = 0, quiet =0):
230         """Run CMD. If IGNORE_ERROR is set, don't complain when CMD returns non zero.
231
232         RETURN VALUE
233
234         Exit status of CMD
235         """
236         
237         if verbose_p:
238                 progress (_ ("Invoking `%s\'") % cmd)
239
240         st = os.system (cmd)
241         if st:
242                 name = re.match ('[ \t]*([^ \t]*)', cmd).group (1)
243                 msg = name + ': ' + _ ("command exited with value %d") % st
244                 if ignore_error:
245                         if not quiet:
246                                 warning (msg + ' ' + _ ("(ignored)") + ' ')
247                 else:
248                         error (msg)
249
250         return st
251
252
253 def cleanup_temp ():
254         if not keep_temp_dir_p:
255                 if verbose_p:
256                         progress (_ ("Cleaning %s...") % temp_dir)
257                 shutil.rmtree (temp_dir)
258
259
260 def strip_extension (f, ext):
261         (p, e) = os.path.splitext (f)
262         if e == ext:
263                 e = ''
264         return p + e
265
266
267 def cp_to_dir (pattern, dir):
268         "Copy files matching re PATTERN from cwd to DIR"
269         # Duh.  Python style portable: cp *.EXT OUTDIR
270         # system ('cp *.%s %s' % (ext, outdir), 1)
271         files = filter (lambda x, p=pattern: re.match (p, x), os.listdir ('.'))
272         map (lambda x, d=dir: shutil.copy2 (x, os.path.join (d, x)), files)
273
274
275 # Python < 1.5.2 compatibility
276 #
277 # On most platforms, this is equivalent to
278 #`normpath(join(os.getcwd()), PATH)'.  *Added in Python version 1.5.2*
279 if os.path.__dict__.has_key ('abspath'):
280         abspath = os.path.abspath
281 else:
282         def abspath (path):
283                 return os.path.normpath (os.path.join (os.getcwd (), path))
284
285 if os.__dict__.has_key ('makedirs'):
286         makedirs = os.makedirs
287 else:
288         def makedirs (dir, mode=0777):
289                 system ('mkdir -p %s' % dir)
290
291
292 def mkdir_p (dir, mode=0777):
293         if not os.path.isdir (dir):
294                 makedirs (dir, mode)
295
296
297 # if set, LILYPONDPREFIX must take prevalence
298 # if datadir is not set, we're doing a build and LILYPONDPREFIX 
299 datadir = '@local_lilypond_datadir@'
300
301 if os.environ.has_key ('LILYPONDPREFIX') :
302         datadir = os.environ['LILYPONDPREFIX']
303 else:
304         datadir = '@local_lilypond_datadir@'
305
306
307 while datadir[-1] == os.sep:
308         datadir= datadir[:-1]
309
310 sys.path.insert (0, os.path.join (datadir, 'python'))
311
312 ################################################################
313 # END Library
314
315
316 program_name = 'ly2dvi'
317
318 original_dir = os.getcwd ()
319 temp_dir = os.path.join (original_dir,  '%s.dir' % program_name)
320 errorport = sys.stderr
321 keep_temp_dir_p = 0
322 verbose_p = 0
323 preview_p = 0
324 preview_resolution = 90
325 pseudo_filter_p = 0
326 latex_cmd = 'latex'
327 tex_extension = '.tex'
328 pdftex_p = 0
329
330 help_summary = _ ("Generate .dvi with LaTeX for LilyPond")
331
332 option_definitions = [
333         ('', 'd', 'dependencies', _ ("write Makefile dependencies for every input file")),
334         ('', 'h', 'help', _ ("this help")),
335         (_ ("DIR"), 'I', 'include', _ ("add DIR to LilyPond's search path")),
336         ('', 'k', 'keep', _ ("keep all output, and name the directory %s.dir") % program_name),
337         ('', '', 'no-lily', _ ("don't run LilyPond")),
338         ('', 'm', 'no-paper', _ ("produce MIDI output only")),
339         (_ ("FILE"), 'o', 'output', _ ("write ouput to FILE")),
340         (_ ("FILE"), 'f', 'find-pfa', _ ("find pfa fonts used in FILE")),
341         # why capital P?
342         ('', '', 'preview', _("Make a picture of the first system.")),
343         (_ ('RES'), '', 'preview-resolution', _("Set the resolution of the preview to RES.")),
344         ('', 'P', 'postscript', _ ("generate PostScript output")),
345         ('', 'p', 'pdf', _ ("generate PDF output")),    
346         (_ ("KEY=VAL"), 's', 'set', _ ("change global setting KEY to VAL")),
347         ('', 'V', 'verbose', _ ("verbose")),
348         ('', 'v', 'version', _ ("print version number")),
349         ('', 'w', 'warranty', _ ("show warranty and copyright")),
350         ('', '', 'pdftex', _("Use pdflatex to generate a PDF output")),
351         ]
352
353 layout_fields = ['dedication', 'title', 'subtitle', 'subsubtitle',
354           'footer', 'head', 'composer', 'arranger', 'instrument',
355           'opus', 'piece', 'metre', 'meter', 'poet', 'texttranslator']
356
357
358 # init to empty; values here take precedence over values in the file
359
360 ## TODO: change name.
361 extra_init = {
362         'language' : [],
363         'latexheaders' : [],
364         'latexpackages' :  ['geometry'],
365         'latexoptions' : [],
366         'papersize' : [],
367         'pagenumber' : [1],
368         'textheight' : [], 
369         'linewidth' : [],
370         'orientation' : [],
371         'unit' : ['pt'],
372 }
373
374 extra_fields = extra_init.keys ()
375 fields = layout_fields + extra_fields
376
377 include_path = ['.']
378 lily_p = 1
379 paper_p = 1
380
381 output_name = ''
382
383 # Output formats that ly2dvi should create
384 targets = ['DVI', 'LATEX', 'MIDI', 'TEX']
385
386 track_dependencies_p = 0
387 dependency_files = []
388
389
390
391 kpse = os.popen ('kpsexpand \$TEXMF').read()
392 kpse = re.sub('[ \t\n]+$','', kpse)
393 type1_paths = os.popen ('kpsewhich -expand-path=\$T1FONTS').read ()
394
395 environment = {
396         # TODO: * prevent multiple addition.
397         #       * clean TEXINPUTS, MFINPUTS, TFMFONTS,
398         #         as these take prevalence over $TEXMF
399         #         and thus may break tex run?
400         'TEXMF' : "{%s,%s}" % (datadir, kpse) ,
401         'GS_FONTPATH' : type1_paths,
402         'GS_LIB' : datadir + '/ps',
403 }
404
405 # tex needs lots of memory, more than it gets by default on Debian
406 non_path_environment = {
407         'extra_mem_top' : '1000000',
408         'extra_mem_bottom' : '1000000',
409         'pool_size' : '250000',
410 }
411
412 def setup_environment ():
413         # $TEXMF is special, previous value is already taken care of
414         if os.environ.has_key ('TEXMF'):
415                 del os.environ['TEXMF']
416  
417         for key in environment.keys ():
418                 val = environment[key]
419                 if os.environ.has_key (key):
420                         val = os.environ[key] + os.pathsep + val 
421                 os.environ[key] = val
422
423         for key in non_path_environment.keys ():
424                 val = non_path_environment[key]
425                 os.environ[key] = val
426
427 #what a name.
428 def set_setting (dict, key, val):
429         try:
430                 val = string.atoi (val)
431         except ValueError:
432                 #warning (_ ("invalid value: %s") % `val`)
433                 pass
434
435         if type(val) == type ('hoi'):
436                 try:
437                         val = string.atof (val)
438                 except ValueError:
439                         #warning (_ ("invalid value: %s") % `val`)
440                         pass
441
442         try:
443                 dict[key].append (val)
444         except KeyError:
445                 warning (_ ("no such setting: %s") % `key`)
446                 dict[key] = [val]
447
448
449 def print_environment ():
450         for (k,v) in os.environ.items ():
451                 sys.stderr.write ("%s=\"%s\"\n" % (k,v)) 
452
453 def quiet_system (cmd, name, ignore_error = 0):
454         if not verbose_p:
455                 progress ( _("Running %s...") % name)
456                 cmd = cmd + ' 1> /dev/null 2> /dev/null'
457         elif pseudo_filter_p:
458                 cmd = cmd + ' 1> /dev/null'
459
460         return system (cmd, ignore_error, quiet = 1)
461
462
463 def run_lilypond (files, dep_prefix):
464
465         opts = ''
466         opts = opts + ' ' + string.join (map (lambda x : '-I ' + x,
467                                               include_path))
468         if pseudo_filter_p:
469                 opts = opts + ' --output=lelie'
470         if paper_p:
471                 opts = opts + ' ' + string.join (map (lambda x : '-H ' + x,
472                                                       fields))
473         else:
474                 opts = opts + ' --no-paper'
475
476         if pdftex_p:
477                 opts = opts + ' -f pdftex'              
478
479         if track_dependencies_p:
480                 opts = opts + " --dependencies"
481                 if dep_prefix:
482                         opts = opts + ' --dep-prefix=%s' % dep_prefix
483
484         fs = string.join (files)
485
486         if not verbose_p:
487                 # cmd = cmd + ' 1> /dev/null 2> /dev/null'
488                 progress ( _("Running %s...") % 'LilyPond')
489         else:
490                 opts = opts + ' --verbose'
491
492                 # for better debugging!
493                 print_environment ()
494
495         cmd = 'lilypond %s %s ' % (opts, fs)
496         if  verbose_p:
497                 progress ("Invoking `%s'"% cmd)
498         status = os.system (cmd)
499
500         signal = 0x0f & status
501         exit_status = status >> 8
502
503         # 2 == user interrupt.
504         if signal and  signal <> 2:
505                 error("\n\nLilyPond crashed (signal %d). Please submit a bugreport to bug-lilypond@gnu.org\n" % signal)
506
507         if status:
508                 error ("\n\nLilyPond failed on the input file. (exit status %d)\n" % exit_status)
509                 
510
511 def analyse_lilypond_output (filename, extra):
512         
513         # urg
514         '''Grep FILENAME for interesting stuff, and
515         put relevant info into EXTRA.'''
516         filename = filename+tex_extension
517         progress (_ ("Analyzing %s...") % filename)
518         s = open (filename).read ()
519
520         # search only the first 10k
521         s = s[:10240]
522         for x in extra_fields:
523                 m = re.search (r'\\def\\lilypondpaper%s{([^}]*)}'%x, s)
524                 if m:
525                         set_setting (extra, x, m.group (1))
526
527 def find_tex_files_for_base (base, extra):
528
529         """
530         Find the \header fields dumped from BASE.
531         """
532         
533         headerfiles = {}
534         for f in layout_fields:
535                 if os.path.exists (base + '.' + f):
536                         headerfiles[f] = base+'.'+f
537
538         if os.path.exists (base  +'.dep'):
539                 dependency_files.append (base + '.dep')
540
541         for f in extra_fields:
542                 if os.path.exists (base + '.' + f):
543                         extra[f].append (open (base + '.' + f).read ())
544         
545         return (base+tex_extension,headerfiles)
546          
547
548 def find_tex_files (files, extra):
549         """
550         Find all .tex files whose prefixes start with some name in FILES. 
551
552         """
553         
554         tfiles = []
555         
556         for f in files:
557                 x = 0
558                 while 1:
559                         fname = os.path.basename (f)
560                         fname = strip_extension (fname, '.ly')
561                         if x:
562                                 fname = fname + '-%d' % x
563
564                         if os.path.exists (fname + tex_extension):
565                                 tfiles.append (find_tex_files_for_base (fname, extra))
566                                 analyse_lilypond_output (fname, extra)
567                         else:
568                                 break
569
570                         x = x + 1
571         if not x:
572                 fstr = string.join (files, ', ')
573                 warning (_ ("no LilyPond output found for %s") % fstr)
574         return tfiles
575
576 def one_latex_definition (defn, first):
577         s = '\n'
578         for (k,v) in defn[1].items ():
579                 val = open (v).read ()
580                 if (string.strip (val)):
581                         s = s + r'''\def\lilypond%s{%s}''' % (k, val)
582                 else:
583                         s = s + r'''\let\lilypond%s\relax''' % k
584                 s = s + '\n'
585
586         if first:
587                 s = s + '\\def\\mustmakelilypondtitle{}\n'
588         else:
589                 s = s + '\\def\\mustmakelilypondpiecetitle{}\n'
590                 
591         s = s + '\\input %s\n' % defn[0] # The final \n seems important here. It ensures that the footers and taglines end up on the right page.
592         return s
593
594
595 ly_paper_to_latexpaper =  {
596         'a4' : 'a4paper',
597         'letter' : 'letterpaper', 
598 }
599
600 #TODO: should set textheight (enlarge) depending on papersize. 
601 def global_latex_preamble (extra):
602         '''construct preamble from EXTRA,'''
603         s = ""
604         s = s + '% generation tag\n'
605
606         options = ''
607
608
609         if extra['papersize']:
610                 try:
611                         options = ly_paper_to_latexpaper[extra['papersize'][0]]
612                 except KeyError:
613                         warning (_ ("invalid value: %s") % `extra['papersize'][0]`)
614                         pass
615
616         if extra['latexoptions']:
617                 options = options + ',' + extra['latexoptions'][-1]
618
619         s = s + '\\documentclass[%s]{article}\n' % options
620
621         if extra['language']:
622                 s = s + r'\usepackage[%s]{babel}' % extra['language'][-1] + '\n'
623
624
625         s = s + '\\usepackage{%s}\n' \
626                 % string.join (extra['latexpackages'], ',')
627
628         if extra['latexheaders']:
629                 s = s + '\\include{%s}\n' \
630                         % string.join (extra['latexheaders'], '}\n\\include{')
631
632         unit = extra['unit'][-1]
633
634         textheight = ''
635         if extra['textheight']:
636                 textheight = ',textheight=%f%s' % (extra['textheight'][0], unit)
637
638         orientation = 'portrait'
639         if extra['orientation']:
640                 orientation = extra['orientation'][0]
641
642         # set sane geometry width (a4-width) for linewidth = -1.
643         maxlw = max (extra['linewidth'] + [-1])
644         if maxlw < 0:
645                 # who the hell is 597 ?
646                 linewidth = '597pt'
647         else:
648                 linewidth = '%d%s' % (maxlw, unit)
649         s = s + '\geometry{width=%s%s,headheight=2mm,footskip=2mm,%s}\n' % (linewidth, textheight, orientation)
650
651         if extra['latexoptions']:
652                 s = s + '\geometry{twosideshift=4mm}\n'
653
654         s = s + r'''
655 \usepackage[latin1]{inputenc}
656 \input{titledefs}
657 '''
658         
659         if extra['pagenumber'] and extra['pagenumber'][-1] and extra['pagenumber'][-1] != 'no':
660                 s = s + '\setcounter{page}{%d}\n' % (extra['pagenumber'][-1])
661                 s = s + '\\pagestyle{plain}\n'
662         else:
663                 s = s + '\\pagestyle{empty}\n'
664
665
666         return s
667
668         
669 def global_latex_definition (tfiles, extra):
670         '''construct preamble from EXTRA, dump Latex stuff for each
671 lily output file in TFILES after that, and return the Latex file constructed.  '''
672
673         
674         s = global_latex_preamble (extra) + '\\begin{document}\n'
675         s = s + '\\thispagestyle{firstpage}\n'
676
677         first = 1
678         for t in tfiles:
679                 s = s + one_latex_definition (t, first)
680                 first = 0
681
682
683         s = s + '\\thispagestyle{lastpage}\n'
684         s = s + '\\end{document}'
685
686         return s
687
688 def run_latex (files, outbase, extra):
689
690         """Construct latex file, for FILES and EXTRA, dump it into
691 OUTBASE.latex. Run LaTeX on it.
692
693 RETURN VALUE
694
695 None
696         """
697
698         latex_fn = outbase + '.latex'
699         
700         wfs = find_tex_files (files, extra)
701         s = global_latex_definition (wfs, extra)
702
703         f = open (latex_fn, 'w')
704         f.write (s)
705         f.close ()
706
707         cmd = latex_cmd + ' \\\\nonstopmode \\\\input %s' % latex_fn
708         status = quiet_system (cmd, 'LaTeX', ignore_error = 1)
709
710         signal = 0xf & status
711         exit_stat = status >> 8
712
713         if exit_stat:
714                 logstr = open (outbase + '.log').read()
715                 m = re.search ("\n!", logstr)
716                 start = m.start (0)
717                 logstr = logstr[start:start+200]
718                 
719                 sys.stderr.write(_("""LaTeX failed on the output file.
720 The error log is as follows:
721 %s...\n""" % logstr))
722                 raise 'LaTeX error'
723         
724         if preview_p:
725                 # make a preview by rendering only the 1st line.
726                 preview_fn = outbase + '.preview.tex'
727                 f = open(preview_fn, 'w')
728                 f.write (r'''
729 %s
730 \input lilyponddefs
731 \pagestyle{empty}
732 \begin{document}
733 \def\interscoreline{\endinput}
734 \input %s
735 \end{document}
736 ''' % (global_latex_preamble (extra), outbase))
737
738                 f.close()
739                 cmd = '%s \\\\nonstopmode \\\\input %s' % (latex_cmd, preview_fn)
740                 quiet_system (cmd, '%s for preview' % latex_cmd)
741         
742
743 def run_dvips (outbase, extra):
744
745
746         """Run dvips using the correct options taken from EXTRA,
747 leaving a PS file in OUTBASE.ps
748
749 RETURN VALUE
750
751 None.
752 """
753         opts = ''
754         if extra['papersize']:
755                 opts = opts + ' -t%s' % extra['papersize'][0]
756
757         if extra['orientation'] and extra['orientation'][0] == 'landscape':
758                 opts = opts + ' -tlandscape'
759
760         if 'PDF' in targets:
761                 opts = opts + ' -Ppdf -G0 -u lilypond.map'
762                 
763         cmd = 'dvips %s -o%s %s' % (opts, outbase + '.ps', outbase + '.dvi')
764         quiet_system (cmd, 'dvips')
765
766         if preview_p:
767                 cmd = 'dvips -E -o%s %s' % ( outbase + '.preview.ps', outbase + '.preview.dvi')         
768                 quiet_system (cmd, 'dvips for preview')
769
770         if 'PDF' in targets:
771                 cmd = 'ps2pdf %s.ps %s.pdf' % (outbase , outbase)
772                 quiet_system (cmd, 'ps2pdf')
773                 
774 def get_bbox (filename):
775         # cut & paste 
776         system ('gs -sDEVICE=bbox -q  -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
777
778         box = open (filename + '.bbox').read()
779         m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
780         gr = []
781         if m:
782                 gr = map (string.atoi, m.groups ())
783         
784         return gr
785
786 #
787 # cut & paste from lilypond-book.
788 #
789 def make_preview (name, extra):
790         bbox = get_bbox (name + '.preview.ps')
791         margin = 0
792         fo = open (name + '.trans.eps' , 'w')
793         fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
794         fo.close ()
795         
796         x = (2* margin + bbox[2] - bbox[0]) * preview_resolution / 72.
797         y = (2* margin + bbox[3] - bbox[1]) * preview_resolution / 72.
798
799         cmd = r'''gs -g%dx%d -sDEVICE=pgm  -dTextAlphaBits=4 -dGraphicsAlphaBits=4  -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit | pnmtopng > %s'''
800         
801         cmd = cmd % (x, y, preview_resolution, name + '.trans.eps', name + '.preview.ps',name + '.png')
802         quiet_system (cmd, 'gs')
803
804         try:
805                 status = system (cmd)
806         except:
807                 os.unlink (name + '.png')
808                 error ("Removing output file")
809
810
811
812 def generate_dependency_file (depfile, outname):
813         df = open (depfile, 'w')
814         df.write (outname + ':' )
815         
816         for d in dependency_files:
817                 s = open (d).read ()
818                 s = re.sub ('#[^\n]*\n', '', s)
819                 s = re.sub (r'\\\n', ' ', s)
820                 m = re.search ('.*:(.*)\n', s)
821
822                 # ugh. Different targets?
823                 if m:
824                         df.write ( m.group (1)  + ' ' )
825
826         df.write ('\n')
827         df.close ();
828
829 def find_file_in_path (path, name):
830         for d in string.split (path, os.pathsep):
831                 if name in os.listdir (d):
832                         return os.path.join (d, name)
833
834 # Added as functionality to ly2dvi, because ly2dvi may well need to do this
835 # in future too.
836 PS = '%!PS-Adobe'
837 def find_pfa_fonts (name):
838         s = open (name).read ()
839         if s[:len (PS)] != PS:
840                 # no ps header?
841                 errorport.write (_( "error: ") + _ ("not a PostScript file: `%s\'" % name))
842                 errorport.write ('\n')
843                 sys.exit (1)
844         here = 0
845         m = re.match ('.*?/(feta[-a-z0-9]+) +findfont', s[here:], re.DOTALL)
846         pfa = []
847         while m:
848                 here = m.end (1)
849                 pfa.append (m.group (1))
850                 m = re.match ('.*?/(feta[-a-z0-9]+) +findfont', s[here:], re.DOTALL)
851         return pfa
852
853         
854 (sh, long) = getopt_args (option_definitions)
855 try:
856         (options, files) = getopt.getopt(sys.argv[1:], sh, long)
857 except getopt.error, s:
858         errorport.write ('\n')
859         errorport.write (_ ("error: ") + _ ("getopt says: `%s\'" % s))
860         errorport.write ('\n')
861         errorport.write ('\n')
862         help ()
863         sys.exit (2)
864         
865 for opt in options:     
866         o = opt[0]
867         a = opt[1]
868
869         if 0:
870                 pass
871         elif o == '--help' or o == '-h':
872                 help ()
873                 sys.exit (0)
874         elif o == '--find-pfa' or o == '-f':
875                 fonts = map (lambda x: x + '.pfa', find_pfa_fonts (a))
876                 files = map (lambda x:
877                              find_file_in_path (os.environ['GS_FONTPATH'], x),
878                              fonts)
879                 print string.join (files, ' ')
880                 sys.exit (0)
881         elif o == '--include' or o == '-I':
882                 include_path.append (a)
883         elif o == '--postscript' or o == '-P':
884                 targets.append ('PS')
885         elif o == '--pdf' or o == '-p':
886                 targets.append ('PS')
887                 targets.append ('PDF')
888         elif o == '--keep' or o == '-k':
889                 keep_temp_dir_p = 1
890         elif o == '--no-lily':
891                 lily_p = 0
892         elif o == '--preview':
893                 preview_p = 1
894                 targets.append ('PNG')
895         elif o == '--preview-resolution':
896                 preview_resolution = string.atoi (a)
897         elif o == '--no-paper' or o == '-m':
898                 targets = ['MIDI'] 
899                 paper_p = 0
900         elif o == '--output' or o == '-o':
901                 output_name = a
902         elif o == '--set' or o == '-s':
903                 ss = string.split (a, '=')
904                 set_setting (extra_init, ss[0], ss[1])
905         elif o == '--dependencies' or o == '-d':
906                 track_dependencies_p = 1
907         elif o == '--verbose' or o == '-V':
908                 verbose_p = 1
909         elif o == '--version' or o == '-v':
910                 identify ()
911                 sys.exit (0)
912         elif o == '--pdftex':
913                 latex_cmd = 'pdflatex'
914                 targets.remove('DVI')
915                 targets.append('PDFTEX')
916                 pdftex_p = 1
917                 tex_extension = '.pdftex'
918         elif o == '--warranty' or o == '-w':
919                 status = system ('lilypond -w', ignore_error = 1)
920                 if status:
921                         warranty ()
922
923                 sys.exit (0)
924
925 # Don't convert input files to abspath, rather prepend '.' to include
926 # path.
927 include_path.insert (0, '.')
928
929 # As a neat trick, add directory part of first input file
930 # to include path.  That way you can do without the clumsy -I in:
931
932 #    ly2dvi -I foe/bar/baz foo/bar/baz/baz.ly
933 if files and files[0] != '-' and os.path.dirname (files[0]) != '.':
934         include_path.append (os.path.dirname (files[0]))
935         
936 include_path = map (abspath, include_path)
937
938 if files and (files[0] == '-' or output_name == '-'):
939         if len (files) == 1:
940                 pseudo_filter_p = 1
941                 output_name = 'lelie'
942                 if verbose_p:
943                         progress (_ ("pseudo filter"))
944         else:
945                 help ()
946                 user_error (_ ("pseudo filter only for single input file."), 2)
947                 
948         
949 original_output = output_name
950
951 if files:
952         
953         # Ugh, maybe make a setup () function
954         files = map (lambda x: strip_extension (x, '.ly'), files)
955
956         # hmmm. Wish I'd 've written comments when I wrote this.
957         # now it looks complicated.
958         
959         (outdir, outbase) = ('','')
960         if not output_name:
961                 outbase = os.path.basename (files[0])
962                 outdir = abspath ('.')
963         elif output_name[-1] == os.sep:
964                 outdir = abspath (output_name)
965                 outbase = os.path.basename (files[0])
966         else:
967                 (outdir, outbase) = os.path.split (abspath (output_name))
968
969         for i in ('.dvi', '.latex', '.ly', '.ps', '.tex', '.pdftex'):
970                 output_name = strip_extension (output_name, i)
971                 outbase = strip_extension (outbase, i)
972
973         for i in files[:] + [output_name]:
974                 if string.find (i, ' ') >= 0:
975                         user_error (_ ("filename should not contain spaces: `%s'") % i)
976                         
977         if os.path.dirname (output_name) != '.':
978                 dep_prefix = os.path.dirname (output_name)
979         else:
980                 dep_prefix = 0
981
982         reldir = os.path.dirname (output_name)
983         if outdir != '.' and (track_dependencies_p or targets):
984                 mkdir_p (outdir, 0777)
985
986         setup_environment ()
987         tmpdir = setup_temp ()
988
989         # to be sure, add tmpdir *in front* of inclusion path.
990         #os.environ['TEXINPUTS'] =  tmpdir + ':' + os.environ['TEXINPUTS']
991         os.chdir (tmpdir)
992         
993         if lily_p:
994                 try:
995                         run_lilypond (files, dep_prefix)
996                 except:
997                         # TODO: friendly message about LilyPond setup/failing?
998                         #
999                         # TODO: lilypond should fail with different
1000                         # error codes for:
1001                         #   - guile setup/startup failure
1002                         #   - font setup failure
1003                         #   - init.ly setup failure
1004                         #   - parse error in .ly
1005                         #   - unexpected: assert/core dump
1006                         targets = []
1007                         traceback.print_exc ()
1008
1009         # Our LilyPond pseudo filter always outputs to 'lelie'
1010         # have subsequent stages and use 'lelie' output.
1011         if pseudo_filter_p:
1012                 files[0] = 'lelie'
1013                 
1014         if 'PNG' in targets and 'PS' not in targets:
1015                 targets.append ('PS')
1016         if 'PS' in targets and 'DVI' not in targets:
1017                 targets.append('DVI')
1018
1019         if 'DVI' in targets:
1020                 try:
1021                         run_latex (files, outbase, extra_init)
1022                         # unless: add --tex, or --latex?
1023                         targets.remove ('TEX')
1024                         targets.remove('LATEX')
1025                 except:
1026                         # TODO: friendly message about TeX/LaTeX setup,
1027                         # trying to run tex/latex by hand
1028                         if 'DVI' in targets:
1029                                 targets.remove ('DVI')
1030                         if 'PS' in targets:
1031                                 targets.remove ('PS')
1032                         traceback.print_exc ()
1033
1034         if 'PS' in targets:
1035                 try:
1036                         run_dvips (outbase, extra_init)
1037                 except: 
1038                         if 'PS' in targets:
1039                                 targets.remove ('PS')
1040                         traceback.print_exc ()
1041
1042         if 'PNG' in  targets:
1043                 make_preview (outbase, extra_init)
1044
1045         if 'PDFTEX' in targets:
1046                 try:
1047                         run_latex (files, outbase, extra_init)
1048                         # unless: add --tex, or --latex?
1049                         targets.remove ('TEX')
1050                         targets.remove ('LATEX')
1051                         targets.remove ('PDFTEX')
1052                         if 'PDF' not in targets:
1053                                 targets.append('PDF')
1054                 except:
1055                         # TODO: friendly message about TeX/LaTeX setup,
1056                         # trying to run tex/latex by hand
1057                         if 'PDFTEX' in targets:
1058                                 targets.remove ('PDFTEX')
1059                         if 'PDF' in targets:
1060                                 targets.remove ('PDF')
1061                         if 'PS' in targets:
1062                                 targets.remove ('PS')
1063                         traceback.print_exc ()
1064                         sys.exit(1)
1065
1066         # add DEP to targets?
1067         if track_dependencies_p:
1068                 depfile = os.path.join (outdir, outbase + '.dep')
1069                 generate_dependency_file (depfile, depfile)
1070                 if os.path.isfile (depfile):
1071                         progress (_ ("dependencies output to `%s'...") %
1072                                   depfile)
1073
1074         if pseudo_filter_p:
1075                 main_target = 0
1076                 for i in 'PDF', 'PS', 'PNG', 'DVI', 'LATEX':
1077                         if i in targets:
1078                                 main_target = i
1079                                 break
1080
1081                 outname = outbase + '.' + string.lower (main_target)
1082                 if os.path.isfile (outname):
1083                         sys.stdout.write (open (outname).read ())
1084                 elif verbose_p:
1085                         warning (_ ("can't find file: `%s'") % outname)
1086                 targets = []
1087                 
1088         # Hmm, if this were a function, we could call it the except: clauses
1089         for i in targets:
1090                 ext = string.lower (i)
1091                 cp_to_dir ('.*\.%s$' % ext, outdir)
1092                 outname = outbase + '.' + string.lower (i)
1093                 abs = os.path.join (outdir, outname)
1094                 if reldir != '.':
1095                         outname = os.path.join (reldir, outname)
1096                 if os.path.isfile (abs):
1097                         progress (_ ("%s output to `%s'...") % (i, outname))
1098                 elif verbose_p:
1099                         warning (_ ("can't find file: `%s'") % outname)
1100                         
1101         os.chdir (original_dir)
1102         cleanup_temp ()
1103         
1104 else:
1105         help ()
1106         user_error (_ ("no files specified on command line."), 2)