]> git.donarmstrong.com Git - lilypond.git/blob - scripts/ly2dvi.py
e8c871ec36d1852d72b0dce41db36f6ff2dd5a62
[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\n')
117         sys.stdout.write ('\n')
118         sys.stdout.write (_ ("Distributed under terms of the GNU General Public License.  It comes with NO WARRANTY."))
119         sys.stdout.write ('\n')
120
121 def progress (s):
122         errorport.write (s + '\n')
123
124 def warning (s):
125         progress (_ ("warning: ") + s)
126
127 def user_error (s, e=1):
128         errorport.write (program_name + ":" + _ ("error: ") + s + '\n')
129         if (e):
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 = _ ("Run LilyPond using LaTeX for titling")
331
332 option_definitions = [
333         ('', 'd', 'dependencies',
334          _ ("write Makefile dependencies for every input file")),
335         ('', 'h', 'help', _ ("this help")),
336         (_ ("DIR"), 'I', 'include', _ ("add DIR to LilyPond's search path")),
337         ('', 'k', 'keep',
338          _ ("keep all output, output to directory %s.dir") % program_name),
339         ('', '', 'no-lily', _ ("don't run LilyPond")),
340         ('', 'm', 'no-paper', _ ("produce MIDI output only")),
341         (_ ("FILE"), 'o', 'output', _ ("write ouput to FILE")),
342         (_ ("FILE"), 'f', 'find-pfa', _ ("find pfa fonts used in FILE")),
343         (_ ('RES'), '', 'preview-resolution',
344          _ ("set the resolution of the preview to RES")),
345         ('', 'P', 'postscript', _ ("generate PostScript output")),
346         ('', 'p', 'pdf', _ ("generate PDF output")),    
347         ('', '', 'pdftex', _ ("use pdflatex to generate a PDF output")),
348         # FIXME: preview, picture; to indicate creation of a PNG?
349         ('', '', 'preview', _ ("make a picture of the first system")),
350         (_ ("KEY=VAL"), 's', 'set', _ ("change global setting KEY to VAL")),
351         ('', 'V', 'verbose', _ ("verbose")),
352         ('', 'v', 'version', _ ("print version number")),
353         ('', 'w', 'warranty', _ ("show warranty and copyright")),
354         ]
355
356 layout_fields = ['dedication', 'title', 'subtitle', 'subsubtitle',
357           'footer', 'head', 'composer', 'arranger', 'instrument',
358           'opus', 'piece', 'metre', 'meter', 'poet', 'texttranslator']
359
360
361 # init to empty; values here take precedence over values in the file
362
363 ## TODO: change name.
364 extra_init = {
365         'language' : [],
366         'latexheaders' : [],
367         'latexpackages' :  ['geometry'],
368         'latexoptions' : [],
369         'papersize' : [],
370         'pagenumber' : [1],
371         'textheight' : [], 
372         'linewidth' : [],
373         'orientation' : [],
374         'unit' : ['pt'],
375 }
376
377 extra_fields = extra_init.keys ()
378 fields = layout_fields + extra_fields
379
380 include_path = ['.']
381 lily_p = 1
382 paper_p = 1
383
384 output_name = ''
385
386 # Output formats that ly2dvi should create
387 targets = ['DVI', 'LATEX', 'MIDI', 'TEX']
388
389 track_dependencies_p = 0
390 dependency_files = []
391
392
393
394 kpse = os.popen ('kpsexpand \$TEXMF').read()
395 kpse = re.sub('[ \t\n]+$','', kpse)
396 type1_paths = os.popen ('kpsewhich -expand-path=\$T1FONTS').read ()
397
398 environment = {
399         # TODO: * prevent multiple addition.
400         #       * clean TEXINPUTS, MFINPUTS, TFMFONTS,
401         #         as these take prevalence over $TEXMF
402         #         and thus may break tex run?
403         'TEXMF' : "{%s,%s}" % (datadir, kpse) ,
404         'GS_FONTPATH' : type1_paths,
405         'GS_LIB' : datadir + '/ps',
406 }
407
408 # tex needs lots of memory, more than it gets by default on Debian
409 non_path_environment = {
410         'extra_mem_top' : '1000000',
411         'extra_mem_bottom' : '1000000',
412         'pool_size' : '250000',
413 }
414
415 def setup_environment ():
416         # $TEXMF is special, previous value is already taken care of
417         if os.environ.has_key ('TEXMF'):
418                 del os.environ['TEXMF']
419  
420         for key in environment.keys ():
421                 val = environment[key]
422                 if os.environ.has_key (key):
423                         val = os.environ[key] + os.pathsep + val 
424                 os.environ[key] = val
425
426         for key in non_path_environment.keys ():
427                 val = non_path_environment[key]
428                 os.environ[key] = val
429
430 #what a name.
431 def set_setting (dict, key, val):
432         try:
433                 val = string.atoi (val)
434         except ValueError:
435                 #warning (_ ("invalid value: %s") % `val`)
436                 pass
437
438         if type(val) == type ('hoi'):
439                 try:
440                         val = string.atof (val)
441                 except ValueError:
442                         #warning (_ ("invalid value: %s") % `val`)
443                         pass
444
445         try:
446                 dict[key].append (val)
447         except KeyError:
448                 warning (_ ("no such setting: `%s'") % `key`)
449                 dict[key] = [val]
450
451
452 def print_environment ():
453         for (k,v) in os.environ.items ():
454                 sys.stderr.write ("%s=\"%s\"\n" % (k,v)) 
455
456 def quiet_system (cmd, name, ignore_error = 0):
457         if not verbose_p:
458                 progress ( _("Running %s...") % name)
459                 cmd = cmd + ' 1> /dev/null 2> /dev/null'
460         elif pseudo_filter_p:
461                 cmd = cmd + ' 1> /dev/null'
462
463         return system (cmd, ignore_error, quiet = 1)
464
465
466 def run_lilypond (files, dep_prefix):
467
468         opts = ''
469         opts = opts + ' ' + string.join (map (lambda x : '-I ' + x,
470                                               include_path))
471         if pseudo_filter_p:
472                 opts = opts + ' --output=lelie'
473         if paper_p:
474                 opts = opts + ' ' + string.join (map (lambda x : '-H ' + x,
475                                                       fields))
476         else:
477                 opts = opts + ' --no-paper'
478
479         if pdftex_p:
480                 opts = opts + ' -f pdftex'              
481
482         if track_dependencies_p:
483                 opts = opts + " --dependencies"
484                 if dep_prefix:
485                         opts = opts + ' --dep-prefix=%s' % dep_prefix
486
487         fs = string.join (files)
488
489         if not verbose_p:
490                 # cmd = cmd + ' 1> /dev/null 2> /dev/null'
491                 progress ( _("Running %s...") % 'LilyPond')
492         else:
493                 opts = opts + ' --verbose'
494
495                 # for better debugging!
496                 print_environment ()
497
498         cmd = 'lilypond %s %s ' % (opts, fs)
499         if  verbose_p:
500                 progress ("Invoking `%s'"% cmd)
501         status = os.system (cmd)
502
503         signal = 0x0f & status
504         exit_status = status >> 8
505
506         # 2 == user interrupt.
507         if signal and  signal != 2:
508                 error ("\n\n" + _ ("LilyPond crashed (signal %d).") % signal \
509                        + _ ("Please submit a bug report to bug-lilypond@gnu.org") + "\n")
510
511         if status:
512                 error ( "\n" \
513                         + _ ("LilyPond failed on the input file (exit status %d).") % exit_status + "\n")
514                 
515
516 def analyse_lilypond_output (filename, extra):
517         
518         # urg
519         '''Grep FILENAME for interesting stuff, and
520         put relevant info into EXTRA.'''
521         filename = filename+tex_extension
522         progress (_ ("Analyzing %s...") % filename)
523         s = open (filename).read ()
524
525         # search only the first 10k
526         s = s[:10240]
527         for x in extra_fields:
528                 m = re.search (r'\\def\\lilypondpaper%s{([^}]*)}'%x, s)
529                 if m:
530                         set_setting (extra, x, m.group (1))
531
532 def find_tex_files_for_base (base, extra):
533
534         """
535         Find the \header fields dumped from BASE.
536         """
537         
538         headerfiles = {}
539         for f in layout_fields:
540                 if os.path.exists (base + '.' + f):
541                         headerfiles[f] = base+'.'+f
542
543         if os.path.exists (base  +'.dep'):
544                 dependency_files.append (base + '.dep')
545
546         for f in extra_fields:
547                 if os.path.exists (base + '.' + f):
548                         extra[f].append (open (base + '.' + f).read ())
549         
550         return (base+tex_extension,headerfiles)
551          
552
553 def find_tex_files (files, extra):
554         """
555         Find all .tex files whose prefixes start with some name in FILES. 
556
557         """
558         
559         tfiles = []
560         
561         for f in files:
562                 x = 0
563                 while 1:
564                         fname = os.path.basename (f)
565                         fname = strip_extension (fname, '.ly')
566                         if x:
567                                 fname = fname + '-%d' % x
568
569                         if os.path.exists (fname + tex_extension):
570                                 tfiles.append (find_tex_files_for_base (fname, extra))
571                                 analyse_lilypond_output (fname, extra)
572                         else:
573                                 break
574
575                         x = x + 1
576         if not x:
577                 fstr = string.join (files, ', ')
578                 warning (_ ("no LilyPond output found for `%s'") % fstr)
579         return tfiles
580
581 def one_latex_definition (defn, first):
582         s = '\n'
583         for (k,v) in defn[1].items ():
584                 val = open (v).read ()
585                 if (string.strip (val)):
586                         s = s + r'''\def\lilypond%s{%s}''' % (k, val)
587                 else:
588                         s = s + r'''\let\lilypond%s\relax''' % k
589                 s = s + '\n'
590
591         if first:
592                 s = s + '\\def\\mustmakelilypondtitle{}\n'
593         else:
594                 s = s + '\\def\\mustmakelilypondpiecetitle{}\n'
595                 
596         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.
597         return s
598
599
600 ly_paper_to_latexpaper =  {
601         'a4' : 'a4paper',
602         'letter' : 'letterpaper', 
603 }
604
605 #TODO: should set textheight (enlarge) depending on papersize. 
606 def global_latex_preamble (extra):
607         '''construct preamble from EXTRA,'''
608         s = ""
609         s = s + '% generation tag\n'
610
611         options = ''
612
613
614         if extra['papersize']:
615                 try:
616                         options = ly_paper_to_latexpaper[extra['papersize'][0]]
617                 except KeyError:
618                         warning (_ ("invalid value: `%s'") % `extra['papersize'][0]`)
619                         pass
620
621         if extra['latexoptions']:
622                 options = options + ',' + extra['latexoptions'][-1]
623
624         s = s + '\\documentclass[%s]{article}\n' % options
625
626         if extra['language']:
627                 s = s + r'\usepackage[%s]{babel}' % extra['language'][-1] + '\n'
628
629
630         s = s + '\\usepackage{%s}\n' \
631                 % string.join (extra['latexpackages'], ',')
632
633         if extra['latexheaders']:
634                 s = s + '\\include{%s}\n' \
635                         % string.join (extra['latexheaders'], '}\n\\include{')
636
637         unit = extra['unit'][-1]
638
639         textheight = ''
640         if extra['textheight']:
641                 textheight = ',textheight=%f%s' % (extra['textheight'][0], unit)
642
643         orientation = 'portrait'
644         if extra['orientation']:
645                 orientation = extra['orientation'][0]
646
647         # set sane geometry width (a4-width) for linewidth = -1.
648         maxlw = max (extra['linewidth'] + [-1])
649         if maxlw < 0:
650                 # who the hell is 597 ?
651                 linewidth = '597pt'
652         else:
653                 linewidth = '%d%s' % (maxlw, unit)
654         s = s + '\geometry{width=%s%s,headheight=2mm,footskip=2mm,%s}\n' % (linewidth, textheight, orientation)
655
656         if extra['latexoptions']:
657                 s = s + '\geometry{twosideshift=4mm}\n'
658
659         s = s + r'''
660 \usepackage[latin1]{inputenc}
661 \input{titledefs}
662 '''
663         
664         if extra['pagenumber'] and extra['pagenumber'][-1] and extra['pagenumber'][-1] != 'no':
665                 s = s + '\setcounter{page}{%d}\n' % (extra['pagenumber'][-1])
666                 s = s + '\\pagestyle{plain}\n'
667         else:
668                 s = s + '\\pagestyle{empty}\n'
669
670
671         return s
672
673         
674 def global_latex_definition (tfiles, extra):
675         '''construct preamble from EXTRA, dump Latex stuff for each
676 lily output file in TFILES after that, and return the Latex file constructed.  '''
677
678         
679         s = global_latex_preamble (extra) + '\\begin{document}\n'
680         s = s + '\\thispagestyle{firstpage}\n'
681
682         first = 1
683         for t in tfiles:
684                 s = s + one_latex_definition (t, first)
685                 first = 0
686
687
688         s = s + '\\thispagestyle{lastpage}\n'
689         s = s + '\\end{document}'
690
691         return s
692
693 def run_latex (files, outbase, extra):
694
695         """Construct latex file, for FILES and EXTRA, dump it into
696 OUTBASE.latex. Run LaTeX on it.
697
698 RETURN VALUE
699
700 None
701         """
702
703         latex_fn = outbase + '.latex'
704         
705         wfs = find_tex_files (files, extra)
706         s = global_latex_definition (wfs, extra)
707
708         f = open (latex_fn, 'w')
709         f.write (s)
710         f.close ()
711
712         cmd = latex_cmd + ' \\\\nonstopmode \\\\input %s' % latex_fn
713         status = quiet_system (cmd, 'LaTeX', ignore_error = 1)
714
715         signal = 0xf & status
716         exit_stat = status >> 8
717
718         if exit_stat:
719                 logstr = open (outbase + '.log').read()
720                 m = re.search ("\n!", logstr)
721                 start = m.start (0)
722                 logstr = logstr[start:start+200]
723                 
724                 user_error (_ ("LaTeX failed on the output file."), 0)
725                 sys.stderr.write ("\n")
726                 user_error (_ ("The error log is as follows:"), 0)
727                 sys.stderr.write ("\n")
728                 sys.stderr.write (logstr)
729                 sys.stderr.write ("\n")
730                 raise 'LaTeX error'
731         
732         if preview_p:
733                 # make a preview by rendering only the 1st line.
734                 preview_fn = outbase + '.preview.tex'
735                 f = open (preview_fn, 'w')
736                 f.write (r'''
737 %s
738 \input lilyponddefs
739 \pagestyle{empty}
740 \begin{document}
741 \def\interscoreline{\endinput}
742 \input %s
743 \end{document}
744 ''' % (global_latex_preamble (extra), outbase))
745
746                 f.close()
747                 cmd = '%s \\\\nonstopmode \\\\input %s' % (latex_cmd, preview_fn)
748                 quiet_system (cmd, '%s for preview' % latex_cmd)
749         
750
751 def run_dvips (outbase, extra):
752
753
754         """Run dvips using the correct options taken from EXTRA,
755 leaving a PS file in OUTBASE.ps
756
757 RETURN VALUE
758
759 None.
760 """
761         opts = ''
762         if extra['papersize']:
763                 opts = opts + ' -t%s' % extra['papersize'][0]
764
765         if extra['orientation'] and extra['orientation'][0] == 'landscape':
766                 opts = opts + ' -tlandscape'
767
768         if 'PDF' in targets:
769                 opts = opts + ' -Ppdf -G0 -u lilypond.map'
770                 
771         cmd = 'dvips %s -o%s %s' % (opts, outbase + '.ps', outbase + '.dvi')
772         quiet_system (cmd, 'dvips')
773
774         if preview_p:
775                 cmd = 'dvips -E -o%s %s' % ( outbase + '.preview.ps', outbase + '.preview.dvi')         
776                 quiet_system (cmd, 'dvips for preview')
777
778         if 'PDF' in targets:
779                 cmd = 'ps2pdf %s.ps %s.pdf' % (outbase , outbase)
780                 quiet_system (cmd, 'ps2pdf')
781                 
782 def get_bbox (filename):
783         # cut & paste 
784         system ('gs -sDEVICE=bbox -q  -sOutputFile=- -dNOPAUSE %s -c quit > %s.bbox 2>&1 ' % (filename, filename))
785
786         box = open (filename + '.bbox').read()
787         m = re.match ('^%%BoundingBox: ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)', box)
788         gr = []
789         if m:
790                 gr = map (string.atoi, m.groups ())
791         
792         return gr
793
794 #
795 # cut & paste from lilypond-book.
796 #
797 def make_preview (name, extra):
798         bbox = get_bbox (name + '.preview.ps')
799         margin = 0
800         fo = open (name + '.trans.eps' , 'w')
801         fo.write ('%d %d translate\n' % (-bbox[0]+margin, -bbox[1]+margin))
802         fo.close ()
803         
804         x = (2* margin + bbox[2] - bbox[0]) * preview_resolution / 72.
805         y = (2* margin + bbox[3] - bbox[1]) * preview_resolution / 72.
806
807         cmd = r'''gs -g%dx%d -sDEVICE=pgm  -dTextAlphaBits=4 -dGraphicsAlphaBits=4  -q -sOutputFile=- -r%d -dNOPAUSE %s %s -c quit | pnmtopng > %s'''
808         
809         cmd = cmd % (x, y, preview_resolution, name + '.trans.eps', name + '.preview.ps',name + '.png')
810         quiet_system (cmd, 'gs')
811
812         try:
813                 status = system (cmd)
814         except:
815                 os.unlink (name + '.png')
816                 error ("Removing output file")
817
818
819
820 def generate_dependency_file (depfile, outname):
821         df = open (depfile, 'w')
822         df.write (outname + ':' )
823         
824         for d in dependency_files:
825                 s = open (d).read ()
826                 s = re.sub ('#[^\n]*\n', '', s)
827                 s = re.sub (r'\\\n', ' ', s)
828                 m = re.search ('.*:(.*)\n', s)
829
830                 # ugh. Different targets?
831                 if m:
832                         df.write ( m.group (1)  + ' ' )
833
834         df.write ('\n')
835         df.close ();
836
837 def find_file_in_path (path, name):
838         for d in string.split (path, os.pathsep):
839                 if name in os.listdir (d):
840                         return os.path.join (d, name)
841
842 # Added as functionality to ly2dvi, because ly2dvi may well need to do this
843 # in future too.
844 PS = '%!PS-Adobe'
845 def find_pfa_fonts (name):
846         s = open (name).read ()
847         if s[:len (PS)] != PS:
848                 # no ps header?
849                 user_error (_ ("not a PostScript file: `%s\'" % name))
850         here = 0
851         m = re.match ('.*?/(feta[-a-z0-9]+) +findfont', s[here:], re.DOTALL)
852         pfa = []
853         while m:
854                 here = m.end (1)
855                 pfa.append (m.group (1))
856                 m = re.match ('.*?/(feta[-a-z0-9]+) +findfont', s[here:], re.DOTALL)
857         return pfa
858
859         
860 (sh, long) = getopt_args (option_definitions)
861 try:
862         (options, files) = getopt.getopt(sys.argv[1:], sh, long)
863 except getopt.error, s:
864         errorport.write ('\n')
865         user_error (_ ("getopt says: `%s\'" % s), 0)
866         errorport.write ('\n')
867         help ()
868         sys.exit (2)
869         
870 for opt in options:     
871         o = opt[0]
872         a = opt[1]
873
874         if 0:
875                 pass
876         elif o == '--help' or o == '-h':
877                 help ()
878                 sys.exit (0)
879         elif o == '--find-pfa' or o == '-f':
880                 fonts = map (lambda x: x + '.pfa', find_pfa_fonts (a))
881                 files = map (lambda x:
882                              find_file_in_path (os.environ['GS_FONTPATH'], x),
883                              fonts)
884                 print string.join (files, ' ')
885                 sys.exit (0)
886         elif o == '--include' or o == '-I':
887                 include_path.append (a)
888         elif o == '--postscript' or o == '-P':
889                 targets.append ('PS')
890         elif o == '--pdf' or o == '-p':
891                 targets.append ('PS')
892                 targets.append ('PDF')
893         elif o == '--keep' or o == '-k':
894                 keep_temp_dir_p = 1
895         elif o == '--no-lily':
896                 lily_p = 0
897         elif o == '--preview':
898                 preview_p = 1
899                 targets.append ('PNG')
900         elif o == '--preview-resolution':
901                 preview_resolution = string.atoi (a)
902         elif o == '--no-paper' or o == '-m':
903                 targets = ['MIDI'] 
904                 paper_p = 0
905         elif o == '--output' or o == '-o':
906                 output_name = a
907         elif o == '--set' or o == '-s':
908                 ss = string.split (a, '=')
909                 set_setting (extra_init, ss[0], ss[1])
910         elif o == '--dependencies' or o == '-d':
911                 track_dependencies_p = 1
912         elif o == '--verbose' or o == '-V':
913                 verbose_p = 1
914         elif o == '--version' or o == '-v':
915                 identify ()
916                 sys.exit (0)
917         elif o == '--pdftex':
918                 latex_cmd = 'pdflatex'
919                 targets.remove('DVI')
920                 targets.append('PDFTEX')
921                 pdftex_p = 1
922                 tex_extension = '.pdftex'
923         elif o == '--warranty' or o == '-w':
924                 status = system ('lilypond -w', ignore_error = 1)
925                 if status:
926                         warranty ()
927
928                 sys.exit (0)
929
930 # Don't convert input files to abspath, rather prepend '.' to include
931 # path.
932 include_path.insert (0, '.')
933
934 # As a neat trick, add directory part of first input file
935 # to include path.  That way you can do without the clumsy -I in:
936
937 #    ly2dvi -I foe/bar/baz foo/bar/baz/baz.ly
938 if files and files[0] != '-' and os.path.dirname (files[0]) != '.':
939         include_path.append (os.path.dirname (files[0]))
940         
941 include_path = map (abspath, include_path)
942
943 if files and (files[0] == '-' or output_name == '-'):
944         if len (files) == 1:
945                 pseudo_filter_p = 1
946                 output_name = 'lelie'
947                 if verbose_p:
948                         progress (_ ("pseudo filter"))
949         else:
950                 help ()
951                 user_error (_ ("pseudo filter only for single input file"), 2)
952                 
953         
954 original_output = output_name
955
956 if files:
957         
958         # Ugh, maybe make a setup () function
959         files = map (lambda x: strip_extension (x, '.ly'), files)
960
961         # hmmm. Wish I'd 've written comments when I wrote this.
962         # now it looks complicated.
963         
964         (outdir, outbase) = ('','')
965         if not output_name:
966                 outbase = os.path.basename (files[0])
967                 outdir = abspath ('.')
968         elif output_name[-1] == os.sep:
969                 outdir = abspath (output_name)
970                 outbase = os.path.basename (files[0])
971         else:
972                 (outdir, outbase) = os.path.split (abspath (output_name))
973
974         for i in ('.dvi', '.latex', '.ly', '.ps', '.tex', '.pdftex'):
975                 output_name = strip_extension (output_name, i)
976                 outbase = strip_extension (outbase, i)
977
978         for i in files[:] + [output_name]:
979                 if string.find (i, ' ') >= 0:
980                         user_error (_ ("filename should not contain spaces: `%s'") % i)
981                         
982         if os.path.dirname (output_name) != '.':
983                 dep_prefix = os.path.dirname (output_name)
984         else:
985                 dep_prefix = 0
986
987         reldir = os.path.dirname (output_name)
988         if outdir != '.' and (track_dependencies_p or targets):
989                 mkdir_p (outdir, 0777)
990
991         setup_environment ()
992         tmpdir = setup_temp ()
993
994         # to be sure, add tmpdir *in front* of inclusion path.
995         #os.environ['TEXINPUTS'] =  tmpdir + ':' + os.environ['TEXINPUTS']
996         os.chdir (tmpdir)
997         
998         if lily_p:
999                 try:
1000                         run_lilypond (files, dep_prefix)
1001                 except:
1002                         # TODO: friendly message about LilyPond setup/failing?
1003                         #
1004                         # TODO: lilypond should fail with different
1005                         # error codes for:
1006                         #   - guile setup/startup failure
1007                         #   - font setup failure
1008                         #   - init.ly setup failure
1009                         #   - parse error in .ly
1010                         #   - unexpected: assert/core dump
1011                         targets = []
1012                         traceback.print_exc ()
1013
1014         # Our LilyPond pseudo filter always outputs to 'lelie'
1015         # have subsequent stages and use 'lelie' output.
1016         if pseudo_filter_p:
1017                 files[0] = 'lelie'
1018                 
1019         if 'PNG' in targets and 'PS' not in targets:
1020                 targets.append ('PS')
1021         if 'PS' in targets and 'DVI' not in targets:
1022                 targets.append('DVI')
1023
1024         if 'DVI' in targets:
1025                 try:
1026                         run_latex (files, outbase, extra_init)
1027                         # unless: add --tex, or --latex?
1028                         targets.remove ('TEX')
1029                         targets.remove('LATEX')
1030                 except:
1031                         # TODO: friendly message about TeX/LaTeX setup,
1032                         # trying to run tex/latex by hand
1033                         if 'DVI' in targets:
1034                                 targets.remove ('DVI')
1035                         if 'PS' in targets:
1036                                 targets.remove ('PS')
1037                         traceback.print_exc ()
1038
1039         if 'PS' in targets:
1040                 try:
1041                         run_dvips (outbase, extra_init)
1042                 except: 
1043                         if 'PS' in targets:
1044                                 targets.remove ('PS')
1045                         traceback.print_exc ()
1046
1047         if 'PNG' in  targets:
1048                 make_preview (outbase, extra_init)
1049
1050         if 'PDFTEX' in targets:
1051                 try:
1052                         run_latex (files, outbase, extra_init)
1053                         # unless: add --tex, or --latex?
1054                         targets.remove ('TEX')
1055                         targets.remove ('LATEX')
1056                         targets.remove ('PDFTEX')
1057                         if 'PDF' not in targets:
1058                                 targets.append('PDF')
1059                 except:
1060                         # TODO: friendly message about TeX/LaTeX setup,
1061                         # trying to run tex/latex by hand
1062                         if 'PDFTEX' in targets:
1063                                 targets.remove ('PDFTEX')
1064                         if 'PDF' in targets:
1065                                 targets.remove ('PDF')
1066                         if 'PS' in targets:
1067                                 targets.remove ('PS')
1068                         traceback.print_exc ()
1069                         sys.exit(1)
1070
1071         # add DEP to targets?
1072         if track_dependencies_p:
1073                 depfile = os.path.join (outdir, outbase + '.dep')
1074                 generate_dependency_file (depfile, depfile)
1075                 if os.path.isfile (depfile):
1076                         progress (_ ("dependencies output to `%s'...") %
1077                                   depfile)
1078
1079         if pseudo_filter_p:
1080                 main_target = 0
1081                 for i in 'PDF', 'PS', 'PNG', 'DVI', 'LATEX':
1082                         if i in targets:
1083                                 main_target = i
1084                                 break
1085
1086                 outname = outbase + '.' + string.lower (main_target)
1087                 if os.path.isfile (outname):
1088                         sys.stdout.write (open (outname).read ())
1089                 elif verbose_p:
1090                         warning (_ ("can't find file: `%s'") % outname)
1091                 targets = []
1092                 
1093         # Hmm, if this were a function, we could call it the except: clauses
1094         for i in targets:
1095                 ext = string.lower (i)
1096                 cp_to_dir ('.*\.%s$' % ext, outdir)
1097                 outname = outbase + '.' + string.lower (i)
1098                 abs = os.path.join (outdir, outname)
1099                 if reldir != '.':
1100                         outname = os.path.join (reldir, outname)
1101                 if os.path.isfile (abs):
1102                         progress (_ ("%s output to `%s'...") % (i, outname))
1103                 elif verbose_p:
1104                         warning (_ ("can't find file: `%s'") % outname)
1105                         
1106         os.chdir (original_dir)
1107         cleanup_temp ()
1108         
1109 else:
1110         help ()
1111         user_error (_ ("no files specified on command line"), 2)