patme 0.4.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of patme might be problematic. Click here for more details.
- patme/__init__.py +52 -0
- patme/buildtools/__init__.py +7 -0
- patme/buildtools/rce_releasecreator.py +336 -0
- patme/buildtools/release.py +26 -0
- patme/femtools/__init__.py +5 -0
- patme/femtools/abqmsgfilechecker.py +137 -0
- patme/femtools/fecall.py +1092 -0
- patme/geometry/__init__.py +0 -0
- patme/geometry/area.py +124 -0
- patme/geometry/coordinatesystem.py +635 -0
- patme/geometry/intersect.py +284 -0
- patme/geometry/line.py +183 -0
- patme/geometry/misc.py +420 -0
- patme/geometry/plane.py +464 -0
- patme/geometry/rotate.py +244 -0
- patme/geometry/scale.py +152 -0
- patme/geometry/shape2d.py +50 -0
- patme/geometry/transformations.py +1831 -0
- patme/geometry/translate.py +139 -0
- patme/mechanics/__init__.py +4 -0
- patme/mechanics/loads.py +435 -0
- patme/mechanics/material.py +1260 -0
- patme/service/__init__.py +7 -0
- patme/service/decorators.py +85 -0
- patme/service/duration.py +96 -0
- patme/service/exceptionhook.py +104 -0
- patme/service/exceptions.py +36 -0
- patme/service/io/__init__.py +3 -0
- patme/service/io/basewriter.py +122 -0
- patme/service/logger.py +375 -0
- patme/service/mathutils.py +108 -0
- patme/service/misc.py +71 -0
- patme/service/moveimports.py +217 -0
- patme/service/stringutils.py +419 -0
- patme/service/systemutils.py +290 -0
- patme/sshtools/__init__.py +3 -0
- patme/sshtools/cara.py +435 -0
- patme/sshtools/clustercaller.py +420 -0
- patme/sshtools/facluster.py +350 -0
- patme/sshtools/sshcall.py +168 -0
- patme-0.4.4.dist-info/LICENSE +21 -0
- patme-0.4.4.dist-info/LICENSES/MIT.txt +9 -0
- patme-0.4.4.dist-info/METADATA +168 -0
- patme-0.4.4.dist-info/RECORD +46 -0
- patme-0.4.4.dist-info/WHEEL +4 -0
- patme-0.4.4.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2022 German Aerospace Center (DLR)
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
r"""
|
|
6
|
+
Adapts the import statements if a package/module/moduleattribute is moved to another location
|
|
7
|
+
|
|
8
|
+
When something is moved to another location, many import statements may have to be modified.
|
|
9
|
+
With this package, the modifications can be automatically performed in a defined scope.
|
|
10
|
+
These are the actions performed:
|
|
11
|
+
|
|
12
|
+
- The files with matches are adapted accordingly.
|
|
13
|
+
E.g the package/module/attribute "foobar" is moved to the new location "newpackage"::
|
|
14
|
+
|
|
15
|
+
import oldpackage.foobar
|
|
16
|
+
from oldpackage import foobar
|
|
17
|
+
|
|
18
|
+
# is turned into
|
|
19
|
+
|
|
20
|
+
import newpackage.foobar
|
|
21
|
+
from newpackage import foobar
|
|
22
|
+
|
|
23
|
+
- An overview of the matches is printed
|
|
24
|
+
- This overview is also put in the file "movedImports.txt" in the sourcePaths
|
|
25
|
+
|
|
26
|
+
Usage::
|
|
27
|
+
|
|
28
|
+
# define attributeName --> newPackage/Module
|
|
29
|
+
changedVariablesDict = OrderedDict([('DelisSshError', 'patme.service.exceptions')])
|
|
30
|
+
|
|
31
|
+
# run with source locations defined
|
|
32
|
+
moveIt([r"C:\eclipse_projects\DELiS\src"], changedVariablesDict, False)
|
|
33
|
+
|
|
34
|
+
.. note::
|
|
35
|
+
|
|
36
|
+
Does not work with imports spanning several lines
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
import glob
|
|
40
|
+
import os
|
|
41
|
+
import re
|
|
42
|
+
from collections import OrderedDict
|
|
43
|
+
|
|
44
|
+
from patme.service.logger import log
|
|
45
|
+
|
|
46
|
+
# regex pattern strings that will be extended (at {}) with the newPackage and attribute names
|
|
47
|
+
patternStrings = [
|
|
48
|
+
# import x.y
|
|
49
|
+
r"""^ # immediately after newline plus optional whitespaces
|
|
50
|
+
(\ *?import\ (?!{})) # optional whitespaces + import statement + exclude the match if the attribute is already in the new package
|
|
51
|
+
(.*?) # preceding packages/module
|
|
52
|
+
({}) # the actual variable
|
|
53
|
+
(.*?) # as-statement, comments etc.
|
|
54
|
+
$""",
|
|
55
|
+
# from x import y
|
|
56
|
+
r"""^ # immediately after newline
|
|
57
|
+
(\ *?from\ (?!{})) # optional whitespaces + from statement + exclude the match if the attribute is already in the new package
|
|
58
|
+
(.+?\ ) # preceding packages/module
|
|
59
|
+
(import\ ) # import statement
|
|
60
|
+
({}) # the actual variable
|
|
61
|
+
(\ *?\#.*|\ ?as\ .*|\ *?)? # as-statement, comments etc.
|
|
62
|
+
$""",
|
|
63
|
+
# from x import y, z
|
|
64
|
+
r"""^ # immediately after newline
|
|
65
|
+
(\ *?from\ (?!{})) # optional whitespace + from statement + exclude the match if the attribute is already in the new package
|
|
66
|
+
(.+?\ ) # preceding packages/module
|
|
67
|
+
(import\ ) # import statement
|
|
68
|
+
(.+?\ *)? # preceding imported variables
|
|
69
|
+
(,\ *{}|{}\ *,\ *) # the actual variable
|
|
70
|
+
(.+?)? # following imported variables
|
|
71
|
+
(\ *?\#.*)? # comments etc
|
|
72
|
+
$""",
|
|
73
|
+
# from x.y import z
|
|
74
|
+
r"""^ # immediately after newline
|
|
75
|
+
(\ *?from\ (?!{})) # optional whitespaces + import statement + exclude the match if the attribute is already in the new package
|
|
76
|
+
(.+?\.) # preceding packages/module
|
|
77
|
+
({})\ # the actual variable
|
|
78
|
+
(import\ .*) # import statement and all the rest
|
|
79
|
+
$""",
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
# regex replacement strings
|
|
83
|
+
replacements = [
|
|
84
|
+
r"\g<1>{}\g<3>\g<4>",
|
|
85
|
+
r"\g<1>{}\g<3>\g<4>\g<5>",
|
|
86
|
+
r"\g<1>\g<2>\g<3>\g<4>\g<6>\g<7>\n\g<1>{}\g<3>{}",
|
|
87
|
+
r"\g<1>{}\g<3> \g<4>",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
replacementNewPackagePostfixes = [".", " ", " ", "."]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def moveIt(sourcePaths, changedVariablesDict, doTestrun=False, blacklist=None):
|
|
94
|
+
"""
|
|
95
|
+
Main entry function of the module - see module description
|
|
96
|
+
|
|
97
|
+
:param sourcePaths: list with paths to search for python files
|
|
98
|
+
:param changedVariables: variable name --> new variable location
|
|
99
|
+
:param doTestrun: Flag if no file should be changed.
|
|
100
|
+
Then only the matches are printed
|
|
101
|
+
:param blacklist: List with packages after import/from statement that should be blacklisted.
|
|
102
|
+
E.g. in the following example if ``import logging as log`` should not be adapted,
|
|
103
|
+
the blacklist would be ['logging']::
|
|
104
|
+
|
|
105
|
+
import logging as log
|
|
106
|
+
from patme.service.logger import log
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
matchLines = []
|
|
110
|
+
|
|
111
|
+
for sourcePath in sourcePaths:
|
|
112
|
+
for ffile in glob.glob(os.path.join(sourcePath, "**", "*.py"), recursive=True):
|
|
113
|
+
with open(ffile) as f:
|
|
114
|
+
stream = f.read()
|
|
115
|
+
matchLines += checkAndReplaceFile(stream, ffile, changedVariablesDict, doTestrun, blacklist)
|
|
116
|
+
|
|
117
|
+
outFile = os.path.join(sourcePath, "movedImports.txt")
|
|
118
|
+
if matchLines:
|
|
119
|
+
msg = "\n".join([""] + matchLines)
|
|
120
|
+
else:
|
|
121
|
+
msg = "No matches found!"
|
|
122
|
+
log.info(msg)
|
|
123
|
+
with open(outFile, "w") as g:
|
|
124
|
+
g.write(msg)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def checkAndReplaceFile(fileString, fileName, changedVariablesDict, doTestrun=False, blacklist=None):
|
|
128
|
+
"""check a file for matching lines and replace them accordingly
|
|
129
|
+
|
|
130
|
+
:return: list with strings containing matching information"""
|
|
131
|
+
|
|
132
|
+
fileString, matchLines = checkAndReplaceString(fileString, fileName, changedVariablesDict, blacklist)
|
|
133
|
+
if not doTestrun:
|
|
134
|
+
with open(fileName, "w") as g:
|
|
135
|
+
g.write(fileString)
|
|
136
|
+
|
|
137
|
+
return matchLines
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def checkAndReplaceString(fileString, fileName, changedVariablesDict, blacklist=None):
|
|
141
|
+
"""check a string for matching lines and replace them accordingly
|
|
142
|
+
|
|
143
|
+
:return: tuple (replacedFileString, matchLines)"""
|
|
144
|
+
|
|
145
|
+
matchLines = []
|
|
146
|
+
for variableName, newPackage in changedVariablesDict.items():
|
|
147
|
+
if variableName == "":
|
|
148
|
+
continue
|
|
149
|
+
for patternStr, replacement, repPostfix in zip(patternStrings, replacements, replacementNewPackagePostfixes):
|
|
150
|
+
pattern = _getPattern(patternStr, variableName, newPackage, blacklist)
|
|
151
|
+
|
|
152
|
+
match = pattern.search(fileString)
|
|
153
|
+
if match:
|
|
154
|
+
# documentation
|
|
155
|
+
matchLines += ["#########################", fileName]
|
|
156
|
+
for allMatches in pattern.findall(fileString):
|
|
157
|
+
matchLines.append("".join(allMatches))
|
|
158
|
+
|
|
159
|
+
# replacement
|
|
160
|
+
replacementStr = _getReplacement(replacement, newPackage, repPostfix, variableName)
|
|
161
|
+
fileString = pattern.sub(replacementStr, fileString)
|
|
162
|
+
return fileString, matchLines
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _getPattern(patternString, variableName, newPackage, blacklist=None):
|
|
166
|
+
"""create a regex pattern including the variable and new package name"""
|
|
167
|
+
if blacklist is None:
|
|
168
|
+
blacklist = newPackage
|
|
169
|
+
else:
|
|
170
|
+
blacklist = "|".join([newPackage] + blacklist)
|
|
171
|
+
patternString = patternString.format(blacklist, variableName, variableName)
|
|
172
|
+
pattern = re.compile(patternString, re.RegexFlag.MULTILINE + re.RegexFlag.VERBOSE)
|
|
173
|
+
return pattern
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _getReplacement(replacement, newPackage, replacementPostfix, variableName=None):
|
|
177
|
+
"""create a regex replacement string including the variable and new package name"""
|
|
178
|
+
if newPackage[-1] != replacementPostfix:
|
|
179
|
+
newPackage += replacementPostfix
|
|
180
|
+
return replacement.format(newPackage, variableName)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def stringMatchesPatterns(inputStr, variableName, newPackage, blacklist=None):
|
|
184
|
+
"""check if a given string matches the patterns defined"""
|
|
185
|
+
matches = []
|
|
186
|
+
for patternStr, _, _ in zip(patternStrings, replacements, replacementNewPackagePostfixes):
|
|
187
|
+
pattern = _getPattern(patternStr, variableName, newPackage, blacklist)
|
|
188
|
+
|
|
189
|
+
matches.append(pattern.search(inputStr))
|
|
190
|
+
|
|
191
|
+
return any(matches)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
if __name__ == "__main__":
|
|
195
|
+
if 0:
|
|
196
|
+
# test single lines
|
|
197
|
+
res = stringMatchesPatterns(
|
|
198
|
+
"from delismm.service.utilities import log", "log", "patme.service.logger", ["logging"]
|
|
199
|
+
)
|
|
200
|
+
print(res)
|
|
201
|
+
else:
|
|
202
|
+
# test and modify imports
|
|
203
|
+
changeVar = OrderedDict(
|
|
204
|
+
[
|
|
205
|
+
("copyClusterFilesSFTP", "patme.sshtools.facluster"),
|
|
206
|
+
("", ""),
|
|
207
|
+
]
|
|
208
|
+
)
|
|
209
|
+
sourcePaths = [
|
|
210
|
+
r"C:\PycharmProjects\DELiS\src",
|
|
211
|
+
r"C:\PycharmProjects\DELiS\test",
|
|
212
|
+
r"C:\PycharmProjects\delismm\src",
|
|
213
|
+
r"C:\PycharmProjects\delismm\test",
|
|
214
|
+
r"C:\eclipse_projects\andecs\src",
|
|
215
|
+
r"C:\eclipse_projects\andecs\test",
|
|
216
|
+
]
|
|
217
|
+
moveIt(sourcePaths, changeVar, False)
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
# Copyright (C) 2013 Deutsches Zentrum fuer Luft- und Raumfahrt(DLR, German Aerospace Center) <www.dlr.de>
|
|
2
|
+
# SPDX-FileCopyrightText: 2022 German Aerospace Center (DLR)
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Functions for string formatting.
|
|
8
|
+
"""
|
|
9
|
+
import functools
|
|
10
|
+
import io
|
|
11
|
+
import itertools
|
|
12
|
+
import logging
|
|
13
|
+
import math
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from tqdm import tqdm
|
|
21
|
+
|
|
22
|
+
HAS_TQDM = True
|
|
23
|
+
except:
|
|
24
|
+
HAS_TQDM = False
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ProgressBar:
|
|
28
|
+
"""This class produces a progress bar in the console that also works in the eclipse console.
|
|
29
|
+
It is intended for iterated procedures where each iteration step takes almost the
|
|
30
|
+
same time. Every call to ``ProgressBar.append`` appends one progress item
|
|
31
|
+
in the progress bar.
|
|
32
|
+
|
|
33
|
+
.. note::
|
|
34
|
+
The progress bar does not work if you do other prints during filling the progress bar
|
|
35
|
+
|
|
36
|
+
Example::
|
|
37
|
+
|
|
38
|
+
pb = ProgressBar(50)
|
|
39
|
+
for i in range(50): pb.append()
|
|
40
|
+
pb.end()
|
|
41
|
+
|
|
42
|
+
Result::
|
|
43
|
+
|
|
44
|
+
[ ]
|
|
45
|
+
#########################
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, numberOfItems, logLevel=logging.INFO, baseLogger=None):
|
|
50
|
+
"""initializes a progress bar
|
|
51
|
+
|
|
52
|
+
:param numberOfItems: number of items that are iterated"""
|
|
53
|
+
# spaces in front of progress bar at each line
|
|
54
|
+
self.preSpaces = " "
|
|
55
|
+
|
|
56
|
+
if not HAS_TQDM:
|
|
57
|
+
|
|
58
|
+
# the progress bar should be 80 elements thick at maximum
|
|
59
|
+
self.maxLength = 80 - len(self.preSpaces)
|
|
60
|
+
self.count = 0
|
|
61
|
+
self.printEveryXItem = int(numberOfItems) // self.maxLength + 1
|
|
62
|
+
self.logLevel = logLevel
|
|
63
|
+
self.baseLogger = baseLogger
|
|
64
|
+
|
|
65
|
+
if not self.isBlocked:
|
|
66
|
+
itemsToInsert = " " * (numberOfItems // self.printEveryXItem)
|
|
67
|
+
sys.stdout.write(f"{self.preSpaces}[{itemsToInsert}]\n {self.preSpaces}")
|
|
68
|
+
sys.stdout.flush()
|
|
69
|
+
|
|
70
|
+
else:
|
|
71
|
+
self._tqdm = tqdm(
|
|
72
|
+
desc=self.preSpaces, total=numberOfItems, bar_format="{desc}{percentage:3.0f}%|{bar:30}| Total: {total}"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def append(self):
|
|
76
|
+
"""doc"""
|
|
77
|
+
if not HAS_TQDM:
|
|
78
|
+
if not self.isBlocked:
|
|
79
|
+
if self.count % self.printEveryXItem == 0:
|
|
80
|
+
sys.stdout.write("#")
|
|
81
|
+
sys.stdout.flush()
|
|
82
|
+
self.count += 1
|
|
83
|
+
else:
|
|
84
|
+
self._tqdm.update()
|
|
85
|
+
|
|
86
|
+
def end(self):
|
|
87
|
+
"""doc"""
|
|
88
|
+
if not HAS_TQDM:
|
|
89
|
+
if not self.isBlocked:
|
|
90
|
+
sys.stdout.write("\n")
|
|
91
|
+
sys.stdout.flush()
|
|
92
|
+
else:
|
|
93
|
+
self._tqdm.close()
|
|
94
|
+
|
|
95
|
+
def _isBlocked(self):
|
|
96
|
+
"""doc"""
|
|
97
|
+
if (self.baseLogger is None) or not hasattr(self.baseLogger, "logLevel"):
|
|
98
|
+
return False
|
|
99
|
+
else:
|
|
100
|
+
return self.logLevel < self.baseLogger.logLevel
|
|
101
|
+
|
|
102
|
+
isBlocked = property(fget=_isBlocked)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def breakStringIntoMultlineString(
|
|
106
|
+
fullString, intendationLengthLeft=0, setContinuationChar=False, maximumLineLength=72, pattern="[,]"
|
|
107
|
+
):
|
|
108
|
+
"""
|
|
109
|
+
Formats a string to match a given maximum line length. Furthermore, all newlines
|
|
110
|
+
starts with an intendation if specified.(Necessary for several NASTRAN cards)
|
|
111
|
+
|
|
112
|
+
:param fullString: String to be splitted
|
|
113
|
+
:param intendationLengthLeft: Intendation length for new lines (beginning at the second line for long string)
|
|
114
|
+
:param setContinuationChar: Flag if continuation character should be added when new line is needed
|
|
115
|
+
(may be used in Nastran)
|
|
116
|
+
:param pattern: Regular expression with all possible characters to search
|
|
117
|
+
within the 'fullString' where the string can be splitted. Please refer to the documentation
|
|
118
|
+
of the python 're' library
|
|
119
|
+
:return: formatted multiline string that matches maximum line length
|
|
120
|
+
"""
|
|
121
|
+
firstRowOffset = intendationLengthLeft
|
|
122
|
+
if setContinuationChar:
|
|
123
|
+
breakStr = "+\n%s+" % (" " * intendationLengthLeft)
|
|
124
|
+
else:
|
|
125
|
+
breakStr = "\n%s" % (" " * intendationLengthLeft)
|
|
126
|
+
|
|
127
|
+
formattedString = ""
|
|
128
|
+
while len(fullString) > maximumLineLength:
|
|
129
|
+
lineLength = maximumLineLength - firstRowOffset
|
|
130
|
+
indexes = [m.start() for m in re.finditer(pattern, fullString) if m.start() < lineLength]
|
|
131
|
+
if indexes:
|
|
132
|
+
nearestIx = max(indexes)
|
|
133
|
+
else:
|
|
134
|
+
nearestIx = next(m.start() for m in re.finditer(pattern, fullString) if m.start() > lineLength)
|
|
135
|
+
|
|
136
|
+
formattedString += fullString[: nearestIx + 1] + breakStr
|
|
137
|
+
fullString = fullString[nearestIx + 1 :]
|
|
138
|
+
|
|
139
|
+
firstRowOffset = 0
|
|
140
|
+
|
|
141
|
+
# add remaining substring which fits into the last line
|
|
142
|
+
formattedString += fullString
|
|
143
|
+
return formattedString
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def createRstTable(inputMatrix, numberOfHeaderLines=1):
|
|
147
|
+
"""Returns a string containing a well formatted table that can be used in rst-documentation.
|
|
148
|
+
|
|
149
|
+
:param inputMatrix: A sequence of sequences of items, one sequence per row.
|
|
150
|
+
:param numberOfHeaderLines: number of lines that are used as header. the header is printed bold.
|
|
151
|
+
:return: string containing well formatted rst table
|
|
152
|
+
|
|
153
|
+
Example::
|
|
154
|
+
|
|
155
|
+
>>> from patme.service.stringutils import createRstTable
|
|
156
|
+
>>> a=[]
|
|
157
|
+
>>> a.append(['','major','minor','revision'])
|
|
158
|
+
>>> a.append(['Example','13','2','0'])
|
|
159
|
+
>>> a.append([ 'Explanation','New feature, incompatibe to prev versions','New feature, compatible to prev versions','Patch/Bugfix'])
|
|
160
|
+
>>> print(createRstTable(a))
|
|
161
|
+
+-------------+-------------------------------------------+------------------------------------------+--------------+
|
|
162
|
+
| | major | minor | revision |
|
|
163
|
+
+=============+===========================================+==========================================+==============+
|
|
164
|
+
| Example | 13 | 2 | 0 |
|
|
165
|
+
+-------------+-------------------------------------------+------------------------------------------+--------------+
|
|
166
|
+
| Explanation | New feature, incompatibe to prev versions | New feature, compatible to prev versions | Patch/Bugfix |
|
|
167
|
+
+-------------+-------------------------------------------+------------------------------------------+--------------+
|
|
168
|
+
"""
|
|
169
|
+
tableString = indent(inputMatrix, separateRows=True, hasHeader=True, headerChar="-", prefix="| ", postfix=" |")
|
|
170
|
+
tableLines = tableString.splitlines()
|
|
171
|
+
# get second row to extract the position of '|'
|
|
172
|
+
pipePositions = []
|
|
173
|
+
line = tableLines[1]
|
|
174
|
+
for index, character in enumerate(line):
|
|
175
|
+
if character == "|":
|
|
176
|
+
pipePositions.append(index)
|
|
177
|
+
|
|
178
|
+
# alter tableLines containing text
|
|
179
|
+
for halfLineNumber, line in enumerate(tableLines[::2]):
|
|
180
|
+
for index in pipePositions:
|
|
181
|
+
line = line[:index] + "+" + line[index + 1 :]
|
|
182
|
+
tableLines[halfLineNumber * 2] = line
|
|
183
|
+
|
|
184
|
+
tableLines[2 * numberOfHeaderLines] = tableLines[2 * numberOfHeaderLines].replace("-", "=")
|
|
185
|
+
return "\n".join(tableLines)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def indentDataFrame(df, *args, **kwargs):
|
|
189
|
+
"""Indents a pandas data frame. All other parameters can be seen in indent()"""
|
|
190
|
+
outList = [list(df)] + df.values.tolist()
|
|
191
|
+
dfIndex = [""] + df.index.tolist()
|
|
192
|
+
outList = [[indexItem] + line for indexItem, line in zip(dfIndex, outList)]
|
|
193
|
+
return indent(outList, *args, **kwargs)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def indent(
|
|
197
|
+
rows,
|
|
198
|
+
hasHeader=False,
|
|
199
|
+
headerChar="-",
|
|
200
|
+
delim=" | ",
|
|
201
|
+
justify="left",
|
|
202
|
+
separateRows=False,
|
|
203
|
+
prefix="",
|
|
204
|
+
postfix="",
|
|
205
|
+
wrapfunc=lambda x: wrap_npstr(x),
|
|
206
|
+
header=None,
|
|
207
|
+
colHeader=None,
|
|
208
|
+
): # lambda x:x):
|
|
209
|
+
"""
|
|
210
|
+
Indents a table by column.
|
|
211
|
+
|
|
212
|
+
:param rows: A sequence of sequences of items, one sequence per row.
|
|
213
|
+
|
|
214
|
+
:param hasHeader: True if the first row consists of the columns' names.
|
|
215
|
+
|
|
216
|
+
:param headerChar: Character to be used for the row separator line
|
|
217
|
+
(if hasHeader==True or separateRows==True).
|
|
218
|
+
|
|
219
|
+
:param delim: The column delimiter.
|
|
220
|
+
|
|
221
|
+
:param justify: Determines how are data justified in their column.
|
|
222
|
+
Valid values are 'left','right' and 'center'.
|
|
223
|
+
|
|
224
|
+
:param separateRows: True if rows are to be separated by astr
|
|
225
|
+
line of 'headerChar's.
|
|
226
|
+
|
|
227
|
+
:param prefix: A string prepended to each printed row.
|
|
228
|
+
|
|
229
|
+
:param postfix: A string appended to each printed row.
|
|
230
|
+
|
|
231
|
+
:param wrapfunc: A function f(text) for wrapping text; each element in
|
|
232
|
+
the table is first wrapped by this function.
|
|
233
|
+
|
|
234
|
+
:param header: header for a table. Will be inserted before rows
|
|
235
|
+
|
|
236
|
+
:param colHeader: column header for a table. Will be inserted before rows
|
|
237
|
+
|
|
238
|
+
remark:
|
|
239
|
+
|
|
240
|
+
:Author: George Sakkis
|
|
241
|
+
:Source: http://code.activestate.com/recipes/267662/
|
|
242
|
+
:License: MIT (http://code.activestate.com/help/terms/)
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
# closure for breaking logical rows to physical, using wrapfunc
|
|
246
|
+
def rowWrapper(row):
|
|
247
|
+
newRows = [str(wrapfunc(item)).split("\n") for item in row]
|
|
248
|
+
return [[substr or "" for substr in item] for item in map(lambda *x: x, *newRows)]
|
|
249
|
+
|
|
250
|
+
if header is not None:
|
|
251
|
+
hasHeader = True
|
|
252
|
+
rows = [header] + [row for row in rows]
|
|
253
|
+
|
|
254
|
+
if colHeader is not None:
|
|
255
|
+
if header is not None:
|
|
256
|
+
colHeader = [""] + list(colHeader)
|
|
257
|
+
rows = [[colHead, headerChar] + list(row) for colHead, row in zip(colHeader, rows)]
|
|
258
|
+
|
|
259
|
+
# break each logical row into one or more physical ones
|
|
260
|
+
logicalRows = [rowWrapper(row) for row in rows]
|
|
261
|
+
# columns of physical rows
|
|
262
|
+
columns = list(itertools.zip_longest(*(row[0] for row in logicalRows)))
|
|
263
|
+
# get the maximum of each column by the string length of its items
|
|
264
|
+
maxWidths = [max(len(str(item)) for item in column) for column in columns]
|
|
265
|
+
rowSeparator = headerChar * (len(prefix) + len(postfix) + sum(maxWidths) + len(delim) * (len(maxWidths) - 1))
|
|
266
|
+
# select the appropriate justify method
|
|
267
|
+
justify = {"center": str.center, "right": str.rjust, "left": str.ljust}[justify.lower()]
|
|
268
|
+
output = io.StringIO()
|
|
269
|
+
if separateRows:
|
|
270
|
+
print(rowSeparator, file=output)
|
|
271
|
+
for physicalRows in logicalRows:
|
|
272
|
+
for row in physicalRows:
|
|
273
|
+
outRow = prefix + delim.join([justify(str(item), width) for (item, width) in zip(row, maxWidths)]) + postfix
|
|
274
|
+
print(outRow, file=output)
|
|
275
|
+
if separateRows or hasHeader:
|
|
276
|
+
print(rowSeparator, file=output)
|
|
277
|
+
hasHeader = False
|
|
278
|
+
return output.getvalue()
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# written by Mike Brown
|
|
282
|
+
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def wrap_npstr(text):
|
|
286
|
+
"""A function to distinguisch between np-arrays and others.
|
|
287
|
+
np-arrays are returned as string without newline symbols that are usually returned by np.ndarray.__str__()
|
|
288
|
+
"""
|
|
289
|
+
if isinstance(text, np.ndarray):
|
|
290
|
+
text = str(text).replace("\n", "").replace(" ", ", ").replace(" -", ", -")
|
|
291
|
+
return text
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def wrap_onspace(text, width):
|
|
295
|
+
"""
|
|
296
|
+
A word-wrap function that preserves existing line breaks
|
|
297
|
+
and most spaces in the text. Expects that existing line
|
|
298
|
+
breaks are posix newlines (\n).
|
|
299
|
+
"""
|
|
300
|
+
return functools.reduce(
|
|
301
|
+
lambda line, word, width=width: "%s%s%s"
|
|
302
|
+
% (line, " \n"[(len(line[line.rfind("\n") + 1 :]) + len(word.split("\n", 1)[0]) >= width)], word),
|
|
303
|
+
text.split(" "),
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def wrap_onspace_strict(text, width):
|
|
308
|
+
"""Similar to wrap_onspace, but enforces the width constraint:
|
|
309
|
+
words longer than width are split."""
|
|
310
|
+
wordRegex = re.compile(r"\S{" + str(width) + r",}")
|
|
311
|
+
return wrap_onspace(wordRegex.sub(lambda m: wrap_always(m.group(), width), text), width)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def wrap_always(text, width):
|
|
315
|
+
"""A simple word-wrap function that wraps text on exactly width characters.
|
|
316
|
+
It doesn't split the text in words."""
|
|
317
|
+
return "\n".join([text[width * i : width * (i + 1)] for i in range(int(math.ceil(1.0 * len(text) / width)))])
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def printMatrix(matrix):
|
|
321
|
+
'''Prints a 2D array with space as separator
|
|
322
|
+
|
|
323
|
+
:return: string with matrix entries separated by " "'''
|
|
324
|
+
return indent(matrix, delim=" ", prefix="|", postfix="|")
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
if __name__ == "__main__":
|
|
328
|
+
|
|
329
|
+
import doctest
|
|
330
|
+
|
|
331
|
+
doctest.testmod()
|
|
332
|
+
|
|
333
|
+
if 0:
|
|
334
|
+
labels = ["First Name", "Last Name", "Age", "Position"]
|
|
335
|
+
data = """John,Smith,24,Software Engineer
|
|
336
|
+
Mary,Brohowski,23,Sales Manager
|
|
337
|
+
Aristidis,Papageorgopoulos,28,Senior Reseacher"""
|
|
338
|
+
rows = [row.strip().split(",") for row in data.splitlines()]
|
|
339
|
+
|
|
340
|
+
print("Without wrapping function\n")
|
|
341
|
+
print(indent([labels] + rows, hasHeader=True))
|
|
342
|
+
# test indent with different wrapping functions
|
|
343
|
+
width = 10
|
|
344
|
+
for wrapper in (wrap_always, wrap_onspace, wrap_onspace_strict):
|
|
345
|
+
print("Wrapping function: %s(x,width=%d)\n" % (wrapper.__name__, width))
|
|
346
|
+
print(
|
|
347
|
+
indent(
|
|
348
|
+
[labels] + rows,
|
|
349
|
+
hasHeader=True,
|
|
350
|
+
separateRows=True,
|
|
351
|
+
prefix="| ",
|
|
352
|
+
postfix=" |",
|
|
353
|
+
wrapfunc=lambda x: wrapper(x, width),
|
|
354
|
+
)
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
# output:
|
|
358
|
+
#
|
|
359
|
+
# Without wrapping function
|
|
360
|
+
#
|
|
361
|
+
# First Name | Last Name | Age | Position
|
|
362
|
+
# -------------------------------------------------------
|
|
363
|
+
# John | Smith | 24 | Software Engineer
|
|
364
|
+
# Mary | Brohowski | 23 | Sales Manager
|
|
365
|
+
# Aristidis | Papageorgopoulos | 28 | Senior Reseacher
|
|
366
|
+
#
|
|
367
|
+
# Wrapping function: wrap_always(x,width=10)
|
|
368
|
+
#
|
|
369
|
+
# ----------------------------------------------
|
|
370
|
+
# | First Name | Last Name | Age | Position |
|
|
371
|
+
# ----------------------------------------------
|
|
372
|
+
# | John | Smith | 24 | Software E |
|
|
373
|
+
# | | | | ngineer |
|
|
374
|
+
# ----------------------------------------------
|
|
375
|
+
# | Mary | Brohowski | 23 | Sales Mana |
|
|
376
|
+
# | | | | ger |
|
|
377
|
+
# ----------------------------------------------
|
|
378
|
+
# | Aristidis | Papageorgo | 28 | Senior Res |
|
|
379
|
+
# | | poulos | | eacher |
|
|
380
|
+
# ----------------------------------------------
|
|
381
|
+
#
|
|
382
|
+
# Wrapping function: wrap_onspace(x,width=10)
|
|
383
|
+
#
|
|
384
|
+
# ---------------------------------------------------
|
|
385
|
+
# | First Name | Last Name | Age | Position |
|
|
386
|
+
# ---------------------------------------------------
|
|
387
|
+
# | John | Smith | 24 | Software |
|
|
388
|
+
# | | | | Engineer |
|
|
389
|
+
# ---------------------------------------------------
|
|
390
|
+
# | Mary | Brohowski | 23 | Sales |
|
|
391
|
+
# | | | | Manager |
|
|
392
|
+
# ---------------------------------------------------
|
|
393
|
+
# | Aristidis | Papageorgopoulos | 28 | Senior |
|
|
394
|
+
# | | | | Reseacher |
|
|
395
|
+
# ---------------------------------------------------
|
|
396
|
+
#
|
|
397
|
+
# Wrapping function: wrap_onspace_strict(x,width=10)
|
|
398
|
+
#
|
|
399
|
+
# ---------------------------------------------
|
|
400
|
+
# | First Name | Last Name | Age | Position |
|
|
401
|
+
# ---------------------------------------------
|
|
402
|
+
# | John | Smith | 24 | Software |
|
|
403
|
+
# | | | | Engineer |
|
|
404
|
+
# ---------------------------------------------
|
|
405
|
+
# | Mary | Brohowski | 23 | Sales |
|
|
406
|
+
# | | | | Manager |
|
|
407
|
+
# ---------------------------------------------
|
|
408
|
+
# | Aristidis | Papageorgo | 28 | Senior |
|
|
409
|
+
# | | poulos | | Reseacher |
|
|
410
|
+
|
|
411
|
+
a = [
|
|
412
|
+
["", "BC for Design", "BC for testing"],
|
|
413
|
+
["Beos 4", -77879, -37020],
|
|
414
|
+
["Beos 5", -134117, -84489],
|
|
415
|
+
["Abaqus Linear", "", ""],
|
|
416
|
+
["Abaqus NonLinear", "", ""],
|
|
417
|
+
["Test result", "", -100000],
|
|
418
|
+
]
|
|
419
|
+
print(createRstTable(a))
|