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.

Files changed (46) hide show
  1. patme/__init__.py +52 -0
  2. patme/buildtools/__init__.py +7 -0
  3. patme/buildtools/rce_releasecreator.py +336 -0
  4. patme/buildtools/release.py +26 -0
  5. patme/femtools/__init__.py +5 -0
  6. patme/femtools/abqmsgfilechecker.py +137 -0
  7. patme/femtools/fecall.py +1092 -0
  8. patme/geometry/__init__.py +0 -0
  9. patme/geometry/area.py +124 -0
  10. patme/geometry/coordinatesystem.py +635 -0
  11. patme/geometry/intersect.py +284 -0
  12. patme/geometry/line.py +183 -0
  13. patme/geometry/misc.py +420 -0
  14. patme/geometry/plane.py +464 -0
  15. patme/geometry/rotate.py +244 -0
  16. patme/geometry/scale.py +152 -0
  17. patme/geometry/shape2d.py +50 -0
  18. patme/geometry/transformations.py +1831 -0
  19. patme/geometry/translate.py +139 -0
  20. patme/mechanics/__init__.py +4 -0
  21. patme/mechanics/loads.py +435 -0
  22. patme/mechanics/material.py +1260 -0
  23. patme/service/__init__.py +7 -0
  24. patme/service/decorators.py +85 -0
  25. patme/service/duration.py +96 -0
  26. patme/service/exceptionhook.py +104 -0
  27. patme/service/exceptions.py +36 -0
  28. patme/service/io/__init__.py +3 -0
  29. patme/service/io/basewriter.py +122 -0
  30. patme/service/logger.py +375 -0
  31. patme/service/mathutils.py +108 -0
  32. patme/service/misc.py +71 -0
  33. patme/service/moveimports.py +217 -0
  34. patme/service/stringutils.py +419 -0
  35. patme/service/systemutils.py +290 -0
  36. patme/sshtools/__init__.py +3 -0
  37. patme/sshtools/cara.py +435 -0
  38. patme/sshtools/clustercaller.py +420 -0
  39. patme/sshtools/facluster.py +350 -0
  40. patme/sshtools/sshcall.py +168 -0
  41. patme-0.4.4.dist-info/LICENSE +21 -0
  42. patme-0.4.4.dist-info/LICENSES/MIT.txt +9 -0
  43. patme-0.4.4.dist-info/METADATA +168 -0
  44. patme-0.4.4.dist-info/RECORD +46 -0
  45. patme-0.4.4.dist-info/WHEEL +4 -0
  46. 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))