]> git.donarmstrong.com Git - lilypond.git/blob - bin/mudela-book.py
partial: 1.1.1.jcn
[lilypond.git] / bin / mudela-book.py
1 #!@PYTHON@
2 # All non-english comments are NOT in swedish, they are norwegian!
3
4 #  TODO:  center option (??)
5 # * clean up handling of filename of inputfile
6 # * steal Props class from ly2dvi?
7 # * \onecolumn, \twocolumn
8 # * fontsize change with commandline parameters
9 # * the   verbatim  option should not be visible in the created latex file
10 # * what the h.. does castingalgorithm do/mean???
11 # * compile all regular expressions
12 # * the following fails because mudelabook doesn't care that the
13 #   last } after \end{mudela} finishes the marginpar:
14 #     \marginpar{
15 #     \begin{mudela}[fragment]
16 #        c d e f g
17 #     \end{mudela}}
18 # * Command line parameter that force all inline mudela to be verbatim, and
19 #   one that forces all to be printed
20 # log:
21
22 # 0.3:
23 #   rewrite in Python.
24 # 0.4:
25 #   much rewritten by new author. I think the work has been split more
26 #   logical between different classes.
27 #   
28
29 import os
30 import string
31 import re
32 import getopt
33 import sys
34 import regsub
35
36 outdir = 'out'
37 program_version = '0.4'
38 default_paper_size_global = 'a4'
39 default_mudela_fontsize = '16pt'
40 force_mudela_fontsize_b = 0
41
42 fontsize_i2a = {11:'eleven', 13:'thirteen', 16:'sixteen', 20:'twenty', 26:'twentysix'}
43 fontsize_pt2i = {'11pt':11, '13pt':13, '16pt':16, '20pt':20, '26pt':26}
44
45 def file_exist_b(name):
46     try: 
47         f = open(name)
48     except IOError:
49         return 0
50     f.close ()
51     return 1
52
53 def ps_dimention(fname):
54     fd = open(fname)
55     lines = fd.readlines()
56     reg = re.compile('%%BoundingBox: ([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)')
57     for line in lines:
58         s = reg.search(line)
59         if s:
60             break
61     return (int(s.groups()[2])-int(s.groups()[0]), 
62             int(s.groups()[3])-int(s.groups()[1]))
63
64
65 class CompileStatus:
66     pass
67
68 def file_mtime (name):
69     return os.stat (name)[8] #mod time
70 #
71 # executes os.system(command) if infile is newer than
72 # outfile or outfile don't exist
73 #
74 def compile (command, workingdir, infile, outfile):
75     indate = file_mtime (workingdir+infile)
76     try:
77         outdate = file_mtime (outfile)
78         recompile = indate > outdate
79
80     except os.error:
81         recompile = 1
82
83     if recompile:
84         sys.stderr.write ('invoking `%s\'\n' % command)
85         if workingdir == '':
86             status = os.system (command)
87         else:
88             status = os.system ('cd %s; %s' %(workingdir, command))
89         if status:
90             raise CompileStatus
91
92
93 class PaperDef:
94     __onecolumn_linewidth = {
95         'a4':{'10pt': 345, '11pt': 360, '12pt':390},
96         'a5':{'10pt': 276, '11pt': 276, '12pt':276},
97         'b5':{'10pt': 345, '11pt': 356, '12pt':356},
98         'letter':{'10pt': 345, '11pt': 360, '12pt':390},
99         'legal':{'10pt': 345, '11pt': 360, '12pt':390},
100         'executive':{'10pt': 345, '11pt': 360, '12pt':379}
101         }
102     __twocolumn_linewidth = {
103          'a4':{'10pt': 167, '11pt': 175, '12pt':190},
104         'a5':{'10pt': 133, '11pt': 133, '12pt':133},
105         'b5':{'10pt': 167, '11pt': 173, '12pt':173},
106         'letter':{'10pt': 167, '11pt': 175, '12pt':190},
107         'legal':{'10pt': 167, '11pt': 175, '12pt':190},
108         'executive':{'10pt': 167, '11pt': 175, '12pt':184}
109         }
110     __numcolumn = 1
111     __fontsize = '11pt'
112     #
113     # init
114     #
115     def __init__(self):
116         self.__papersize = default_paper_size_global
117     def set_papersize (self, p):
118         if not self.__onecolumn_linewidth.has_key(p):
119             print "warning:unsupported papersize", p, \
120                   "will use", default_paper_size_global
121             self.__papersize = default_paper_size_global
122         else:
123             self.__papersize = p
124     def set_fontsize(self, pt):
125         self.__fontsize = pt
126     def get_linewidth (self):
127         if self.__numcolumn == 1:
128             return self.__onecolumn_linewidth[self.__papersize][self.__fontsize]
129         else:
130             return self.__twocolumn_linewidth[self.__papersize][self.__fontsize]
131     def onecolumn (slef):
132         self.__numcolumn = 1
133     def twocolumn (self):
134         self.__numcolumn = 2
135
136
137 class Mudela_output:
138     def __init__ (self, basename):
139         self.basename = basename
140         # it's an integer!
141         self.feta_pt_size = fontsize_pt2i[default_mudela_fontsize]
142         self.temp_filename = "%s/%s" %(outdir, 'mudela-temp.ly')
143         self.file = open (self.temp_filename, 'w')
144         # 'tex' or 'eps'
145         self.graphic_type = 'tex'
146         self.fragment = 0
147     def write (self, line):
148         # match only if there is nothing but whitespace before \begin
149         if re.search('^\s*\\\\begin{mudela}', line):
150             self.scan_begin_statement(line)
151             self.write_red_tape()
152         else:
153             self.file.write (line)
154     def scan_begin_statement(self, line):
155         r  = begin_mudela_opts_re.search(line)
156         if r:
157             o = r.group()[1:][:-1]
158             optlist =  re.compile('[ ,]*').split(o)
159         else:
160             optlist = []
161         if 'floating' in optlist:
162             self.graphic_type = 'eps'
163         else:
164             self.graphic_type = 'tex'
165         if 'fragment' in optlist:
166             self.fragment = 1
167         else:
168             self.fragment = 0
169         for pt in fontsize_pt2i.keys():
170             if pt in optlist:
171                 self.feta_pt_size = fontsize_pt2i[pt]
172     def write_red_tape(self):
173         self.file.write ('\\include \"paper%d.ly\"\n' % self.feta_pt_size)
174         s = fontsize_i2a[self.feta_pt_size]
175         if self.fragment:
176             self.file.write("default_paper = \\paper {"
177                             + "\\paper_%s\n linewidth = -1.\\pt;" % s
178                             + "castingalgorithm = \Wordwrap; indent = 2.\cm; \n}")
179             self.file.write("\\score{\n\\notes") #HACK
180         else:
181             self.file.write ("default_paper = \\paper {"
182                              + "\\paper_%s\n linewidth = %i.\\pt;" % \
183                              (s, Paper.get_linewidth()) \
184                              + "castingalgorithm = \Wordwrap; indent = 2.\cm;\n}")
185     def close (self):
186         if self.fragment:
187             self.file.write ('\\paper { \\default_paper; } }\n')
188         self.file.close ()
189
190         inf = self.basename + '.ly'
191         outf = self.basename + '.tex'           
192         if not file_exist_b (inf):
193             status = 1
194         else:
195             status = os.system ('diff -q %s %s' % (self.temp_filename, inf))
196
197         if status:
198             os.rename (self.temp_filename, inf)
199         compile ('lilypond  -o %s %s;'%  (self.basename, inf), '', inf, outf)
200         if self.graphic_type == 'eps':
201             bname = self.basename[string.rfind(self.basename, '/')+1:]
202             tex_name = bname+'.tex'
203             dvi_name = bname+'.dvi'
204             eps_name = bname+'.eps'
205             compile ('tex %s' % tex_name, outdir, tex_name, dvi_name)
206             compile ('dvips -E -o %s %s' % (eps_name, dvi_name), outdir, dvi_name, eps_name)
207     def insert_me_string(self):
208         "Returns a string that can be used directly in latex."
209         if self.graphic_type == 'tex':
210             return '\\preMudelaExample\\input %s\n\postMudelaExample\n' % self.basename
211         elif self.graphic_type == 'eps':
212             ps_dim = ps_dimention('%s.eps' % self.basename)
213             return '\\parbox{%ipt}{\includegraphics{%s.eps}}' % (ps_dim[0], self.basename)
214         else:
215             print "Unsupported graphic type '%s'" % self.graphic_type
216             sys.exit(1)
217
218 class Tex_output:
219     def __init__ (self, name):
220         self.output_fn = '%s/%s' % (outdir, name)
221         self.file = open (self.output_fn , 'w')
222     def open_mudela (self, basename):
223         self.mudela_basename = basename
224     def open_verbatim (self):
225         self.file.write ('\\begin{verbatim}\n')
226     def close_verbatim (self):
227         self.file.write ('\\end{verbatim}\n')
228     def write (self, s):
229         self.file.write (s)
230
231 begin_mudela_re = re.compile ('^ *\\\\begin{mudela}')
232 begin_mudela_opts_re = re.compile('\[[^\]]*\]')
233 end_mudela_re = re.compile ('^ *\\\\end{mudela}')
234 section_re = re.compile ('\\\\section')
235 chapter_re = re.compile ('\\\\chapter')
236 input_re = re.compile ('^\\\\input{([^}]*)')
237 include_re = re.compile ('^\\\\include{([^}]*)')
238 begin_document_re = re.compile ('^ *\\\\begin{document}')
239 documentclass_re = re.compile('\\\\documentclass')
240 twocolumn_re = re.compile('\\\\twocolumn')
241 onecolumn_re = re.compile('\\\\onecolumn')
242
243 class Tex_input:
244     def __init__ (self,name):
245         # HACK
246         if (name[-4:] != '.tex') and (name[-4:] != '.doc'):
247             name = name + '.tex'
248         self.filename = name
249         self.infile = open (name)
250                 
251     def get_lines (self):
252         lines = self.infile.readlines ()
253         (retlines, retdeps) = ([],[self.filename])
254         for line in lines:
255             r = input_re.search (line)
256             ri = include_re.search (line)
257             if r:
258                 t = Tex_input (r.groups()[0])
259                 ls =t.get_lines ()
260                 retlines = retlines + ls[0]
261                 retdeps = retdeps + ls[1]
262             elif ri:
263                 t = Tex_input (ri.groups()[0])
264                 ls =t.get_lines ()
265                 ls[0].insert(0, '\\newpage')
266                 ls[0].append('\\newpage')
267                 retlines = retlines + ls[0]
268                 retdeps = retdeps + ls[1]                
269             else:
270                 retlines.append (line)
271         return (retlines, retdeps)
272
273
274 class Main_tex_input(Tex_input):
275     def __init__ (self, name, outname):
276                 
277         Tex_input.__init__ (self, name) # ugh
278
279         self.outname = outname
280         self.chapter = 0
281         self.section = 0
282         self.fine_count =0
283         self.mudtex = Tex_output (self.outname)
284         self.mudela = None
285         self.deps = []
286         self.verbatim = 0
287         # set to 'mudela' when we are processing mudela code,
288         # both verbatim and graphic-to-be
289         self.mode = 'latex'
290     def set_sections (self, l):
291         if section_re.search (l):
292             self.section = self.section + 1
293         if chapter_re.search (l):
294             self.section = 0
295             self.chapter = self.chapter + 1
296
297     def gen_basename (self):
298         return '%s/%s-%d.%d.%d' % (outdir, self.outname,self.chapter,self.section,self.fine_count)
299
300     def extract_papersize_from_documentclass(self, line):
301         pre = re.search('\\\\documentclass[\[, ]*(\w*)paper[\w ,]*\]\{\w*\}', line)
302         if not pre:
303             return default_paper_size_global
304         return pre.groups()[0]
305     def extract_fontsize_from_documentclass(self, line):
306         if re.search('\\\\documentclass\[[^\]]*\]\{[^\}]*\}', line):
307             r = re.search('[ ,\[]*([0-9]*pt)', line)
308             if r:
309                 return r.groups()[0]
310         return '10pt'
311     def do_it(self):
312         (lines, self.deps) = self.get_lines ()
313         for line in lines:
314             if documentclass_re.search (line):
315                 Paper.set_papersize (self.extract_papersize_from_documentclass (line) )
316                 Paper.set_fontsize (self.extract_fontsize_from_documentclass (line) )
317             elif twocolumn_re.search (line):
318                 Paper.twocolumn ()
319             elif onecolumn_re.search (line):
320                 Paper.onecolumn ()
321             elif begin_document_re.search (line):
322                 self.mudtex.write ('\\def\\preMudelaExample{}\n')
323                 self.mudtex.write ('\\def\\postMudelaExample{}\n')
324             elif begin_mudela_re.search (line):
325                 if __debug__:
326                     if self.mode == 'mudela':
327                         raise AssertionError
328                 self.mode = 'mudela'
329                 r  = begin_mudela_opts_re.search (line)
330                 if r:
331                     o = r.group()[1:][:-1]
332                     optlist =  re.compile('[ ,]*').split(o)
333                 else:
334                     optlist = []
335                 if 'verbatim' in optlist:
336                     self.verbatim = 1
337                     self.mudtex.open_verbatim ()
338                 else:
339                     self.verbatim = 0
340                     self.mudela = Mudela_output (self.gen_basename ())
341
342             elif end_mudela_re.search (line):
343                 if __debug__:
344                     if self.mode != 'mudela':
345                         raise AssertionError
346                 if self.mudela:
347                     self.mudela.close ()
348                     self.mudtex.write (self.mudela.insert_me_string())
349                     del self.mudela
350                     self.mudela = None
351                     self.fine_count = self.fine_count + 1
352                 else:                    
353                     self.mudtex.write (line)
354                     self.mudtex.close_verbatim ()
355                 self.mode = 'latex'
356                 continue
357
358             if self.mode == 'mudela' and not self.verbatim:
359                 self.mudela.write (line)
360             else:
361                 self.mudtex.write (line)
362                 self.set_sections(line)
363         del self.mudtex
364                 
365
366 def help():
367     sys.stdout.write("Usage: mudela-book [options] FILE\n"
368                  + "Generate hybrid LaTeX input from Latex + mudela"
369                  + "Options:\n"
370                  + "  -h, --help             print this help\n"
371                  + "  -d, --outdir=DIR       directory to put generated files\n" 
372                  + "  -o, --outname=FILE     prefix for filenames\n"
373                  + "  --mudela-fontsize=??pt default fontsize when no parameter for \\begin{mudela}\n"
374                  + "  --force-mudela-fontsize=??pt force fontsize for all inline mudela\n"
375                      )
376     sys.exit (0)
377
378
379 def write_deps (fn, out,  deps):
380         out_fn = outdir + '/' + fn
381         print '\`writing \`%s\'\n\'' % out_fn
382         
383         f = open (out_fn, 'w')
384         f.write ('%s: %s\n'% (outdir + '/' + out + '.dvi',
385                               reduce (lambda x,y: x + ' '+ y, deps)))
386         f.close ()
387
388 def identify():
389     sys.stderr.write('*** Lokal versjon av mudela-book ***\n')
390     sys.stderr.write ('This is %s version %s\n' % ('mudela-book', program_version))
391
392 def main():
393     global default_mudela_fontsize, outdir
394     outname = ''
395     try:
396         (options, files) = getopt.getopt(
397             sys.argv[1:], 'hd:o:', ['outdir=', 'outname=', 'mudela-fontsize=',
398                                     'force-mudela-fontsize=', 'help', 'dependencies'])
399     except getopt.error, msg:
400         print "error:", msg
401         sys.exit(1)
402         
403     do_deps = 0
404     for opt in options:
405         o = opt[0]
406         a = opt[1]
407         if o == '--outname' or o == '-o':
408             outname = a
409         if o == '--outdir' or o == '-d':
410             outdir = a
411         if o == '--help' or o == '-h':
412             help ()
413         if o == '--dependencies':
414             do_deps = 1
415         if o == '--mudela-fontsize':
416             default_mudela_fontsize = a
417         if o == '--force-mudela-fontsize':
418             default_mudela_fontsize = a
419             force_mudela_fontsize_b = 1
420
421     if outdir[-1:] != '/':
422         outdir = outdir + '/'
423
424     if not file_exist_b(outdir):
425         os.system('mkdir %s' % outdir)
426
427     if not fontsize_pt2i.has_key(default_mudela_fontsize):
428         print "warning: fontsize %s is not supported using 16pt" % default_mudela_fontsize
429         default_mudela_fontsize = '16pt'
430     
431     for f in files:
432         my_outname = outname
433         if not my_outname:
434             my_outname = regsub.sub ('\\(.*\\)\\.doc', '\\1', f)
435
436         my_depname = my_outname + '.dep'
437         
438         inp = Main_tex_input (f, my_outname)
439         inp.do_it ()
440
441     if do_deps:
442                 write_deps (my_depname, my_outname, inp.deps)
443
444
445
446 identify()
447 Paper = PaperDef()
448 main()