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