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