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