Source code for TemplateGenerator

import sys
import re
from copy import deepcopy
#import os.path
import time
from datetime import datetime
import pickle


[docs]def main(): modulenames = [] dd = {'mpi' : False, 'dbg' : True, 'imps' : True, 'macacc' : False} for arg in sys.argv[1:]: if('=' in arg): key, value = arg.split('=') if(key == '--mpi'): dd['mpi'] = (value in ['mpif90']) if(key == '--dbg'): dd['dbg'] = (value in ['T', 'True']) if(key == '--imps'): dd['imps'] = (value in ['T', 'True']) if(key == '--macacc'): dd['macacc'] = (value in ['T', 'True']) if(key == '--hdf5'): dd['hdf5'] = (value in ['T', 'True']) else: modulenames.append(arg.split('/')[-1].split('.')[0]) for modulename in modulenames: build_module(modulename, mpi=dd['mpi'], dbg=dd['dbg'], imps=dd['imps'], macacc=dd['macacc'], has_hdf5=dd['hdf5']) return
[docs]def check_skip(dest_file, file_list): """ Check if the time stamp make it necessary to regenerate the module. Regenerate if returning `False`, skip generating module for `True`. **Arguments** dest_file : str path to the module file. Function checks first if it exists and takes then the time stamp to compare it with the time stamps of the files in the list file_list. file_list : list of strings The module is generated out of these template files. If any of those time stamps is newer that the dest_file, the module is regenerated. """ # Check if module exists or was deleted with `make clean` if(not os.path.exists(dest_file)): # No module, generate module from templated return False # Get time stamp of the module tmp = time.ctime(os.path.getmtime(dest_file))[4:] mod = datetime.strptime(tmp,'%b %d %H:%M:%S %Y') recent = datetime.strptime(tmp,'%b %d %H:%M:%S %Y') # Look for the most recent file in the templates for template in file_list: if(not os.path.exists(template)): continue # Get the time stamp and cut out the day ('Mon Jan 1 ...) tmp = time.ctime(os.path.getmtime(template))[4:] this = datetime.strptime(tmp, '%b %d %H:%M:%S %Y') recent = max(recent, this) return (recent == mod)
[docs]def build_module(modulename, mpi=True, dbg=True, imps=True, macacc=False, has_hdf5=True): dst = modulename + '.f90' base = modulename + '_templates/' + modulename filelist = [base + '_templates.f90', base + '_include.f90', base + '_types_templates.f90', base + '_mpi.f90', base + '_omp.f90', base + '_hdf5.f90', base + '_tests.f90'] # Check time stamps if(check_skip(dst, filelist)): return {} basedic = {} basedic['!CHECK_'] = '' if(dbg) else '!' basedic['IFMPI_'] = '' if(mpi) else '!' basedic['IFIMPS_'] = '' if(imps) else '!' basedic['IFNOIMPS_'] = '' if(not imps) else '!' basedic['IFMACACC_'] = '' if(macacc) else '!' basedic['IFNOTMACACC_'] = '' if(not macacc) else '!' basedic['IFHDF5_'] = '' if(has_hdf5) else '!' basedic['IFNOHDF5_'] = '' if(not has_hdf5) else '!' if('PyInterface' in 'modulename'): print('basedic', basedic, base) src = base + '_include.f90' subroutines, interfaces, ldinclude, nlincl = render_subroutines(src, basedic) if(mpi): src = base + '_mpi.f90' mpisubs, mpiinter, ldmpi, nlmpi = render_subroutines(src, basedic) if(mpisubs is not None): subroutines += mpisubs interfaces += mpiinter if(has_hdf5): src = base + '_hdf5.f90' h5subs, h5inter, ldh5, nlh5 = render_subroutines(src, basedic) if(h5subs is not None): subroutines += h5subs interfaces += h5inter src = base + '_tests.f90' render_tests(src, basedic, modulename) testroutines, testinterfaces, ldtest, nltest = render_subroutines(src, basedic) if(testroutines is not None): subroutines += testroutines interfaces += testinterfaces src = base + '_types_templates.f90' types, ldtypes, nltypes = render_types(src, basedic) dic = deepcopy(basedic) dic[' INCLUDE_TYPES'] = types dic[' INCLUDE_PUBLICINTERFACES'] = interfaces dic[' INCLUDE_FILES'] = subroutines if('PyInterface' in 'modulename'): print('base', base) src = base + '_templates.f90' linesin = render_module(src, dst, dic) linedic = {} lineii = 0 linetmpl = 0 start = 0 if('INCLUDE_TYPES' in linesin): end = linesin['INCLUDE_TYPES'] - 1 for ii in range(start, end): lineii += 1 linetmpl += 1 linedic[(dst, lineii)] = (modulename + '_templates.f90', linetmpl) start = end + 1 for key in sorted(ldtypes.keys()): lineii += 1 linedic[(dst, key)] = ldtypes[key] # \n from INCLUDE_TYPES lineii += 1 linetmpl += 1 linedic[(dst, lineii)] = (modulename + '_templates.f90', linetmpl) if('INCLUDE_PUBLICINTERFACES' in linesin): end = linesin['INCLUDE_PUBLICINTERFACES'] - 1 for ii in range(start, end): lineii += 1 linetmpl += 1 linedic[(dst, lineii)] = (modulename + '_templates.f90', linetmpl) lineii += nlincl if(mpi): lineii += nlmpi if(has_hdf5): lineii += nlh5 lineii += nltest start = end + 1 # \n from INCLUDE_PUBLICINTERFACES lineii += 1 linetmpl += 1 linedic[(dst, lineii)] = (modulename + '_templates.f90', linetmpl) # default include end = linesin['INCLUDE_FILES'] - 1 for ii in range(start, end): lineii += 1 linetmpl += 1 linedic[(dst, lineii)] = (modulename + '_templates.f90', linetmpl) for key in sorted(ldinclude.keys()): lineii += 1 linedic[(dst, lineii)] = ldinclude[key] if(mpi): for key in sorted(ldmpi.keys()): lineii += 1 linedic[(dst, lineii)] = ldmpi[key] if(has_hdf5): for key in sorted(ldh5.keys()): lineii += 1 linedic[(dst, lineii)] = ldh5[key] for key in sorted(ldtest.keys()): lineii += 1 linedic[(dst, lineii)] = ldtest[key] dicfile = open(modulename + '_templates/' + modulename + '.pkl', 'wb') pickle.dump(linedic, dicfile) dicfile.close() return
[docs]def render_module(flnm, dst, dic): fh = open(flnm, 'r') fd = open(dst, 'w+') rc = re.compile('|'.join(map(re.escape, dic))) def translate(match): return dic[match.group(0)] # Identify necessary line numbers linedic = {} lineii = 0 for line in fh: lineii += 1 if('INCLUDE_TYPES' in line): linedic['INCLUDE_TYPES'] = lineii elif('INCLUDE_PUBLICINTERFACES' in line): linedic['INCLUDE_PUBLICINTERFACES'] = lineii elif('INCLUDE_FILES' in line): linedic['INCLUDE_FILES'] = lineii fd.write(rc.sub(translate, line)) fh.close() fd.close() return linedic
[docs]def render_types(flnm, basedic): # Quick return if(not os.path.isfile(flnm)): return '', {}, 0 # Results of replacement types = '' linedic = {} # Admin tmpstr = [] dic = None linenew = 0 # Filename without path f90file = flnm.split('/')[-1] # Iterate over file fh = open(flnm, 'r') lineii = 0 for orig_line in fh: lineii += 1 line = orig_line.replace('LINE_ID', f90file + ':' + str(lineii)) if(('!!TDic' in line) and ('RESET' in line)): # Write tmpstr with present dic # ----------------------------- if(dic is not None): for ii in range(len(dic)): rc = re.compile('|'.join(map(re.escape, dic[ii]))) def translate(match): return dic[ii][match.group(0)] for elem in tmpstr: types += rc.sub(translate, elem[0]) linenew += 1 linedic[linenew] = (f90file, elem[1]) else: rc = re.compile('|'.join(map(re.escape, basedic))) def translate(match): return basedic[match.group(0)] for elem in tmpstr: types += rc.sub(translate, elem[0]) linenew += 1 linedic[linenew] = (f90file, elem[1]) tmpstr = [] dic = None elif('!!TDic' in line): # Update dic # ---------- keyvals = line.split('!TDic')[1].split(':') key = keyvals[0].replace(' ', '') vals = keyvals[1].replace(' ', '').replace('\n', '').split(';') nn = len(vals) if(dic is None): dic = [] for ii in range(nn): dic.append(deepcopy(basedic)) else: if(len(dic) != nn): raise Exception for ii in range(nn): dic[ii][key] = vals[ii] else: tmpstr.append((line, lineii)) if(dic is None): rc = re.compile('|'.join(map(re.escape, basedic))) def translate(match): return basedic[match.group(0)] for elem in tmpstr: types += rc.sub(translate, elem[0]) linenew += 1 linedic[linenew] = (f90file, elem[1]) else: for ii in range(len(dic)): rc = re.compile('|'.join(map(re.escape, dic[ii]))) def translate(match): return dic[ii][match.group(0)] for elem in tmpstr: types += rc.sub(translate, elem[0]) linenew += 1 linedic[linenew] = (f90file, elem[1]) fh.close() return types, linedic, linenew
[docs]def render_subroutines(flnm, basedic): """ Build the subroutines from a base dictionary and the specifications inside the file. Functions returns string with subroutines and string with interfaces. """ # Quick return if(not os.path.isfile(flnm)): return None, None, {}, 0 # Results subroutines = '' interfaces = {} publicprocedures = [] linedic = {} # Admin tmpstr = [] dic = None linenew = 0 lineint = 0 # Filename without path f90file = flnm.split('/')[-1] # Iterate over file fh = open(flnm, 'r') lineii = 0 for orig_line in fh: lineii += 1 line = orig_line.replace('LINE_ID', f90file + ':' + str(lineii)) if(('!!TDic' in line) and ('RESET' in line)): # Write tmpstr with present dic # ----------------------------- if(dic is not None): for ii in range(len(dic)): rc = re.compile('|'.join(map(re.escape, dic[ii]))) def translate(match): return dic[ii][match.group(0)] for elem in tmpstr: subroutines += rc.sub(translate, elem[0]) linenew += 1 linedic[linenew] = (f90file, elem[1]) else: rc = re.compile('|'.join(map(re.escape, basedic))) def translate(match): return basedic[match.group(0)] for elem in tmpstr: subroutines += rc.sub(translate, elem[0]) linenew += 1 linedic[linenew] = (f90file, elem[1]) tmpstr = [] dic = None elif('!!TDic' in line): # Update dic # ---------- dic = update_dic(dic, line, basedic, lineii=lineii, flnm=flnm) elif('PUBLIC_INTERFACE_' in line): tmp = line.split('PUBLIC_INTERFACE_')[1].replace('\n', '') try: name, ext = tmp.split('&') except: print('Problem in file ' + fh.name + ' in line %5d.'%lineii) raise if(dic is None): extii = name + '_' + ext if(name in interfaces): interfaces[name].append(extii) else: interfaces[name] = [extii] else: for ii in range(len(dic)): rc = re.compile('|'.join(map(re.escape, dic[ii]))) def translate(match): return dic[ii][match.group(0)] nameii = rc.sub(translate, name) extii = nameii + '_' + rc.sub(translate, ext) if(nameii in interfaces): interfaces[nameii].append(extii) else: interfaces[nameii] = [extii] elif('PUBLIC_PROCEDURE_' in line): tmp = line.split('PUBLIC_PROCEDURE_')[1].replace('\n', '') if(dic is None): publicprocedures.append(tmp) else: for ii in range(len(dic)): rc = re.compile('|'.join(map(re.escape, dic[ii]))) def translate(match): return dic[ii][match.group(0)] name = rc.sub(translate, tmp) publicprocedures.append(name) else: tmpstr.append((line, lineii)) if(dic is None): rc = re.compile('|'.join(map(re.escape, basedic))) def translate(match): return basedic[match.group(0)] for elem in tmpstr: subroutines += rc.sub(translate, elem[0]) linenew += 1 linedic[linenew] = (f90file, elem[1]) else: for ii in range(len(dic)): rc = re.compile('|'.join(map(re.escape, dic[ii]))) def translate(match): return dic[ii][match.group(0)] for elem in tmpstr: subroutines += rc.sub(translate, elem[0]) linenew += 1 linedic[linenew] = (f90file, elem[1]) interstr = '' for key in sorted(interfaces.keys()): lineint += 1 interstr += ' interface ' + key + '\n' for innerkey in interfaces[key]: lineint += 1 interstr += ' module procedure ' + innerkey + '\n' lineint += 3 interstr += ' end interface ' + key + '\n' interstr += ' public :: ' + key + '\n' interstr += '\n' for name in sorted(publicprocedures): lineint += 1 interstr += ' public :: ' + name + '\n' lineint += 1 interstr += '\n' fh.close() return subroutines, interstr, linedic, lineint
[docs]def render_tests(flnm, basedic, modulename): """ Build fortran main and python tests for a module. """ subroutines = [] dic = None # Iterate over file try: fh = open(flnm, 'r') except: fh = [] # Filename without path f90file = flnm.split('/')[-1] lineii = 0 for orig_line in fh: lineii += 1 line = orig_line.replace('LINE_ID', f90file + ':' + str(lineii)) if(('!!TDic' in line) and ('RESET' in line)): # Reset dictionary dic = None elif('!!TDic' in line): # Update dic # ---------- dic = update_dic(dic, line, basedic, lineii=lineii, flnm=flnm) elif('end subroutine' in line.lower()): # Identify with end subroutine subii = line.split('subroutine ')[1].split('\n')[0] if(dic is None): subroutines.append(subii) else: for ii in range(len(dic)): rc = re.compile('|'.join(map(re.escape, dic[ii]))) def translate(match): return dic[ii][match.group(0)] subroutines.append(rc.sub(translate, subii)) # Write the fortran main # ---------------------- f90dst = open(modulename + '_tests.f90', 'w+') f90dst.write('program test\n\n') for subii in subroutines: if('test' not in subii): continue f90dst.write('use ' + modulename + ', only : ' + subii + '\n') f90dst.write('\nimplicit none\n\n') f90dst.write('integer :: ii\n') f90dst.write('character(30) :: arg\n\n') f90dst.write('call get_command_argument(1, arg)\n') f90dst.write('read(arg, *) ii\n\n') ii = -1 for subii in subroutines: if('test' not in subii): continue ii += 1 f90dst.write('if(ii == ' + str(ii) + ') call ' + subii + '()\n') f90dst.write('end program test\n') f90dst.close() pydst = open('test_' + modulename + '.py', 'w+') pydst.write('import subprocess as spc\n\n') pydst.write('import numpy.testing as npt\n\n') ii = -1 for subii in subroutines: if('test' not in subii): continue ii += 1 pydst.write('def ' + subii + '():\n') pydst.write(' cmd = ["./MPSFortLib/Mods/' + modulename + '_tests.out", "' + str(ii) + '"]\n') pydst.write(' stat = spc.call(spc.list2cmdline(cmd), shell=True)\n') pydst.write(' assert(stat == 0)\n') pydst.write(' return\n\n\n') pydst.close() return
[docs]def update_dic(dics, line, basedic, lineii=-1, flnm=''): """ Update the list of dictionaries. **Arguments** dics : list List of dictionary with all patterns. line : str The current line with the replacements. basedic : dic The basis dictionary with file independent replacements. """ keyvals = line.split('!TDic')[1].split(':') key = keyvals[0].replace(' ', '') vals = keyvals[1].replace(' ', '').replace('\n', '').split(';') nn = len(vals) if(dics is None): dics = [] for ii in range(nn): dics.append(deepcopy(basedic)) elif(len(dics) != nn): raise Exception('line ' + str(lineii) + ' in ' + flnm) for ii in range(nn): dics[ii][key] = vals[ii] return dics
if(__name__ == '__main__'): main()