jk_prettyprintobj 0.2024.2.18__tar.gz

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.
@@ -0,0 +1,221 @@
1
+ Metadata-Version: 2.1
2
+ Name: jk_prettyprintobj
3
+ Version: 0.2024.2.18
4
+ Summary: This python module provides a mixin for creating pretty debugging output for objects. This is especially useful for semi-complex data structures.
5
+ Keywords: pretty-print,debugging,debug
6
+ Author-email: Jürgen Knauth <pubsrc@binary-overflow.de>
7
+ Maintainer-email: Jürgen Knauth <pubsrc@binary-overflow.de>
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: License :: OSI Approved :: Apache Software License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Software Development :: Testing
14
+
15
+ jk_prettyprintobj
16
+ ========
17
+
18
+ Introduction
19
+ ------------
20
+
21
+ This python module provides a mixin for dumping objects. This is ment for debugging purposes: Sometimes it is very convenient to have a way of writing all data of an object to STDOUT in a human readable way. This module assists in such implementations.
22
+
23
+ Information about this module can be found here:
24
+
25
+ * [github.org](https://github.com/jkpubsrc/python-module-jk-prettyprintobj)
26
+ * [pypi.python.org](https://pypi.python.org/pypi/jk_prettyprintobj)
27
+
28
+ How to use this module
29
+ ----------------------
30
+
31
+ ### Import the module
32
+
33
+ In order to use this module you need to import it first. So add this line to the head of your python source code file:
34
+
35
+ ```python
36
+ import jk_prettyprintobj
37
+ ```
38
+
39
+ ### Use the provided mixin
40
+
41
+ To make use of the features of this module you must add a mixin to your class. Example:
42
+
43
+ ```python
44
+ class ExampleClass(jk_prettyprintobj.DumpMixin):
45
+
46
+ def __init__(self, ...):
47
+ ...
48
+
49
+ ...
50
+ ```
51
+
52
+ If you derive your class from a base class just add the mixin to your list of base classes. The order does not matter in this case. Here is an example how to do this:
53
+
54
+ ```python
55
+ class MyFancyException(Exception, jk_prettyprintobj.DumpMixin):
56
+
57
+ def __init__(self, msg:str):
58
+ super().__init__(msg)
59
+
60
+ ...
61
+ ```
62
+
63
+ In this example we use `Exception` as a base class to keep this example simple. It just demonstrates the technique. You can use any base class for inheritance, it is just necessary that you somewhere in the list of base classes add `jk_prettyprintobj.DumpMixin`. This does not yet make use of the features provided by `jk_prettyprintobj` but prepares its use.
64
+
65
+ This mixin adds a regular method named `dump()` to the class. For all things to work it is important that you have no other method named `dump()` in your class that might conflict with the implementation provided by `DumpMixin`. This method can be called later, but some additional implementation steps need to be taken first. (See next section!)
66
+
67
+ ### Implement a helper method
68
+
69
+ To actually enable the class to produce output we must implement one of the helper methods. These are:
70
+
71
+ | **Method name** | **Description** |
72
+ | --- | --- |
73
+ | `_dump(ctx:jk_prettyprintobj.DumpCtx) -> None` | Implement dumping data on your own |
74
+ | `_dumpVarNames() -> typing.List[str]` | Provide the names of the variable to output |
75
+
76
+ More to these options in the next sections.
77
+
78
+ ### Helper method _dumpVarNames()
79
+
80
+ If you implement the method `_dumpVarNames() -> typing.List[str]` your method needs to return a list of variable names that should be dumped to STDOUT.
81
+
82
+ Here is an example of a simple but working implementation.
83
+
84
+ ```python
85
+ class Matrix(jk_prettyprintobj.DumpMixin):
86
+
87
+ def __init__(self, m):
88
+ self.m = m
89
+ self.nRows = len(m)
90
+ self.nColumns = len(m[0])
91
+
92
+ def _dumpVarNames(self) -> list:
93
+ return [
94
+ "nRows",
95
+ "nColumns",
96
+ "m",
97
+ ]
98
+ ```
99
+
100
+ Now what `_dumpVarNames()` will do is simply returning a list of variables to access for output.
101
+
102
+ As private variables can not be accessed by mixins all variables in this example have therefore been defined as public variables. This is a general limitation of python so there is no way around this: All variables to output this way need to be non-private.
103
+
104
+ Now let's create an instance of `Matrix` and invoke `dump()`:
105
+
106
+ ```python
107
+ m = Matrix([
108
+ [ 1, 2, 3 ],
109
+ [ 4, 5, 6 ],
110
+ [ 7, 8, 9.1234567 ],
111
+ ])
112
+
113
+ m.dump()
114
+ ```
115
+
116
+ If `dump()` is invoked on an initialized instance of `Matrix` from this example such an object will render to something like this:
117
+
118
+ ```
119
+ <Matrix(
120
+ nRows = 3
121
+ nColumns = 3
122
+ m = [
123
+ [ 1, 2, 3 ],
124
+ [ 4, 5, 6 ],
125
+ [ 7, 8, 9.1234567 ],
126
+ ]
127
+ )>
128
+ ```
129
+
130
+ ### Helper method _dump(ctx)
131
+
132
+ If you implement the method `_dump(ctx:jk_prettyprintobj.DumpCtx) -> None` your method needs to use the provided context object to implement dumping variables to STDOUT on your own. This variant is helpful if you - for some reason - require to output private variables that an implementation of `_dumpVarNames()` will not be able to access.
133
+
134
+ By implementing this method you will also be able to modify the way how the contents of a variable will be written to STDOUT.
135
+
136
+ Here is an example of a simple but working implementation:
137
+
138
+ ```python
139
+ class Matrix(jk_prettyprintobj.DumpMixin):
140
+
141
+ def __init__(self, m):
142
+ self.__m = m
143
+ self.__nRows = len(m)
144
+ self.__nColumns = len(m[0])
145
+
146
+ def _dump(self, ctx:jk_prettyprintobj.DumpCtx):
147
+ ctx.dumpVar("nRows", self.__nRows)
148
+ ctx.dumpVar("nColumns", self.__nColumns)
149
+ ctx.dumpVar("m", self.__m, "float_round3")
150
+ ```
151
+
152
+ This class is expected to represent a mathematical matrix and therefore should receive a two-dimensional field of `float` values during construction. During construction this data is stored in a private variable named `__m`. Additional private variables are created. For simplicity no other methods except `dump_()` are implemented in this example.
153
+
154
+ Now what `_dump()` will do is to invoke `dumpVar()` for every single variable. `dumpVar()` has the following signature:
155
+
156
+ * `dumpVar(varName:str, value, postProcessorName:str = None) -> None`
157
+
158
+ This method requires to receive up to three arguments:
159
+ * `str varName`: The name to use for output. In this example we use `nRows` as we might add a property of exactly this name. (Not implemented in this example!)
160
+ * `* value`: A value of any kind. This is the value that should later on be written to STDOUT.
161
+ * `str processorName`: This optional value can be one of several identifiers that indicate how to process the value *before* it is converted to a string. (See section below.)
162
+
163
+ If `dump()` is invoked on an initialized instance of `Matrix` from this example such an object will render to something like this:
164
+
165
+ ```
166
+ <Matrix(
167
+ nRows = 3
168
+ nColumns = 3
169
+ m = [
170
+ [ 1, 2, 3 ],
171
+ [ 4, 5, 6 ],
172
+ [ 7, 8, 9.123 ],
173
+ ]
174
+ )>
175
+ ```
176
+
177
+ Please note that in this case the output of the very last `float` in the matrix might be rounded to three digits as defined by the processor `float_round3`. This is different to an implementation providing `_dumpVarNames()`.
178
+
179
+ ### Processors
180
+
181
+ For producing output you can apply a processor that will preprocess the output before writing it to STDOUT. This is useful to achieve a more human readable representation of data in some cases.
182
+
183
+ These are the processors you can use:
184
+
185
+ | **Name** | **Description** |
186
+ | --- | --- |
187
+ | `float_round1` | Round to 1 fractional digit |
188
+ | `float_round2` | Round to 2 fractional digit |
189
+ | `float_round3` | Round to 3 fractional digit |
190
+ | `float_round4` | Round to 4 fractional digit |
191
+ | `float_round5` | Round to 5 fractional digit |
192
+ | `float_round6` | Round to 6 fractional digit |
193
+ | `float_round7` | Round to 7 fractional digit |
194
+ | `int_hex` | Convert to hexadecimal representation |
195
+ | `int_bit` | Convert to binary representation |
196
+ | `str_shorten` | Shorten a string to at most 40 characters. If you have objects with large amounts of text this feature can make your output more readable. |
197
+
198
+ Futher Development
199
+ -------------------
200
+
201
+ It is likely that future developments will add more alternatives for dumping an objects. If you have any ideas, requirements or recommendations please feel free to leave a comment.
202
+
203
+ Contact Information
204
+ -------------------
205
+
206
+ This is Open Source code. That not only gives you the possibility of freely using this code it also
207
+ allows you to contribute. Feel free to contact the author(s) of this software listed below, either
208
+ for comments, collaboration requests, suggestions for improvement or reporting bugs:
209
+
210
+ * Jürgen Knauth: pubsrc@binary-overflow.de
211
+
212
+ License
213
+ -------
214
+
215
+ This software is provided under the following license:
216
+
217
+ * Apache Software License 2.0
218
+
219
+
220
+
221
+
@@ -0,0 +1,206 @@
1
+ jk_prettyprintobj
2
+ ========
3
+
4
+ Introduction
5
+ ------------
6
+
7
+ This python module provides a mixin for dumping objects. This is ment for debugging purposes: Sometimes it is very convenient to have a way of writing all data of an object to STDOUT in a human readable way. This module assists in such implementations.
8
+
9
+ Information about this module can be found here:
10
+
11
+ * [github.org](https://github.com/jkpubsrc/python-module-jk-prettyprintobj)
12
+ * [pypi.python.org](https://pypi.python.org/pypi/jk_prettyprintobj)
13
+
14
+ How to use this module
15
+ ----------------------
16
+
17
+ ### Import the module
18
+
19
+ In order to use this module you need to import it first. So add this line to the head of your python source code file:
20
+
21
+ ```python
22
+ import jk_prettyprintobj
23
+ ```
24
+
25
+ ### Use the provided mixin
26
+
27
+ To make use of the features of this module you must add a mixin to your class. Example:
28
+
29
+ ```python
30
+ class ExampleClass(jk_prettyprintobj.DumpMixin):
31
+
32
+ def __init__(self, ...):
33
+ ...
34
+
35
+ ...
36
+ ```
37
+
38
+ If you derive your class from a base class just add the mixin to your list of base classes. The order does not matter in this case. Here is an example how to do this:
39
+
40
+ ```python
41
+ class MyFancyException(Exception, jk_prettyprintobj.DumpMixin):
42
+
43
+ def __init__(self, msg:str):
44
+ super().__init__(msg)
45
+
46
+ ...
47
+ ```
48
+
49
+ In this example we use `Exception` as a base class to keep this example simple. It just demonstrates the technique. You can use any base class for inheritance, it is just necessary that you somewhere in the list of base classes add `jk_prettyprintobj.DumpMixin`. This does not yet make use of the features provided by `jk_prettyprintobj` but prepares its use.
50
+
51
+ This mixin adds a regular method named `dump()` to the class. For all things to work it is important that you have no other method named `dump()` in your class that might conflict with the implementation provided by `DumpMixin`. This method can be called later, but some additional implementation steps need to be taken first. (See next section!)
52
+
53
+ ### Implement a helper method
54
+
55
+ To actually enable the class to produce output we must implement one of the helper methods. These are:
56
+
57
+ | **Method name** | **Description** |
58
+ | --- | --- |
59
+ | `_dump(ctx:jk_prettyprintobj.DumpCtx) -> None` | Implement dumping data on your own |
60
+ | `_dumpVarNames() -> typing.List[str]` | Provide the names of the variable to output |
61
+
62
+ More to these options in the next sections.
63
+
64
+ ### Helper method _dumpVarNames()
65
+
66
+ If you implement the method `_dumpVarNames() -> typing.List[str]` your method needs to return a list of variable names that should be dumped to STDOUT.
67
+
68
+ Here is an example of a simple but working implementation.
69
+
70
+ ```python
71
+ class Matrix(jk_prettyprintobj.DumpMixin):
72
+
73
+ def __init__(self, m):
74
+ self.m = m
75
+ self.nRows = len(m)
76
+ self.nColumns = len(m[0])
77
+
78
+ def _dumpVarNames(self) -> list:
79
+ return [
80
+ "nRows",
81
+ "nColumns",
82
+ "m",
83
+ ]
84
+ ```
85
+
86
+ Now what `_dumpVarNames()` will do is simply returning a list of variables to access for output.
87
+
88
+ As private variables can not be accessed by mixins all variables in this example have therefore been defined as public variables. This is a general limitation of python so there is no way around this: All variables to output this way need to be non-private.
89
+
90
+ Now let's create an instance of `Matrix` and invoke `dump()`:
91
+
92
+ ```python
93
+ m = Matrix([
94
+ [ 1, 2, 3 ],
95
+ [ 4, 5, 6 ],
96
+ [ 7, 8, 9.1234567 ],
97
+ ])
98
+
99
+ m.dump()
100
+ ```
101
+
102
+ If `dump()` is invoked on an initialized instance of `Matrix` from this example such an object will render to something like this:
103
+
104
+ ```
105
+ <Matrix(
106
+ nRows = 3
107
+ nColumns = 3
108
+ m = [
109
+ [ 1, 2, 3 ],
110
+ [ 4, 5, 6 ],
111
+ [ 7, 8, 9.1234567 ],
112
+ ]
113
+ )>
114
+ ```
115
+
116
+ ### Helper method _dump(ctx)
117
+
118
+ If you implement the method `_dump(ctx:jk_prettyprintobj.DumpCtx) -> None` your method needs to use the provided context object to implement dumping variables to STDOUT on your own. This variant is helpful if you - for some reason - require to output private variables that an implementation of `_dumpVarNames()` will not be able to access.
119
+
120
+ By implementing this method you will also be able to modify the way how the contents of a variable will be written to STDOUT.
121
+
122
+ Here is an example of a simple but working implementation:
123
+
124
+ ```python
125
+ class Matrix(jk_prettyprintobj.DumpMixin):
126
+
127
+ def __init__(self, m):
128
+ self.__m = m
129
+ self.__nRows = len(m)
130
+ self.__nColumns = len(m[0])
131
+
132
+ def _dump(self, ctx:jk_prettyprintobj.DumpCtx):
133
+ ctx.dumpVar("nRows", self.__nRows)
134
+ ctx.dumpVar("nColumns", self.__nColumns)
135
+ ctx.dumpVar("m", self.__m, "float_round3")
136
+ ```
137
+
138
+ This class is expected to represent a mathematical matrix and therefore should receive a two-dimensional field of `float` values during construction. During construction this data is stored in a private variable named `__m`. Additional private variables are created. For simplicity no other methods except `dump_()` are implemented in this example.
139
+
140
+ Now what `_dump()` will do is to invoke `dumpVar()` for every single variable. `dumpVar()` has the following signature:
141
+
142
+ * `dumpVar(varName:str, value, postProcessorName:str = None) -> None`
143
+
144
+ This method requires to receive up to three arguments:
145
+ * `str varName`: The name to use for output. In this example we use `nRows` as we might add a property of exactly this name. (Not implemented in this example!)
146
+ * `* value`: A value of any kind. This is the value that should later on be written to STDOUT.
147
+ * `str processorName`: This optional value can be one of several identifiers that indicate how to process the value *before* it is converted to a string. (See section below.)
148
+
149
+ If `dump()` is invoked on an initialized instance of `Matrix` from this example such an object will render to something like this:
150
+
151
+ ```
152
+ <Matrix(
153
+ nRows = 3
154
+ nColumns = 3
155
+ m = [
156
+ [ 1, 2, 3 ],
157
+ [ 4, 5, 6 ],
158
+ [ 7, 8, 9.123 ],
159
+ ]
160
+ )>
161
+ ```
162
+
163
+ Please note that in this case the output of the very last `float` in the matrix might be rounded to three digits as defined by the processor `float_round3`. This is different to an implementation providing `_dumpVarNames()`.
164
+
165
+ ### Processors
166
+
167
+ For producing output you can apply a processor that will preprocess the output before writing it to STDOUT. This is useful to achieve a more human readable representation of data in some cases.
168
+
169
+ These are the processors you can use:
170
+
171
+ | **Name** | **Description** |
172
+ | --- | --- |
173
+ | `float_round1` | Round to 1 fractional digit |
174
+ | `float_round2` | Round to 2 fractional digit |
175
+ | `float_round3` | Round to 3 fractional digit |
176
+ | `float_round4` | Round to 4 fractional digit |
177
+ | `float_round5` | Round to 5 fractional digit |
178
+ | `float_round6` | Round to 6 fractional digit |
179
+ | `float_round7` | Round to 7 fractional digit |
180
+ | `int_hex` | Convert to hexadecimal representation |
181
+ | `int_bit` | Convert to binary representation |
182
+ | `str_shorten` | Shorten a string to at most 40 characters. If you have objects with large amounts of text this feature can make your output more readable. |
183
+
184
+ Futher Development
185
+ -------------------
186
+
187
+ It is likely that future developments will add more alternatives for dumping an objects. If you have any ideas, requirements or recommendations please feel free to leave a comment.
188
+
189
+ Contact Information
190
+ -------------------
191
+
192
+ This is Open Source code. That not only gives you the possibility of freely using this code it also
193
+ allows you to contribute. Feel free to contact the author(s) of this software listed below, either
194
+ for comments, collaboration requests, suggestions for improvement or reporting bugs:
195
+
196
+ * Jürgen Knauth: pubsrc@binary-overflow.de
197
+
198
+ License
199
+ -------
200
+
201
+ This software is provided under the following license:
202
+
203
+ * Apache Software License 2.0
204
+
205
+
206
+
@@ -0,0 +1,52 @@
1
+
2
+
3
+
4
+ #
5
+ # This class wraps around an integer value and represents a value in binary representation.
6
+ # This is used to simlify output.
7
+ #
8
+ class _Bits(object):
9
+
10
+ __slots__ = (
11
+ "value",
12
+ )
13
+
14
+ def __init__(self, value:int):
15
+ assert isinstance(value, int)
16
+ assert value >= 0
17
+ self.value = value
18
+ #
19
+
20
+ def __repr__(self):
21
+ s = bin(self.value)[2:]
22
+ n = len(s)
23
+ if (n == 8) or (n == 16) or (n == 32) or (n == 64):
24
+ return "b" + s
25
+ elif n < 8:
26
+ r = n % 8
27
+ return "b" + ("0" * (8 - r)) + s
28
+ elif n < 16:
29
+ r = n % 16
30
+ return "b" + ("0" * (16 - r)) + s
31
+ elif n < 32:
32
+ r = n % 32
33
+ return "b" + ("0" * (32 - r)) + s
34
+ elif n < 64:
35
+ r = n % 64
36
+ return "b" + ("0" * (64 - r)) + s
37
+ else:
38
+ r = n % 8
39
+ return "b" + ("0" * (8 - r)) + s
40
+ #
41
+
42
+ def __str__(self):
43
+ return self.__repr__()
44
+ #
45
+
46
+ #
47
+
48
+
49
+
50
+
51
+
52
+
@@ -0,0 +1,42 @@
1
+
2
+
3
+
4
+ #
5
+ # This class wraps around an integer value and represents a hex value.
6
+ # This is used to simlify output.
7
+ #
8
+ class _Hex(object):
9
+
10
+ __slots__ = (
11
+ "value",
12
+ )
13
+
14
+ def __init__(self, value:int):
15
+ assert isinstance(value, int)
16
+ assert value >= 0
17
+ self.value = value
18
+ #
19
+
20
+ def __repr__(self):
21
+ s = hex(self.value)[2:]
22
+ n = len(s)
23
+ if n == 1:
24
+ return "x0" + s
25
+ elif n == 2:
26
+ return "x" + s
27
+ else:
28
+ r = n % 4
29
+ return "x" + ("0" * (4 - r)) + s
30
+ #
31
+
32
+ def __str__(self):
33
+ return self.__repr__()
34
+ #
35
+
36
+ #
37
+
38
+
39
+
40
+
41
+
42
+
@@ -0,0 +1,16 @@
1
+
2
+
3
+
4
+ __author__ = "Jürgen Knauth"
5
+ __version__ = "0.2024.2.18"
6
+
7
+
8
+
9
+ from .dumper import DumpMixin, Dumper, DumpCtx, DEFAULT_DUMPER_SETTINGS, RawValue
10
+
11
+
12
+
13
+
14
+
15
+
16
+
@@ -0,0 +1,767 @@
1
+
2
+
3
+ import chunk
4
+ import typing
5
+ import collections
6
+ import math
7
+ import codecs
8
+ #import datetime
9
+
10
+ from ._Hex import _Hex
11
+ from ._Bits import _Bits
12
+
13
+
14
+
15
+ ################################################################################################################################
16
+ ################################################################################################################################
17
+ ##
18
+ ## Dumpable objects should use the following import:
19
+ ##
20
+ ## import jk_prettyprintobj
21
+ ##
22
+ ## Dumpable objects should then be defined like this:
23
+ ##
24
+ ## class FooBar(SomeBaseClassA,SomeMixinB,jk_prettyprintobj.DumpMixin)
25
+ ## ....
26
+ ## #
27
+ ##
28
+ ## Dumpable objects should then implement one of these two methods:
29
+ ##
30
+ ## def _dump(self, ctx:jk_prettyprintobj.DumpCtx):
31
+ ## ctx.dumpVar(...)
32
+ ## #
33
+ ##
34
+ ## or:
35
+ ##
36
+ ## def _dumpVarNames(self) -> typing.List[str]:
37
+ ## return [
38
+ ## "....",
39
+ ## ]
40
+ ## #
41
+ ##
42
+ ################################################################################################################################
43
+ ################################################################################################################################
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+ class DumperSettings(object):
53
+
54
+ def __init__(self):
55
+ self.showPrimitivesWithType = False
56
+ self.showDictKeysWithType = False
57
+ self.showComplexStructsWithType = False
58
+ self.compactSequenceLengthLimit = 8
59
+ self.compactSequenceItemLengthLimit = 50
60
+ self.bytesLineSize = 32
61
+ self.compactBytesLinesLengthLimit = 32
62
+ #
63
+
64
+ #
65
+
66
+ DEFAULT_DUMPER_SETTINGS = DumperSettings()
67
+
68
+
69
+
70
+
71
+ def _str_shortenText(text:str) -> str:
72
+ if len(text) > 40:
73
+ return text[:40] + "..."
74
+ else:
75
+ return text
76
+ #
77
+
78
+ def _float_roundTo7FractionDigits(data:typing.Union[int,float]) -> float:
79
+ return round(data, 7)
80
+ #
81
+
82
+ def _float_roundTo6FractionDigits(data:typing.Union[int,float]) -> float:
83
+ return round(data, 6)
84
+ #
85
+
86
+ def _float_roundTo5FractionDigits(data:typing.Union[int,float]) -> float:
87
+ return round(data, 5)
88
+ #
89
+
90
+ def _float_roundTo4FractionDigits(data:typing.Union[int,float]) -> float:
91
+ return round(data, 4)
92
+ #
93
+
94
+ def _float_roundTo3FractionDigits(data:typing.Union[int,float]) -> float:
95
+ return round(data, 3)
96
+ #
97
+
98
+ def _float_roundTo2FractionDigits(data:typing.Union[int,float]) -> float:
99
+ return round(data, 2)
100
+ #
101
+
102
+ def _float_roundTo1FractionDigits(data:typing.Union[int,float]) -> float:
103
+ return round(data, 1)
104
+ #
105
+
106
+ def _int_toHex(data:int) -> typing.Union[int,str]:
107
+ if data < 0:
108
+ return data
109
+ return _Hex(data)
110
+ #
111
+
112
+ def _int_toBits(data:int) -> typing.Union[int,str]:
113
+ if data < 0:
114
+ return data
115
+ return _Bits(data)
116
+ #
117
+
118
+ def _byteChunker(data:bytes, chunkSize:int) -> typing.Sequence[bytes]:
119
+ assert isinstance(data, bytes)
120
+ assert isinstance(chunkSize, int)
121
+ assert chunkSize > 0
122
+
123
+ iFrom = 0
124
+ iTo = chunkSize
125
+ while iFrom < len(data):
126
+ yield data[iFrom:iTo]
127
+ iFrom = iTo
128
+ iTo += chunkSize
129
+ #
130
+
131
+ def _x_byteChunkToHex(data:bytes) -> str:
132
+ hexSpanLength = 16 # == 8 bytes for a span
133
+
134
+ s = codecs.encode(data, "hex").decode("ascii")
135
+ chunks = [ s[i:i+hexSpanLength] for i in range(0, len(s), hexSpanLength) ]
136
+ return " ".join(chunks)
137
+ #
138
+
139
+ def _x_byteChunkToASCII(data:bytes) -> str:
140
+ spanLength = 8 # == 8 bytes for a span
141
+
142
+ ret = []
143
+ for i, b in enumerate(data):
144
+ if (i % spanLength) == 0:
145
+ ret.append(" ")
146
+ if 32 <= b <= 127:
147
+ ret.append(chr(b))
148
+ else:
149
+ ret.append(".")
150
+
151
+ return "".join(ret)
152
+ #
153
+
154
+ #
155
+ # Returns chunks of the specified data.
156
+ #
157
+ def _byteChunkerWithOfs(settings:DumperSettings, data:bytes, processorName:str = None) -> typing.Sequence[typing.Tuple[str,str,str]]:
158
+ assert isinstance(settings, DumperSettings)
159
+ assert isinstance(data, bytes)
160
+ chunkSize = settings.bytesLineSize
161
+ assert isinstance(chunkSize, int)
162
+ assert chunkSize > 0
163
+ hexStrPadded = chunkSize*2 + math.ceil(chunkSize / 8) - 1
164
+ if processorName is not None:
165
+ assert isinstance(processorName, str)
166
+
167
+ # ----
168
+
169
+ nTotalLength = len(data)
170
+ nTotalLines = math.ceil(nTotalLength / chunkSize)
171
+ formatStrFragment = None
172
+ formatStrFragmentEllipsis = None
173
+ if nTotalLength <= 256*256:
174
+ formatStrFragment = "{:04x}"
175
+ formatStrFragmentEllipsis = "... "
176
+ elif nTotalLength <= 256*256*256:
177
+ formatStrFragment = "{:06x}"
178
+ formatStrFragmentEllipsis = "... "
179
+ else:
180
+ formatStrFragment = "{:08x}"
181
+ formatStrFragmentEllipsis = "... "
182
+
183
+ # ----
184
+
185
+ skipFrom = -1
186
+ skipTo = -1
187
+ if processorName:
188
+ if processorName == "shorten":
189
+ skipFrom = settings.compactBytesLinesLengthLimit
190
+ skipTo = nTotalLines - 4
191
+ if skipFrom >= skipTo:
192
+ skipFrom = -1
193
+ skipTo = -1
194
+ else:
195
+ raise Exception("No such postprocessor: " + repr(processorName))
196
+
197
+ # ----
198
+
199
+ if skipFrom < 0:
200
+ # direct loop, no addtional if statements
201
+ iFrom = 0
202
+ iTo = chunkSize
203
+ while iFrom < len(data):
204
+ chunk = data[iFrom:iTo]
205
+ yield formatStrFragment.format(iFrom), _x_byteChunkToHex(chunk).ljust(hexStrPadded), _x_byteChunkToASCII(chunk)
206
+ iFrom = iTo
207
+ iTo += chunkSize
208
+ else:
209
+ # loop with if statements
210
+ lineNo = 0
211
+ iFrom = 0
212
+ iTo = chunkSize
213
+ while iFrom < len(data):
214
+ chunk = data[iFrom:iTo]
215
+ if skipFrom <= lineNo <= skipTo:
216
+ if skipFrom == lineNo:
217
+ yield formatStrFragmentEllipsis, "...".ljust(hexStrPadded), "..."
218
+ else:
219
+ yield formatStrFragment.format(iFrom), _x_byteChunkToHex(chunk).ljust(hexStrPadded), _x_byteChunkToASCII(chunk)
220
+ iFrom = iTo
221
+ iTo += chunkSize
222
+ lineNo += 1
223
+ #
224
+
225
+
226
+
227
+
228
+
229
+ POST_PROCESSORS = {
230
+ "shorten": (str, _str_shortenText),
231
+ "hex": (int, _int_toHex),
232
+ "bit": (int, _int_toBits),
233
+ "round1": ((float, int), _float_roundTo1FractionDigits),
234
+ "round2": ((float, int), _float_roundTo2FractionDigits),
235
+ "round3": ((float, int), _float_roundTo3FractionDigits),
236
+ "round4": ((float, int), _float_roundTo4FractionDigits),
237
+ "round5": ((float, int), _float_roundTo5FractionDigits),
238
+ "round6": ((float, int), _float_roundTo6FractionDigits),
239
+ "round7": ((float, int), _float_roundTo7FractionDigits),
240
+
241
+ "str_shorten": (str, _str_shortenText),
242
+ "float_round7": ((float, int), _float_roundTo7FractionDigits),
243
+ "float_round6": ((float, int), _float_roundTo6FractionDigits),
244
+ "float_round5": ((float, int), _float_roundTo5FractionDigits),
245
+ "float_round4": ((float, int), _float_roundTo4FractionDigits),
246
+ "float_round3": ((float, int), _float_roundTo3FractionDigits),
247
+ "float_round2": ((float, int), _float_roundTo2FractionDigits),
248
+ "float_round1": ((float, int), _float_roundTo1FractionDigits),
249
+ "int_hex": (int, _int_toHex),
250
+ "int_bit": (int, _int_toBits),
251
+ }
252
+
253
+
254
+
255
+
256
+ class _Omitted:
257
+ pass
258
+ #
259
+
260
+ class RawValue:
261
+
262
+ def __init__(self, text:str) -> None:
263
+ self.text = text
264
+ #
265
+
266
+ #
267
+
268
+ _OMITTED = _Omitted()
269
+
270
+
271
+
272
+
273
+
274
+ class DumpCtx(object):
275
+
276
+ _TYPE_MAP = {} # type -> function
277
+
278
+ def __init__(self, s:DumperSettings, outputLines:list, exitAppend:str, prefix:str):
279
+ self.__s = s
280
+ self.outputLines = outputLines
281
+ self.__exitAppend = exitAppend
282
+ self.prefix = prefix
283
+ #
284
+
285
+ ################################################################################################################################
286
+ #### Methods that should be called by implementors
287
+ ################################################################################################################################
288
+
289
+ #
290
+ # if you implement `void _dump(DumpCtx ctx)` invoke this method to dump a specific variable explicitely.
291
+ #
292
+ def dumpVar(self, varName:str, value, processorName:str = None) -> None:
293
+ self._dumpX(varName + " = ", value, processorName)
294
+ #
295
+
296
+ def dumpVarRaw(self, varName:str, value:RawValue) -> None:
297
+ self._dumpX(varName + " = ", value.text)
298
+ #
299
+
300
+ #
301
+ # This method is invoked if an object implements _dumpVarNames()
302
+ #
303
+ def dumpVars(self, caller, *args):
304
+ if len(args) == 0:
305
+ if hasattr(caller, "_dumpVarNames"):
306
+ varNames = caller._dumpVarNames()
307
+ assert isinstance(varNames, (list, tuple))
308
+ else:
309
+ raise Exception("Specify either variable names or a list of variables to dump!")
310
+
311
+ elif len(args) == 1:
312
+ if isinstance(args[0], str):
313
+ varNames = args
314
+ elif isinstance(args[0], (tuple, list)):
315
+ varNames = args[0]
316
+ else:
317
+ raise Exception("Unexpected data in args: " + repr(args))
318
+
319
+ else:
320
+ varNames = args
321
+
322
+ for varName in varNames:
323
+ assert isinstance(varName, str)
324
+
325
+ processorName = None
326
+ pos = varName.find(":")
327
+ if pos == 0:
328
+ raise Exception()
329
+ elif pos > 0:
330
+ processorName = varName[pos+1:]
331
+ varName = varName[:pos]
332
+
333
+ value = getattr(caller, varName)
334
+ self._dumpX(varName + " = ", value, processorName)
335
+ #
336
+
337
+ ################################################################################################################################
338
+ #### Dispatcher method
339
+ ################################################################################################################################
340
+
341
+ #
342
+ # This method outputs a value (recursively).
343
+ # To achieve this this method analyses the data type of the specified value and invokes individual type processing methods if available.
344
+ #
345
+ def _dumpX(self, extraPrefix:str, value, processorName:str = None):
346
+ if value is None:
347
+ self._dumpPrimitive(extraPrefix, None, processorName)
348
+ return
349
+
350
+ # is it a raw value?
351
+
352
+ if isinstance(value, RawValue):
353
+ if processorName is not None:
354
+ raise Exception("Raw values can not have processors.")
355
+ self._dumpRawValue(extraPrefix, value)
356
+ return
357
+
358
+ # is it one of our types?
359
+
360
+ t = type(value)
361
+ m = DumpCtx._TYPE_MAP.get(t)
362
+ if m:
363
+ m(self, extraPrefix, value, processorName)
364
+ return
365
+
366
+ # is it an object with a DumpMixin?
367
+
368
+ if isinstance(value, DumpMixin):
369
+ self._dumpObj(extraPrefix, value, processorName)
370
+ return
371
+
372
+ # is it derived from on of our types?
373
+
374
+ for storedT, m in DumpCtx._TYPE_MAP.items():
375
+ if isinstance(value, storedT):
376
+ m(self, extraPrefix, value, processorName)
377
+ return
378
+
379
+ # fallback
380
+
381
+ self.outputLines.append(self.prefix + extraPrefix + repr(value))
382
+ #
383
+
384
+ ################################################################################################################################
385
+ #### Type specific dump methods
386
+ ################################################################################################################################
387
+
388
+ def _isDumpableObj(self, obj):
389
+ if hasattr(obj, "_dump"):
390
+ return True
391
+ if hasattr(obj, "_dumpVarNames"):
392
+ return True
393
+ return False
394
+ #
395
+
396
+ #
397
+ # Dump the specified object.
398
+ #
399
+ # @param str processorName (optional) The name of an output processor.
400
+ # Supports: "shorten"
401
+ #
402
+ def _dumpObj(self, extraPrefix:str, value:object, processorName:str = None):
403
+ if processorName == "shorten":
404
+ self.outputLines.append(self.prefix + extraPrefix + "<" + value.__class__.__name__ + "(...)>")
405
+
406
+ else:
407
+ self.outputLines.append(self.prefix + extraPrefix + "<" + value.__class__.__name__ + "(")
408
+
409
+ ctx = DumpCtx(self.__s, self.outputLines, None, self.prefix + "\t")
410
+ with ctx as ctx2:
411
+ if hasattr(value, "_dump"):
412
+ value._dump(ctx2)
413
+ elif hasattr(value, "_dumpVarNames"):
414
+ ctx2.dumpVars(value)
415
+ else:
416
+ raise Exception("Improper object encountered for prettyprinting: " + type(value).__name__)
417
+
418
+ self.outputLines.append(self.prefix + ")>")
419
+ #
420
+
421
+ #
422
+ # Dump the specified dictionary.
423
+ #
424
+ def _dumpDict(self, extraPrefix:str, value:dict, processorName:str = None):
425
+ e = (type(value).__name__ + ":") if self.__s.showComplexStructsWithType else ""
426
+
427
+ self.outputLines.append(self.prefix + extraPrefix + e + "{")
428
+
429
+ ctx = DumpCtx(self.__s, self.outputLines, None, self.prefix + "\t")
430
+ with ctx as ctx2:
431
+ for k, v in value.items():
432
+ if processorName == "omitValues":
433
+ v = _OMITTED
434
+ ctx2._dumpX(self._dictKeyToStr(k) + " : ", v)
435
+ self.outputLines[-1] += ","
436
+
437
+ self.outputLines.append(self.prefix + "}")
438
+ #
439
+
440
+ def _dumpOrderedDict(self, extraPrefix:str, value:dict, processorName:str = None):
441
+ e = (type(value).__name__ + ":") if self.__s.showComplexStructsWithType else ""
442
+
443
+ self.outputLines.append(self.prefix + extraPrefix + e + "{")
444
+
445
+ ctx = DumpCtx(self.__s, self.outputLines, None, self.prefix + "\t")
446
+ with ctx as ctx2:
447
+ for k, v in value.items():
448
+ ctx2._dumpX(self._dictKeyToStr(k) + " : ", v)
449
+ self.outputLines[-1] += ","
450
+
451
+ self.outputLines.append(self.prefix + "}")
452
+ #
453
+
454
+ #
455
+ # Dump the specified list.
456
+ #
457
+ def _dumpList(self, extraPrefix:str, value:list, processorName:str = None):
458
+ e = (type(value).__name__ + ":") if self.__s.showComplexStructsWithType else ""
459
+
460
+ if self._canCompactSequence(value):
461
+ self.outputLines.append(self.prefix + extraPrefix + e + "[ " + self._compactSequence(value, processorName) + " ]")
462
+
463
+ else:
464
+ self.outputLines.append(self.prefix + extraPrefix + e + "[")
465
+
466
+ ctx = DumpCtx(self.__s, self.outputLines, None, self.prefix + "\t")
467
+ with ctx as ctx2:
468
+ for vItem in value:
469
+ ctx2._dumpX("", vItem, processorName)
470
+ self.outputLines[-1] += ","
471
+
472
+ self.outputLines.append(self.prefix + "]")
473
+ #
474
+
475
+ #
476
+ # Dump the specified byte array.
477
+ #
478
+ def _dumpBytes(self, extraPrefix:str, value:bytes, processorName:str = None):
479
+ e = (type(value).__name__ + ":") if self.__s.showComplexStructsWithType else ""
480
+
481
+ if len(value) <= self.__s.bytesLineSize:
482
+ self.outputLines.append(self.prefix + extraPrefix + e + repr(value))
483
+
484
+ else:
485
+ self.outputLines.append(self.prefix + extraPrefix + e + "<")
486
+
487
+ for sOfs, chunk, sAscii in _byteChunkerWithOfs(self.__s, value, processorName):
488
+ self.outputLines.append(self.prefix + "\t" + sOfs + " " + chunk + " " + sAscii)
489
+
490
+ if len(value) == 1:
491
+ self.outputLines.append(self.prefix + "\ttotal: 1 byte")
492
+ else:
493
+ self.outputLines.append(self.prefix + "\ttotal: " + str(len(value)) + " bytes")
494
+
495
+ self.outputLines.append(self.prefix + ">")
496
+ #
497
+
498
+ #
499
+ # Dump the specified tuple.
500
+ #
501
+ def _dumpTuple(self, extraPrefix:str, value:set, processorName:str = None):
502
+ e = (type(value).__name__ + ":") if self.__s.showComplexStructsWithType else ""
503
+
504
+ if self._canCompactSequence(value):
505
+ self.outputLines.append(self.prefix + extraPrefix + e + "( " + self._compactSequence(value, processorName) + " )")
506
+
507
+ else:
508
+ self.outputLines.append(self.prefix + extraPrefix + e + "(")
509
+
510
+ ctx = DumpCtx(self.__s, self.outputLines, None, self.prefix + "\t")
511
+ with ctx as ctx2:
512
+ for vItem in value:
513
+ ctx2._dumpX("", vItem, processorName)
514
+ self.outputLines[-1] += ","
515
+
516
+ self.outputLines.append(self.prefix + ")")
517
+ #
518
+
519
+ #
520
+ # Dump the specified set.
521
+ #
522
+ def _dumpSet(self, extraPrefix:str, value:set, processorName:str = None):
523
+ e = (type(value).__name__ + ":") if self.__s.showComplexStructsWithType else ""
524
+
525
+ sequence = sorted(value)
526
+
527
+ if self._canCompactSequence(sequence):
528
+ self.outputLines.append(self.prefix + extraPrefix + e + "{ " + self._compactSequence(sequence, processorName) + " }")
529
+
530
+ else:
531
+ self.outputLines.append(self.prefix + extraPrefix + e + "{")
532
+
533
+ ctx = DumpCtx(self.__s, self.outputLines, None, self.prefix + "\t")
534
+ with ctx as ctx2:
535
+ for vItem in sequence:
536
+ ctx2._dumpX("", vItem, processorName)
537
+ self.outputLines[-1] += ","
538
+
539
+ self.outputLines.append(self.prefix + "}")
540
+ #
541
+
542
+ #
543
+ # Dump the specified frozen set.
544
+ #
545
+ def _dumpFrozenSet(self, extraPrefix:str, value:frozenset, processorName:str = None):
546
+ e = (type(value).__name__ + ":") if self.__s.showComplexStructsWithType else ""
547
+
548
+ sequence = sorted(value)
549
+
550
+ if self._canCompactSequence(sequence):
551
+ self.outputLines.append(self.prefix + extraPrefix + e + "{ " + self._compactSequence(sequence, processorName) + " }")
552
+
553
+ else:
554
+ self.outputLines.append(self.prefix + extraPrefix + e + "{")
555
+
556
+ ctx = DumpCtx(self.__s, self.outputLines, None, self.prefix + "\t")
557
+ with ctx as ctx2:
558
+ for vItem in sequence:
559
+ ctx2._dumpX("", vItem, processorName)
560
+ self.outputLines[-1] += ","
561
+
562
+ self.outputLines.append(self.prefix + "}")
563
+ #
564
+
565
+ def _dumpPrimitive(self, extraPrefix:str, value, processorName:str = None):
566
+ self.outputLines.append(self.prefix + extraPrefix + self._primitiveValueToStr(value, processorName))
567
+ #
568
+
569
+ def _dumpRawValue(self, extraPrefix:str, value:RawValue):
570
+ self.outputLines.append(self.prefix + extraPrefix + value.text)
571
+ #
572
+
573
+ def _dumpOmitted(self, extraPrefix:str, value, processorName:str = None):
574
+ self.outputLines.append(self.prefix + extraPrefix + "...")
575
+ #
576
+
577
+ ################################################################################################################################
578
+ #### Helper methods
579
+ ################################################################################################################################
580
+
581
+ def _canCompactSequence(self, someSequence):
582
+ if len(someSequence) > self.__s.compactSequenceLengthLimit:
583
+ return False
584
+ for v in someSequence:
585
+ if v is not None:
586
+ if type(v) not in [ int, str, float, bool ]:
587
+ return False
588
+ if isinstance(v, str):
589
+ if len(v) > self.__s.compactSequenceItemLengthLimit:
590
+ return False
591
+ return True
592
+ #
593
+
594
+ def _compactSequence(self, someSequence, processorName:str = None) -> str:
595
+ ret = []
596
+ for v in someSequence:
597
+ ret.append(self._primitiveValueToStr(v, processorName))
598
+ return ", ".join(ret)
599
+ #
600
+
601
+ #
602
+ # Converts a single dictionary key to str
603
+ #
604
+ def _dictKeyToStr(self, value):
605
+ if value is None:
606
+ return "(null)"
607
+ else:
608
+ if self.__s.showDictKeysWithType:
609
+ if isinstance(value, float):
610
+ return "float:" + repr(value)
611
+ elif isinstance(value, bool):
612
+ return "bool:" + repr(value)
613
+ elif isinstance(value, int):
614
+ return "int:" + repr(value)
615
+ else:
616
+ return type(value).__name__ + ":" + repr(value)
617
+ else:
618
+ return repr(value)
619
+ #
620
+
621
+ #
622
+ # Converts a single primitive value to str
623
+ #
624
+ def _primitiveValueToStr(self, value, processorName:str = None):
625
+ if value is None:
626
+ return "(null)"
627
+ else:
628
+ # process value before converting it to str
629
+ if processorName:
630
+ if processorName not in POST_PROCESSORS:
631
+ raise Exception("No such postprocessor: " + repr(processorName))
632
+ postProcessorTypeCompatibility, postProcessor = POST_PROCESSORS[processorName]
633
+ if isinstance(value, postProcessorTypeCompatibility):
634
+ value = postProcessor(value)
635
+
636
+ # return value as str
637
+ if self.__s.showPrimitivesWithType:
638
+ if isinstance(value, float):
639
+ return "float:" + repr(value)
640
+ elif isinstance(value, bool):
641
+ return "bool:" + repr(value)
642
+ elif isinstance(value, int):
643
+ return "int:" + repr(value)
644
+ elif isinstance(value, str):
645
+ return "str:" + repr(value)
646
+ else:
647
+ return type(value).__name__ + ":" + repr(value)
648
+ else:
649
+ if isinstance(value, str):
650
+ return repr(value)
651
+ else:
652
+ return repr(value)
653
+ #
654
+
655
+ ################################################################################################################################
656
+ #### Magic methods
657
+ ################################################################################################################################
658
+
659
+ def __enter__(self):
660
+ return self
661
+ #
662
+
663
+ def __exit__(self, *args):
664
+ if self.__exitAppend:
665
+ self.outputLines.append(self.__exitAppend)
666
+ return False
667
+ #
668
+
669
+ #
670
+
671
+
672
+
673
+
674
+ #
675
+ # Now let's register the types
676
+ #
677
+ if not DumpCtx._TYPE_MAP:
678
+ DumpCtx._TYPE_MAP[bytes] = DumpCtx._dumpBytes
679
+ DumpCtx._TYPE_MAP[set] = DumpCtx._dumpSet
680
+ DumpCtx._TYPE_MAP[frozenset] = DumpCtx._dumpFrozenSet
681
+ DumpCtx._TYPE_MAP[tuple] = DumpCtx._dumpTuple
682
+ DumpCtx._TYPE_MAP[list] = DumpCtx._dumpList
683
+ DumpCtx._TYPE_MAP[collections.OrderedDict] = DumpCtx._dumpOrderedDict
684
+ DumpCtx._TYPE_MAP[dict] = DumpCtx._dumpDict
685
+ DumpCtx._TYPE_MAP[int] = DumpCtx._dumpPrimitive
686
+ DumpCtx._TYPE_MAP[float] = DumpCtx._dumpPrimitive
687
+ DumpCtx._TYPE_MAP[bool] = DumpCtx._dumpPrimitive
688
+ DumpCtx._TYPE_MAP[str] = DumpCtx._dumpPrimitive
689
+ DumpCtx._TYPE_MAP[_Omitted] = DumpCtx._dumpOmitted
690
+
691
+
692
+
693
+
694
+
695
+
696
+ class Dumper(object):
697
+
698
+ def __init__(self):
699
+ self.__outputLines = []
700
+ self.__contexts = []
701
+ self.__currentPrefix = ""
702
+ #
703
+
704
+ def createContext(self, obj, prefix:str = None):
705
+ if prefix is not None:
706
+ assert isinstance(prefix, str)
707
+ else:
708
+ prefix = ""
709
+
710
+ return DumpCtx(DEFAULT_DUMPER_SETTINGS, self.__outputLines, None, prefix)
711
+ #
712
+
713
+ def print(self, printFunc = None):
714
+ if printFunc is None:
715
+ printFunc = print
716
+ else:
717
+ assert callable(printFunc)
718
+
719
+ for line in self.__outputLines:
720
+ printFunc(line)
721
+ #
722
+
723
+ def toStr(self) -> str:
724
+ return "\n".join(self.__outputLines)
725
+ #
726
+
727
+ #
728
+
729
+
730
+
731
+
732
+
733
+ class DumpMixin:
734
+
735
+ __slots__ = tuple()
736
+
737
+ def dump(self, prefix:str = None, printFunc = None) -> None:
738
+ dumper = Dumper()
739
+ with dumper.createContext(self, prefix) as dumper2:
740
+ if not dumper2._isDumpableObj(self):
741
+ raise Exception("Improper object encountered for prettyprinting: " + self.__class__.__name__ + " - Either implement _dump(ctx:DumpCtx) or _dumpVarNames()!")
742
+ dumper2._dumpObj("", self)
743
+ dumper.print(printFunc)
744
+ #
745
+
746
+ def dumpToStr(self, prefix:str = None) -> str:
747
+ dumper = Dumper()
748
+ with dumper.createContext(self, prefix) as dumper2:
749
+ if not dumper2._isDumpableObj(self):
750
+ raise Exception("Improper object encountered for prettyprinting: " + self.__class__.__name__ + " - Either implement _dump(ctx:DumpCtx) or _dumpVarNames()!")
751
+ dumper2._dumpObj("", self)
752
+ return dumper.toStr()
753
+ #
754
+
755
+ #
756
+
757
+
758
+
759
+
760
+
761
+
762
+
763
+
764
+
765
+
766
+
767
+
@@ -0,0 +1,54 @@
1
+ [build-system]
2
+ requires = ["flit_core >=3.2,<4"]
3
+ build-backend = "flit_core.buildapi"
4
+
5
+ [project]
6
+ name = "jk_prettyprintobj"
7
+ dynamic = [ "version" ]
8
+ authors = [
9
+ { name = "Jürgen Knauth", email = "pubsrc@binary-overflow.de" },
10
+ ]
11
+ maintainers = [
12
+ { name = "Jürgen Knauth", email = "pubsrc@binary-overflow.de" },
13
+ ]
14
+ description = "This python module provides a mixin for creating pretty debugging output for objects. This is especially useful for semi-complex data structures."
15
+ readme = "README.md"
16
+ requires-python = ">=3.8"
17
+ keywords = [
18
+ "pretty-print",
19
+ "debugging",
20
+ "debug",
21
+ ]
22
+ license = { text = "Apache2" }
23
+ classifiers = [
24
+ "Development Status :: 5 - Production/Stable",
25
+ "License :: OSI Approved :: Apache Software License",
26
+ "Programming Language :: Python :: 3",
27
+ "Topic :: Software Development :: Testing",
28
+ ]
29
+ dependencies = [
30
+ ]
31
+
32
+ #[project.urls]
33
+ #Homepage = "https://example.com"
34
+ #Documentation = "https://readthedocs.org"
35
+ #Repository = "https://github.com/me/spam.git"
36
+ #Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md"
37
+
38
+ [tool.flit.sdist]
39
+ exclude = [
40
+ "bin/",
41
+ "build/",
42
+ "dist/",
43
+ "sdist/",
44
+ "*.egg-info",
45
+ "*.OLD",
46
+ "setup.cfg",
47
+ ]
48
+
49
+ #[project.scripts]
50
+
51
+
52
+
53
+
54
+