]> git.donarmstrong.com Git - lilypond.git/blob - scripts/ly2dvi.py
release: 1.3.148
[lilypond.git] / scripts / ly2dvi.py
1 #!@PYTHON@
2 # Run lilypond, latex, dvips.
3 #
4 # This is the third incarnation of ly2dvi.
5 #
6 # Earlier incarnations of ly2dvi were written by
7 # Jeffrey B. Reed<daboys@austin.rr.com> (Python version)
8 # Jan Arne Fagertun <Jan.A.Fagertun@@energy.sintef.no> (Bourne shell script)
9 #
10
11
12 # Note: gettext work best if we use ' for docstrings and "
13 # for gettextable strings
14
15 '''
16 TODO:
17
18   * figure out which set of command line options should make ly2dvi:
19
20       na: create tex only?  
21       na: create latex only? 
22       na: create tex and latex
23       default: create dvi only
24       na: create tex, latex and dvi
25       -P: create dvi and ps
26       na: * create ps only
27
28      etc.
29
30      for foo.ly, rename ly2dvi.dir to out-ly2dvi, foo.ly2dvi, foo.dir ?
31      
32   * move versatile taglines, 
33   
34      \header {
35         beginfooter=\mutopiaPD
36         endfooter=\tagline  -> 'lily was here <version>'
37      }
38
39      lilytagline (->lily was here), usertagline, copyright etc.
40
41   * head/header tagline/endfooter
42
43   * dvi from lilypond .tex output?  This is hairy, because we create dvi
44     from lilypond .tex *and* header output.
45
46   * multiple \score blocks?
47   
48 '''
49
50
51 import os
52 import stat
53 import string
54 import re
55 import getopt
56 import sys
57 import shutil
58 import __main__
59 import operator
60 import tempfile
61
62 datadir = '@datadir@'
63 sys.path.append (datadir + '/python')
64 try:
65         import gettext
66         gettext.bindtextdomain ('lilypond', '@localedir@')
67         gettext.textdomain('lilypond')
68         _ = gettext.gettext
69 except:
70         def _ (s):
71                 return s
72
73
74 layout_fields = ['title', 'subtitle', 'subsubtitle', 'footer', 'head',
75           'composer', 'arranger', 'instrument', 'opus', 'piece', 'metre',
76           'meter', 'poet']
77
78
79 # init to empty; values here take precedence over values in the file 
80 extra_init = {
81         'language' : [],
82         'latexheaders' : [],
83         'latexpackages' :  ['geometry'],
84         'latexoptions' : [],
85         'papersize' : [],
86         'pagenumber' : [1],
87         'textheight' : [], 
88         'linewidth' : [],
89         'orientation' : []
90 }
91
92 extra_fields = extra_init.keys ()
93
94 fields = layout_fields + extra_fields
95 program_name = 'ly2dvi'
96 help_summary = _ ("Generate .dvi with LaTeX for LilyPond")
97
98 include_path = ['.']
99 lily_p = 1
100 paper_p = 1
101
102 output = 0
103 targets = {
104         'DVI' : 0,
105         'LATEX' : 0,
106         'MIDI' : 0,
107         'TEX' : 0,
108         }
109
110 track_dependencies_p = 0
111 dependency_files = []
112
113
114 # lily_py.py -- options and stuff
115
116 # source file of the GNU LilyPond music typesetter
117
118 # BEGIN Library for these?
119 # cut-n-paste from ly2dvi
120
121 program_version = '@TOPLEVEL_VERSION@'
122 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
123         program_version = '1.3.142'
124
125
126 original_dir = os.getcwd ()
127 temp_dir = '%s.dir' % program_name
128 keep_temp_dir_p = 0
129 verbose_p = 0
130
131 #
132 # Try to cater for bad installations of LilyPond, that have
133 # broken TeX setup.  Just hope this doesn't hurt good TeX
134 # setups.  Maybe we should check if kpsewhich can find
135 # feta16.{afm,mf,tex,tfm}, and only set env upon failure.
136 #
137 environment = {
138         'MFINPUTS' : datadir + '/mf:',
139         'TEXINPUTS': datadir + '/tex:' + datadir + '/ps:.:',
140         'TFMFONTS' : datadir + '/tfm:',
141         'GS_FONTPATH' : datadir + '/afm:' + datadir + '/pfa',
142         'GS_LIB' : datadir + '/ps',
143 }
144
145 def setup_environment ():
146         for key in environment.keys ():
147                 val = environment[key]
148                 if os.environ.has_key (key):
149                         val = val + os.pathsep + os.environ[key]
150                 os.environ[key] = val
151
152 def identify ():
153         sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
154
155 def warranty ():
156         identify ()
157         sys.stdout.write ('\n')
158         sys.stdout.write (_ ('Copyright (c) %s by' % ' 2001'))
159         sys.stdout.write ('\n')
160         sys.stdout.write ('  Han-Wen Nienhuys')
161         sys.stdout.write ('  Jan Nieuwenhuizen')
162         sys.stdout.write ('\n')
163         sys.stdout.write (_ (r'''
164 Distributed under terms of the GNU General Public License. It comes with
165 NO WARRANTY.'''))
166         sys.stdout.write ('\n')
167
168 if ( os.name == 'posix' ):
169         errorport=sys.stderr
170 else:
171         errorport=sys.stdout
172
173 def progress (s):
174         errorport.write (s + '\n')
175
176 def warning (s):
177         progress (_ ("warning: ") + s)
178                 
179 def error (s):
180         progress (_ ("error: ") + s)
181         raise _ ("Exiting ... ")
182
183 def getopt_args (opts):
184         '''Construct arguments (LONG, SHORT) for getopt from  list of options.'''
185         short = ''
186         long = []
187         for o in opts:
188                 if o[1]:
189                         short = short + o[1]
190                         if o[0]:
191                                 short = short + ':'
192                 if o[2]:
193                         l = o[2]
194                         if o[0]:
195                                 l = l + '='
196                         long.append (l)
197         return (short, long)
198
199 def option_help_str (o):
200         '''Transform one option description (4-tuple ) into neatly formatted string'''
201         sh = '  '       
202         if o[1]:
203                 sh = '-%s' % o[1]
204
205         sep = ' '
206         if o[1] and o[2]:
207                 sep = ','
208                 
209         long = ''
210         if o[2]:
211                 long= '--%s' % o[2]
212
213         arg = ''
214         if o[0]:
215                 if o[2]:
216                         arg = '='
217                 arg = arg + o[0]
218         return '  ' + sh + sep + long + arg
219
220
221 def options_help_str (opts):
222         '''Convert a list of options into a neatly formatted string'''
223         w = 0
224         strs =[]
225         helps = []
226
227         for o in opts:
228                 s = option_help_str (o)
229                 strs.append ((s, o[3]))
230                 if len (s) > w:
231                         w = len (s)
232
233         str = ''
234         for s in strs:
235                 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0])  + 3), s[1])
236         return str
237
238 def help ():
239         sys.stdout.write (_ ("Usage: %s [OPTION]... FILE") % program_name)
240         sys.stdout.write ('\n\n')
241         sys.stdout.write (help_summary)
242         sys.stdout.write ('\n\n')
243         sys.stdout.write (_ ("Options:"))
244         sys.stdout.write ('\n')
245         sys.stdout.write (options_help_str (option_definitions))
246         sys.stdout.write ('\n\n')
247         sys.stdout.write (_ ("Report bugs to %s") % 'bug-gnu-music@gnu.org')
248         sys.stdout.write ('\n')
249
250 def setup_temp ():
251         global temp_dir
252         if not keep_temp_dir_p:
253                 temp_dir = tempfile.mktemp (program_name)
254         try:
255                 os.mkdir (temp_dir, 0777)
256         except OSError:
257                 pass
258         os.chdir (temp_dir)
259
260
261 def system (cmd, ignore_error = 0):
262         if verbose_p:
263                 progress (_ ("Invoking `%s\'") % cmd)
264         st = os.system (cmd) >> 8
265         if st:
266                 name = re.match ('[ \t]*([^ \t]*)', cmd).group (1)
267                 msg = name + ': ' + _ ("command exited with value %d") % st
268                 if ignore_error:
269                         warning (msg + ' ' + _ ("(ignored)") + ' ')
270                 else:
271                         error (msg)
272
273         return st
274
275
276 def cleanup_temp ():
277         if not keep_temp_dir_p:
278                 if verbose_p:
279                         progress (_ ("Cleaning %s...") % temp_dir)
280                 shutil.rmtree (temp_dir)
281
282
283 def set_setting (dict, key, val):
284         try:
285                 val = string.atof (val)
286         except ValueError:
287                 #warning (_ ("invalid value: %s") % `val`)
288                 pass
289
290         try:
291                 dict[key].append (val)
292         except KeyError:
293                 warning (_ ("no such setting: %s") % `key`)
294                 dict[key] = [val]
295
296 def strip_extension (f, ext):
297         (p, e) = os.path.splitext (f)
298         if e == ext:
299                 e = ''
300         return p + e
301
302 # END Library
303
304 option_definitions = [
305         ('', 'd', 'dependencies', _ ("write Makefile dependencies for every input file")),
306         ('', 'h', 'help', _ ("this help")),
307         (_ ("DIR"), 'I', 'include', _ ("add DIR to LilyPond's search path")),
308         ('', 'k', 'keep', _ ("keep all output, and name the directory %s.dir") % program_name),
309         ('', '', 'no-lily', _ ("don't run LilyPond")),
310         ('', 'm', 'no-paper', _ ("produce MIDI output only")),
311         (_ ("FILE"), 'o', 'output', _ ("write ouput to FILE")),
312         # why capital P?
313         ('', 'P', 'postscript', _ ("generate PostScript output")),
314         (_ ("KEY=VAL"), 's', 'set', _ ("change global setting KEY to VAL")),
315         ('', 'V', 'verbose', _ ("verbose")),
316         ('', 'v', 'version', _ ("print version number")),
317         ('', 'w', 'warranty', _ ("show warranty and copyright")),
318         ]
319
320 def run_lilypond (files, outbase, dep_prefix):
321         opts = '--output=%s.tex' % outbase
322         opts = opts + ' ' + string.join (map (lambda x : '-I ' + x,
323                                               include_path))
324         if paper_p:
325                 opts = opts + ' ' + string.join (map (lambda x : '-H ' + x,
326                                                       fields))
327         else:
328                 opts = opts + ' --no-paper'
329                 
330         if track_dependencies_p:
331                 opts = opts + " --dependencies"
332                 if dep_prefix:
333                         opts = opts + ' --dep-prefix=%s' % dep_prefix
334
335         fs = string.join (files)
336
337         if not verbose_p:
338                 progress ( _("Running %s...") % 'LilyPond')
339                 # cmd = cmd + ' 1> /dev/null 2> /dev/null'
340         else:
341                 opts = opts + ' --verbose'
342         
343         system ('lilypond %s %s ' % (opts, fs))
344
345 def analyse_lilypond_output (filename, extra):
346         
347         # urg
348         '''Grep FILENAME for interesting stuff, and
349         put relevant info into EXTRA.'''
350         filename = filename+'.tex'
351         progress (_ ("Analyzing %s...") % filename)
352         s = open (filename).read ()
353
354         # search only the first 10k
355         s = s[:10240]
356         for x in ('textheight', 'linewidth', 'papersize', 'orientation'):
357                 m = re.search (r'\\def\\lilypondpaper%s{([^}]*)}'%x, s)
358                 if m:
359                         set_setting (extra, x, m.group (1))
360
361 def find_tex_files_for_base (base, extra):
362         headerfiles = {}
363         for f in layout_fields:
364                 if os.path.exists (base + '.' + f):
365                         headerfiles[f] = base+'.'+f
366
367         if os.path.exists (base  +'.dep'):
368                 dependency_files.append (base + '.dep')
369
370         for f in extra_fields:
371                 if os.path.exists (base + '.' + f):
372                         extra[f].append (open (base + '.' + f).read ())
373         
374         return (base  +'.tex',headerfiles)
375          
376
377 def find_tex_files (files, extra):
378         tfiles = []
379         
380         for f in files:
381                 x = 0
382                 while 1:
383                         fname = os.path.basename (f)
384                         fname = strip_extension (fname, '.ly')
385                         if x:
386                                 fname = fname + '-%d' % x
387
388                         if os.path.exists (fname + '.tex'):
389                                 tfiles.append (find_tex_files_for_base (fname, extra))
390                                 analyse_lilypond_output (fname, extra)
391                         else:
392                                 break
393
394                         x = x + 1
395         if not x:
396                 warning (_ ("no lilypond output found for %s") % `files`)
397         return tfiles
398
399 def one_latex_definition (defn, first):
400         s = '\n'
401         for (k,v) in defn[1].items ():
402                 val = open (v).read ()
403                 if (string.strip (val)):
404                         s = s + r'''\def\lilypond%s{%s}''' % (k, val)
405                 else:
406                         s = s + r'''\let\lilypond%s\relax''' % k
407                 s = s + '\n'
408
409         if first:
410                 s = s + '\\def\\mustmakelilypondtitle{}\n'
411         else:
412                 s = s + '\\def\\mustmakelilypondpiecetitle{}\n'
413                 
414         s = s + '\\input %s' % defn[0]
415         return s
416
417
418 ly_paper_to_latexpaper =  {
419         'a4' : 'a4paper',
420         'letter' : 'letterpaper', 
421 }
422
423 def global_latex_definition (tfiles, extra):
424         '''construct preamble from EXTRA,
425         dump lily output files after that, and return result.
426         '''
427
428
429         s = ""
430         s = s + '% generation tag\n'
431
432         options = ''
433
434         if extra['papersize']:
435                 try:
436                         options = '%s' % ly_paper_to_latexpaper[extra['papersize'][0]]
437                 except:
438                         warning (_ ("invalid value: %s") % `extra['papersize'][0]`)
439                         pass
440
441         if extra['latexoptions']:
442                 options = options + ',' + extra['latexoptions'][-1]
443         
444         s = s + '\\documentclass[%s]{article}\n' % options
445
446         if extra['language']:
447                 s = s + r'\usepackage[%s]{babel}\n' % extra['language'][-1]
448
449
450         s = s + '\\usepackage{%s}\n' \
451                 % string.join (extra['latexpackages'], ',')
452
453         if extra['latexheaders']:
454                 s = s + '\\include{%s}\n' \
455                         % string.join (extra['latexheaders'], '}\n\\include{')
456
457         textheight = ''
458         if extra['textheight']:
459                 textheight = ',textheight=%fpt' % extra['textheight'][0]
460
461         orientation = 'portrait'
462         if extra['orientation']:
463                 orientation = extra['orientation'][0]
464
465         # set sane geometry width (a4-width) for linewidth = -1.
466         if not extra['linewidth'] or extra['linewidth'][0] < 0:
467                 linewidth = 597
468         else:
469                 linewidth = extra['linewidth'][0]
470         s = s + '\geometry{width=%spt%s,headheight=2mm,headsep=12pt,footskip=2mm,%s}\n' % (linewidth, textheight, orientation)
471
472         if extra['latexoptions']:
473                 s = s + '\geometry{twosideshift=4mm}\n'
474                 
475         s = s + r'''
476 \usepackage[latin1]{inputenc}
477 \input{titledefs}
478 \makeatletter
479 \renewcommand{\@oddfoot}{\parbox{\textwidth}{\mbox{}\thefooter}}%
480 \renewcommand{\@evenfoot}{\parbox{\textwidth}{\mbox{}\thefooter}}%
481 '''
482         
483         if extra['pagenumber'] and extra['pagenumber'][-1] and extra['pagenumber'][-1] != 'no':
484                 s = s + r'''
485 \renewcommand{\@evenhead}{\parbox{\textwidth}%
486     {\mbox{}\textbf{\thepage}\hfill\small\theheader}}
487 \renewcommand{\@oddhead}{\parbox{\textwidth}%
488     {\mbox{}\small\theheader\hfill\textbf{\thepage}}}
489 '''
490         else:
491                 s = s + '\\pagestyle{empty}\n'
492
493         s = s + '\\makeatother\n'
494         s = s + '\\begin{document}\n'
495
496
497         first = 1
498         for t in tfiles:
499                 s = s + one_latex_definition (t, first)
500                 first = 0
501
502         s = s + r'''
503 % I do not see why we want to clobber the footer here
504 \vfill\hfill\parbox{\textwidth}{\mbox{}\makelilypondtagline}
505 %\makeatletter
506 %\renewcommand{\@oddfoot}{\parbox{\textwidth}{\mbox{}\makelilypondtagline}}%
507 %\makeatother
508 '''
509         s = s + '\\end{document}'
510
511         return s
512
513 def run_latex (files, outbase, extra):
514         wfs = find_tex_files ([outbase] + files[1:], extra)
515         s = global_latex_definition (wfs, extra)
516
517         f = open (outbase + '.latex', 'w')
518         f.write (s)
519         f.close ()
520
521         if ( os.name == 'posix' ):
522                 cmd = 'latex \\\\nonstopmode \\\\input %s' % outbase + '.latex'
523         else:
524                 cmd = 'latex \\nonstopmode \\input %s' % outbase + '.latex'
525         if not verbose_p:
526                 progress ( _("Running %s...") % 'LaTeX')
527                 cmd = cmd + ' 1> /dev/null 2> /dev/null'
528
529         system (cmd)
530
531 def run_dvips (outbase, extra):
532
533         opts = ''
534         if extra['papersize']:
535                 opts = opts + ' -t%s' % extra['papersize'][0]
536
537         if extra['orientation'] and extra['orientation'][0] == 'landscape':
538                 opts = opts + ' -tlandscape'
539
540         cmd = 'dvips %s -o%s %s' % (opts, outbase + '.ps', outbase + '.dvi')
541         
542         if not verbose_p:
543                 progress ( _("Running %s...") % 'dvips')
544                 if os.name == 'posix':
545                         cmd = cmd + ' 1> /dev/null 2> /dev/null'
546                 
547         system (cmd)
548
549 def generate_dependency_file (depfile, outname):
550         df = open (depfile, 'w')
551         df.write (outname + ':' )
552         
553         for d in dependency_files:
554                 s = open (d).read ()
555                 s = re.sub ('#[^\n]*\n', '', s)
556                 s = re.sub (r'\\\n', ' ', s)
557                 m = re.search ('.*:(.*)\n', s)
558
559                 # ugh. Different targets?
560                 if m:
561                         df.write ( m.group (1)  + ' ' )
562
563         df.write ('\n')
564         df.close ();
565
566 (sh, long) = getopt_args (__main__.option_definitions)
567 try:
568         (options, files) = getopt.getopt(sys.argv[1:], sh, long)
569 except:
570         help ()
571         sys.exit (2)
572         
573 for opt in options:     
574         o = opt[0]
575         a = opt[1]
576
577         if 0:
578                 pass
579         elif o == '--help' or o == '-h':
580                 help ()
581                 sys.exit (0)
582         elif o == '--include' or o == '-I':
583                 include_path.append (a)
584         elif o == '--postscript' or o == '-P':
585                 targets['PS'] = 0
586         elif o == '--keep' or o == '-k':
587                 keep_temp_dir_p = 1
588         elif o == '--no-lily':
589                 lily_p = 0
590         elif o == '--no-paper' or o == '-m':
591                 targets = {}
592                 targets['MIDI'] = 0
593                 paper_p = 0
594         elif o == '--output' or o == '-o':
595                 output = a
596         elif o == '--set' or o == '-s':
597                 ss = string.split (a, '=')
598                 set_setting (extra_init, ss[0], ss[1])
599         elif o == '--dependencies' or o == '-d':
600                 track_dependencies_p = 1
601         elif o == '--verbose' or o == '-V':
602                 verbose_p = 1
603         elif o == '--version' or o == '-v':
604                 identify ()
605                 sys.exit (0)
606         elif o == '--warranty' or o == '-w':
607                 try:
608                         system ('lilypond -w')
609                 except:
610                         warranty ()
611                 sys.exit (0)
612
613
614 def cp_to_dir (pattern, dir):
615         "Copy files matching re PATTERN from cwd to DIR"
616         # Duh.  Python style portable: cp *.EXT OUTDIR
617         # system ('cp *.%s %s' % (ext, outdir), 1)
618         files = filter (lambda x, p=pattern: re.match (p, x), os.listdir ('.'))
619         map (lambda x, d=dir: shutil.copy2 (x, os.path.join (d, x)), files)
620
621 # Python < 1.5.2 compatibility
622 #
623 # On most platforms, this is equivalent to
624 #`normpath(join(os.getcwd()), PATH)'.  *Added in Python version 1.5.2*
625 if os.path.__dict__.has_key ('abspath'):
626         abspath = os.path.abspath
627 else:
628         def abspath (path):
629                 return os.path.normpath (os.path.join (os.getcwd (), path))
630
631 if os.__dict__.has_key ('makedirs'):
632         makedirs = os.makedirs
633 else:
634         def makedirs (dir, mode=0777):
635                 system ('mkdir -p %s' % dir)
636
637 def mkdir_p (dir, mode=0777):
638         if not os.path.isdir (dir):
639                 makedirs (dir, mode)
640
641 include_path = map (abspath, include_path)
642
643 original_output = output
644
645 if files and files[0] != '-':
646
647         files = map (lambda x: strip_extension (x, '.ly'), files)
648
649         if not output:
650                 output = os.path.basename (files[0])
651
652         for i in ('.dvi', '.latex', '.ly', '.ps', '.tex'):
653                 output = strip_extension (output, i)
654
655         files = map (abspath, files) 
656
657         if os.path.dirname (output) != '.':
658                 dep_prefix = os.path.dirname (output)
659         else:
660                 dep_prefix = 0
661
662         reldir = os.path.dirname (output)
663         (outdir, outbase) = os.path.split (abspath (output))
664         
665         setup_environment ()
666         setup_temp ()
667         
668         extra = extra_init
669         
670         if lily_p:
671                 try:
672                         run_lilypond (files, outbase, dep_prefix)
673                 except:
674                         # TODO: friendly message about LilyPond setup/failing?
675                         #
676                         # TODO: lilypond should fail with different
677                         # error codes for:
678                         #   - guile setup/startup failure
679                         #   - font setup failure
680                         #   - init.ly setup failure
681                         #   - parse error in .ly
682                         #   - unexpected: assert/core dump
683                         targets = {}
684
685         if targets.has_key ('DVI') or targets.has_key ('PS'):
686                 try:
687                         run_latex (files, outbase, extra)
688                         # unless: add --tex, or --latex?
689                         del targets['TEX']
690                         del targets['LATEX']
691                 except:
692                         # TODO: friendly message about TeX/LaTeX setup,
693                         # trying to run tex/latex by hand
694                         if targets.has_key ('DVI'):
695                                 del targets['DVI']
696                         if targets.has_key ('PS'):
697                                 del targets['PS']
698
699         # TODO: does dvips ever fail?
700         if targets.has_key ('PS'):
701                 run_dvips (outbase, extra)
702
703         if outdir != '.' and (track_dependencies_p or targets.keys ()):
704                 mkdir_p (outdir, 0777)
705
706         # add DEP to targets?
707         if track_dependencies_p:
708                 depfile = os.path.join (outdir, outbase + '.dep')
709                 generate_dependency_file (depfile, depfile)
710                 if os.path.isfile (depfile):
711                         progress (_ ("dependencies output to %s...") % depfile)
712
713         for i in targets.keys ():
714                 ext = string.lower (i)
715                 cp_to_dir ('.*\.%s$' % ext, outdir)
716                 outname = outbase + '.' + string.lower (i)
717                 abs = os.path.join (outdir, outname)
718                 if reldir != '.':
719                         outname = os.path.join (reldir, outname)
720                 if os.path.isfile (abs):
721                         progress (_ ("%s output to %s...") % (i, outname))
722                 elif verbose_p:
723                         warning (_ ("can't find file: `%s'") % outname)
724
725         os.chdir (original_dir)
726         cleanup_temp ()
727         
728 else:
729         # FIXME
730         help ()
731         progress ('\n')
732         try:
733                 error (_ ("no FILEs specified, can't invoke as filter"))
734         except:
735                 pass
736         sys.exit (2)
737
738
739