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