nano-dev-utils 1.4.0__py3-none-any.whl → 1.5.2__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 nano-dev-utils might be problematic. Click here for more details.
- nano_dev_utils/common.py +51 -17
- nano_dev_utils/file_tree_display.py +187 -86
- nano_dev_utils/timers.py +83 -49
- nano_dev_utils-1.5.2.dist-info/METADATA +372 -0
- nano_dev_utils-1.5.2.dist-info/RECORD +10 -0
- nano_dev_utils-1.4.0.dist-info/METADATA +0 -270
- nano_dev_utils-1.4.0.dist-info/RECORD +0 -10
- {nano_dev_utils-1.4.0.dist-info → nano_dev_utils-1.5.2.dist-info}/WHEEL +0 -0
- {nano_dev_utils-1.4.0.dist-info → nano_dev_utils-1.5.2.dist-info}/licenses/LICENSE +0 -0
nano_dev_utils/common.py
CHANGED
|
@@ -61,7 +61,9 @@ def encode_dict(input_dict: dict) -> bytes:
|
|
|
61
61
|
return b' '.join(str(v).encode() for v in input_dict.values())
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
def str2file(
|
|
64
|
+
def str2file(
|
|
65
|
+
content: AnyStr, filepath: str, mode: str = 'w', enc: str = 'utf-8'
|
|
66
|
+
) -> None:
|
|
65
67
|
"""Simply save file directly from any string content.
|
|
66
68
|
|
|
67
69
|
Args:
|
|
@@ -71,6 +73,10 @@ def str2file(content: AnyStr, filepath: str, mode: str = 'w', enc: str = 'utf-8'
|
|
|
71
73
|
enc (str): Encoding used in text modes; ignored in binary modes. Defaults to 'utf-8'.
|
|
72
74
|
"""
|
|
73
75
|
out_file_path = Path(filepath)
|
|
76
|
+
|
|
77
|
+
if not out_file_path.parent.exists():
|
|
78
|
+
out_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
|
|
74
80
|
try:
|
|
75
81
|
if 'b' in mode:
|
|
76
82
|
with out_file_path.open(mode) as f:
|
|
@@ -86,32 +92,48 @@ def str2file(content: AnyStr, filepath: str, mode: str = 'w', enc: str = 'utf-8'
|
|
|
86
92
|
|
|
87
93
|
|
|
88
94
|
class PredicateBuilder:
|
|
89
|
-
def build_predicate(
|
|
95
|
+
def build_predicate(
|
|
96
|
+
self, allow: FilterSet, block: FilterSet
|
|
97
|
+
) -> Callable[[str], bool]:
|
|
90
98
|
"""Build a memory-efficient predicate function."""
|
|
91
99
|
compile_patts = self.compile_patts
|
|
92
100
|
|
|
93
101
|
allow_lits, allow_patts = compile_patts(allow)
|
|
94
102
|
block_lits, block_patts = compile_patts(block)
|
|
95
103
|
|
|
96
|
-
flag = (
|
|
97
|
-
|
|
104
|
+
flag = (
|
|
105
|
+
1 if allow_lits or allow_patts else 0,
|
|
106
|
+
1 if block_lits or block_patts else 0,
|
|
107
|
+
)
|
|
98
108
|
|
|
99
109
|
match flag: # (allow, block)
|
|
100
110
|
case (0, 0):
|
|
101
111
|
return lambda name: True
|
|
102
112
|
|
|
103
113
|
case (0, 1):
|
|
104
|
-
return partial(
|
|
105
|
-
|
|
114
|
+
return partial(
|
|
115
|
+
self._match_patt_with_lits,
|
|
116
|
+
name_patts=block_patts,
|
|
117
|
+
name_lits=block_lits,
|
|
118
|
+
negate=True,
|
|
119
|
+
)
|
|
106
120
|
|
|
107
121
|
case (1, 0):
|
|
108
|
-
return partial(
|
|
109
|
-
|
|
122
|
+
return partial(
|
|
123
|
+
self._match_patt_with_lits,
|
|
124
|
+
name_patts=allow_patts,
|
|
125
|
+
name_lits=allow_lits,
|
|
126
|
+
negate=False,
|
|
127
|
+
)
|
|
110
128
|
|
|
111
129
|
case (1, 1):
|
|
112
|
-
return partial(
|
|
113
|
-
|
|
114
|
-
|
|
130
|
+
return partial(
|
|
131
|
+
self._allow_block_predicate,
|
|
132
|
+
allow_lits=allow_lits,
|
|
133
|
+
allow_patts=allow_patts,
|
|
134
|
+
block_lits=block_lits,
|
|
135
|
+
block_patts=block_patts,
|
|
136
|
+
)
|
|
115
137
|
|
|
116
138
|
@staticmethod
|
|
117
139
|
def compile_patts(fs: FilterSet) -> tuple[set[str], list[re.Pattern]]:
|
|
@@ -119,7 +141,7 @@ class PredicateBuilder:
|
|
|
119
141
|
return set(), []
|
|
120
142
|
literals, patterns = set(), []
|
|
121
143
|
for item in fs:
|
|
122
|
-
if
|
|
144
|
+
if '*' in item or '?' in item or '[' in item:
|
|
123
145
|
patterns.append(re.compile(fnmatch.translate(item)))
|
|
124
146
|
else:
|
|
125
147
|
literals.add(item)
|
|
@@ -130,15 +152,27 @@ class PredicateBuilder:
|
|
|
130
152
|
"""Return True if name matches any compiled regex pattern."""
|
|
131
153
|
return any(pat.fullmatch(name) for pat in patterns)
|
|
132
154
|
|
|
133
|
-
def _match_patt_with_lits(
|
|
134
|
-
|
|
155
|
+
def _match_patt_with_lits(
|
|
156
|
+
self,
|
|
157
|
+
name: str,
|
|
158
|
+
*,
|
|
159
|
+
name_lits: set[str],
|
|
160
|
+
name_patts: list[re.Pattern],
|
|
161
|
+
negate: bool = False,
|
|
162
|
+
) -> bool:
|
|
135
163
|
"""Return True if name is in literals or matches any pattern."""
|
|
136
164
|
res = name in name_lits or self._match_patts(name, name_patts)
|
|
137
165
|
return not res if negate else res
|
|
138
166
|
|
|
139
|
-
def _allow_block_predicate(
|
|
140
|
-
|
|
141
|
-
|
|
167
|
+
def _allow_block_predicate(
|
|
168
|
+
self,
|
|
169
|
+
name: str,
|
|
170
|
+
*,
|
|
171
|
+
allow_lits: set[str],
|
|
172
|
+
allow_patts: list[re.Pattern],
|
|
173
|
+
block_lits: set[str],
|
|
174
|
+
block_patts: list[re.Pattern],
|
|
175
|
+
) -> bool:
|
|
142
176
|
"""Return True if name is allowed and not blocked (block takes precedence)."""
|
|
143
177
|
if name in block_lits or self._match_patts(name, block_patts):
|
|
144
178
|
return False
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
import io
|
|
1
2
|
import os
|
|
2
3
|
import re
|
|
3
4
|
|
|
4
5
|
from collections.abc import Generator
|
|
6
|
+
from itertools import chain
|
|
5
7
|
from pathlib import Path
|
|
6
|
-
from typing_extensions import
|
|
8
|
+
from typing_extensions import Callable, Any
|
|
9
|
+
from typing import Iterable
|
|
7
10
|
|
|
8
11
|
from .common import str2file, FilterSet, PredicateBuilder
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
DEFAULT_SFX = '_filetree.txt'
|
|
12
15
|
|
|
13
|
-
STYLES: list[str] = [' ', '-', '—', '_', '*', '>', '<', '+', '.']
|
|
14
16
|
|
|
15
17
|
_NUM_SPLIT = re.compile(r'(\d+)').split
|
|
16
18
|
|
|
@@ -22,22 +24,47 @@ class FileTreeDisplay:
|
|
|
22
24
|
visual representations of directories and files.
|
|
23
25
|
Supports exclusion lists, configurable indentation, and custom prefix styles.
|
|
24
26
|
"""
|
|
27
|
+
|
|
28
|
+
__slots__ = (
|
|
29
|
+
'root_path',
|
|
30
|
+
'filepath',
|
|
31
|
+
'ignore_dirs',
|
|
32
|
+
'ignore_files',
|
|
33
|
+
'include_dirs',
|
|
34
|
+
'include_files',
|
|
35
|
+
'style',
|
|
36
|
+
'indent',
|
|
37
|
+
'files_first',
|
|
38
|
+
'skip_sorting',
|
|
39
|
+
'sort_key_name',
|
|
40
|
+
'reverse',
|
|
41
|
+
'custom_sort',
|
|
42
|
+
'save2file',
|
|
43
|
+
'printout',
|
|
44
|
+
'style_dict',
|
|
45
|
+
'sort_keys',
|
|
46
|
+
'pb',
|
|
47
|
+
'dir_filter',
|
|
48
|
+
'file_filter',
|
|
49
|
+
)
|
|
50
|
+
|
|
25
51
|
def __init__(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
self,
|
|
53
|
+
root_dir: str | None = None,
|
|
54
|
+
filepath: str | None = None,
|
|
55
|
+
ignore_dirs: FilterSet = None,
|
|
56
|
+
ignore_files: FilterSet = None,
|
|
57
|
+
include_dirs: FilterSet = None,
|
|
58
|
+
include_files: FilterSet = None,
|
|
59
|
+
style: str = 'classic',
|
|
60
|
+
indent: int = 2,
|
|
61
|
+
files_first: bool = False,
|
|
62
|
+
skip_sorting: bool = False,
|
|
63
|
+
sort_key_name: str = 'natural',
|
|
64
|
+
reverse: bool = False,
|
|
65
|
+
custom_sort: Callable[[str], Any] | None = None,
|
|
66
|
+
save2file: bool = True,
|
|
67
|
+
printout: bool = False,
|
|
41
68
|
) -> None:
|
|
42
69
|
"""Initialize the FileTreeDisplay instance.
|
|
43
70
|
|
|
@@ -51,8 +78,8 @@ class FileTreeDisplay:
|
|
|
51
78
|
style (str): Character(s) used to represent hierarchy levels. Defaults to " ".
|
|
52
79
|
indent (int): Number of style characters used per hierarchy level. Defaults to 2.
|
|
53
80
|
files_first (bool): Determines whether to list files first. Defaults to False.
|
|
81
|
+
skip_sorting (bool): Skip sorting directly, even if configured.
|
|
54
82
|
sort_key_name (str): sorting key name, e.g. 'lex' for lexicographic or 'custom'. Defaults to 'natural'.
|
|
55
|
-
'' means no sorting.
|
|
56
83
|
reverse (bool): reversed sorting.
|
|
57
84
|
custom_sort (Callable[[str], Any] | None):
|
|
58
85
|
save2file (bool): save file tree info to a file.
|
|
@@ -67,69 +94,106 @@ class FileTreeDisplay:
|
|
|
67
94
|
self.style = style
|
|
68
95
|
self.indent = indent
|
|
69
96
|
self.files_first = files_first
|
|
97
|
+
self.skip_sorting = skip_sorting
|
|
70
98
|
self.sort_key_name = sort_key_name
|
|
71
99
|
self.reverse = reverse
|
|
72
100
|
self.custom_sort = custom_sort
|
|
73
101
|
self.save2file = save2file
|
|
74
102
|
self.printout = printout
|
|
75
103
|
|
|
104
|
+
self.style_dict: dict = {
|
|
105
|
+
'classic': self.connector_styler('├── ', '└── '),
|
|
106
|
+
'dash': self.connector_styler('|-- ', '`-- '),
|
|
107
|
+
'arrow': self.connector_styler('├─> ', '└─> '),
|
|
108
|
+
'plus': self.connector_styler('+--- ', '\\--- '),
|
|
109
|
+
}
|
|
110
|
+
|
|
76
111
|
self.sort_keys = {
|
|
77
112
|
'natural': self._nat_key,
|
|
78
113
|
'lex': self._lex_key,
|
|
79
114
|
'custom': self.custom_sort,
|
|
80
|
-
'': None,
|
|
81
115
|
}
|
|
82
116
|
|
|
83
117
|
self.pb = PredicateBuilder()
|
|
84
118
|
self.dir_filter = self.pb.build_predicate(self.include_dirs, self.ignore_dirs)
|
|
85
|
-
self.file_filter = self.pb.build_predicate(
|
|
119
|
+
self.file_filter = self.pb.build_predicate(
|
|
120
|
+
self.include_files, self.ignore_files
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def format_style(self) -> dict:
|
|
124
|
+
style, style_dict = self.style, self.style_dict
|
|
125
|
+
style_keys = list(style_dict.keys())
|
|
126
|
+
if style not in style_keys:
|
|
127
|
+
raise ValueError(f"'{style}' is invalid: must be one of {style_keys}\n")
|
|
128
|
+
return style_dict[style]
|
|
86
129
|
|
|
87
130
|
def init(self, *args, **kwargs) -> None:
|
|
88
131
|
self.__init__(*args, **kwargs)
|
|
89
132
|
|
|
90
|
-
def
|
|
91
|
-
self.__dict__.update(attrs)
|
|
92
|
-
pattern = re.compile(r'^(ign|inc)')
|
|
93
|
-
if any(pattern.match(key) for key in attrs):
|
|
94
|
-
self.update_predicates()
|
|
95
|
-
|
|
96
|
-
def update_predicates(self):
|
|
133
|
+
def update_predicates(self) -> None:
|
|
97
134
|
self.dir_filter = self.pb.build_predicate(self.include_dirs, self.ignore_dirs)
|
|
98
|
-
self.file_filter = self.pb.build_predicate(
|
|
135
|
+
self.file_filter = self.pb.build_predicate(
|
|
136
|
+
self.include_files, self.ignore_files
|
|
137
|
+
)
|
|
99
138
|
|
|
100
139
|
@staticmethod
|
|
101
|
-
def _nat_key(name: str) -> list[int |
|
|
140
|
+
def _nat_key(name: str) -> list[int | str | Any]:
|
|
102
141
|
"""Natural sorting key"""
|
|
103
|
-
return [
|
|
104
|
-
|
|
142
|
+
return [
|
|
143
|
+
int(part) if part.isdigit() else part.lower() for part in _NUM_SPLIT(name)
|
|
144
|
+
]
|
|
105
145
|
|
|
106
146
|
@staticmethod
|
|
107
147
|
def _lex_key(name: str) -> str:
|
|
108
148
|
"""Lexicographic sorting key"""
|
|
109
149
|
return name.lower()
|
|
110
150
|
|
|
151
|
+
def _resolve_sort_key(self) -> Callable[[str], Any]:
|
|
152
|
+
sort_key_name, sort_keys = self.sort_key_name, self.sort_keys
|
|
153
|
+
key = sort_keys.get(sort_key_name)
|
|
154
|
+
if key is None:
|
|
155
|
+
if self.sort_key_name == 'custom':
|
|
156
|
+
raise ValueError(
|
|
157
|
+
"custom_sort function must be specified when sort_key_name='custom'"
|
|
158
|
+
)
|
|
159
|
+
raise ValueError(f'Invalid sort key name: "{sort_key_name}"! '
|
|
160
|
+
f'Currently defined keys are: {list(sort_keys.keys())}')
|
|
161
|
+
return key
|
|
162
|
+
|
|
111
163
|
def file_tree_display(self) -> str:
|
|
112
|
-
"""
|
|
164
|
+
"""Generates a directory tree and saves it to a text file.
|
|
113
165
|
|
|
114
166
|
Returns:
|
|
115
|
-
|
|
116
|
-
or the whole built tree, as a string of CRLF-separated lines.
|
|
167
|
+
str: The complete directory tree as a single CRLF-delimited string.
|
|
117
168
|
"""
|
|
118
169
|
root_path_str = str(self.root_path)
|
|
119
170
|
filepath = self.filepath
|
|
171
|
+
|
|
120
172
|
if not self.root_path.is_dir():
|
|
121
173
|
raise NotADirectoryError(f"The path '{root_path_str}' is not a directory.")
|
|
122
174
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
175
|
+
style = self.format_style()
|
|
176
|
+
sort_key = None if self.skip_sorting else self._resolve_sort_key()
|
|
177
|
+
dir_filter, file_filter = self.dir_filter, self.file_filter
|
|
178
|
+
files_first, reverse = self.files_first, self.reverse
|
|
179
|
+
indent = self.indent
|
|
180
|
+
|
|
181
|
+
iterator = self._build_tree(
|
|
182
|
+
root_path_str,
|
|
183
|
+
prefix='',
|
|
184
|
+
style=style,
|
|
185
|
+
sort_key=sort_key,
|
|
186
|
+
files_first=files_first,
|
|
187
|
+
dir_filter=dir_filter,
|
|
188
|
+
file_filter=file_filter,
|
|
189
|
+
reverse=reverse,
|
|
190
|
+
indent=indent,
|
|
191
|
+
)
|
|
127
192
|
|
|
128
193
|
tree_info = self.get_tree_info(iterator)
|
|
129
194
|
|
|
130
|
-
if self.save2file:
|
|
195
|
+
if self.save2file and filepath:
|
|
131
196
|
str2file(tree_info, filepath)
|
|
132
|
-
return filepath
|
|
133
197
|
|
|
134
198
|
if self.printout:
|
|
135
199
|
print(tree_info)
|
|
@@ -137,73 +201,110 @@ class FileTreeDisplay:
|
|
|
137
201
|
return tree_info
|
|
138
202
|
|
|
139
203
|
def get_tree_info(self, iterator: Generator[str, None, None]) -> str:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
204
|
+
buf = io.StringIO()
|
|
205
|
+
write = buf.write
|
|
206
|
+
write(f'{self.root_path.name}/\n')
|
|
207
|
+
buf.writelines(f'{line}\n' for line in iterator)
|
|
208
|
+
out = buf.getvalue()
|
|
209
|
+
return out[:-1] if out.endswith('\n') else out
|
|
210
|
+
|
|
211
|
+
def _build_tree(
|
|
212
|
+
self,
|
|
213
|
+
dir_path: str,
|
|
214
|
+
*,
|
|
215
|
+
prefix: str,
|
|
216
|
+
style: dict,
|
|
217
|
+
sort_key: Callable[[str], Any] | None,
|
|
218
|
+
files_first: bool,
|
|
219
|
+
dir_filter: Callable[[str], bool],
|
|
220
|
+
file_filter: Callable[[str], bool],
|
|
221
|
+
reverse: bool,
|
|
222
|
+
indent: int,
|
|
223
|
+
) -> Generator[str, None, None]:
|
|
224
|
+
"""Yields lines representing a formatted folder structure using a recursive DFS.
|
|
225
|
+
The internal recursive generator has runtime consts threaded through to avoid attrib. lookups.
|
|
148
226
|
|
|
149
227
|
Args:
|
|
150
|
-
dir_path (str): The directory path currently being traversed.
|
|
228
|
+
dir_path (str): The directory path or disk drive currently being traversed.
|
|
151
229
|
prefix (str): Hierarchical prefix applied to each level.
|
|
152
230
|
|
|
153
231
|
Yields:
|
|
154
|
-
str: A formatted
|
|
232
|
+
str: A formatted text representation of the folder structure.
|
|
155
233
|
"""
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
curr_indent = self.style * self.indent
|
|
234
|
+
branch = style['branch']
|
|
235
|
+
end = style['end']
|
|
236
|
+
vertical = style['vertical']
|
|
237
|
+
space = style['space']
|
|
161
238
|
|
|
162
|
-
|
|
239
|
+
recurse = self._build_tree
|
|
163
240
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
raise ValueError("custom_sort function must be specified"
|
|
167
|
-
" when sort_key_name='custom'")
|
|
168
|
-
raise ValueError(f"Invalid sort key name: {sort_key_name}")
|
|
241
|
+
dirs: list[tuple[str, str]] = []
|
|
242
|
+
files: list[str] = []
|
|
169
243
|
|
|
170
244
|
try:
|
|
171
|
-
with os.scandir(dir_path) as
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
for entry in
|
|
245
|
+
with os.scandir(dir_path) as it:
|
|
246
|
+
append_dir = dirs.append
|
|
247
|
+
append_file = files.append
|
|
248
|
+
for entry in it:
|
|
175
249
|
name = entry.name
|
|
176
|
-
|
|
250
|
+
try:
|
|
251
|
+
is_dir = entry.is_dir(follow_symlinks=False)
|
|
252
|
+
except OSError:
|
|
253
|
+
continue
|
|
254
|
+
|
|
255
|
+
if is_dir:
|
|
177
256
|
if dir_filter(name):
|
|
178
257
|
append_dir((name, entry.path))
|
|
179
258
|
else:
|
|
180
259
|
if file_filter(name):
|
|
181
260
|
append_file(name)
|
|
182
261
|
|
|
183
|
-
except (PermissionError, OSError) as e:
|
|
184
|
-
|
|
185
|
-
|
|
262
|
+
except (PermissionError, OSError, FileNotFoundError) as e:
|
|
263
|
+
if isinstance(e, PermissionError):
|
|
264
|
+
yield '[Permission Denied]'
|
|
265
|
+
else:
|
|
266
|
+
yield '[Error reading directory]'
|
|
186
267
|
return
|
|
187
268
|
|
|
188
269
|
if sort_key:
|
|
189
270
|
dirs.sort(key=lambda d: sort_key(d[0]), reverse=reverse)
|
|
190
271
|
files.sort(key=sort_key, reverse=reverse)
|
|
191
272
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
yield f'{next_prefix}{name}/'
|
|
198
|
-
yield from self.build_tree(path, next_prefix)
|
|
199
|
-
|
|
200
|
-
if not files_first:
|
|
201
|
-
for name in files:
|
|
202
|
-
yield next_prefix + name
|
|
203
|
-
|
|
204
|
-
def format_out_path(self) -> Path:
|
|
205
|
-
alt_file_name = f'{self.root_path.name}{DEFAULT_SFX}'
|
|
206
|
-
out_file = (
|
|
207
|
-
Path(self.filepath) if self.filepath else (self.root_path / alt_file_name)
|
|
273
|
+
# Compute combined sequence without extra temporary tuples where possible
|
|
274
|
+
f_iter: Iterable[tuple[str, None, bool]] = ((f, None, False) for f in files)
|
|
275
|
+
d_iter: Iterable[tuple[str, str, bool]] = ((d[0], d[1], True) for d in dirs)
|
|
276
|
+
seq: Iterable[tuple[str, str | None, bool]] = (
|
|
277
|
+
chain(f_iter, d_iter) if files_first else chain(d_iter, f_iter)
|
|
208
278
|
)
|
|
209
|
-
|
|
279
|
+
|
|
280
|
+
combined = list(seq)
|
|
281
|
+
last_index = len(combined) - 1
|
|
282
|
+
|
|
283
|
+
for idx, (name, path, is_dir) in enumerate(combined):
|
|
284
|
+
is_last = idx == last_index
|
|
285
|
+
connector = end if is_last else branch
|
|
286
|
+
formatted_name = f'{name}/' if is_dir else name
|
|
287
|
+
yield f'{prefix}{connector}{formatted_name}'
|
|
288
|
+
extension = space if is_last else vertical
|
|
289
|
+
|
|
290
|
+
if is_dir and path:
|
|
291
|
+
yield from recurse(
|
|
292
|
+
path,
|
|
293
|
+
prefix=prefix + extension,
|
|
294
|
+
style=style,
|
|
295
|
+
sort_key=sort_key,
|
|
296
|
+
files_first=files_first,
|
|
297
|
+
dir_filter=dir_filter,
|
|
298
|
+
file_filter=file_filter,
|
|
299
|
+
reverse=reverse,
|
|
300
|
+
indent=indent,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
def connector_styler(self, branch: str, end: str) -> dict:
|
|
304
|
+
indent = self.indent
|
|
305
|
+
return {
|
|
306
|
+
'space': ' ' * indent,
|
|
307
|
+
'vertical': f'│{" " * (indent - 1)}',
|
|
308
|
+
'branch': branch,
|
|
309
|
+
'end': end,
|
|
310
|
+
}
|