3 # Run lilypond, latex, dvips.
5 # This is the third incarnation of ly2dvi.
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)
14 # TODO: should allow to set a central pk cache directory from the command line.
15 # TODO: should allow to switch off pk cache.
19 # Note: gettext work best if we use ' for docstrings and "
20 # for gettextable strings.
21 # --> DO NOT USE """ for docstrings.
26 * figure out which set of command line options should make ly2dvi:
29 na: create latex only?
30 na: create tex and latex
31 default: create dvi only
32 na: create tex, latex and dvi
38 for foo.ly, rename ly2dvi.dir to out-ly2dvi, foo.ly2dvi, foo.dir ?
40 * move versatile taglines,
43 beginfooter=\mutopiaPD
44 endfooter=\tagline -> 'lily was here <version>'
47 lilytagline (->lily was here), usertagline, copyright etc.
49 * head/header tagline/endfooter
51 * dvi from lilypond .tex output? This is hairy, because we create dvi
52 from lilypond .tex *and* header output.
54 * multiple \score blocks?
75 ################################################################
76 # lilylib.py -- options and stuff
78 # source file of the GNU LilyPond music typesetter
82 gettext
.bindtextdomain ('lilypond', localedir
)
83 gettext
.textdomain ('lilypond')
89 program_version
= '@TOPLEVEL_VERSION@'
90 if program_version
== '@' + 'TOPLEVEL_VERSION' + '@':
91 program_version
= '1.5.17'
94 sys
.stdout
.write ('%s (GNU LilyPond) %s\n' % (program_name
, program_version
))
98 sys
.stdout
.write ('\n')
99 sys
.stdout
.write (_ ('Copyright (c) %s by' % ' 2001--2002'))
100 sys
.stdout
.write ('\n')
101 sys
.stdout
.write (' Han-Wen Nienhuys')
102 sys
.stdout
.write (' Jan Nieuwenhuizen')
103 sys
.stdout
.write ('\n')
104 sys
.stdout
.write (_ (r
'''
105 Distributed under terms of the GNU General Public License. It comes with
107 sys
.stdout
.write ('\n')
110 errorport
.write (s
+ '\n')
113 progress (_ ("warning: ") + s
)
115 def user_error (s
, e
=1):
116 errorport
.write (program_name
+ ":" + _ ("error: ") + s
+ '\n')
122 '''Report the error S. Exit by raising an exception. Please
123 do not abuse by trying to catch this error. If you do not want
124 a stack trace, write to the output directly.
132 progress (_ ("error: ") + s
)
133 raise _ ("Exiting ... ")
135 def getopt_args (opts
):
136 '''Construct arguments (LONG, SHORT) for getopt from list of options.'''
151 def option_help_str (o
):
152 '''Transform one option description (4-tuple ) into neatly formatted string'''
170 return ' ' + sh
+ sep
+ long + arg
173 def options_help_str (opts
):
174 '''Convert a list of options into a neatly formatted string'''
180 s
= option_help_str (o
)
181 strs
.append ((s
, o
[3]))
187 str = str + '%s%s%s\n' % (s
[0], ' ' * (w
- len(s
[0]) + 3), s
[1])
191 ls
= [(_ ("Usage: %s [OPTION]... FILE") % program_name
),
197 (options_help_str (option_definitions
)),
199 (_ ("Report bugs to %s") % 'bug-lilypond@gnu.org'),
201 map (sys
.stdout
.write
, ls
)
205 Create a temporary directory, and return its name.
208 if not keep_temp_dir_p
:
209 temp_dir
= tempfile
.mktemp (program_name
)
211 os
.mkdir (temp_dir
, 0777)
218 def system (cmd
, ignore_error
= 0):
219 """Run CMD. If IGNORE_ERROR is set, don't complain when CMD returns non zero.
227 progress (_ ("Invoking `%s\'") % cmd
)
230 name
= re
.match ('[ \t]*([^ \t]*)', cmd
).group (1)
231 msg
= name
+ ': ' + _ ("command exited with value %d") % st
233 warning (msg
+ ' ' + _ ("(ignored)") + ' ')
241 if not keep_temp_dir_p
:
243 progress (_ ("Cleaning %s...") % temp_dir
)
244 shutil
.rmtree (temp_dir
)
247 def strip_extension (f
, ext
):
248 (p
, e
) = os
.path
.splitext (f
)
253 ################################################################
261 # if set, LILYPONDPREFIX must take prevalence
262 # if datadir is not set, we're doing a build and LILYPONDPREFIX
263 datadir
= '@datadir@'
266 if os
.environ
.has_key ('LILYPONDPREFIX') :
267 datadir
= os
.environ
['LILYPONDPREFIX']
269 datadir
= '@datadir@'
272 while datadir
[-1] == os
.sep
:
273 datadir
= datadir
[:-1]
275 program_name
= 'ly2dvi'
277 original_dir
= os
.getcwd ()
278 temp_dir
= os
.path
.join (original_dir
, '%s.dir' % program_name
)
279 errorport
= sys
.stderr
285 gettext
.bindtextdomain ('lilypond', '@localedir@')
286 gettext
.textdomain ('lilypond')
292 # Attempt to fix problems with limited stack size set by Python!
293 # Sets unlimited stack size. Note that the resource module only
294 # is available on UNIX.
297 resource
.setrlimit (resource
.RLIMIT_STACK
, (-1, -1))
301 help_summary
= _ ("Generate .dvi with LaTeX for LilyPond")
303 option_definitions
= [
304 ('', 'd', 'dependencies', _ ("write Makefile dependencies for every input file")),
305 ('', 'h', 'help', _ ("this help")),
306 (_ ("DIR"), 'I', 'include', _ ("add DIR to LilyPond's search path")),
307 ('', 'k', 'keep', _ ("keep all output, and name the directory %s.dir") % program_name
),
308 ('', '', 'no-lily', _ ("don't run LilyPond")),
309 ('', 'm', 'no-paper', _ ("produce MIDI output only")),
310 (_ ("FILE"), 'o', 'output', _ ("write ouput to FILE")),
311 (_ ("FILE"), 'f', 'find-pfa', _ ("find pfa fonts used in FILE")),
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")),
320 layout_fields
= ['dedication', 'title', 'subtitle', 'subsubtitle',
321 'footer', 'head', 'composer', 'arranger', 'instrument',
322 'opus', 'piece', 'metre', 'meter', 'poet', 'texttranslator']
325 # init to empty; values here take precedence over values in the file
327 ## TODO: change name.
331 'latexpackages' : ['geometry'],
340 extra_fields
= extra_init
.keys ()
341 fields
= layout_fields
+ extra_fields
348 PK_PATTERN
='feta.*\.[0-9]+pk'
358 track_dependencies_p
= 0
359 dependency_files
= []
363 # Try to cater for bad installations of LilyPond, that have
364 # broken TeX setup. Just hope this doesn't hurt good TeX
365 # setups. Maybe we should check if kpsewhich can find
366 # feta16.{afm,mf,tex,tfm}, and only set env upon failure.
369 'MFINPUTS' : datadir
+ '/mf' + ':',
370 'TEXINPUTS': datadir
+ '/tex:' + datadir
+ '/ps:' + '.:'
372 'TFMFONTS' : datadir
+ '/tfm' + ':',
373 'GS_FONTPATH' : datadir
+ '/afm:' + datadir
+ '/pfa',
374 'GS_LIB' : datadir
+ '/ps',
377 # tex needs lots of memory, more than it gets by default on Debian
378 non_path_environment
= {
379 'extra_mem_top' : '1000000',
380 'extra_mem_bottom' : '1000000',
381 'pool_size' : '250000',
384 def setup_environment ():
385 for key
in environment
.keys ():
386 val
= environment
[key
]
387 if os
.environ
.has_key (key
):
388 val
= os
.environ
[key
] + os
.pathsep
+ val
389 os
.environ
[key
] = val
391 for key
in non_path_environment
.keys ():
392 val
= non_path_environment
[key
]
393 os
.environ
[key
] = val
396 def set_setting (dict, key
, val
):
398 val
= string
.atof (val
)
400 #warning (_ ("invalid value: %s") % `val`)
404 dict[key
].append (val
)
406 warning (_ ("no such setting: %s") % `key`
)
410 def print_environment ():
411 for (k
,v
) in os
.environ
.items ():
412 sys
.stderr
.write ("%s=\"%s\"\n" % (k
,v
))
414 def run_lilypond (files
, outbase
, dep_prefix
):
416 # opts = opts + '--output=%s.tex' % outbase
417 opts
= opts
+ ' ' + string
.join (map (lambda x
: '-I ' + x
,
420 opts
= opts
+ ' ' + string
.join (map (lambda x
: '-H ' + x
,
423 opts
= opts
+ ' --no-paper'
425 if track_dependencies_p
:
426 opts
= opts
+ " --dependencies"
428 opts
= opts
+ ' --dep-prefix=%s' % dep_prefix
430 fs
= string
.join (files
)
433 # cmd = cmd + ' 1> /dev/null 2> /dev/null'
434 progress ( _("Running %s...") % 'LilyPond')
436 opts
= opts
+ ' --verbose'
438 # for better debugging!
441 system ('lilypond %s %s ' % (opts
, fs
))
443 def analyse_lilypond_output (filename
, extra
):
446 '''Grep FILENAME for interesting stuff, and
447 put relevant info into EXTRA.'''
448 filename
= filename
+'.tex'
449 progress (_ ("Analyzing %s...") % filename
)
450 s
= open (filename
).read ()
452 # search only the first 10k
454 for x
in extra_fields
:
455 m
= re
.search (r
'\\def\\lilypondpaper%s{([^}]*)}'%x, s
)
457 set_setting (extra
, x
, m
.group (1))
459 def find_tex_files_for_base (base
, extra
):
462 Find the \header fields dumped from BASE.
466 for f
in layout_fields
:
467 if os
.path
.exists (base
+ '.' + f
):
468 headerfiles
[f
] = base
+'.'+f
470 if os
.path
.exists (base
+'.dep'):
471 dependency_files
.append (base
+ '.dep')
473 for f
in extra_fields
:
474 if os
.path
.exists (base
+ '.' + f
):
475 extra
[f
].append (open (base
+ '.' + f
).read ())
477 return (base
+'.tex',headerfiles
)
480 def find_tex_files (files
, extra
):
482 Find all .tex files whose prefixes start with some name in FILES.
491 fname
= os
.path
.basename (f
)
492 fname
= strip_extension (fname
, '.ly')
494 fname
= fname
+ '-%d' % x
496 if os
.path
.exists (fname
+ '.tex'):
497 tfiles
.append (find_tex_files_for_base (fname
, extra
))
498 analyse_lilypond_output (fname
, extra
)
504 fstr
= string
.join (files
, ', ')
505 warning (_ ("no lilypond output found for %s") % fstr
)
508 def one_latex_definition (defn
, first
):
510 for (k
,v
) in defn
[1].items ():
511 val
= open (v
).read ()
512 if (string
.strip (val
)):
513 s
= s
+ r
'''\def\lilypond%s{%s}''' % (k
, val
)
515 s
= s
+ r
'''\let\lilypond%s\relax''' % k
519 s
= s
+ '\\def\\mustmakelilypondtitle{}\n'
521 s
= s
+ '\\def\\mustmakelilypondpiecetitle{}\n'
523 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.
527 ly_paper_to_latexpaper
= {
529 'letter' : 'letterpaper',
532 def global_latex_definition (tfiles
, extra
):
534 '''construct preamble from EXTRA, dump Latex stuff for each
535 lily output file in TFILES after that, and return the Latex file constructed. '''
539 s
= s
+ '% generation tag\n'
543 if extra
['papersize']:
545 options
= '%s' % ly_paper_to_latexpaper
[extra
['papersize'][0]]
547 warning (_ ("invalid value: %s") % `extra
['papersize'][0]`
)
550 if extra
['latexoptions']:
551 options
= options
+ ',' + extra
['latexoptions'][-1]
553 s
= s
+ '\\documentclass[%s]{article}\n' % options
555 if extra
['language']:
556 s
= s
+ r
'\usepackage[%s]{babel}\n' % extra
['language'][-1]
559 s
= s
+ '\\usepackage{%s}\n' \
560 % string
.join (extra
['latexpackages'], ',')
562 if extra
['latexheaders']:
563 s
= s
+ '\\include{%s}\n' \
564 % string
.join (extra
['latexheaders'], '}\n\\include{')
567 if extra
['textheight']:
568 textheight
= ',textheight=%fpt' % extra
['textheight'][0]
570 orientation
= 'portrait'
571 if extra
['orientation']:
572 orientation
= extra
['orientation'][0]
574 # set sane geometry width (a4-width) for linewidth = -1.
575 maxlw
= max (extra
['linewidth'] + [-1])
577 # who the hell is 597 ?
581 s
= s
+ '\geometry{width=%spt%s,headheight=2mm,footskip=2mm,%s}\n' % (linewidth
, textheight
, orientation
)
583 if extra
['latexoptions']:
584 s
= s
+ '\geometry{twosideshift=4mm}\n'
587 \usepackage[latin1]{inputenc}
591 if extra
['pagenumber'] and extra
['pagenumber'][-1] and extra
['pagenumber'][-1] != 'no':
592 s
= s
+ '\setcounter{page}{%s}\n' % (extra
['pagenumber'][-1])
593 s
= s
+ '\\pagestyle{plain}\n'
595 s
= s
+ '\\pagestyle{empty}\n'
597 s
= s
+ '\\begin{document}\n'
598 s
= s
+ '\\thispagestyle{firstpage}\n'
602 s
= s
+ one_latex_definition (t
, first
)
606 s
= s
+ '\\thispagestyle{lastpage}\n'
607 s
= s
+ '\\end{document}'
611 def run_latex (files
, outbase
, extra
):
613 """Construct latex file, for FILES and EXTRA, dump it into
614 OUTBASE.latex. Run LaTeX on it.
620 latex_fn
= outbase
+ '.latex'
622 wfs
= find_tex_files (files
, extra
)
623 s
= global_latex_definition (wfs
, extra
)
625 f
= open (latex_fn
, 'w')
629 cmd
= 'latex \\\\nonstopmode \\\\input %s' % latex_fn
632 progress ( _("Running %s...") % 'LaTeX')
633 cmd
= cmd
+ ' 1> /dev/null 2> /dev/null'
637 def run_dvips (outbase
, extra
):
640 """Run dvips using the correct options taken from EXTRA,
641 leaving a PS file in OUTBASE.ps
648 if extra
['papersize']:
649 opts
= opts
+ ' -t%s' % extra
['papersize'][0]
651 if extra
['orientation'] and extra
['orientation'][0] == 'landscape':
652 opts
= opts
+ ' -tlandscape'
654 cmd
= 'dvips %s -o%s %s' % (opts
, outbase
+ '.ps', outbase
+ '.dvi')
657 progress ( _("Running %s...") % 'dvips')
658 cmd
= cmd
+ ' 1> /dev/null 2> /dev/null'
662 def generate_dependency_file (depfile
, outname
):
663 df
= open (depfile
, 'w')
664 df
.write (outname
+ ':' )
666 for d
in dependency_files
:
668 s
= re
.sub ('#[^\n]*\n', '', s
)
669 s
= re
.sub (r
'\\\n', ' ', s
)
670 m
= re
.search ('.*:(.*)\n', s
)
672 # ugh. Different targets?
674 df
.write ( m
.group (1) + ' ' )
679 def find_file_in_path (path
, name
):
680 for d
in string
.split (path
, os
.pathsep
):
681 if name
in os
.listdir (d
):
682 return os
.path
.join (d
, name
)
684 # Added as functionality to ly2dvi, because ly2dvi may well need to do this
687 def find_pfa_fonts (name
):
688 s
= open (name
).read ()
689 if s
[:len (PS
)] != PS
:
691 errorport
.write (_( "error: ") + _ ("not a PostScript file: `%s\'" % name
))
692 errorport
.write ('\n')
695 m
= re
.match ('.*?/(feta[-a-z0-9]+) +findfont', s
[here
:], re
.DOTALL
)
699 pfa
.append (m
.group (1))
700 m
= re
.match ('.*?/(feta[-a-z0-9]+) +findfont', s
[here
:], re
.DOTALL
)
704 (sh
, long) = getopt_args (option_definitions
)
706 (options
, files
) = getopt
.getopt(sys
.argv
[1:], sh
, long)
707 except getopt
.error
, s
:
708 errorport
.write ('\n')
709 errorport
.write (_ ("error: ") + _ ("getopt says: `%s\'" % s
))
710 errorport
.write ('\n')
711 errorport
.write ('\n')
721 elif o
== '--help' or o
== '-h':
724 elif o
== '--find-pfa' or o
== '-f':
725 fonts
= map (lambda x
: x
+ '.pfa', find_pfa_fonts (a
))
726 files
= map (lambda x
:
727 find_file_in_path (os
.environ
['GS_FONTPATH'], x
),
729 print string
.join (files
, ' ')
731 elif o
== '--include' or o
== '-I':
732 include_path
.append (a
)
733 elif o
== '--postscript' or o
== '-P':
735 elif o
== '--keep' or o
== '-k':
737 elif o
== '--no-lily':
739 elif o
== '--no-paper' or o
== '-m':
743 elif o
== '--output' or o
== '-o':
745 elif o
== '--set' or o
== '-s':
746 ss
= string
.split (a
, '=')
747 set_setting (extra_init
, ss
[0], ss
[1])
748 elif o
== '--dependencies' or o
== '-d':
749 track_dependencies_p
= 1
750 elif o
== '--verbose' or o
== '-V':
752 elif o
== '--version' or o
== '-v':
755 elif o
== '--warranty' or o
== '-w':
756 status
= system ('lilypond -w', ignore_error
= 1)
763 def cp_to_dir (pattern
, dir):
764 "Copy files matching re PATTERN from cwd to DIR"
765 # Duh. Python style portable: cp *.EXT OUTDIR
766 # system ('cp *.%s %s' % (ext, outdir), 1)
767 files
= filter (lambda x
, p
=pattern
: re
.match (p
, x
), os
.listdir ('.'))
768 map (lambda x
, d
=dir: shutil
.copy2 (x
, os
.path
.join (d
, x
)), files
)
770 # Python < 1.5.2 compatibility
772 # On most platforms, this is equivalent to
773 #`normpath(join(os.getcwd()), PATH)'. *Added in Python version 1.5.2*
774 if os
.path
.__dict
__.has_key ('abspath'):
775 abspath
= os
.path
.abspath
778 return os
.path
.normpath (os
.path
.join (os
.getcwd (), path
))
780 if os
.__dict
__.has_key ('makedirs'):
781 makedirs
= os
.makedirs
783 def makedirs (dir, mode
=0777):
784 system ('mkdir -p %s' % dir)
786 def mkdir_p (dir, mode
=0777):
787 if not os
.path
.isdir (dir):
790 include_path
= map (abspath
, include_path
)
792 original_output
= output_name
795 if files
and files
[0] != '-':
797 # Ugh, maybe make a setup () function
798 files
= map (lambda x
: strip_extension (x
, '.ly'), files
)
800 # hmmm. Wish I'd 've written comments when I wrote this.
801 # now it looks complicated.
803 (outdir
, outbase
) = ('','')
805 outbase
= os
.path
.basename (files
[0])
806 outdir
= abspath('.')
807 elif output_name
[-1] == os
.sep
:
808 outdir
= abspath (output_name
)
809 outbase
= os
.path
.basename (files
[0])
811 (outdir
, outbase
) = os
.path
.split (abspath (output_name
))
813 for i
in ('.dvi', '.latex', '.ly', '.ps', '.tex'):
814 output_name
= strip_extension (output_name
, i
)
815 outbase
= strip_extension (outbase
, i
)
816 files
= map (abspath
, files
)
818 for i
in files
[:] + [output_name
]:
819 if string
.find (i
, ' ') >= 0:
820 user_error (_ ("filename should not contain spaces: `%s'") % i
)
822 if os
.path
.dirname (output_name
) != '.':
823 dep_prefix
= os
.path
.dirname (output_name
)
827 reldir
= os
.path
.dirname (output_name
)
828 if outdir
!= '.' and (track_dependencies_p
or targets
.keys ()):
829 mkdir_p (outdir
, 0777)
832 tmpdir
= setup_temp ()
835 cp_to_dir (PK_PATTERN
, tmpdir
)
837 # to be sure, add tmpdir *in front* of inclusion path.
838 #os.environ['TEXINPUTS'] = tmpdir + ':' + os.environ['TEXINPUTS']
843 run_lilypond (files
, outbase
, dep_prefix
)
845 # TODO: friendly message about LilyPond setup/failing?
847 # TODO: lilypond should fail with different
849 # - guile setup/startup failure
850 # - font setup failure
851 # - init.ly setup failure
852 # - parse error in .ly
853 # - unexpected: assert/core dump
855 traceback
.print_exc ()
857 if targets
.has_key ('DVI') or targets
.has_key ('PS'):
859 run_latex (files
, outbase
, extra_init
)
860 # unless: add --tex, or --latex?
864 # TODO: friendly message about TeX/LaTeX setup,
865 # trying to run tex/latex by hand
866 if targets
.has_key ('DVI'):
868 if targets
.has_key ('PS'):
870 traceback
.print_exc ()
872 if targets
.has_key ('PS'):
874 run_dvips (outbase
, extra_init
)
876 if targets
.has_key ('PS'):
878 traceback
.print_exc ()
880 # add DEP to targets?
881 if track_dependencies_p
:
882 depfile
= os
.path
.join (outdir
, outbase
+ '.dep')
883 generate_dependency_file (depfile
, depfile
)
884 if os
.path
.isfile (depfile
):
885 progress (_ ("dependencies output to `%s'...") % depfile
)
887 # Hmm, if this were a function, we could call it the except: clauses
888 for i
in targets
.keys ():
889 ext
= string
.lower (i
)
890 cp_to_dir ('.*\.%s$' % ext
, outdir
)
891 outname
= outbase
+ '.' + string
.lower (i
)
892 abs = os
.path
.join (outdir
, outname
)
894 outname
= os
.path
.join (reldir
, outname
)
895 if os
.path
.isfile (abs):
896 progress (_ ("%s output to `%s'...") % (i
, outname
))
898 warning (_ ("can't find file: `%s'") % outname
)
901 cp_to_dir (PK_PATTERN
, outdir
)
903 os
.chdir (original_dir
)
907 # FIXME: read from stdin when files[0] = '-'
909 user_error (_ ("no files specified on command line."), 2)