]> git.donarmstrong.com Git - lilypond.git/blob - scripts/ly2dvi.py
release: 1.3.135
[lilypond.git] / scripts / ly2dvi.py
1 #!@PYTHON@
2 # run lily, setup LaTeX input.
3
4 """ TODO: --dependencies
5
6 """
7
8
9 import os
10 import stat
11 import string
12 import re
13 import getopt
14 import sys
15 import __main__
16 import operator
17 import tempfile
18
19
20 layout_fields = ['title', 'subtitle', 'subsubtitle', 'footer', 'head',
21           'composer', 'arranger', 'instrument', 'opus', 'piece', 'metre',
22           'meter', 'poet']
23
24
25 # init to empty; values here take precedence over values in the file 
26 extra_init = {
27         'language' : [],
28         'latexheaders' : [],
29         'latexpackages' :  ['geometry'],
30         'papersizename' : [],
31         'pagenumber' : [],
32         'textheight' : [], 
33         'linewidth' : [],
34         'orientation' : []
35 }
36
37 extra_fields = extra_init.keys ()
38
39 fields = layout_fields + extra_fields
40 original_dir = os.getcwd ()
41 include_path = ['.']
42 temp_dir = ''
43 keep_temp_dir = 0
44 no_lily = 0
45 outdir = '.'
46 track_dependencies_p = 0
47
48 dependency_files = []
49
50
51 program_version = '@TOPLEVEL_VERSION@'
52 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
53         program_version = '1.3.134'
54
55 # generate ps ?
56 postscript_p = 0
57
58 option_definitions = [
59         ('', 'h', 'help', 'print help'),
60         ('KEY=VAL', 's', 'set', 'change global setting KEY to VAL'),    
61         ('', 'P', 'postscript', 'Generate PostScript output'),
62         ('', 'k', 'keep', 'Keep all output, and name the directory ly2dvi.dir'),
63         ('', '', 'no-lily', 'Don\'t run lilypond'),
64         ('', 'v', 'version', "Print version and copyright info"),
65         ('DIR', '', 'outdir', 'Dump all final output into DIR'),
66         ('', 'd', 'dependencies', 'Dump all final output into DIR'),
67         ]
68
69
70
71 def identify():
72         sys.stdout.write ('lilypond-book (GNU LilyPond) %s\n' % program_version)
73
74 def print_version ():
75         identify()
76         sys.stdout.write (r"""Copyright 1998--1999
77 Distributed under terms of the GNU General Public License. It comes with
78 NO WARRANTY.""")
79
80
81
82 def progress (s):
83         """Make the progress messages stand out between lilypond stuff"""
84         sys.stderr.write (' *** ' + s+ '\n')
85         
86 def error (s):
87         sys.stderr.write (s)
88         raise 'Exiting ... '
89
90
91 def find_file (name):
92         """
93         Search the include path for NAME. If found, return the (CONTENTS, PATH) of the file.
94         """
95         
96         f = None
97         nm = ''
98         for a in include_path:
99                 try:
100                         nm = os.path.join (a, name)
101                         f = open (nm)
102                         __main__.read_files.append (nm)
103                         break
104                 except IOError:
105                         pass
106         if f:
107                 sys.stderr.write ("Reading `%s'\n" % nm)
108                 return (f.read (), nm)
109         else:
110                 error ("File not found `%s'\n" % name)
111                 return ('', '')
112
113
114
115
116 def getopt_args (opts):
117         "Construct arguments (LONG, SHORT) for getopt from  list of options."
118         short = ''
119         long = []
120         for o in opts:
121                 if o[1]:
122                         short = short + o[1]
123                         if o[0]:
124                                 short = short + ':'
125                 if o[2]:
126                         l = o[2]
127                         if o[0]:
128                                 l = l + '='
129                         long.append (l)
130         return (short, long)
131
132 def option_help_str (o):
133         "Transform one option description (4-tuple ) into neatly formatted string"
134         sh = '  '       
135         if o[1]:
136                 sh = '-%s' % o[1]
137
138         sep = ' '
139         if o[1] and o[2]:
140                 sep = ','
141                 
142         long = ''
143         if o[2]:
144                 long= '--%s' % o[2]
145
146         arg = ''
147         if o[0]:
148                 if o[2]:
149                         arg = '='
150                 arg = arg + o[0]
151         return '  ' + sh + sep + long + arg
152
153
154 def options_help_str (opts):
155         "Convert a list of options into a neatly formatted string"
156         w = 0
157         strs =[]
158         helps = []
159
160         for o in opts:
161                 s = option_help_str (o)
162                 strs.append ((s, o[3]))
163                 if len (s) > w:
164                         w = len (s)
165
166         str = ''
167         for s in strs:
168                 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0])  + 3), s[1])
169         return str
170
171 def help():
172         sys.stdout.write("""Usage: lyvi [options] FILE\n
173 Generate .dvi with LaTeX for lilypond
174 Options:
175 """)
176         sys.stdout.write (options_help_str (option_definitions))
177         sys.stdout.write (r"""Warning all output is written in the CURRENT directory
178
179
180
181 Report bugs to bug-gnu-music@gnu.org.
182
183 Written by
184 Han-Wen Nienhuys <hanwen@cs.uu.nl>
185 """)
186
187         sys.exit (0)
188
189
190 def setup_temp ():
191         global temp_dir
192         temp_dir = 'ly2dvi.dir'
193         if not keep_temp_dir:
194                 temp_dir = tempfile.mktemp ('ly2dvi')
195                 
196         try:
197                 os.mkdir (temp_dir)
198         except OSError:
199                 pass
200                 
201
202         # try not to gen/search MF stuff in temp dir
203         fp = ''
204         try:
205                 fp = ':' + os.environ['TFMFONTS']
206         except KeyError:
207                 fp = '://:'
208
209                 
210         os.environ['TFMFONTS'] =  original_dir + fp
211
212         os.chdir (temp_dir)
213         progress ('Temp directory is `%s\'\n' % temp_dir) 
214
215         
216 def system (cmd, ignore_error = 0):
217         sys.stderr.write ("invoking `%s\'\n" % cmd)
218         st = os.system (cmd)
219         if st:
220                 msg =  ('Error command exited with value %d' % st)
221                 if ignore_error:
222                         sys.stderr.write (msg + ' (ignored)\n')
223                 else:
224                         error (msg)
225
226         return st
227
228 def cleanup_temp ():
229         if not keep_temp_dir:
230                 progress ('Cleaning up `%s\'' % temp_dir)
231                 system ('rm -rf %s' % temp_dir)
232         
233
234 def run_lilypond (files):
235         opts = ''
236         opts = opts + ' ' + string.join (map (lambda x : '-I ' + x, include_path))
237         opts = opts + ' ' + string.join (map (lambda x : '-H ' + x, fields))
238
239         if track_dependencies_p:
240                 opts = opts + " --dependencies "
241
242         fs = string.join (files)
243         
244         system ('lilypond  %s %s ' % (opts, fs))
245
246
247 def set_setting (dict, key, val):
248         try:
249                 val = string.atof (val)
250         except ValueError:
251                 pass
252
253         try:
254                 dict[key].append (val)
255         except KeyError:
256                 dict[key] = [val]
257         
258
259 def analyse_lilypond_output (filename, extra):
260         """Grep FILENAME for interesting stuff, and
261         put relevant info into EXTRA."""
262         filename = filename+'.tex'
263         progress ("Analyzing `%s'" % filename)
264         s = open (filename).read ()
265
266         # search only the first 10k
267         s = s[:10240]
268         for x in ('textheight', 'linewidth', 'papersizename', 'orientation'):
269                 m = re.search (r'\\def\\lilypondpaper%s{([^}]*)}'%x, s)
270                 if m:
271                         set_setting (extra, x, m.group (1))
272
273 def find_tex_files_for_base (base, extra):
274         headerfiles = {}
275         for f in layout_fields:
276                 if os.path.exists (base + '.' + f):
277                         headerfiles[f] = base+'.'+f
278
279         if os.path.exists (base  +'.dep'):
280                 dependency_files.append (base + '.dep')
281
282         for f in extra_fields:
283                 if os.path.exists (base + '.' + f):
284                         extra[f].append (open (base + '.' + f).read ())
285         
286         return (base  +'.tex',headerfiles)
287          
288
289 def find_tex_files (files, extra):
290         tfiles = []
291         for f in files:
292                 x = 0
293                 while 1:
294                         fname = os.path.basename (f)
295                         fname = os.path.splitext (fname)[0]
296                         if x:
297                                 fname = fname + '-%d' % x
298
299                         if os.path.exists (fname + '.tex'):
300                                 tfiles.append (find_tex_files_for_base (fname, extra))
301                                 analyse_lilypond_output (fname, extra)
302                         else:
303                                 break
304
305                         x = x +1 
306         return tfiles
307
308 def one_latex_definition (defn, first):
309         s = ''
310         for (k,v) in defn[1].items ():
311                 s = r"""\def\the%s{%s}""" % (k,open (v).read ())
312
313         if first:
314                 s = s + '\\makelilytitle\n'
315         else:
316                 s = s + '\\makelilypiecetitle\n'
317                 
318         s = s + '\\input %s' % defn[0]
319         return s
320
321
322 ly_paper_to_latexpaper =  {
323         'a4' : 'a4paper',
324         
325 }
326
327 def global_latex_definition (tfiles, extra):
328         """construct preamble from EXTRA,
329         dump lily output files after that, and return result.
330         """
331
332
333         s = ""
334         s = s + '% generation tag\n'
335
336         paper = ''
337
338         if extra['papersizename']:
339                 paper = '[%s]' % ly_paper_to_latexpaper[extra['papersizename'][0]]
340         s = s + '\\documentclass%s{article}\n' % paper
341
342         if extra['language']:
343                 s = s + r'\usepackage[%s]{babel}\n' % extra['language'][-1]
344
345
346         s = s + '\\usepackage{%s}\n' \
347                 % string.join (extra['latexpackages'], ',')
348         
349         s = s + string.join (extra['latexheaders'], ' ')
350
351         textheight = ''
352         if extra['textheight']:
353                 textheight = ',textheight=%fpt' % extra['textheight'][0]
354
355         orientation = 'portrait'
356         if extra['orientation']:
357                 orientation = extra['orientation'][0]
358  
359         s = s + '\geometry{width=%spt%s,headheight=2mm,headsep=0pt,footskip=2mm,%s}\n' % (extra['linewidth'][0], textheight, orientation)
360
361         s= s + r"""
362 \usepackage[latin1]{inputenc} 
363 \input{titledefs}
364 \makeatletter
365 \renewcommand{\@oddfoot}{\parbox{\textwidth}{\mbox{}\thefooter}}%%
366 """
367         if extra['pagenumber'] and  extra['pagenumber'][-1]:
368                 s = s + r"""
369                 \renewcommand{\@oddhead}{\parbox{\textwidth}%%
370                 {\mbox{}\small\theheader\hfill\textbf{\thepage}}}%%"""
371         else:
372                 s = s + '\\pagestyle{empty}'
373                 
374         s = s + '\\begin{document}'
375
376         first = 1
377         for t in tfiles:
378                 s = s + one_latex_definition (t, first)
379                 first = 0
380                 
381         s = s + '\\end{document}'
382
383         return s
384
385 def do_files (fs, extra):
386
387         """process the list of filenames in FS, using standard settings in EXTRA.
388         """
389         if not no_lily:
390                 run_lilypond (fs)
391
392         wfs = find_tex_files (fs, extra)
393         s = global_latex_definition (wfs, extra)
394
395         latex_file ='ly2dvi.out'
396         f = open (latex_file + '.tex', 'w')
397         f.write (s)
398         f.close ()
399
400         # todo: nonstopmode
401         system ('latex %s' % latex_file)
402         return latex_file + '.dvi'
403
404 def generate_postscript (dvi_name, extra):
405         """Run dvips on DVI_NAME, optionally doing -t landscape"""
406
407         opts = ''
408         if extra['papersizename']:
409                 opts = opts + ' -t %s' % extra['papersizename'][0]
410
411         if extra['orientation'] and extra['orientation'][0] == 'landscape':
412                 opts = opts + ' -t landscape'
413
414         ps_name = re.sub (r'\.dvi', r'.ps', dvi_name)
415         system ('dvips %s -o %s %s' % (opts, ps_name, dvi_name))
416
417         return ps_name
418                 
419
420
421 def generate_dependency_file (depfile, outname):
422         df = open (depfile, 'w')
423         df.write (outname + ':' )
424         
425         for d in dependency_files:
426                 s = open (d).read ()
427                 s = re.sub ('#[^\n]*\n', '', s)
428                 s = re.sub (r'\\\n', ' ', s)
429                 m = re.search ('.*:(.*)\n', s)
430
431                 # ugh. Different targets?
432                 if m:
433                         df.write ( m.group (1)  + ' ' )
434
435         df.write ('\n')
436         df.close ();
437
438 (sh, long) = getopt_args (__main__.option_definitions)
439 (options, files) = getopt.getopt(sys.argv[1:], sh, long)
440 for opt in options:     
441         o = opt[0]
442         a = opt[1]
443
444         if 0:
445                 pass
446         elif o == '--help' or o == '-h':
447                 help ()
448         elif o == '--include' or o == '-I':
449                 include_path.append (a)
450         elif o == '--postscript' or o == '-P':
451                 postscript_p = 1
452         elif o == '--keep' or o == '-k':
453                 keep_temp_dir = 1
454         elif o == '--no-lily':
455                 no_lily = 1
456         elif o == '--outdir':
457                 outdir = a
458         elif o == '--set' or o == '-s':
459                 ss = string.split (a, '=')
460                 set_setting (extra_init, ss[0], ss[1])
461         elif o == '--dependencies' or o == '-d':
462                 track_dependencies_p = 1
463         elif o == '--version' or o == '-v':
464                 identify ()
465                 
466                 
467 include_path = map (os.path.abspath, include_path)
468 files = map (os.path.abspath, files) 
469 outdir = os.path.abspath (outdir)
470
471 def strip_ly_suffix (f):
472         (p, e) =os.path.splitext (f)
473         if e == '.ly':
474                 e = ''
475         return p +e
476         
477 files = map (strip_ly_suffix, files)
478
479 if files:
480         setup_temp ()
481         extra = extra_init
482         
483         dvi_name = do_files (files, extra)
484
485         if postscript_p:
486                 ps_name = generate_postscript (dvi_name, extra)
487
488
489
490         base = os.path.basename (files[0])
491         dest = base
492         type = 'foobar'
493         srcname = 'foobar'
494         
495         if postscript_p:
496                 srcname = ps_name
497                 dest = dest + '.ps'
498                 type = 'PS'
499         else:
500                 srcname = dvi_name
501                 dest= dest + '.dvi'
502                 type = 'DVI'
503
504         dest = os.path.join (outdir, dest)
505         system ('cp \"%s\" \"%s\"' % (srcname, dest ))
506         system ('cp *.midi %s' % outdir, ignore_error = 1)
507
508         progress ("%s file left in `%s'\n" % (type, dest))
509
510         depfile = os.path.join (outdir, base + '.dep')
511
512         if track_dependencies_p:
513                 generate_dependency_file (depfile, dest)
514         progress ("Dependency file left in `%s'\n" % depfile)
515
516         cleanup_temp ()
517
518