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