PyForTool
Python-fortran-tool
Loading...
Searching...
No Matches
pyfortool.py
1#!/usr/bin/env python3
2
3"""
4This module contains the main PYFT class
5PYFT is the file level class (read/write...)
6"""
7
8import os
9import sys
10from multiprocessing import Lock, RLock
11
12from pyfortool.scope import PYFTscope
13from pyfortool.tree import Tree, updateTree
14from pyfortool.util import (debugDecor, tostring, tofortran, fortran2xml,
15 setVerbosity, printInfos, PYFTError)
16
17
18@debugDecor
19def conservativePYFT(filename, parser, parserOptions, wrapH,
20 tree=None, verbosity=None, clsPYFT=None):
21 """
22 Return a conservative PYFT object usable for tree manipulation
23 :param filename: name of the file to open
24 :param parser, parserOptions, wrapH: see the PYFT class
25 :param tree: Tree instance or None
26 :param verbosity: if not None, sets the verbosity level
27 :param clsPYFT: PYFT class to use
28 :return: PYFT object
29 """
30 options = PYFT.DEFAULT_FXTRAN_OPTIONS if parserOptions is None else parserOptions
31 options = options.copy()
32 if len(set(options).intersection(('-no-include', '-noinclude'))) == 0:
33 # We add the option to not include 'include files' when analysing the tree
34 options.append('-no-include')
35 if clsPYFT is None:
36 clsPYFT = PYFT
37 pft = clsPYFT(filename, parser=parser, parserOptions=options, wrapH=wrapH,
38 tree=tree, verbosity=verbosity)
39 return pft
40
41
42def generateEmptyPYFT(filename, fortran=None, **kwargs):
43 """
44 Generates an empty PYFT scope
45 :param filename: file name corresponding to this new PYFT scope
46 :param fortran: fortran text to include
47 :param **kwargs: other arguments for the PYFT class
48 """
49 with open(filename, 'w', encoding='utf-8') as fo:
50 fo.write('SUBROUTINE FOO\nEND' if fortran is None else fortran)
51 pft = PYFT(filename, **kwargs)
52 if fortran is None:
53 pft.remove(pft.find('.//{*}program-unit'))
54 return pft
55
56
58 """
59 This class extends the PYFTscope one by adding file support (read/write)
60 """
61 DEFAULT_FXTRAN_OPTIONS = ['-construct-tag', '-no-include', '-no-cpp', '-line-length', '9999']
62 MANDATORY_FXTRAN_OPTIONS = ['-construct-tag']
63 SHARED_TREE = None # Can be updated by setParallel
64 NO_PARALLEL_LOCK = None # Can be updated by setParallel
65 PARALLEL_FILE_LOCKS = None # Can be updated by setParallel
66
67 @updateTree('signal')
68 def __init__(self, filename, output=None, parser=None, parserOptions=None, verbosity=None,
69 wrapH=False, tree=None, enableCache=False):
70 """
71 :param filename: Input file name containing FORTRAN code
72 :param output: Output file name, None to replace input file
73 :param parser: path to the fxtran parser
74 :param parserOptions: dictionnary holding the parser options
75 :param verbosity: if not None, sets the verbosity level
76 :param wrapH: if True, content of .h file is put in a .F90 file (to force
77 fxtran to recognize it as free form) inside a module (to
78 enable the reading of files containing only a code part)
79 :param tree: an optional Tree instance
80 :param enableCache: True to cache node parents
81 """
82 self.__class__.lockFile(filename)
83 if not sys.version_info >= (3, 8):
84 # At least version 3.7 for ordered dictionary
85 # At least verison 3.8 for namsepace wildcard (use of '{*}' in find or findall)
86 raise PYFTError("PyForTool needs at least version 3.8 of python")
87 self._filename = filename
88 self._originalName = filename
89 assert os.path.exists(filename), 'Input filename must exist'
90 self._output = output
91 self._parser = 'fxtran' if parser is None else parser
92 tree = Tree() if tree is None else tree
93 if self.SHARED_TREE is not None:
94 assert tree is not None, 'tree must be None if setParallel has been called'
95 tree.copyFromOtherTree(self.SHARED_TREE)
96 if parserOptions is None:
97 self._parserOptions = self.DEFAULT_FXTRAN_OPTIONS.copy()
98 else:
99 self._parserOptions = parserOptions.copy()
100 for tDir in tree.getDirs():
101 self._parserOptions.extend(['-I', tDir])
102 for option in self.MANDATORY_FXTRAN_OPTIONS:
103 if option not in self._parserOptions:
104 self._parserOptions.append(option)
105 includesRemoved, xml = fortran2xml(self._filename, self._parser, self._parserOptions, wrapH)
106 super().__init__(xml, enableCache=enableCache, tree=tree)
107 if includesRemoved:
108 self.tree.signal(self)
109 if verbosity is not None:
110 setVerbosity(verbosity)
111
112 @classmethod
113 def setParallel(cls, tree, clsLock=None, clsRLock=None):
114 """
115 Prepare the class to be used to instanciate parallel objects
116 :param tree: Tree object shared among processes
117 :param clsLock: class to use for Lock (defaults to multiprocessing.Lock)
118 :param clsRLock: class to use for RLock (defaults to multiprocessing.RLock)
119 """
120 if clsLock is None:
121 clsLock = Lock
122 if clsRLock is None:
123 clsRLock = RLock
124 cls.NO_PARALLEL_LOCK = clsRLock()
125 cls.SHARED_TREE = tree
126 cls.PARALLEL_FILE_LOCKS = {os.path.normpath(file): clsLock()
127 for file in tree.getFiles()}
128
129 @classmethod
130 def lockFile(cls, filename):
131 """
132 Acquire lock for filename
133 :param filename: name of the file whose lock must be acquired
134 """
135 filename = os.path.normpath(filename)
136 # pylint: disable-next=unsupported-membership-test
137 if cls.PARALLEL_FILE_LOCKS is not None and filename in cls.PARALLEL_FILE_LOCKS:
138 # pylint: disable-next=unsubscriptable-object
139 cls.PARALLEL_FILE_LOCKS[filename].acquire()
140
141 @classmethod
142 def unlockFile(cls, filename, silence=False):
143 """
144 Release lock for filename
145 :param filename: name of the file whose lock must be acquired
146 :param silence: do not raise exception if file is already unlocked
147 """
148 filename = os.path.normpath(filename)
149 # pylint: disable-next=unsupported-membership-test
150 if cls.PARALLEL_FILE_LOCKS is not None and filename in cls.PARALLEL_FILE_LOCKS:
151 try:
152 # pylint: disable-next=unsubscriptable-object
153 cls.PARALLEL_FILE_LOCKS[filename].release()
154 except ValueError:
155 if not silence:
156 raise
157
158 def __enter__(self):
159 """
160 Context manager
161 """
162 return self
163
164 def __exit__(self, excType, excVal, excTb):
165 """
166 Context manager
167 """
168 self.close()
169
170 def close(self):
171 """
172 Closes the FORTRAN file
173 """
174 printInfos()
175 self.__class__.unlockFile(self.getFileName())
176
177 @property
178 def xml(self):
179 """
180 Returns the xml as a string
181 """
182 return tostring(self)
183
184 @property
185 def fortran(self):
186 """
187 Returns the FORTRAN as a string
188 """
189 return tofortran(self)
190
191 def renameUpper(self):
192 """
193 The output file will have an upper case extension
194 """
195 self._rename(str.upper)
196
197 def renameLower(self):
198 """
199 The output file will have a lower case extension
200 """
201 self._rename(str.lower)
202
203 def _rename(self, mod):
204 """
205 The output file will have a modified extension.
206 :param mod: function to apply to the file extension
207 """
208 def _transExt(path, mod):
209 filename, ext = os.path.splitext(path)
210 return filename + mod(ext)
211 if self._output is None:
212 self._filename = _transExt(self._filename, mod)
213 else:
214 self._output = _transExt(self._output, mod)
215
216 def write(self):
217 """
218 Writes the output FORTRAN file
219 """
220 with open(self._filename if self._output is None else self._output, 'w',
221 encoding='utf-8') as fo:
222 fo.write(self.fortranfortran)
223 fo.flush() # ensuring all the existing buffers are written
224 os.fsync(fo.fileno()) # forcefully writing to the file
225
226 if self._output is None and self._filename != self._originalName:
227 # We must perform an in-place update of the file, but the output file
228 # name has been updated. Then, we must remove the original file.
229 os.unlink(self._originalName)
230
231 def writeXML(self, filename):
232 """
233 Writes the output XML file
234 :param filename: XML output file name
235 """
236 with open(filename, 'w', encoding='utf-8') as fo:
237 fo.write(self.xmlxml)
unlockFile(cls, filename, silence=False)
Definition pyfortool.py:142
writeXML(self, filename)
Definition pyfortool.py:231
lockFile(cls, filename)
Definition pyfortool.py:130
__init__(self, filename, output=None, parser=None, parserOptions=None, verbosity=None, wrapH=False, tree=None, enableCache=False)
Definition pyfortool.py:69
__exit__(self, excType, excVal, excTb)
Definition pyfortool.py:164
setParallel(cls, tree, clsLock=None, clsRLock=None)
Definition pyfortool.py:113
append(self, *args, **kwargs)
Definition scope.py:139
extend(self, *args, **kwargs)
Definition scope.py:146
generateEmptyPYFT(filename, fortran=None, **kwargs)
Definition pyfortool.py:42