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, 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 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, 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, 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 parserOptions: dictionnary holding the parser options
74 :param verbosity: if not None, sets the verbosity level
75 :param wrapH: if True, content of .h file is put in a .F90 file (to force
76 fxtran to recognize it as free form) inside a module (to
77 enable the reading of files containing only a code part)
78 :param tree: an optional Tree instance
79 :param enableCache: True to cache node parents
80 """
81 self.__class__.lockFile(filename)
82 if not sys.version_info >= (3, 8):
83 # At least version 3.7 for ordered dictionary
84 # At least verison 3.8 for namsepace wildcard (use of '{*}' in find or findall)
85 raise PYFTError("PyForTool needs at least version 3.8 of python")
86 self._filename = filename
87 self._originalName = filename
88 assert os.path.exists(filename), 'Input filename must exist'
89 self._output = output
90 tree = Tree() if tree is None else tree
91 if self.SHARED_TREE is not None:
92 assert tree is not None, 'tree must be None if setParallel has been called'
93 tree.copyFromOtherTree(self.SHARED_TREE)
94 if parserOptions is None:
95 self._parserOptions = self.DEFAULT_FXTRAN_OPTIONS.copy()
96 else:
97 self._parserOptions = parserOptions.copy()
98 for tDir in tree.getDirs():
99 self._parserOptions.extend(['-I', tDir])
100 for option in self.MANDATORY_FXTRAN_OPTIONS:
101 if option not in self._parserOptions:
102 self._parserOptions.append(option)
103 includesRemoved, xml = fortran2xml(self._filename, self._parserOptions, wrapH)
104 super().__init__(xml, enableCache=enableCache, tree=tree)
105 if includesRemoved:
106 self.tree.signal(self)
107 if verbosity is not None:
108 setVerbosity(verbosity)
109
110 @classmethod
111 def setParallel(cls, tree, clsLock=None, clsRLock=None):
112 """
113 Prepare the class to be used to instanciate parallel objects
114 :param tree: Tree object shared among processes
115 :param clsLock: class to use for Lock (defaults to multiprocessing.Lock)
116 :param clsRLock: class to use for RLock (defaults to multiprocessing.RLock)
117 """
118 if clsLock is None:
119 clsLock = Lock
120 if clsRLock is None:
121 clsRLock = RLock
122 cls.NO_PARALLEL_LOCK = clsRLock()
123 cls.SHARED_TREE = tree
124 cls.PARALLEL_FILE_LOCKS = {os.path.normpath(file): clsLock()
125 for file in tree.getFiles()}
126
127 @classmethod
128 def lockFile(cls, filename):
129 """
130 Acquire lock for filename
131 :param filename: name of the file whose lock must be acquired
132 """
133 filename = os.path.normpath(filename)
134 # pylint: disable-next=unsupported-membership-test
135 if cls.PARALLEL_FILE_LOCKS is not None and filename in cls.PARALLEL_FILE_LOCKS:
136 # pylint: disable-next=unsubscriptable-object
137 cls.PARALLEL_FILE_LOCKS[filename].acquire()
138
139 @classmethod
140 def unlockFile(cls, filename, silence=False):
141 """
142 Release lock for filename
143 :param filename: name of the file whose lock must be acquired
144 :param silence: do not raise exception if file is already unlocked
145 """
146 filename = os.path.normpath(filename)
147 # pylint: disable-next=unsupported-membership-test
148 if cls.PARALLEL_FILE_LOCKS is not None and filename in cls.PARALLEL_FILE_LOCKS:
149 try:
150 # pylint: disable-next=unsubscriptable-object
151 cls.PARALLEL_FILE_LOCKS[filename].release()
152 except ValueError:
153 if not silence:
154 raise
155
156 def __enter__(self):
157 """
158 Context manager
159 """
160 return self
161
162 def __exit__(self, excType, excVal, excTb):
163 """
164 Context manager
165 """
166 self.close()
167
168 def close(self):
169 """
170 Closes the FORTRAN file
171 """
172 printInfos()
173 self.__class__.unlockFile(self.getFileName())
174
175 @property
176 def xml(self):
177 """
178 Returns the xml as a string
179 """
180 return tostring(self)
181
182 @property
183 def fortran(self):
184 """
185 Returns the FORTRAN as a string
186 """
187 return tofortran(self)
188
189 def renameUpper(self):
190 """
191 The output file will have an upper case extension
192 """
193 self._rename(str.upper)
194
195 def renameLower(self):
196 """
197 The output file will have a lower case extension
198 """
199 self._rename(str.lower)
200
201 def _rename(self, mod):
202 """
203 The output file will have a modified extension.
204 :param mod: function to apply to the file extension
205 """
206 def _transExt(path, mod):
207 filename, ext = os.path.splitext(path)
208 return filename + mod(ext)
209 if self._output is None:
210 self._filename = _transExt(self._filename, mod)
211 else:
212 self._output = _transExt(self._output, mod)
213
214 def write(self):
215 """
216 Writes the output FORTRAN file
217 """
218 with open(self._filename if self._output is None else self._output, 'w',
219 encoding='utf-8') as fo:
220 fo.write(self.fortranfortran)
221 fo.flush() # ensuring all the existing buffers are written
222 os.fsync(fo.fileno()) # forcefully writing to the file
223
224 if self._output is None and self._filename != self._originalName:
225 # We must perform an in-place update of the file, but the output file
226 # name has been updated. Then, we must remove the original file.
227 os.unlink(self._originalName)
228
229 def writeXML(self, filename):
230 """
231 Writes the output XML file
232 :param filename: XML output file name
233 """
234 with open(filename, 'w', encoding='utf-8') as fo:
235 fo.write(self.xmlxml)
unlockFile(cls, filename, silence=False)
Definition pyfortool.py:140
writeXML(self, filename)
Definition pyfortool.py:229
lockFile(cls, filename)
Definition pyfortool.py:128
__exit__(self, excType, excVal, excTb)
Definition pyfortool.py:162
setParallel(cls, tree, clsLock=None, clsRLock=None)
Definition pyfortool.py:111
__init__(self, filename, output=None, parserOptions=None, verbosity=None, wrapH=False, tree=None, enableCache=False)
Definition pyfortool.py:69
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