source: src/mpy.py @ 6

Last change on this file since 6 was 6, checked in by cfuguet, 11 years ago

Introducing a modelname paremeter on the mpygen
function to force the name of the entity to
generate

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