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