danielutils 1.1.28__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.
Files changed (287) hide show
  1. danielutils/__init__.py +50 -0
  2. danielutils/abstractions/__init__.py +3 -0
  3. danielutils/abstractions/db/__init__.py +10 -0
  4. danielutils/abstractions/db/database.py +206 -0
  5. danielutils/abstractions/db/database_definitions.py +219 -0
  6. danielutils/abstractions/db/database_exceptions.py +34 -0
  7. danielutils/abstractions/db/database_factory.py +70 -0
  8. danielutils/abstractions/db/database_initializer.py +261 -0
  9. danielutils/abstractions/db/database_models.py +213 -0
  10. danielutils/abstractions/db/dependencies.py +6 -0
  11. danielutils/abstractions/db/implementations/__init__.py +4 -0
  12. danielutils/abstractions/db/implementations/in_memory_database.py +365 -0
  13. danielutils/abstractions/db/implementations/persistent_in_memory_database.py +174 -0
  14. danielutils/abstractions/db/implementations/redis_database.py +403 -0
  15. danielutils/abstractions/db/implementations/sqlite_database.py +491 -0
  16. danielutils/abstractions/multiprogramming/__init__.py +3 -0
  17. danielutils/abstractions/multiprogramming/multi_id.py +26 -0
  18. danielutils/abstractions/multiprogramming/worker.py +104 -0
  19. danielutils/abstractions/multiprogramming/worker_pool.py +98 -0
  20. danielutils/abstractions/repl.py +125 -0
  21. danielutils/aliases.py +174 -0
  22. danielutils/async_/__init__.py +7 -0
  23. danielutils/async_/async_cmd.py +28 -0
  24. danielutils/async_/async_command.py +879 -0
  25. danielutils/async_/async_layered_command.py +125 -0
  26. danielutils/async_/async_retry_executor.py +86 -0
  27. danielutils/async_/async_worker_pool.py +110 -0
  28. danielutils/async_/time_strategy.py +73 -0
  29. danielutils/async_/utils.py +57 -0
  30. danielutils/better_builtins/__init__.py +3 -0
  31. danielutils/better_builtins/counter.py +48 -0
  32. danielutils/better_builtins/frange.py +266 -0
  33. danielutils/better_builtins/typed_builtins/__init__.py +4 -0
  34. danielutils/better_builtins/typed_builtins/factory.py +126 -0
  35. danielutils/better_builtins/typed_builtins/tdict.py +36 -0
  36. danielutils/better_builtins/typed_builtins/tlist.py +223 -0
  37. danielutils/better_builtins/typed_builtins/tset.py +67 -0
  38. danielutils/better_builtins/typed_builtins/ttuple.py +15 -0
  39. danielutils/colors.py +203 -0
  40. danielutils/context_managers/__init__.py +5 -0
  41. danielutils/context_managers/attr_context.py +40 -0
  42. danielutils/context_managers/multi_context.py +34 -0
  43. danielutils/context_managers/optional_context.py +33 -0
  44. danielutils/context_managers/state_context.py +25 -0
  45. danielutils/context_managers/temporary_file.py +108 -0
  46. danielutils/convenience.py +12 -0
  47. danielutils/conversions/__init__.py +2 -0
  48. danielutils/conversions/main_conversions.py +131 -0
  49. danielutils/conversions/specialized_conversions/__init__.py +2 -0
  50. danielutils/conversions/specialized_conversions/to_hex.py +25 -0
  51. danielutils/conversions/specialized_conversions/to_int.py +19 -0
  52. danielutils/custom_types.py +18 -0
  53. danielutils/data_structures/__init__.py +8 -0
  54. danielutils/data_structures/algorithms.py +52 -0
  55. danielutils/data_structures/comparer.py +47 -0
  56. danielutils/data_structures/default_dict.py +23 -0
  57. danielutils/data_structures/functions.py +35 -0
  58. danielutils/data_structures/graph/__init__.py +4 -0
  59. danielutils/data_structures/graph/binary_node.py +71 -0
  60. danielutils/data_structures/graph/graph.py +208 -0
  61. danielutils/data_structures/graph/multinode.py +101 -0
  62. danielutils/data_structures/graph/node.py +60 -0
  63. danielutils/data_structures/heap/__init__.py +3 -0
  64. danielutils/data_structures/heap/heap.py +119 -0
  65. danielutils/data_structures/heap/max_heap.py +18 -0
  66. danielutils/data_structures/heap/min_heap.py +18 -0
  67. danielutils/data_structures/queue/__init__.py +3 -0
  68. danielutils/data_structures/queue/atomic_queue.py +15 -0
  69. danielutils/data_structures/queue/priority_queue.py +110 -0
  70. danielutils/data_structures/queue/queue.py +92 -0
  71. danielutils/data_structures/stack.py +102 -0
  72. danielutils/data_structures/trees/__init__.py +2 -0
  73. danielutils/data_structures/trees/binary_syntax_tree.py +84 -0
  74. danielutils/data_structures/trees/binary_tree.py +63 -0
  75. danielutils/date.py +43 -0
  76. danielutils/date_time.py +23 -0
  77. danielutils/decorators/__init__.py +18 -0
  78. danielutils/decorators/atomic.py +45 -0
  79. danielutils/decorators/attach.py +61 -0
  80. danielutils/decorators/chain_decorators.py +39 -0
  81. danielutils/decorators/decorate_conditionally.py +32 -0
  82. danielutils/decorators/delay_call.py +47 -0
  83. danielutils/decorators/deprecate.py +46 -0
  84. danielutils/decorators/final.py +39 -0
  85. danielutils/decorators/limit_recursion.py +63 -0
  86. danielutils/decorators/memo.py +68 -0
  87. danielutils/decorators/normalize_decorator.py +55 -0
  88. danielutils/decorators/overload.py +204 -0
  89. danielutils/decorators/partially_implemented.py +32 -0
  90. danielutils/decorators/processify.py +74 -0
  91. danielutils/decorators/property.py +42 -0
  92. danielutils/decorators/singleton.py +59 -0
  93. danielutils/decorators/threadify.py +40 -0
  94. danielutils/decorators/timeout.py +82 -0
  95. danielutils/decorators/total_ordering.py +82 -0
  96. danielutils/decorators/validate.py +227 -0
  97. danielutils/exceptions.py +90 -0
  98. danielutils/functions/__init__.py +13 -0
  99. danielutils/functions/areoneof.py +47 -0
  100. danielutils/functions/check_foreach.py +39 -0
  101. danielutils/functions/factorial.py +24 -0
  102. danielutils/functions/flatten.py +33 -0
  103. danielutils/functions/foreach.py +20 -0
  104. danielutils/functions/isoftype.py +475 -0
  105. danielutils/functions/isoneof.py +71 -0
  106. danielutils/functions/multiloop.py +51 -0
  107. danielutils/functions/parallel_for.py +47 -0
  108. danielutils/functions/partition.py +45 -0
  109. danielutils/functions/powerset.py +31 -0
  110. danielutils/functions/subseteq.py +25 -0
  111. danielutils/functions/types_subseteq.py +51 -0
  112. danielutils/generators/__init__.py +3 -0
  113. danielutils/generators/conditional_generator.py +59 -0
  114. danielutils/generators/generator_from_stream.py +31 -0
  115. danielutils/generators/join_generators.py +109 -0
  116. danielutils/internet.py +92 -0
  117. danielutils/io_.py +451 -0
  118. danielutils/java/__init__.py +18 -0
  119. danielutils/java/interfaces/__init__.py +1 -0
  120. danielutils/java/interfaces/comparable.py +31 -0
  121. danielutils/java/java_interface.py +59 -0
  122. danielutils/logging_/__init__.py +2 -0
  123. danielutils/logging_/_impl/__init__.py +4 -0
  124. danielutils/logging_/_impl/builtin_impls/__init__.py +2 -0
  125. danielutils/logging_/_impl/builtin_impls/file_logger.py +35 -0
  126. danielutils/logging_/_impl/builtin_impls/print_logger.py +21 -0
  127. danielutils/logging_/_impl/log_level.py +13 -0
  128. danielutils/logging_/_impl/logger.py +109 -0
  129. danielutils/logging_/_impl/logger_strategy_impl_base.py +37 -0
  130. danielutils/logging_/utils.py +199 -0
  131. danielutils/lombok/__init__.py +1 -0
  132. danielutils/lombok/builder.py +68 -0
  133. danielutils/math_/__init__.py +2 -0
  134. danielutils/math_/constants.py +103 -0
  135. danielutils/math_/functions.py +34 -0
  136. danielutils/math_/math_print.py +52 -0
  137. danielutils/math_/math_symbols.py +125 -0
  138. danielutils/metaclasses/__init__.py +5 -0
  139. danielutils/metaclasses/atomic_class_meta.py +36 -0
  140. danielutils/metaclasses/implicit_data_deleter_meta.py +87 -0
  141. danielutils/metaclasses/instance_cache_meta.py +66 -0
  142. danielutils/metaclasses/interface.py +328 -0
  143. danielutils/metaclasses/overload_meta.py +48 -0
  144. danielutils/mock_/__init__.py +1 -0
  145. danielutils/mock_/mock_module.py +27 -0
  146. danielutils/path.py +55 -0
  147. danielutils/print_.py +123 -0
  148. danielutils/progress_bar/__init__.py +3 -0
  149. danielutils/progress_bar/ascii_progress_bar.py +161 -0
  150. danielutils/progress_bar/progress_bar.py +75 -0
  151. danielutils/progress_bar/progress_bar_pool.py +72 -0
  152. danielutils/protocols/__init__.py +3 -0
  153. danielutils/protocols/dictable.py +38 -0
  154. danielutils/protocols/evaluable.py +26 -0
  155. danielutils/protocols/serializable.py +51 -0
  156. danielutils/py.typed +0 -0
  157. danielutils/random_.py +47 -0
  158. danielutils/reflection/__init__.py +6 -0
  159. danielutils/reflection/declarations.py +288 -0
  160. danielutils/reflection/file/__init__.py +1 -0
  161. danielutils/reflection/file/file_reflection.py +85 -0
  162. danielutils/reflection/function/__init__.py +1 -0
  163. danielutils/reflection/function/function_reflections.py +152 -0
  164. danielutils/reflection/info_classes/__init__.py +6 -0
  165. danielutils/reflection/info_classes/argument_info.py +278 -0
  166. danielutils/reflection/info_classes/class_info.py +204 -0
  167. danielutils/reflection/info_classes/decorator_info.py +54 -0
  168. danielutils/reflection/info_classes/file_info.py +1180 -0
  169. danielutils/reflection/info_classes/folder_info.py +592 -0
  170. danielutils/reflection/info_classes/function_info.py +551 -0
  171. danielutils/reflection/info_classes/import_info.py +207 -0
  172. danielutils/reflection/interpreter/__init__.py +9 -0
  173. danielutils/reflection/interpreter/callstack.py +57 -0
  174. danielutils/reflection/interpreter/get_traceback.py +27 -0
  175. danielutils/reflection/interpreter/interpreter.py +41 -0
  176. danielutils/reflection/interpreter/is_debugging.py +13 -0
  177. danielutils/reflection/interpreter/os_.py +33 -0
  178. danielutils/reflection/interpreter/packages.py +33 -0
  179. danielutils/reflection/interpreter/python_version.py +33 -0
  180. danielutils/reflection/interpreter/signals.py +18 -0
  181. danielutils/reflection/interpreter/tracer.py +175 -0
  182. danielutils/reflection/module/__init__.py +3 -0
  183. danielutils/reflection/module/lazy_module.py +104 -0
  184. danielutils/reflection/module/module_reflections.py +34 -0
  185. danielutils/reflection/module/package_reflection.py +161 -0
  186. danielutils/retry_executor/__init__.py +3 -0
  187. danielutils/retry_executor/backoff_strategies/__init__.py +6 -0
  188. danielutils/retry_executor/backoff_strategies/constant_backoff.py +25 -0
  189. danielutils/retry_executor/backoff_strategies/exponential_backoff.py +26 -0
  190. danielutils/retry_executor/backoff_strategies/functional_backoff.py +29 -0
  191. danielutils/retry_executor/backoff_strategies/linear_backoff.py +23 -0
  192. danielutils/retry_executor/backoff_strategies/multiplicative_backoff.py +23 -0
  193. danielutils/retry_executor/backoff_strategies/no_backoff.py +17 -0
  194. danielutils/retry_executor/backoff_strategy.py +28 -0
  195. danielutils/retry_executor/retry_executor.py +55 -0
  196. danielutils/snippets/__init__.py +1 -0
  197. danielutils/snippets/try_get.py +28 -0
  198. danielutils/system/__init__.py +3 -0
  199. danielutils/system/independent.py +197 -0
  200. danielutils/system/layered_command.py +130 -0
  201. danielutils/system/windows/__init__.py +2 -0
  202. danielutils/system/windows/utils/__init__.py +1 -0
  203. danielutils/system/windows/utils/filetime.py +19 -0
  204. danielutils/system/windows/win32_ctime.py +183 -0
  205. danielutils/system/windows/windows.py +78 -0
  206. danielutils/testing/__init__.py +1 -0
  207. danielutils/testing/unittest_/__init__.py +2 -0
  208. danielutils/testing/unittest_/always_teardown_testcase.py +75 -0
  209. danielutils/testing/unittest_/auto_cwd_testcase.py +114 -0
  210. danielutils/text.py +169 -0
  211. danielutils/time.py +64 -0
  212. danielutils/tools/__init__.py +1 -0
  213. danielutils/tools/unittest_test_runner/__init__.py +9 -0
  214. danielutils/tools/unittest_test_runner/cli.py +70 -0
  215. danielutils/tools/unittest_test_runner/core/__init__.py +17 -0
  216. danielutils/tools/unittest_test_runner/core/discovery.py +322 -0
  217. danielutils/tools/unittest_test_runner/core/execution.py +244 -0
  218. danielutils/tools/unittest_test_runner/core/parser.py +163 -0
  219. danielutils/tools/unittest_test_runner/core/runner.py +561 -0
  220. danielutils/tools/unittest_test_runner/models.py +82 -0
  221. danielutils/university/__init__.py +7 -0
  222. danielutils/university/computability_and_complexity/__init__.py +2 -0
  223. danielutils/university/computability_and_complexity/discreate_finite_automaton.py +89 -0
  224. danielutils/university/computability_and_complexity/languages/__init__.py +0 -0
  225. danielutils/university/computability_and_complexity/languages/language.py +17 -0
  226. danielutils/university/computability_and_complexity/languages/sat.py +71 -0
  227. danielutils/university/computability_and_complexity/turing_machine.py +47 -0
  228. danielutils/university/databases/__init__.py +1 -0
  229. danielutils/university/databases/all.py +756 -0
  230. danielutils/university/image_proccesing/__init__.py +1 -0
  231. danielutils/university/image_proccesing/encoding/__init__.py +3 -0
  232. danielutils/university/image_proccesing/encoding/encoding.py +35 -0
  233. danielutils/university/image_proccesing/encoding/lossless/__init__.py +4 -0
  234. danielutils/university/image_proccesing/encoding/lossless/huffman.py +0 -0
  235. danielutils/university/image_proccesing/encoding/lossless/lossless_encoding.py +10 -0
  236. danielutils/university/image_proccesing/encoding/lossless/lzw.py +0 -0
  237. danielutils/university/image_proccesing/encoding/lossless/run_length.py +22 -0
  238. danielutils/university/image_proccesing/encoding/lossy/__init__.py +1 -0
  239. danielutils/university/image_proccesing/encoding/lossy/lossy_encoding.py +10 -0
  240. danielutils/university/image_proccesing/tansformations/__init__.py +0 -0
  241. danielutils/university/image_proccesing/tansformations/gaussian.py +0 -0
  242. danielutils/university/image_proccesing/tansformations/gradient.py +0 -0
  243. danielutils/university/image_proccesing/tansformations/hough.py +0 -0
  244. danielutils/university/image_proccesing/tansformations/laplacian.py +0 -0
  245. danielutils/university/image_proccesing/tansformations/transformation.py +11 -0
  246. danielutils/university/linear_algebra/__init__.py +1 -0
  247. danielutils/university/linear_algebra/matrix.py +404 -0
  248. danielutils/university/machine_learning/__init__.py +0 -0
  249. danielutils/university/machine_learning/activation_functions/__init__.py +2 -0
  250. danielutils/university/machine_learning/activation_functions/activation_function.py +11 -0
  251. danielutils/university/machine_learning/activation_functions/relu.py +11 -0
  252. danielutils/university/machine_learning/neuron.py +15 -0
  253. danielutils/university/oop/__init__.py +1 -0
  254. danielutils/university/oop/observer.py +31 -0
  255. danielutils/university/oop/strategy.py +9 -0
  256. danielutils/university/probability/__init__.py +7 -0
  257. danielutils/university/probability/conditional_variable/__init__.py +3 -0
  258. danielutils/university/probability/conditional_variable/conditional_variable.py +138 -0
  259. danielutils/university/probability/conditional_variable/continuous/__init__.py +0 -0
  260. danielutils/university/probability/conditional_variable/discrete/__init__.py +8 -0
  261. danielutils/university/probability/conditional_variable/discrete/bernoulli.py +38 -0
  262. danielutils/university/probability/conditional_variable/discrete/bernoulli_sum.py +31 -0
  263. danielutils/university/probability/conditional_variable/discrete/binomial.py +40 -0
  264. danielutils/university/probability/conditional_variable/discrete/conditional_from_discrete_probability_func.py +36 -0
  265. danielutils/university/probability/conditional_variable/discrete/discrete.py +42 -0
  266. danielutils/university/probability/conditional_variable/discrete/geometric.py +43 -0
  267. danielutils/university/probability/conditional_variable/discrete/poisson.py +32 -0
  268. danielutils/university/probability/conditional_variable/discrete/uniform.py +33 -0
  269. danielutils/university/probability/distributions.py +35 -0
  270. danielutils/university/probability/expressions/__init__.py +2 -0
  271. danielutils/university/probability/expressions/accumulation_expression.py +291 -0
  272. danielutils/university/probability/expressions/probability_expression.py +110 -0
  273. danielutils/university/probability/funcs/__init__.py +4 -0
  274. danielutils/university/probability/funcs/covariance.py +12 -0
  275. danielutils/university/probability/funcs/expected_value.py +41 -0
  276. danielutils/university/probability/funcs/probability_function.py +14 -0
  277. danielutils/university/probability/funcs/variance.py +17 -0
  278. danielutils/university/probability/operator.py +79 -0
  279. danielutils/university/probability/protocols.py +32 -0
  280. danielutils/university/probability/supp.py +101 -0
  281. danielutils/university/probability/transformation.py +44 -0
  282. danielutils/versioned_imports.py +31 -0
  283. danielutils-1.1.28.dist-info/METADATA +390 -0
  284. danielutils-1.1.28.dist-info/RECORD +287 -0
  285. danielutils-1.1.28.dist-info/WHEEL +5 -0
  286. danielutils-1.1.28.dist-info/licenses/LICENSE +21 -0
  287. danielutils-1.1.28.dist-info/top_level.txt +1 -0
@@ -0,0 +1,592 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Folder Information Analyzer
4
+ Provides comprehensive information about folders and their contents recursively.
5
+ """
6
+
7
+ import os
8
+ import fnmatch
9
+ from pathlib import Path
10
+ from collections import defaultdict, Counter
11
+ import statistics
12
+ from typing import Dict, List, Tuple, Any, Set, Optional, Union
13
+ from dataclasses import dataclass, field
14
+ from datetime import datetime
15
+
16
+
17
+ @dataclass
18
+ class FileInfo:
19
+ """Information about a single file."""
20
+ name: str
21
+ path: str
22
+ size: int
23
+ extension: str
24
+ is_python: bool = False
25
+ line_count: int = 0
26
+ code_lines: int = 0
27
+ comment_lines: int = 0
28
+ empty_lines: int = 0
29
+ classes: int = 0
30
+ functions: int = 0
31
+ docstrings: int = 0
32
+ imports: List[str] = field(default_factory=list)
33
+ created_time: Optional[datetime] = None
34
+ modified_time: Optional[datetime] = None
35
+ accessed_time: Optional[datetime] = None
36
+
37
+ def __post_init__(self):
38
+ """Set file type and analyze Python files."""
39
+ self.is_python = self.extension.lower() == '.py'
40
+ if self.is_python:
41
+ self._analyze_python_file()
42
+
43
+ def _analyze_python_file(self):
44
+ """Analyze Python file content for detailed statistics."""
45
+ try:
46
+ with open(self.path, 'r', encoding='utf-8', errors='ignore') as f:
47
+ lines = f.readlines()
48
+ self.line_count = len(lines)
49
+
50
+ for line in lines:
51
+ stripped = line.strip()
52
+ if not stripped:
53
+ self.empty_lines += 1
54
+ elif stripped.startswith('#'):
55
+ self.comment_lines += 1
56
+ else:
57
+ self.code_lines += 1
58
+
59
+ # Count imports, classes, functions, docstrings
60
+ if stripped.startswith('import ') or stripped.startswith('from '):
61
+ import_part = stripped.split()[1]
62
+ if ' as ' in stripped:
63
+ import_part = import_part.split(' as ')[0]
64
+ self.imports.append(import_part)
65
+ elif stripped.startswith('class '):
66
+ self.classes += 1
67
+ elif stripped.startswith('def '):
68
+ self.functions += 1
69
+ elif '"""' in line or "'''" in line:
70
+ self.docstrings += 1
71
+ except (OSError, UnicodeDecodeError):
72
+ pass
73
+
74
+ def to_dict(self) -> Dict[str, Any]:
75
+ """Convert FileInfo to dictionary."""
76
+ return {
77
+ 'name': self.name,
78
+ 'path': self.path,
79
+ 'size': self.size,
80
+ 'extension': self.extension,
81
+ 'is_python': self.is_python,
82
+ 'line_count': self.line_count,
83
+ 'code_lines': self.code_lines,
84
+ 'comment_lines': self.comment_lines,
85
+ 'empty_lines': self.empty_lines,
86
+ 'classes': self.classes,
87
+ 'functions': self.functions,
88
+ 'docstrings': self.docstrings,
89
+ 'imports': self.imports,
90
+ 'created_time': self.created_time.isoformat() if self.created_time else None,
91
+ 'modified_time': self.modified_time.isoformat() if self.modified_time else None,
92
+ 'accessed_time': self.accessed_time.isoformat() if self.accessed_time else None
93
+ }
94
+
95
+
96
+ @dataclass
97
+ class FolderInfo:
98
+ """Information about a folder and its contents."""
99
+ name: str
100
+ path: str
101
+ total_files: int = 0
102
+ total_folders: int = 0
103
+ python_files: int = 0
104
+ total_lines: int = 0
105
+ code_lines: int = 0
106
+ comment_lines: int = 0
107
+ empty_lines: int = 0
108
+ total_size: int = 0
109
+ file_extensions: Counter = field(default_factory=Counter)
110
+ files: List[FileInfo] = field(default_factory=list)
111
+ subfolders: List['FolderInfo'] = field(default_factory=list)
112
+ depth: int = 0
113
+ excluded_files: int = 0
114
+ excluded_folders: int = 0
115
+ created_time: Optional[datetime] = None
116
+ modified_time: Optional[datetime] = None
117
+ accessed_time: Optional[datetime] = None
118
+
119
+ # Computed properties
120
+ _stats_computed: bool = False
121
+ _file_stats: Dict[str, Any] = field(default_factory=dict)
122
+
123
+ def __post_init__(self):
124
+ """Initialize computed properties."""
125
+ self._file_stats = {}
126
+
127
+ def add_file(self, file_info: FileInfo):
128
+ """Add a file to this folder."""
129
+ self.files.append(file_info)
130
+ self.total_files += 1
131
+ self.total_size += file_info.size
132
+ self.file_extensions[file_info.extension] += 1
133
+
134
+ if file_info.is_python:
135
+ self.python_files += 1
136
+ self.total_lines += file_info.line_count
137
+ self.code_lines += file_info.code_lines
138
+ self.comment_lines += file_info.comment_lines
139
+ self.empty_lines += file_info.empty_lines
140
+
141
+ def add_subfolder(self, folder_info: 'FolderInfo'):
142
+ """Add a subfolder to this folder."""
143
+ self.subfolders.append(folder_info)
144
+ self.total_folders += 1
145
+
146
+ def compute_statistics(self):
147
+ """Compute comprehensive statistics for this folder."""
148
+ if self._stats_computed:
149
+ return self._file_stats
150
+
151
+ # Aggregate statistics from files
152
+ line_counts = [
153
+ f.line_count for f in self.files if f.is_python and f.line_count > 0]
154
+ file_sizes = [f.size for f in self.files]
155
+
156
+ # Aggregate statistics from subfolders
157
+ for subfolder in self.subfolders:
158
+ subfolder.compute_statistics()
159
+ self.total_files += subfolder.total_files
160
+ self.total_folders += subfolder.total_folders
161
+ self.python_files += subfolder.python_files
162
+ self.total_lines += subfolder.total_lines
163
+ self.code_lines += subfolder.code_lines
164
+ self.comment_lines += subfolder.comment_lines
165
+ self.empty_lines += subfolder.empty_lines
166
+ self.total_size += subfolder.total_size
167
+
168
+ # Merge file extensions
169
+ for ext, count in subfolder.file_extensions.items():
170
+ self.file_extensions[ext] += count
171
+
172
+ # Calculate computed statistics
173
+ self._file_stats = {
174
+ 'avg_lines_per_file': statistics.mean(line_counts) if line_counts else 0,
175
+ 'median_lines_per_file': statistics.median(line_counts) if line_counts else 0,
176
+ 'min_lines': min(line_counts) if line_counts else 0,
177
+ 'max_lines': max(line_counts) if line_counts else 0,
178
+ 'avg_file_size': statistics.mean(file_sizes) if file_sizes else 0,
179
+ 'comment_percentage': (self.comment_lines / self.total_lines * 100) if self.total_lines > 0 else 0,
180
+ 'empty_percentage': (self.empty_lines / self.total_lines * 100) if self.total_lines > 0 else 0,
181
+ 'code_percentage': (self.code_lines / self.total_lines * 100) if self.total_lines > 0 else 0,
182
+ }
183
+
184
+ self._stats_computed = True
185
+ return self._file_stats
186
+
187
+ def get_largest_file(self) -> Optional[FileInfo]:
188
+ """Get the largest file in this folder (recursively)."""
189
+ largest_file = None
190
+ largest_size = 0
191
+
192
+ for file_info in self.files:
193
+ if file_info.size > largest_size:
194
+ largest_size = file_info.size
195
+ largest_file = file_info
196
+
197
+ for subfolder in self.subfolders:
198
+ sub_largest = subfolder.get_largest_file()
199
+ if sub_largest and sub_largest.size > largest_size:
200
+ largest_size = sub_largest.size
201
+ largest_file = sub_largest
202
+
203
+ return largest_file
204
+
205
+ def get_smallest_file(self) -> Optional[FileInfo]:
206
+ """Get the smallest file in this folder (recursively)."""
207
+ smallest_file = None
208
+ smallest_size = float('inf')
209
+
210
+ for file_info in self.files:
211
+ if file_info.size < smallest_size:
212
+ smallest_size = file_info.size
213
+ smallest_file = file_info
214
+
215
+ for subfolder in self.subfolders:
216
+ sub_smallest = subfolder.get_smallest_file()
217
+ if sub_smallest and sub_smallest.size < smallest_size:
218
+ smallest_size = sub_smallest.size
219
+ smallest_file = sub_smallest
220
+
221
+ return smallest_file
222
+
223
+ def get_files_by_extension(self, extension: str) -> List[FileInfo]:
224
+ """Get all files with a specific extension (recursively)."""
225
+ files = [f for f in self.files if f.extension.lower() ==
226
+ extension.lower()]
227
+
228
+ for subfolder in self.subfolders:
229
+ files.extend(subfolder.get_files_by_extension(extension))
230
+
231
+ return files
232
+
233
+ def get_python_files(self) -> List[FileInfo]:
234
+ """Get all Python files (recursively)."""
235
+ return self.get_files_by_extension('.py')
236
+
237
+ def get_folder_structure(self) -> Dict[str, int]:
238
+ """Get folder structure by depth."""
239
+ structure: Dict[str, int] = defaultdict(int)
240
+ structure[f"depth_{self.depth}"] += 1
241
+
242
+ for subfolder in self.subfolders:
243
+ sub_structure = subfolder.get_folder_structure()
244
+ for depth, count in sub_structure.items():
245
+ structure[depth] += count
246
+
247
+ return structure
248
+
249
+ def get_imports_summary(self) -> Counter:
250
+ """Get summary of all imports (recursively)."""
251
+ imports: Counter[str] = Counter()
252
+
253
+ for file_info in self.files:
254
+ if file_info.is_python:
255
+ imports.update(file_info.imports)
256
+
257
+ for subfolder in self.subfolders:
258
+ imports.update(subfolder.get_imports_summary())
259
+
260
+ return imports
261
+
262
+ def to_dict(self) -> Dict[str, Any]:
263
+ """Convert FolderInfo to dictionary."""
264
+ stats = self.compute_statistics()
265
+
266
+ return {
267
+ 'name': self.name,
268
+ 'path': self.path,
269
+ 'depth': self.depth,
270
+ 'total_files': self.total_files,
271
+ 'total_folders': self.total_folders,
272
+ 'python_files': self.python_files,
273
+ 'total_lines': self.total_lines,
274
+ 'code_lines': self.code_lines,
275
+ 'comment_lines': self.comment_lines,
276
+ 'empty_lines': self.empty_lines,
277
+ 'total_size': self.total_size,
278
+ 'file_extensions': dict(self.file_extensions),
279
+ 'excluded_files': self.excluded_files,
280
+ 'excluded_folders': self.excluded_folders,
281
+ 'created_time': self.created_time.isoformat() if self.created_time else None,
282
+ 'modified_time': self.modified_time.isoformat() if self.modified_time else None,
283
+ 'accessed_time': self.accessed_time.isoformat() if self.accessed_time else None,
284
+ 'statistics': stats,
285
+ 'files': [f.to_dict() for f in self.files],
286
+ 'subfolders': [sf.to_dict() for sf in self.subfolders]
287
+ }
288
+
289
+ def print_summary(self, indent: int = 0):
290
+ """Print a summary of this folder."""
291
+ prefix = " " * indent
292
+ stats = self.compute_statistics()
293
+
294
+ print(f"{prefix}šŸ“ {self.name}/")
295
+ print(f"{prefix} šŸ“Š Files: {self.total_files}, Folders: {self.total_folders}")
296
+ print(
297
+ f"{prefix} šŸ Python: {self.python_files} files, {self.total_lines} lines")
298
+ print(
299
+ f"{prefix} šŸ’¾ Size: {self.total_size:,} bytes ({self.total_size/1024:.1f} KB)")
300
+
301
+ if self.python_files > 0:
302
+ print(f"{prefix} šŸ“ˆ Avg lines: {stats['avg_lines_per_file']:.1f}")
303
+ print(
304
+ f"{prefix} šŸ’» Code: {stats['code_percentage']:.1f}%, Comments: {stats['comment_percentage']:.1f}%")
305
+
306
+ # Print top file extensions
307
+ if self.file_extensions:
308
+ top_extensions = self.file_extensions.most_common(3)
309
+ ext_str = ", ".join(
310
+ [f"{ext}: {count}" for ext, count in top_extensions])
311
+ print(f"{prefix} šŸ“„ Extensions: {ext_str}")
312
+
313
+ # Print subfolders
314
+ for subfolder in self.subfolders:
315
+ subfolder.print_summary(indent + 1)
316
+
317
+
318
+ class FolderAnalyzer:
319
+ """Analyzer for creating FolderInfo instances recursively."""
320
+
321
+ def __init__(self, blacklist: Optional[Set[str]] = None):
322
+ """Initialize the analyzer with optional blacklist."""
323
+ self.blacklist = blacklist or self._get_default_blacklist()
324
+
325
+ def _get_default_blacklist(self) -> Set[str]:
326
+ """Get default blacklist patterns."""
327
+ return {
328
+ # Node.js
329
+ "node_modules", "npm-debug.log*", "yarn-debug.log*", "yarn-error.log*",
330
+
331
+ # Python
332
+ "__pycache__", "*.pyc", "*.pyo", "*.pyd", ".Python", "venv", "env",
333
+ ".env", ".venv", "ENV", "env.bak", "venv.bak",
334
+
335
+ # Build and distribution
336
+ "build", "develop-eggs", "dist", "downloads", "eggs", ".eggs", "lib",
337
+ "lib64", "parts", "sdist", "var", "wheels", "*.egg-info",
338
+ ".installed.cfg", "*.egg",
339
+
340
+ # IDE and editor files
341
+ ".vscode", ".idea", "*.swp", "*.swo", "*~", ".DS_Store", "Thumbs.db",
342
+
343
+ # Git and version control
344
+ ".git", ".gitignore", ".gitattributes", ".svn", ".hg",
345
+
346
+ # OS generated files
347
+ ".DS_Store?", "._*", ".Spotlight-V100", ".Trashes", "ehthumbs.db",
348
+
349
+ # Logs and temporary files
350
+ "*.log", "*.tmp", "*.temp", "temp", "tmp",
351
+
352
+ # Archive and backup
353
+ "*.zip", "*.tar.gz", "*.rar", "*.7z", "*.bak", "*.backup",
354
+ }
355
+
356
+ def is_blacklisted(self, path: Path) -> bool:
357
+ """Check if a path matches any blacklist pattern."""
358
+ path_str = str(path)
359
+
360
+ for pattern in self.blacklist:
361
+ # Handle directory patterns
362
+ if pattern.endswith('/') or pattern.endswith('\\'):
363
+ if path_str.endswith(pattern.rstrip('/\\')):
364
+ return True
365
+ # Handle glob patterns
366
+ elif '*' in pattern or '?' in pattern:
367
+ if fnmatch.fnmatch(path.name, pattern):
368
+ return True
369
+ # Handle exact matches
370
+ elif path.name == pattern:
371
+ return True
372
+ # Handle path contains
373
+ elif pattern in path_str:
374
+ return True
375
+
376
+ return False
377
+
378
+ def analyze_folder(self, folder_path: Union[str, Path], max_depth: Optional[int] = None) -> FolderInfo:
379
+ """Analyze a folder and return a FolderInfo instance."""
380
+ folder = Path(folder_path)
381
+
382
+ if not folder.exists() or not folder.is_dir():
383
+ raise ValueError(
384
+ f"Folder {folder_path} does not exist or is not a directory")
385
+
386
+ return self._analyze_folder_recursive(folder, depth=0, max_depth=max_depth)
387
+
388
+ def _analyze_folder_recursive(self, folder: Path, depth: int = 0, max_depth: Optional[int] = None) -> FolderInfo:
389
+ """Recursively analyze a folder."""
390
+ if max_depth is not None and depth > max_depth:
391
+ return FolderInfo(
392
+ name=folder.name,
393
+ path=str(folder),
394
+ depth=depth
395
+ )
396
+
397
+ # Create FolderInfo instance
398
+ folder_info = FolderInfo(
399
+ name=folder.name,
400
+ path=str(folder.absolute()),
401
+ depth=depth
402
+ )
403
+
404
+ # Get folder timestamps
405
+ try:
406
+ stat = folder.stat()
407
+ folder_info.created_time = datetime.fromtimestamp(stat.st_ctime)
408
+ folder_info.modified_time = datetime.fromtimestamp(stat.st_mtime)
409
+ folder_info.accessed_time = datetime.fromtimestamp(stat.st_atime)
410
+ except OSError:
411
+ pass
412
+
413
+ # Analyze files in current folder
414
+ for file_path in folder.iterdir():
415
+ if file_path.is_file():
416
+ if self.is_blacklisted(file_path):
417
+ folder_info.excluded_files += 1
418
+ continue
419
+
420
+ # Create FileInfo instance
421
+ file_info = self._create_file_info(file_path)
422
+ folder_info.add_file(file_info)
423
+
424
+ elif file_path.is_dir():
425
+ if self.is_blacklisted(file_path):
426
+ folder_info.excluded_folders += 1
427
+ continue
428
+
429
+ # Recursively analyze subfolder
430
+ subfolder_info = self._analyze_folder_recursive(
431
+ file_path, depth + 1, max_depth
432
+ )
433
+ folder_info.add_subfolder(subfolder_info)
434
+
435
+ return folder_info
436
+
437
+ def _create_file_info(self, file_path: Path) -> FileInfo:
438
+ """Create a FileInfo instance for a file."""
439
+ try:
440
+ stat = file_path.stat()
441
+ file_info = FileInfo(
442
+ name=file_path.name,
443
+ path=str(file_path),
444
+ size=stat.st_size,
445
+ extension=file_path.suffix,
446
+ created_time=datetime.fromtimestamp(stat.st_ctime),
447
+ modified_time=datetime.fromtimestamp(stat.st_mtime),
448
+ accessed_time=datetime.fromtimestamp(stat.st_atime)
449
+ )
450
+ return file_info
451
+ except OSError:
452
+ # Fallback for files we can't stat
453
+ return FileInfo(
454
+ name=file_path.name,
455
+ path=str(file_path),
456
+ size=0,
457
+ extension=file_path.suffix
458
+ )
459
+
460
+
461
+ def analyze_folder(folder_path: Union[str, Path], blacklist: Optional[Set[str]] = None,
462
+ max_depth: Optional[int] = None) -> FolderInfo:
463
+ """Convenience function to analyze a folder."""
464
+ analyzer = FolderAnalyzer(blacklist)
465
+ return analyzer.analyze_folder(folder_path, max_depth)
466
+
467
+
468
+ def print_folder_info(folder_info: FolderInfo, show_files: bool = False, show_subfolders: bool = True):
469
+ """Print comprehensive information about a folder."""
470
+ print(f"\n{'='*80}")
471
+ print(f"šŸ“ FOLDER ANALYSIS: {folder_info.name}")
472
+ print(f"{'='*80}")
473
+
474
+ # Print basic statistics
475
+ stats = folder_info.compute_statistics()
476
+
477
+ print(f"\nšŸ“Š BASIC STATISTICS:")
478
+ print(f" Total Files: {folder_info.total_files:,}")
479
+ print(f" Total Folders: {folder_info.total_folders:,}")
480
+ print(f" Python Files: {folder_info.python_files:,}")
481
+ print(f" Total Lines: {folder_info.total_lines:,}")
482
+ print(
483
+ f" Total Size: {folder_info.total_size:,} bytes ({folder_info.total_size/1024:.1f} KB)")
484
+
485
+ if folder_info.excluded_files > 0 or folder_info.excluded_folders > 0:
486
+ print(f" Excluded Files: {folder_info.excluded_files:,}")
487
+ print(f" Excluded Folders: {folder_info.excluded_folders:,}")
488
+
489
+ # Print line analysis if Python files exist
490
+ if folder_info.python_files > 0:
491
+ print(f"\nšŸ“ˆ LINE ANALYSIS:")
492
+ print(f" Average Lines per File: {stats['avg_lines_per_file']:.1f}")
493
+ print(
494
+ f" Median Lines per File: {stats['median_lines_per_file']:.1f}")
495
+ print(f" Min Lines: {stats['min_lines']:,}")
496
+ print(f" Max Lines: {stats['max_lines']:,}")
497
+
498
+ print(f"\nšŸ’» CODE COMPOSITION:")
499
+ print(
500
+ f" Code Lines: {folder_info.code_lines:,} ({stats['code_percentage']:.1f}%)")
501
+ print(
502
+ f" Comment Lines: {folder_info.comment_lines:,} ({stats['comment_percentage']:.1f}%)")
503
+ print(
504
+ f" Empty Lines: {folder_info.empty_lines:,} ({stats['empty_percentage']:.1f}%)")
505
+
506
+ # Print file extensions
507
+ print(f"\nšŸ“ FILE EXTENSIONS (Top 10):")
508
+ for ext, count in folder_info.file_extensions.most_common(10):
509
+ print(f" {ext}: {count:,}")
510
+
511
+ # Print largest and smallest files
512
+ largest_file = folder_info.get_largest_file()
513
+ smallest_file = folder_info.get_smallest_file()
514
+
515
+ if largest_file:
516
+ print(f"\nšŸ† EXTREMES:")
517
+ print(
518
+ f" Largest File: {largest_file.name} ({largest_file.size:,} bytes)")
519
+ if largest_file.is_python:
520
+ print(
521
+ f" {largest_file.path} ({largest_file.line_count:,} lines)")
522
+
523
+ if smallest_file and smallest_file.size > 0:
524
+ print(
525
+ f" Smallest File: {smallest_file.name} ({smallest_file.size:,} bytes)")
526
+
527
+ # Print imports summary
528
+ imports = folder_info.get_imports_summary()
529
+ if imports:
530
+ print(f"\nšŸ“¦ TOP IMPORTS (Top 10):")
531
+ for module, count in imports.most_common(10):
532
+ print(f" {module}: {count:,}")
533
+
534
+ # Print folder structure
535
+ structure = folder_info.get_folder_structure()
536
+ if structure:
537
+ print(f"\nšŸ“‚ FOLDER STRUCTURE:")
538
+ for depth, count in sorted(structure.items()):
539
+ print(f" {depth}: {count:,} folders")
540
+
541
+ # Print files if requested
542
+ if show_files and folder_info.files:
543
+ print(f"\nšŸ“„ FILES IN THIS FOLDER:")
544
+ for file_info in sorted(folder_info.files, key=lambda x: x.name.lower()):
545
+ print(f" {file_info.name} ({file_info.size:,} bytes)")
546
+ if file_info.is_python and file_info.line_count > 0:
547
+ print(
548
+ f" Lines: {file_info.line_count}, Classes: {file_info.classes}, Functions: {file_info.functions}")
549
+
550
+ # Print subfolders if requested
551
+ if show_subfolders and folder_info.subfolders:
552
+ print(f"\nšŸ“ SUBFOLDERS:")
553
+ for subfolder in sorted(folder_info.subfolders, key=lambda x: x.name.lower()):
554
+ subfolder.print_summary(indent=1)
555
+
556
+
557
+ if __name__ == "__main__":
558
+ import sys
559
+
560
+ if len(sys.argv) < 2:
561
+ print(
562
+ "Usage: python folder_info.py <folder_path> [--max-depth N] [--show-files] [--no-subfolders]")
563
+ print("Example: python folder_info.py danielutils --max-depth 3 --show-files")
564
+ sys.exit(1)
565
+
566
+ folder_path = sys.argv[1]
567
+ max_depth = None
568
+ show_files = False
569
+ show_subfolders = True
570
+
571
+ # Parse arguments
572
+ i = 2
573
+ while i < len(sys.argv):
574
+ if sys.argv[i] == "--max-depth" and i + 1 < len(sys.argv):
575
+ max_depth = int(sys.argv[i + 1])
576
+ i += 2
577
+ elif sys.argv[i] == "--show-files":
578
+ show_files = True
579
+ i += 1
580
+ elif sys.argv[i] == "--no-subfolders":
581
+ show_subfolders = False
582
+ i += 1
583
+ else:
584
+ i += 1
585
+
586
+ try:
587
+ folder_info = analyze_folder(folder_path, max_depth=max_depth)
588
+ print_folder_info(folder_info, show_files=show_files,
589
+ show_subfolders=show_subfolders)
590
+ except Exception as e:
591
+ print(f"āŒ Error analyzing folder: {e}")
592
+ sys.exit(1)