source: src/mpy.py @ 3

Last change on this file since 3 was 2, checked in by nipo, 15 years ago

Allow to change instance name, and generate an output file list

  • Property svn:executable set to *
File size: 20.7 KB
Line 
1#!/usr/bin/env python
2# -------------------------------------------------------------------------------------------------
3# Macroprocessor vith PYthon script inclusion
4#
5# Written by Franck Wajsburt at LIP6 2009
6#
7# This program is free software; you can redistribute it and/or
8# modify it under the terms of the GNU General Public License as
9# published by the Free Software Foundation; either version 2 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15# General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20# -------------------------------------------------------------------------------------------------
21# requires doctutils package to run rst2html
22#
23# TODO exectution step by step
24# TODO ecrire un manuel
25# TODO faire des tests unitaires
26# -------------------------------------------------------------------------------------------------
27_date = '2009-12-30 16:00'
28_version = '1.07'
29_authors = 'Franck Wajsburt'
30_organization = '(UPMC/LIP6/SOC)'
31__doc__ = '''\
32===========================================================================
33Macroprocessor vith PYthon script inclusion
34 - Authors:      '''+_authors+'''
35 - Date:         '''+_date+'''
36 - Version:      '''+_version+'''
37 - Organisation: '''+_organization+'''
38==========================================================================='''
39
40import os, sys, pdb
41from optparse import OptionParser
42from datetime import datetime
43
44# -------------------------------------------------------------------------------------------------
45# os and log utilities
46# -------------------------------------------------------------------------------------------------
47
48def _mpyhash(s):
49   return "%08x" % (hash(s) & 0xffffffff)
50
51def _exit(error):
52   if (error <> ''):
53      print >> sys.stderr, "\n*** mpy:", error
54   print >> sys.stderr, "*** preprocessing aborted\n"
55   exit(1)
56
57def _mpyfindfile(fname, mpyidir):
58   "try to find fname in any directory (or subdir) of mpyidir list"
59   "if fname is 'stdin' then just returns 'stdin' else returns real filename"
60   "if fname does not exist then returns ''"
61   if fname == 'stdin':
62      return fname
63   else:
64      for path in mpyidir:
65         for root, dirs, files  in os.walk(path):
66            for f in files:
67               if f == fname: # fname found
68                  return os.path.join(root,f)
69      return ''
70
71def _mpyopen(fname = '', mpyidir = [], realfname = ''):
72   "two behaviors:"
73   " 1. try to open realfname but if realfname is '' then 2."
74   " 2. try to open fname in any directory OR SUBDIR of mpyidir list1"
75   "returns tuple (string_with_file_content, real_file_name)"
76   "if fname is 'stdin' then open sys.stdin file"
77   if realfname == '':
78      realfname = _mpyfindfile(fname,mpyidir)
79   if realfname == '':
80      _exit("unable to find file %s" % fname)
81   if realfname == 'stdin':
82      return sys.stdin, fname
83   else:
84      try: mpyin = open(realfname,'r')
85      except IOError, e: _exit(e) # but cannot be open
86      return mpyin, realfname
87
88def _mpytree(mess):
89   print >> mpyglobals['mpytree'], mess
90   mpyglobals['mpytree'].flush()
91
92def _mpylog(mess):
93   global mpyglobals
94   print >> mpyglobals['mpylog'], mess
95   mpyglobals['mpylog'].flush()
96   if mpyglobals['mpyverbose']:
97      print >> sys.stderr, mess
98
99def _dictlog(dic, exclude=[]):
100   for key,val in dic.items():
101      for exc in exclude:
102         if key[0:len(exc)] == exc: 
103            break
104      else: 
105         _mpylog(' :%s: %s' % (key, str(val)))
106
107# -------------------------------------------------------------------------------------------------
108# macro expander
109# -------------------------------------------------------------------------------------------------
110
111def _mpy( mpyins  = []    # ['infile1',  ...] 
112        , mpytxt  = ''    # 'python code'
113        , mpyout  = ''    # 'outfile'
114        , mpydict = {} ): # global dictionary
115
116   # to allow _mpy call in inclusion
117   exec 'from mpy import mpyglobals, mpygen, mpyexp' in mpydict
118
119   # get arguments from the global dictionary
120   global mpyglobals
121   mpylog     = mpyglobals['mpylog']     # log file
122   mpylist    = mpyglobals['mpylist']    # file list log file
123   mpydebug   = mpyglobals['mpydebug']   # True|False
124   mpyverbose = mpyglobals['mpyverbose'] # True|False
125   mpywdir    = mpyglobals['mpywdir']    # working directory
126   mpyidir    = mpyglobals['mpyidir']    # list of input directory
127   mpyoutlist = mpyglobals['mpyoutlist'] # list of opened output
128   mpytabwidth= mpyglobals['mpytabwidth']# number of char of a tabulation character
129   
130   # this is necessary in order to write in mpyout file with just print
131   if mpyout not in ('', 'stdout'):
132      stdout = sys.stdout
133      try: 
134         sys.stdout = open( mpywdir+'/'+mpyout, "w")
135         mpyoutlist.append(mpywdir+'/'+mpyout)
136         mpylist.write(mpywdir+'/'+mpyout+'\n')
137      except IOError, e: _exit(e)
138
139   opened_mpyin = [] # list of opened mpyin (include)
140
141   # default values, a way to define the variable type
142   mpyin = sys.stdin # mpyin file
143   mpyfname = ''     # real filename of mpyin file
144   mpylineno = 1     # line number in mpyin file
145   mpycharno = 0     # character number in mpyin file
146   mpylength = 0     # number of character of mpyin file
147   level = 0;        # level > 0 in python inclusion
148   mode = ''         # '' outside python section, 'eval' | 'code' inside
149   buf = ''          # buffer for python section
150
151   # the first item in mpyins is a buffer to exec
152   mpyins = [mpytxt] + mpyins
153
154   # for all files in mpyins list (first name is a buffer) {
155   nbloop = 0
156   for fname in mpyins:
157
158      if nbloop == 0:
159         # the fist item of mpyins is a buffer
160         mpyin, mpyfname = None, 'buffer'
161         mpybuf = fname
162         if mpybuf != '':
163            lbuf = len(mpybuf)
164            if lbuf > 31: lbuf = 31
165            if mpydebug == True:
166               _mpylog("\n**Expand to file** %s **from text** `%s...`" 
167                      % (mpyoutlist[-1], mpybuf[0:lbuf]))
168            if mpydebug == True: 
169               _dictlog(mpydict, exclude=['_','mpy'])
170      else:
171         # try to open fnam then initialize all associated counters
172         mpyin, mpyfname = _mpyopen(fname,mpyidir)
173         mpybuf = mpyin.read(-1)   # all chars of mpyin file
174         if __name__ == '__main__':
175            _mpylog('\nLog\n'+'-'*40) 
176           
177         _mpylog("\n**Generate to file** %s **from file** %s" 
178                % (mpyoutlist[-1], mpyfname))
179         if nbloop == 1 and mpytxt == '': 
180            _dictlog(mpydict, exclude=['_','mpy'])
181
182      mpylineno = 1              # line number in mpyin file
183      mpycharno = 0              # character number in mpyin file
184      mpylinecharno = 0          # character number in current line
185      startlineno = 0            # first ligne of python inclusion
186      mpylength = len(mpybuf)    # number of character of mpyin file
187      level = 0;                 # level > 0 in python inclusion
188      mode = ''                  # '' because outside python section at first
189      buf = ''                   # buffer for python section
190      instr = 0                  # in string boolean (0 outside, 1 inside)
191      erasespace = 0             # nb space to erase for current line
192      nbspacetab = 0             # nb space to erase for current expands
193
194      # fname may include files, when a file ends, it is maybe an
195      # include file and then we must continue to process.
196      # This while exits with a break statement
197      # while the hierarchical file continues {
198      while True:
199
200         # foreach char in current mpybuf {
201         while mpycharno < mpylength:
202
203            # get the current char
204            c = mpybuf[mpycharno]
205            mpycharno += 1
206
207            # in order to get line number of error
208            if c == '\n':
209               mpylinecharno = 0
210               mpylineno += 1
211            else:
212               mpylinecharno += 1
213
214            # debug mode merges python section with result file
215            if mpydebug == True :
216               sys.stdout.write(c)
217
218            # outside python section
219            if level == 0:
220               startlineno = mpylineno
221               if c == '[':
222                  level += 1
223                  mode = 'eval'
224               elif c == '{':
225                  level += 1
226                  mode = 'code'
227               else:
228                  if mpydebug == False: # in debug mode c is already written
229                     sys.stdout.write(c)
230
231            # inside python section
232            else:
233
234               # tabulation of expansion
235               if erasespace != 0:
236                  if c == ' ' :
237                     erasespace -= 1
238                     continue
239                  if c == '\t' :
240                     erasespace -= mpytabwidth
241                     continue
242                  else:
243                     if c == '\n':
244                        erasespace = nbspacetab
245                        buf += c
246                        continue
247                     else:
248                        _exit ( "%s[%d]: %d spaces expected not %c" 
249                              % (mpyfname, mpylineno, nbspacetab, c))
250               
251               # we are inside a string is it a newline ?
252               if instr == 1 and c == '\n':
253                  erasespace = nbspacetab
254
255               # is it a string delimitor ?
256               if buf[-2:]+c == "'''" :
257                  instr = 1 - instr
258                  if instr == 1:
259                     nbspacetab = mpylinecharno-3
260
261               # count [ and {
262               if c == '[' :
263                  if mode == 'eval':
264                     level += 1
265                  buf += c
266               elif c == '{':
267                  if mode == 'code':
268                     level += 1
269                  buf += c
270
271               # test for evaluation
272               elif c == ']':
273                  if level == 0:
274                     if startlineno == mpylineno:
275                        _exit ( "%s[%d]: unexpected ']'" % (mpyfname, mpylineno))
276                     else:
277                        _exit ( "%s[%d-%d]: unexpected ']'" % (mpyfname, startlineno, mpylineno))
278                  elif mode == 'code':
279                     buf += c
280                  else: # mode eval
281                     level -= 1
282                     if level != 0:
283                        buf += c
284                     else:
285                        # test if buf is a file name, if yes includes file
286                        # therefore pushes the current file and opens the new
287                        incname = _mpyfindfile(buf,mpyidir)
288                        if incname != '':
289                           if mpydebug == True:
290                              _mpylog("\nInclude  to %s from %s" % (mpyout, incname))
291
292                           # push known parameters of the current opened file
293                           opened_mpyin.append((mpyin, mpybuf, mpyfname,
294                                                mpylineno, mpycharno, mpylength))
295
296                           # old values required for test of recursivity just below
297                           oldmpyfname = mpyfname
298                           oldmpylineno = mpylineno
299
300                           # open the include file
301                           mpyin, mpyfname = _mpyopen(realfname = incname)
302                           mpybuf = mpyin.read(-1)
303                           mpylineno = 1
304                           mpycharno = 0
305                           mpylength = len(mpybuf)
306                           level = 0
307                           buf = mode = ''
308
309                           # test recursive include
310                           for f in opened_mpyin[:-1]:
311                              if mpyfname in f:
312                                 _exit ("%s[%d]: forbidden recursive inclusion on %s"
313                                        % (oldmpyfname, oldmpylineno, mpyfname))
314
315                        # just evaluate buf
316                        else:
317                           try: print eval(buf, mpydict),
318                           except Exception, e:
319                              if startlineno == mpylineno:
320                                 _exit ( "%s[%d]: %s" % (mpyfname, mpylineno, e))
321                              else:
322                                 _exit ( "%s[%d-%d]: %s" % (mpyfname, startlineno, mpylineno, e))
323                           buf = mode = ''
324
325               # test for execute           
326               elif c == '}':
327                 
328                  if level == 0:
329                     if startlineno == mpylineno:
330                        _exit ( "%s[%d]: unexpected '}'" % (mpyfname, mpylineno))
331                     else:
332                        _exit ( "%s[%d-%d]: unexpected '}'" % (mpyfname, startlineno, mpylineno))
333                  elif mode == 'eval':
334                     buf += c
335                  else: # mode code
336                     level -= 1
337                     if level != 0:
338                        buf += c
339                     else:
340                        try:
341                           mpydict
342                           # print >> sys.stderr, buf
343                           exec(buf, mpydict)
344                           # pdb.run(buf, mpydict)
345                        except Exception, e:
346                           if startlineno == mpylineno:
347                              _exit ( "%s[%d]: %s" 
348                                     % (mpyfname, mpylineno, e))
349                           else:
350                              _exit ( "%s[%d-%d]: %s" 
351                                    % (mpyfname, startlineno, mpylineno, e))
352                        buf = mode = ''
353               else:
354                  buf += c
355
356         # } end while mpycharno < mpylength:
357
358         if mpyfname not in ['stdin', 'buffer']:
359            mpyin.close()
360
361         if level <> 0:
362            if mode == 'eval':
363               _exit ("%s[%d]: expected ']'" % (mpyfname,mpylineno))
364            else:
365               _exit ("%s[%d]: expected '}'" % (mpyfname,mpylineno))
366
367         # pops included file if there is one, else breaks while
368         try:
369            mpyin, mpybuf, mpyfname, mpylineno, mpycharno, mpylength = opened_mpyin.pop()
370            level = 0
371            buf = mode = ''
372
373         except: break
374
375      # } end while True:
376
377      # because the first fname is actually a buffer
378      nbloop += 1
379
380   # } end for fname in mpyins:
381
382   # close and redirect sys.stdout
383   if mpyout not in ('', 'stdout'):
384      sys.stdout.close()
385      sys.stdout = stdout
386      mpyoutlist.pop()
387
388# -------------------------------------------------------------------------------------------------
389
390def mpygen(mpyin, param):
391   "call the macro processor on file mpyin with its own dictionaty, produce a newfile"
392   global mpyglobals
393
394   # check parameters
395   if type(mpyin) <> str:
396      _exit('first argument of mpygen: %s is not a valid filename' % str(mpyin))
397   if type(param) <> dict:
398      _exit('second argument of mpygen: %s is not a valid dictionary' % str(param))
399
400   # test input filename syntax
401   mpyout = os.path.basename(mpyin).split('.')
402   if mpyout[-2] <> 'mpy':
403      _exit('filename %s has not a valid form (anyroot.mpy.anyext)' % str(mpyin))
404
405   # generate output filename
406   hashnumber = _mpyhash(`param`)
407   mpyout_base = '.'.join(mpyout[0:-2])
408   mpyout = mpyout_base+'_'+hashnumber+'.'+mpyout[-1]
409   mpymodelname = mpyout_base+'_'+hashnumber
410   param['mpymodelname'] = mpymodelname
411
412   # call the macro processor
413   _mpytree('%s* %s <- %s\n' % ('  '*mpyglobals['mpylevelgen'], mpyout, mpyin))
414   mpyglobals['mpylevelgen'] += 1
415   _mpy(mpyins = [mpyin], mpydict = param, mpyout = mpyout)
416   mpyglobals['mpylevelgen'] -= 1
417   _mpytree('')
418
419   # return the computed model name
420   return mpymodelname
421
422# -------------------------------------------------------------------------------------------------
423
424def mpyexp(txt, param):
425   "call the macro processor on a buffer of chars with its own dictionary"
426   if type(txt) <> str:
427      _exit('argument of mpyexp: must be a string not %s' % type(txt))
428   _mpy(mpytxt = txt, mpydict = param)
429
430# -------------------------------------------------------------------------------------------------
431# define globals dictionnary
432# -------------------------------------------------------------------------------------------------
433
434_parser = OptionParser( 
435            usage = "%prog [options] [infiles...]",
436            version = "version "+_version, 
437            description = "mpy opens all infiles list (default stdin) to macro-process " 
438                          "them and writes the result in OFILE (default stdout). "
439                          "Infiles are searched in any dir or subdir of PATHS (default "
440                          "MPYIDIR environment variable)")
441
442_parser.set_defaults( verbose = False , debug = False , txt = '', tab = 8 
443                    , wdir = '.' , idir = '' , output = 'stdout')
444
445_parser.add_option("-v", "--verbose", action = "store_true", dest = "verbose"
446                  , help = "trace processing step")
447_parser.add_option("-d", "--debug", action = "store_true", dest = "debug" 
448                  , help = "do not remove python inclusions")
449_parser.add_option("-t", "--txt", dest = "txt" 
450                  , help = "text (chars) to macro process")
451_parser.add_option("-b", "--tab", dest = "tab" 
452                  , help = "tabulation width [default: %default]")
453_parser.add_option("-w", "--wdir", dest = "wdir"
454                  , help = "define the working directory [default: %default]")
455_parser.add_option("-o", "--output", dest = "ofile", default = 'stdout'
456                  , help = "define output filename [default: %default] ")
457_parser.add_option("-n", "--name", dest = "name", default = 'mpy'
458                  , help = "define instance name, basename for logs [default: 'mpy']")
459_parser.add_option("-i", "--idir", dest = "idir" 
460                  , help = "list of directories (separated with colon ':') "
461                           "where to find input files "
462                           "(default %prog use MPYPATH environment variable) "
463                           "%prog begins to search in the working directory (-w option) "
464                           "then in all the directories or subdirectories of the path")
465
466(_options, _mpyins) = _parser.parse_args()
467
468# if no path dirctories are given in command line, getenv MPYIDIR
469if _options.idir == '':
470   try: _options.idir = os.environ['MPYPATH']
471   except: pass
472
473# gets the input files
474if len(_mpyins) == 0: 
475   _mpyins = ['stdin']
476
477# open log file
478if not os.path.isdir(_options.wdir): 
479   _exit('%s is not a working directory' % _options.wdir)
480try: 
481   _mpymetaname = os.path.join(_options.wdir,_options.name+"_meta.log")
482   _mpytreename = os.path.join(_options.wdir,_options.name+"_tree.log")
483   _mpylistname = os.path.join(_options.wdir,_options.name+".list")
484   _mpymetafile = open(_mpymetaname,"a")
485   _mpytreefile = open(_mpytreename,"a")
486   _mpylistfile = open(_mpylistname,"a")
487   _logname = os.path.join(_options.wdir, _options.name+".log")
488   _htmlname = os.path.join(_options.wdir,_options.name+".html")
489except IOError, e: 
490   _exit(e) 
491
492mpyglobals = dict( mpyverbose = _options.verbose # True|False
493                 , mpydebug = _options.debug # True|False
494                 , mpywdir = _options.wdir # working directory
495                 , mpyidir = [_options.wdir]+_options.idir.split(':') # split pathes in a list
496                 , mpylevelgen = 1              # level of generation (tree deep)
497                 , mpylog  = _mpymetafile
498                 , mpytree = _mpytreefile
499                 , mpylist = _mpylistfile
500                 , mpytabwidth = _options.tab
501                 , mpyoutlist = [_options.ofile])
502
503# -------------------------------------------------------------------------------------------------
504# main function
505# -------------------------------------------------------------------------------------------------
506
507if __name__ == '__main__':
508   _mpytree('''
509========================================================================
510MPY: Macroprocessor with PYthon script inclusion (V'''+_version+''')
511========================================================================
512Generation date '''+datetime.now().strftime("%A, %d. %B %Y %I:%M%p")+'''
513~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
514''')
515   _mpytree( '\nTree Generation\n'+'-'*40+'\n\n')
516   _mpytree( '* %s <- %s\n' % (_options.ofile, _mpyins[0]))
517   _mpy(mpytxt = _options.txt, mpyout = _options.ofile, mpyins = _mpyins, mpydict = {})
518   try:
519      os.system('cat '+_mpytreename+' '+_mpymetaname+' > '+_logname)
520      os.remove(_mpytreename)
521      os.remove(_mpymetaname)
522      os.system('rst2html '+_logname+' '+_htmlname+' 2> /dev/null')
523   except:
524      pass
Note: See TracBrowser for help on using the repository browser.