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()