source: src/mpy.py @ 1

Last change on this file since 1 was 1, checked in by franck, 15 years ago

Initial Commit

  • Property svn:executable set to *
File size: 20.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 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   mpydebug   = mpyglobals['mpydebug']   # True|False
123   mpyverbose = mpyglobals['mpyverbose'] # True|False
124   mpywdir    = mpyglobals['mpywdir']    # working directory
125   mpyidir    = mpyglobals['mpyidir']    # list of input directory
126   mpyoutlist = mpyglobals['mpyoutlist'] # list of opened output
127   mpytabwidth= mpyglobals['mpytabwidth']# number of char of a tabulation character
128   
129   # this is necessary in order to write in mpyout file with just print
130   if mpyout not in ('', 'stdout'):
131      stdout = sys.stdout
132      try: 
133         sys.stdout = open( mpywdir+'/'+mpyout, "w")
134         mpyoutlist.append(mpywdir+'/'+mpyout)
135      except IOError, e: _exit(e)
136
137   opened_mpyin = [] # list of opened mpyin (include)
138
139   # default values, a way to define the variable type
140   mpyin = sys.stdin # mpyin file
141   mpyfname = ''     # real filename of mpyin file
142   mpylineno = 1     # line number in mpyin file
143   mpycharno = 0     # character number in mpyin file
144   mpylength = 0     # number of character of mpyin file
145   level = 0;        # level > 0 in python inclusion
146   mode = ''         # '' outside python section, 'eval' | 'code' inside
147   buf = ''          # buffer for python section
148
149   # the first item in mpyins is a buffer to exec
150   mpyins = [mpytxt] + mpyins
151
152   # for all files in mpyins list (first name is a buffer) {
153   nbloop = 0
154   for fname in mpyins:
155
156      if nbloop == 0:
157         # the fist item of mpyins is a buffer
158         mpyin, mpyfname = None, 'buffer'
159         mpybuf = fname
160         if mpybuf != '':
161            lbuf = len(mpybuf)
162            if lbuf > 31: lbuf = 31
163            if mpydebug == True:
164               _mpylog("\n**Expand to file** %s **from text** `%s...`" 
165                      % (mpyoutlist[-1], mpybuf[0:lbuf]))
166            if mpydebug == True: 
167               _dictlog(mpydict, exclude=['_','mpy'])
168      else:
169         # try to open fnam then initialize all associated counters
170         mpyin, mpyfname = _mpyopen(fname,mpyidir)
171         mpybuf = mpyin.read(-1)   # all chars of mpyin file
172         if __name__ == '__main__':
173            _mpylog('\nLog\n'+'-'*40) 
174           
175         _mpylog("\n**Generate to file** %s **from file** %s" 
176                % (mpyoutlist[-1], mpyfname))
177         if nbloop == 1 and mpytxt == '': 
178            _dictlog(mpydict, exclude=['_','mpy'])
179
180      mpylineno = 1              # line number in mpyin file
181      mpycharno = 0              # character number in mpyin file
182      mpylinecharno = 0          # character number in current line
183      startlineno = 0            # first ligne of python inclusion
184      mpylength = len(mpybuf)    # number of character of mpyin file
185      level = 0;                 # level > 0 in python inclusion
186      mode = ''                  # '' because outside python section at first
187      buf = ''                   # buffer for python section
188      instr = 0                  # in string boolean (0 outside, 1 inside)
189      erasespace = 0             # nb space to erase for current line
190      nbspacetab = 0             # nb space to erase for current expands
191
192      # fname may include files, when a file ends, it is maybe an
193      # include file and then we must continue to process.
194      # This while exits with a break statement
195      # while the hierarchical file continues {
196      while True:
197
198         # foreach char in current mpybuf {
199         while mpycharno < mpylength:
200
201            # get the current char
202            c = mpybuf[mpycharno]
203            mpycharno += 1
204
205            # in order to get line number of error
206            if c == '\n':
207               mpylinecharno = 0
208               mpylineno += 1
209            else:
210               mpylinecharno += 1
211
212            # debug mode merges python section with result file
213            if mpydebug == True :
214               sys.stdout.write(c)
215
216            # outside python section
217            if level == 0:
218               startlineno = mpylineno
219               if c == '[':
220                  level += 1
221                  mode = 'eval'
222               elif c == '{':
223                  level += 1
224                  mode = 'code'
225               else:
226                  if mpydebug == False: # in debug mode c is already written
227                     sys.stdout.write(c)
228
229            # inside python section
230            else:
231
232               # tabulation of expansion
233               if erasespace != 0:
234                  if c == ' ' :
235                     erasespace -= 1
236                     continue
237                  if c == '\t' :
238                     erasespace -= mpytabwidth
239                     continue
240                  else:
241                     if c == '\n':
242                        erasespace = nbspacetab
243                        buf += c
244                        continue
245                     else:
246                        _exit ( "%s[%d]: %d spaces expected not %c" 
247                              % (mpyfname, mpylineno, nbspacetab, c))
248               
249               # we are inside a string is it a newline ?
250               if instr == 1 and c == '\n':
251                  erasespace = nbspacetab
252
253               # is it a string delimitor ?
254               if buf[-2:]+c == "'''" :
255                  instr = 1 - instr
256                  if instr == 1:
257                     nbspacetab = mpylinecharno-3
258
259               # count [ and {
260               if c == '[' :
261                  if mode == 'eval':
262                     level += 1
263                  buf += c
264               elif c == '{':
265                  if mode == 'code':
266                     level += 1
267                  buf += c
268
269               # test for evaluation
270               elif c == ']':
271                  if level == 0:
272                     if startlineno == mpylineno:
273                        _exit ( "%s[%d]: unexpected ']'" % (mpyfname, mpylineno))
274                     else:
275                        _exit ( "%s[%d-%d]: unexpected ']'" % (mpyfname, startlineno, mpylineno))
276                  elif mode == 'code':
277                     buf += c
278                  else: # mode eval
279                     level -= 1
280                     if level != 0:
281                        buf += c
282                     else:
283                        # test if buf is a file name, if yes includes file
284                        # therefore pushes the current file and opens the new
285                        incname = _mpyfindfile(buf,mpyidir)
286                        if incname != '':
287                           if mpydebug == True:
288                              _mpylog("\nInclude  to %s from %s" % (mpyout, incname))
289
290                           # push known parameters of the current opened file
291                           opened_mpyin.append((mpyin, mpybuf, mpyfname,
292                                                mpylineno, mpycharno, mpylength))
293
294                           # old values required for test of recursivity just below
295                           oldmpyfname = mpyfname
296                           oldmpylineno = mpylineno
297
298                           # open the include file
299                           mpyin, mpyfname = _mpyopen(realfname = incname)
300                           mpybuf = mpyin.read(-1)
301                           mpylineno = 1
302                           mpycharno = 0
303                           mpylength = len(mpybuf)
304                           level = 0
305                           buf = mode = ''
306
307                           # test recursive include
308                           for f in opened_mpyin[:-1]:
309                              if mpyfname in f:
310                                 _exit ("%s[%d]: forbidden recursive inclusion on %s"
311                                        % (oldmpyfname, oldmpylineno, mpyfname))
312
313                        # just evaluate buf
314                        else:
315                           try: print eval(buf, mpydict),
316                           except Exception, e:
317                              if startlineno == mpylineno:
318                                 _exit ( "%s[%d]: %s" % (mpyfname, mpylineno, e))
319                              else:
320                                 _exit ( "%s[%d-%d]: %s" % (mpyfname, startlineno, mpylineno, e))
321                           buf = mode = ''
322
323               # test for execute           
324               elif c == '}':
325                 
326                  if level == 0:
327                     if startlineno == mpylineno:
328                        _exit ( "%s[%d]: unexpected '}'" % (mpyfname, mpylineno))
329                     else:
330                        _exit ( "%s[%d-%d]: unexpected '}'" % (mpyfname, startlineno, mpylineno))
331                  elif mode == 'eval':
332                     buf += c
333                  else: # mode code
334                     level -= 1
335                     if level != 0:
336                        buf += c
337                     else:
338                        try:
339                           mpydict
340                           # print >> sys.stderr, buf
341                           exec(buf, mpydict)
342                           # pdb.run(buf, mpydict)
343                        except Exception, e:
344                           if startlineno == mpylineno:
345                              _exit ( "%s[%d]: %s" 
346                                     % (mpyfname, mpylineno, e))
347                           else:
348                              _exit ( "%s[%d-%d]: %s" 
349                                    % (mpyfname, startlineno, mpylineno, e))
350                        buf = mode = ''
351               else:
352                  buf += c
353
354         # } end while mpycharno < mpylength:
355
356         if mpyfname not in ['stdin', 'buffer']:
357            mpyin.close()
358
359         if level <> 0:
360            if mode == 'eval':
361               _exit ("%s[%d]: expected ']'" % (mpyfname,mpylineno))
362            else:
363               _exit ("%s[%d]: expected '}'" % (mpyfname,mpylineno))
364
365         # pops included file if there is one, else breaks while
366         try:
367            mpyin, mpybuf, mpyfname, mpylineno, mpycharno, mpylength = opened_mpyin.pop()
368            level = 0
369            buf = mode = ''
370
371         except: break
372
373      # } end while True:
374
375      # because the first fname is actually a buffer
376      nbloop += 1
377
378   # } end for fname in mpyins:
379
380   # close and redirect sys.stdout
381   if mpyout not in ('', 'stdout'):
382      sys.stdout.close()
383      sys.stdout = stdout
384      mpyoutlist.pop()
385
386# -------------------------------------------------------------------------------------------------
387
388def mpygen(mpyin, param):
389   "call the macro processor on file mpyin with its own dictionaty, produce a newfile"
390   global mpyglobals
391
392   # check parameters
393   if type(mpyin) <> str:
394      _exit('first argument of mpygen: %s is not a valid filename' % str(mpyin))
395   if type(param) <> dict:
396      _exit('second argument of mpygen: %s is not a valid dictionary' % str(param))
397
398   # test input filename syntax
399   mpyout = os.path.basename(mpyin).split('.')
400   if mpyout[-2] <> 'mpy':
401      _exit('filename %s has not a valid form (anyroot.mpy.anyext)' % str(mpyin))
402
403   # generate output filename
404   hashnumber = _mpyhash(`param`)
405   mpyout_base = '.'.join(mpyout[0:-2])
406   mpyout = mpyout_base+'_'+hashnumber+'.'+mpyout[-1]
407   mpymodelname = mpyout_base+'_'+hashnumber
408   param['mpymodelname'] = mpymodelname
409
410   # call the macro processor
411   _mpytree('%s* %s <- %s\n' % ('  '*mpyglobals['mpylevelgen'], mpyout, mpyin))
412   mpyglobals['mpylevelgen'] += 1
413   _mpy(mpyins = [mpyin], mpydict = param, mpyout = mpyout)
414   mpyglobals['mpylevelgen'] -= 1
415   _mpytree('')
416
417   # return the computed model name
418   return mpymodelname
419
420# -------------------------------------------------------------------------------------------------
421
422def mpyexp(txt, param):
423   "call the macro processor on a buffer of chars with its own dictionary"
424   if type(txt) <> str:
425      _exit('argument of mpyexp: must be a string not %s' % type(txt))
426   _mpy(mpytxt = txt, mpydict = param)
427
428# -------------------------------------------------------------------------------------------------
429# define globals dictionnary
430# -------------------------------------------------------------------------------------------------
431
432_parser = OptionParser( 
433            usage = "%prog [options] [infiles...]",
434            version = "version "+_version, 
435            description = "mpy opens all infiles list (default stdin) to macro-process " 
436                          "them and writes the result in OFILE (default stdout). "
437                          "Infiles are searched in any dir or subdir of PATHS (default "
438                          "MPYIDIR environment variable)")
439
440_parser.set_defaults( verbose = False , debug = False , txt = '', tab = 8 
441                    , wdir = '.' , idir = '' , output = 'stdout')
442
443_parser.add_option("-v", "--verbose", action = "store_true", dest = "verbose"
444                  , help = "trace processing step")
445_parser.add_option("-d", "--debug", action = "store_true", dest = "debug" 
446                  , help = "do not remove python inclusions")
447_parser.add_option("-t", "--txt", dest = "txt" 
448                  , help = "text (chars) to macro process")
449_parser.add_option("-b", "--tab", dest = "tab" 
450                  , help = "tabulation width [default: %default]")
451_parser.add_option("-w", "--wdir", dest = "wdir"
452                  , help = "define the working directory [default: %default]")
453_parser.add_option("-o", "--output", dest = "ofile", default = 'stdout'
454                  , help = "define output filename [default: %default] ")
455_parser.add_option("-i", "--idir", dest = "idir" 
456                  , help = "list of directories (separated with colon ':') "
457                           "where to find input files "
458                           "(default %prog use MPYPATH environment variable) "
459                           "%prog begins to search in the working directory (-w option) "
460                           "then in all the directories or subdirectories of the path")
461
462(_options, _mpyins) = _parser.parse_args()
463
464# if no path dirctories are given in command line, getenv MPYIDIR
465if _options.idir == '':
466   try: _options.idir = os.environ['MPYPATH']
467   except: pass
468
469# gets the input files
470if len(_mpyins) == 0: 
471   _mpyins = ['stdin']
472
473# open log file
474if not os.path.isdir(_options.wdir): 
475   _exit('%s is not a working directory' % _options.wdir)
476try: 
477   _mpymetaname = os.path.join(_options.wdir,"mpy_meta.log")
478   _mpytreename = os.path.join(_options.wdir,"mpy_tree.log")
479   _mpymetafile = open(_mpymetaname,"a")
480   _mpytreefile = open(_mpytreename,"a")
481   _logname = os.path.join(_options.wdir,"mpy.log")
482   _htmlname = os.path.join(_options.wdir,"mpy.html")
483except IOError, e: 
484   _exit(e) 
485
486mpyglobals = dict( mpyverbose = _options.verbose # True|False
487                 , mpydebug = _options.debug # True|False
488                 , mpywdir = _options.wdir # working directory
489                 , mpyidir = [_options.wdir]+_options.idir.split(':') # split pathes in a list
490                 , mpylevelgen = 1              # level of generation (tree deep)
491                 , mpylog  = _mpymetafile
492                 , mpytree = _mpytreefile
493                 , mpytabwidth = _options.tab
494                 , mpyoutlist = [_options.ofile])
495
496# -------------------------------------------------------------------------------------------------
497# main function
498# -------------------------------------------------------------------------------------------------
499
500if __name__ == '__main__':
501   _mpytree('''
502========================================================================
503MPY: Macroprocessor with PYthon script inclusion (V'''+_version+''')
504========================================================================
505Generation date '''+datetime.now().strftime("%A, %d. %B %Y %I:%M%p")+'''
506~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
507''')
508   _mpytree( '\nTree Generation\n'+'-'*40+'\n\n')
509   _mpytree( '* %s <- %s\n' % (_options.ofile, _mpyins[0]))
510   _mpy(mpytxt = _options.txt, mpyout = _options.ofile, mpyins = _mpyins, mpydict = {})
511   try:
512      os.system('cat '+_mpytreename+' '+_mpymetaname+' > '+_logname)
513      os.system('rst2html '+_logname+' '+_htmlname)
514      os.remove(_mpytreename)
515      os.remove(_mpymetaname)
516   except:
517      pass
Note: See TracBrowser for help on using the repository browser.