pyenv-virtualenv for Windows 1.2
A 'pyenv' plugin to manage Python virtual environments, depending on different Python versions, for various Python projects.
Loading...
Searching...
No Matches
tre.py
Go to the documentation of this file.
1##
2# @package tre
3# @file tre.py
4# @author Michael Paul Korthals
5# @date 2025-07-10
6# @version 1.0.0
7# @copyright © 2025 Michael Paul Korthals. All rights reserved.
8# See License details in the documentation.
9#
10# Library to output the project properties "pyenv-virtualenv"
11# folder tree in the CLI terminal.
12# I derived this code for "pyenv-virtualenv" from a StackOverflow article.
13# Many thanks to its contributor.
14# @see https://stackoverflow.com/questions/9727673/list-directory-tree-structure-in-python
15#
16
17# --- IMPORTS ----------------------------------------------------------
18
19# Python
20from itertools import islice
21from pathlib import Path
22import sys
23
24# Community
25# (None)
26
27# My
28try:
29 # noinspection PyUnresolvedReferences
30 import lib.log as log
31except ImportError():
32 try:
33 # noinspection PyUnresolvedReferences
34 import log
35 except ImportError():
36 # noinspection PyUnresolvedReferences
37 import log
38
39# --- GLOBAL VARIABLES -------------------------------------------------
40
41space = '\x1b[94m \x1b[0m'
42branch = '\x1b[94m│ \x1b[0m'
43tee = '\x1b[94m├───\x1b[0m'
44last = '\x1b[94m└───\x1b[0m'
45
46# --- HELPER -----------------------------------------------------------
47
48## Get the content of project property file.
49#
50# @param file_path Path to project property file.
51# @return Text, which the file is containing or empty string in case of error.
52def getProjectPropertyFileStr(file_path: str) -> str:
53 result = ''
54 # noinspection PyBroadException
55 try:
56 with open(file_path, 'r') as f:
57 result = f.read().strip()
58 return result
59 except:
60 log.error(sys.exc_info())
61 return ''
62
63## Given a directory path, print a visual tree structure.
64# Output and highlight the content of 'pyenv-virtualenv'
65# project property files with colors:
66# * LIGHT CYAN: Python version.
67# * LIGHT YELLOW: Virtual environment name.
68#
69# @param root_path Folder path, from which the tree will be parsed.
70# @param recursion_level Max recursion level to parse.
71# Default: -1 = unlimited recursion.
72# @param limit_to_directories Flag to permit parsing for folders only.
73# Default: False = parse for both, files and folders
74# @param length_limit Max. output length of the tree in lines.
75# Default: 1000.
76# @param exclude List of folder names to exclude.
77# Default empty list = nothing to exclude.
78def tree(
79 root_path: (Path, str),
80 recursion_level: int = -1,
81 limit_to_directories: bool = False,
82 length_limit: int = 1000,
83 exclude: tuple = ()
84):
85 # Accept type "str" coercible to "Path"
86 if isinstance(root_path, str):
87 root_path = Path(root_path)
88 # Initialize
89 files = 0
90 directories = 0
91
92 def is_junction(path: str) -> bool:
93 import os
94 try:
95 return bool(os.readlink(path))
96 except OSError:
97 return False
98
99 def inner(
100 dir_path: Path,
101 prefix: str= '',
102 level: int = -1,
103 excl: tuple = ()
104 ):
105 nonlocal files, directories
106 if not level:
107 return # 0, stop iterating
108 # Scan for files and folders Path objects
109 if limit_to_directories:
110 objects = dir_path.iterdir()
111 contents = [d for d in objects if d.is_dir()]
112 contents1 = list(objects)
113 else:
114 contents = list(dir_path.iterdir())
115 contents1 = contents
116 # Detect file ".tree-excludes" in files
117 excl1 = excl
118 for content in contents1:
119 if content.is_file() and (content.name == '.tree-excludes'):
120 with open(content, 'r') as f:
121 excl1 = eval(f.read())
122 if content not in contents:
123 contents.insert(0, content)
124 break
125 # End if
126 # End for
127 pointers = [tee] * (len(contents) - 1) + [last]
128 for pointer, path in zip(pointers, contents):
129 if path.is_dir():
130 # Exclude spam folders
131 excluded = False
132 for item in excl1:
133 if path.parts[-1] == item:
134 excluded = True
135 break
136 # End if
137 # End for
138 if excluded: continue
139 # OUTPUT: colorized directory
140 name = path.name
141 if (
142 (hasattr(path, 'is_junction') and path.is_junction()) # Python 3.12+
143 or
144 (is_junction(str(path))) # Python 3.11-
145 ):
146 name = '\x1b[105m {} \x1b[0m \x1b[95m→\x1b[0m \x1b[104m {} \x1b[0m'.format(
147 name,
148 str(path.readlink()).split('\\\\?\\')[-1]
149 )
150 else: # Is directory
151 name = '\x1b[104m {} \x1b[0m'.format(name)
152 yield prefix + pointer + name
153 directories += 1
154 extension = branch if pointer == tee else space
155 # OUTPUT: path recursively
156 yield from inner(
157 path,
158 prefix=prefix+extension,
159 level=level - 1,
160 excl=excl1
161 )
162 elif not limit_to_directories:
163 # OUTPUT: colorized file
164 name = path.name
165 if path.is_symlink():
166 name1 = '\x1b[95m● {}\x1b[0m'.format(name)
167 if name == '.python-version':
168 name1 += ' \x1b[96m({})\x1b[0m'.format(
170 )
171 elif name == '.python-env':
172 name1 += ' \x1b[93m({})\x1b[0m'.format(
174 )
175 elif name == '.tree-excludes':
176 name1 += ' \x1b[95m{}\x1b[0m'.format(
178 )
179 else:
180 name1 = '\x1b[95m● {} → \x1b[37m● {}\x1b[0m'.format(
181 name,
182 str(path.readlink())
183 )
184 else: # Is file
185 name1 = '\x1b[37m● {}\x1b[0m'.format(name)
186 if name == '.python-version':
187 name1 += ' \x1b[96m({})\x1b[0m'.format(
189 )
190 elif name == '.python-env':
191 name1 += ' \x1b[93m({})\x1b[0m'.format(
193 )
194 elif name == '.tree-excludes':
195 name1 += ' \x1b[95m{}\x1b[0m'.format(
197 )
198 else:
199 name1 = '\x1b[37m● {}\x1b[0m'.format(name)
200 yield prefix + pointer + name1
201 files += 1
202
203 # Output the root directory path in the beginning
204 print('\x1b[104m {} \x1b[0m'.format(str(root_path)))
205 # Exclude spam folders
206 for excl_item in exclude:
207 if root_path.parts[-1] == excl_item:
208 return
209 # Iterate through the folder tree
210 iterator = inner(
211 root_path,
212 level=recursion_level,
213 excl=exclude
214 )
215 # OUTPUT: folder tree
216 for line in islice(iterator, length_limit):
217 print(line)
218 if next(iterator, None):
219 # Obey to the length_limit
220 print('... length_limit, {}, reached, counted:'.format(length_limit))
221 # OUTPUT: statistics
222 print(
223 '\n{} directories'.format(directories) + (
224 ', {} files'.format(files) if files else ''
225 )
226 )
227
228
229# --- END OF CODE ------------------------------------------------------
error((str, tuple) msg)
Log error message colored to console only.
Definition log.py:179
tree((Path, str) root_path, int recursion_level=-1, bool limit_to_directories=False, int length_limit=1000, tuple exclude=())
Given a directory path, print a visual tree structure.
Definition tre.py:84
str getProjectPropertyFileStr(str file_path)
Get the content of project property file.
Definition tre.py:52