jtil 0.1.0__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.
- jtil-0.1.0/PKG-INFO +6 -0
- jtil-0.1.0/README.md +0 -0
- jtil-0.1.0/pyproject.toml +7 -0
- jtil-0.1.0/setup.cfg +4 -0
- jtil-0.1.0/src/jtil/__init__.py +0 -0
- jtil-0.1.0/src/jtil/separator.py +71 -0
- jtil-0.1.0/src/jtil/visualize.py +157 -0
- jtil-0.1.0/src/jtil.egg-info/PKG-INFO +6 -0
- jtil-0.1.0/src/jtil.egg-info/SOURCES.txt +9 -0
- jtil-0.1.0/src/jtil.egg-info/dependency_links.txt +1 -0
- jtil-0.1.0/src/jtil.egg-info/top_level.txt +1 -0
jtil-0.1.0/PKG-INFO
ADDED
jtil-0.1.0/README.md
ADDED
|
File without changes
|
jtil-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Install editable package `util` with `uv pip install -e .` command.
|
|
3
|
+
|
|
4
|
+
We have setup the pyproject.toml for you, you can use:
|
|
5
|
+
|
|
6
|
+
```shell
|
|
7
|
+
cd Python-Package-Tutorial
|
|
8
|
+
uv pip install -e .
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
to install editable package `util` and all its subpackages, otherwise you won't be able to use the `Separator` class in the `separator.py` module in other module like `import_tutorial_d/import_tutorial.py`.
|
|
12
|
+
|
|
13
|
+
The essential code of the `pyproject.toml` file is:
|
|
14
|
+
|
|
15
|
+
```toml
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["setuptools"]
|
|
18
|
+
build-backend = "setuptools.build_meta"
|
|
19
|
+
|
|
20
|
+
[tool.setuptools.packages.find]
|
|
21
|
+
# tell Python to find the packages in the current directory
|
|
22
|
+
where = ["."]
|
|
23
|
+
# tell Python to find the util package specifically
|
|
24
|
+
include = ["util*"] # `*` means also include all subpackages in the util package
|
|
25
|
+
```
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from enum import Enum
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BColors(Enum):
|
|
32
|
+
"""text color for terminal font"""
|
|
33
|
+
|
|
34
|
+
BOLD = "\033[1m"
|
|
35
|
+
UNDERLINE = "\033[4m"
|
|
36
|
+
|
|
37
|
+
ENDC = "\033[0m"
|
|
38
|
+
BLACK = "\033[30m"
|
|
39
|
+
RED = "\033[31m"
|
|
40
|
+
GREEN = "\033[32m"
|
|
41
|
+
YELLOW = "\033[33m"
|
|
42
|
+
BLUE = "\033[34m"
|
|
43
|
+
MAGENTA = "\033[35m"
|
|
44
|
+
CYAN = "\033[36m"
|
|
45
|
+
WHITE = "\033[37m"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Separator:
|
|
49
|
+
"""Separator in the terminal"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, title: str, level: int = 3, color: BColors = BColors.GREEN):
|
|
52
|
+
assert level >= 1 and level <= 6, "Level must be between 1 and 6"
|
|
53
|
+
n_symbol = (6 - level) * 2 # 6 levels in total as in markdown
|
|
54
|
+
space = " " if level != 6 else ""
|
|
55
|
+
print(
|
|
56
|
+
f"{color.value}{'=' * n_symbol}{space}{title}{space}{'=' * n_symbol}{BColors.ENDC.value}",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def cprint(*value: object, color: BColors = BColors.GREEN, **kwargs) -> None:
|
|
61
|
+
"""Print text with color."""
|
|
62
|
+
print(color.value, end="")
|
|
63
|
+
print(*value, **kwargs)
|
|
64
|
+
print(BColors.ENDC.value, end="")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
Separator("Test Separator", color=BColors.RED)
|
|
69
|
+
Separator("Test Separator", level=1)
|
|
70
|
+
Separator("Test Separator", level=6)
|
|
71
|
+
Separator("Test Separator", level=5, color=BColors.BLUE)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
def list_tree(
|
|
2
|
+
obj, name=None, prefix="", is_last=True, formatter: dict[type, callable] = None
|
|
3
|
+
):
|
|
4
|
+
"""
|
|
5
|
+
Recursively print nested lists and tuples in a tree-like structure.
|
|
6
|
+
Groups consecutive elements of the same type and displays their index.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
obj: The object to print.
|
|
10
|
+
name: The key or index name of the current node.
|
|
11
|
+
prefix: String used for structural indentation.
|
|
12
|
+
is_last: Boolean flag indicating if it's the last child in a branch.
|
|
13
|
+
formatter: Dictionary of type -> lambda or function taking `obj` as input.
|
|
14
|
+
It should return a custom string for specific types, or None to fallback to repr().
|
|
15
|
+
"""
|
|
16
|
+
# Determine the string representation of the current node's type
|
|
17
|
+
obj_type = type(obj).__name__
|
|
18
|
+
|
|
19
|
+
# Build the node label
|
|
20
|
+
label_parts = []
|
|
21
|
+
if name:
|
|
22
|
+
label_parts.append(f"{name}:")
|
|
23
|
+
label_parts.append(f"<{obj_type}>")
|
|
24
|
+
|
|
25
|
+
# Display length for containers, or truncated value for basic data types
|
|
26
|
+
if isinstance(obj, (list, tuple)):
|
|
27
|
+
label_parts.append(f"(len={len(obj)})")
|
|
28
|
+
else:
|
|
29
|
+
# Apply custom formatter if provided
|
|
30
|
+
val_str = None
|
|
31
|
+
|
|
32
|
+
if formatter is not None:
|
|
33
|
+
format_fn = formatter.get(type(obj))
|
|
34
|
+
if format_fn is not None:
|
|
35
|
+
try:
|
|
36
|
+
# Attempt to use the lambda/function to format the object
|
|
37
|
+
val_str = format_fn(obj)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
print(e)
|
|
40
|
+
|
|
41
|
+
# Fallback to standard __repr__ if formatter returns None or fails
|
|
42
|
+
if val_str is None:
|
|
43
|
+
val_str = repr(obj)
|
|
44
|
+
|
|
45
|
+
# Truncate excessively long strings for better terminal visualization
|
|
46
|
+
if len(val_str) > 50:
|
|
47
|
+
val_str = val_str[:47] + "..."
|
|
48
|
+
label_parts.append(val_str)
|
|
49
|
+
|
|
50
|
+
node_label = " ".join(label_parts)
|
|
51
|
+
|
|
52
|
+
# Print the current node with appropriate tree branches
|
|
53
|
+
if not prefix:
|
|
54
|
+
print(node_label)
|
|
55
|
+
else:
|
|
56
|
+
connector = "└── " if is_last else "├── "
|
|
57
|
+
print(f"{prefix}{connector}{node_label}")
|
|
58
|
+
|
|
59
|
+
# Recursively process children if the current object is a list or tuple
|
|
60
|
+
if isinstance(obj, (list, tuple)):
|
|
61
|
+
# Calculate the prefix for the next depth level
|
|
62
|
+
child_prefix = prefix + (" " if is_last else "│ ")
|
|
63
|
+
|
|
64
|
+
# Group consecutive child elements by their data type
|
|
65
|
+
groups = []
|
|
66
|
+
for idx, item in enumerate(obj):
|
|
67
|
+
item_type = type(item)
|
|
68
|
+
if not groups:
|
|
69
|
+
groups.append(
|
|
70
|
+
{
|
|
71
|
+
"type": item_type,
|
|
72
|
+
"first_item": item,
|
|
73
|
+
"count": 1,
|
|
74
|
+
"start_idx": idx,
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
elif groups[-1]["type"] == item_type:
|
|
78
|
+
groups[-1]["count"] += 1
|
|
79
|
+
else:
|
|
80
|
+
groups.append(
|
|
81
|
+
{
|
|
82
|
+
"type": item_type,
|
|
83
|
+
"first_item": item,
|
|
84
|
+
"count": 1,
|
|
85
|
+
"start_idx": idx,
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Iterate and print the grouped children
|
|
90
|
+
for i, group in enumerate(groups):
|
|
91
|
+
is_last_group = i == len(groups) - 1
|
|
92
|
+
start_idx = group["start_idx"]
|
|
93
|
+
|
|
94
|
+
if group["count"] == 1:
|
|
95
|
+
# Only one element of this type, print single element with its exact index
|
|
96
|
+
list_tree(
|
|
97
|
+
group["first_item"],
|
|
98
|
+
name=f"[{start_idx}]",
|
|
99
|
+
prefix=child_prefix,
|
|
100
|
+
is_last=is_last_group,
|
|
101
|
+
formatter=formatter,
|
|
102
|
+
)
|
|
103
|
+
else:
|
|
104
|
+
# Print the first element of the group. It is explicitly NOT the last branch,
|
|
105
|
+
# because we still need to append the "... + n more" summary node below it.
|
|
106
|
+
list_tree(
|
|
107
|
+
group["first_item"],
|
|
108
|
+
name=f"[{start_idx}]",
|
|
109
|
+
prefix=child_prefix,
|
|
110
|
+
is_last=False,
|
|
111
|
+
formatter=formatter,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Calculate the index range for the folded elements
|
|
115
|
+
next_idx = start_idx + 1
|
|
116
|
+
end_idx = start_idx + group["count"] - 1
|
|
117
|
+
|
|
118
|
+
# Format the summary index label (e.g., [1] or [1..3])
|
|
119
|
+
if next_idx == end_idx:
|
|
120
|
+
summary_idx_label = f"[{next_idx}]"
|
|
121
|
+
else:
|
|
122
|
+
summary_idx_label = f"[{next_idx}..{end_idx}]"
|
|
123
|
+
|
|
124
|
+
# Print the summary node for the remaining identical types
|
|
125
|
+
summary_connector = "└── " if is_last_group else "├── "
|
|
126
|
+
summary_label = f"{summary_idx_label} ... +{group['count'] - 1} more <{group['type'].__name__}>"
|
|
127
|
+
print(f"{child_prefix}{summary_connector}{summary_label}")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if __name__ == "__main__":
|
|
131
|
+
from separator import Separator
|
|
132
|
+
|
|
133
|
+
Separator("Basic Test")
|
|
134
|
+
list_tree(1)
|
|
135
|
+
list_tree([1, 2, 3])
|
|
136
|
+
list_tree([1, [41, 42]])
|
|
137
|
+
|
|
138
|
+
Separator("Param Test")
|
|
139
|
+
list_tree([1, [41, 42]], name="AAA")
|
|
140
|
+
list_tree([1, [41, 42]], prefix="@")
|
|
141
|
+
list_tree([1, [41, 42]], name="AAA", prefix="@")
|
|
142
|
+
|
|
143
|
+
Separator("Class Type Test")
|
|
144
|
+
|
|
145
|
+
class TestData:
|
|
146
|
+
def __init__(self, data: int, shape=(1,)):
|
|
147
|
+
self.data = data
|
|
148
|
+
self.shape = shape
|
|
149
|
+
|
|
150
|
+
def __repr__(self):
|
|
151
|
+
return f"A TestData Object: {self.data}"
|
|
152
|
+
|
|
153
|
+
list_tree([TestData(42), 2])
|
|
154
|
+
list_tree(
|
|
155
|
+
[(99, 100), TestData(43, shape=(2, 2)), (101, 102), 2, 3, 4],
|
|
156
|
+
formatter={TestData: lambda x: f"TestData(shape={x.shape})"},
|
|
157
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
jtil
|