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.
- danielutils/__init__.py +50 -0
- danielutils/abstractions/__init__.py +3 -0
- danielutils/abstractions/db/__init__.py +10 -0
- danielutils/abstractions/db/database.py +206 -0
- danielutils/abstractions/db/database_definitions.py +219 -0
- danielutils/abstractions/db/database_exceptions.py +34 -0
- danielutils/abstractions/db/database_factory.py +70 -0
- danielutils/abstractions/db/database_initializer.py +261 -0
- danielutils/abstractions/db/database_models.py +213 -0
- danielutils/abstractions/db/dependencies.py +6 -0
- danielutils/abstractions/db/implementations/__init__.py +4 -0
- danielutils/abstractions/db/implementations/in_memory_database.py +365 -0
- danielutils/abstractions/db/implementations/persistent_in_memory_database.py +174 -0
- danielutils/abstractions/db/implementations/redis_database.py +403 -0
- danielutils/abstractions/db/implementations/sqlite_database.py +491 -0
- danielutils/abstractions/multiprogramming/__init__.py +3 -0
- danielutils/abstractions/multiprogramming/multi_id.py +26 -0
- danielutils/abstractions/multiprogramming/worker.py +104 -0
- danielutils/abstractions/multiprogramming/worker_pool.py +98 -0
- danielutils/abstractions/repl.py +125 -0
- danielutils/aliases.py +174 -0
- danielutils/async_/__init__.py +7 -0
- danielutils/async_/async_cmd.py +28 -0
- danielutils/async_/async_command.py +879 -0
- danielutils/async_/async_layered_command.py +125 -0
- danielutils/async_/async_retry_executor.py +86 -0
- danielutils/async_/async_worker_pool.py +110 -0
- danielutils/async_/time_strategy.py +73 -0
- danielutils/async_/utils.py +57 -0
- danielutils/better_builtins/__init__.py +3 -0
- danielutils/better_builtins/counter.py +48 -0
- danielutils/better_builtins/frange.py +266 -0
- danielutils/better_builtins/typed_builtins/__init__.py +4 -0
- danielutils/better_builtins/typed_builtins/factory.py +126 -0
- danielutils/better_builtins/typed_builtins/tdict.py +36 -0
- danielutils/better_builtins/typed_builtins/tlist.py +223 -0
- danielutils/better_builtins/typed_builtins/tset.py +67 -0
- danielutils/better_builtins/typed_builtins/ttuple.py +15 -0
- danielutils/colors.py +203 -0
- danielutils/context_managers/__init__.py +5 -0
- danielutils/context_managers/attr_context.py +40 -0
- danielutils/context_managers/multi_context.py +34 -0
- danielutils/context_managers/optional_context.py +33 -0
- danielutils/context_managers/state_context.py +25 -0
- danielutils/context_managers/temporary_file.py +108 -0
- danielutils/convenience.py +12 -0
- danielutils/conversions/__init__.py +2 -0
- danielutils/conversions/main_conversions.py +131 -0
- danielutils/conversions/specialized_conversions/__init__.py +2 -0
- danielutils/conversions/specialized_conversions/to_hex.py +25 -0
- danielutils/conversions/specialized_conversions/to_int.py +19 -0
- danielutils/custom_types.py +18 -0
- danielutils/data_structures/__init__.py +8 -0
- danielutils/data_structures/algorithms.py +52 -0
- danielutils/data_structures/comparer.py +47 -0
- danielutils/data_structures/default_dict.py +23 -0
- danielutils/data_structures/functions.py +35 -0
- danielutils/data_structures/graph/__init__.py +4 -0
- danielutils/data_structures/graph/binary_node.py +71 -0
- danielutils/data_structures/graph/graph.py +208 -0
- danielutils/data_structures/graph/multinode.py +101 -0
- danielutils/data_structures/graph/node.py +60 -0
- danielutils/data_structures/heap/__init__.py +3 -0
- danielutils/data_structures/heap/heap.py +119 -0
- danielutils/data_structures/heap/max_heap.py +18 -0
- danielutils/data_structures/heap/min_heap.py +18 -0
- danielutils/data_structures/queue/__init__.py +3 -0
- danielutils/data_structures/queue/atomic_queue.py +15 -0
- danielutils/data_structures/queue/priority_queue.py +110 -0
- danielutils/data_structures/queue/queue.py +92 -0
- danielutils/data_structures/stack.py +102 -0
- danielutils/data_structures/trees/__init__.py +2 -0
- danielutils/data_structures/trees/binary_syntax_tree.py +84 -0
- danielutils/data_structures/trees/binary_tree.py +63 -0
- danielutils/date.py +43 -0
- danielutils/date_time.py +23 -0
- danielutils/decorators/__init__.py +18 -0
- danielutils/decorators/atomic.py +45 -0
- danielutils/decorators/attach.py +61 -0
- danielutils/decorators/chain_decorators.py +39 -0
- danielutils/decorators/decorate_conditionally.py +32 -0
- danielutils/decorators/delay_call.py +47 -0
- danielutils/decorators/deprecate.py +46 -0
- danielutils/decorators/final.py +39 -0
- danielutils/decorators/limit_recursion.py +63 -0
- danielutils/decorators/memo.py +68 -0
- danielutils/decorators/normalize_decorator.py +55 -0
- danielutils/decorators/overload.py +204 -0
- danielutils/decorators/partially_implemented.py +32 -0
- danielutils/decorators/processify.py +74 -0
- danielutils/decorators/property.py +42 -0
- danielutils/decorators/singleton.py +59 -0
- danielutils/decorators/threadify.py +40 -0
- danielutils/decorators/timeout.py +82 -0
- danielutils/decorators/total_ordering.py +82 -0
- danielutils/decorators/validate.py +227 -0
- danielutils/exceptions.py +90 -0
- danielutils/functions/__init__.py +13 -0
- danielutils/functions/areoneof.py +47 -0
- danielutils/functions/check_foreach.py +39 -0
- danielutils/functions/factorial.py +24 -0
- danielutils/functions/flatten.py +33 -0
- danielutils/functions/foreach.py +20 -0
- danielutils/functions/isoftype.py +475 -0
- danielutils/functions/isoneof.py +71 -0
- danielutils/functions/multiloop.py +51 -0
- danielutils/functions/parallel_for.py +47 -0
- danielutils/functions/partition.py +45 -0
- danielutils/functions/powerset.py +31 -0
- danielutils/functions/subseteq.py +25 -0
- danielutils/functions/types_subseteq.py +51 -0
- danielutils/generators/__init__.py +3 -0
- danielutils/generators/conditional_generator.py +59 -0
- danielutils/generators/generator_from_stream.py +31 -0
- danielutils/generators/join_generators.py +109 -0
- danielutils/internet.py +92 -0
- danielutils/io_.py +451 -0
- danielutils/java/__init__.py +18 -0
- danielutils/java/interfaces/__init__.py +1 -0
- danielutils/java/interfaces/comparable.py +31 -0
- danielutils/java/java_interface.py +59 -0
- danielutils/logging_/__init__.py +2 -0
- danielutils/logging_/_impl/__init__.py +4 -0
- danielutils/logging_/_impl/builtin_impls/__init__.py +2 -0
- danielutils/logging_/_impl/builtin_impls/file_logger.py +35 -0
- danielutils/logging_/_impl/builtin_impls/print_logger.py +21 -0
- danielutils/logging_/_impl/log_level.py +13 -0
- danielutils/logging_/_impl/logger.py +109 -0
- danielutils/logging_/_impl/logger_strategy_impl_base.py +37 -0
- danielutils/logging_/utils.py +199 -0
- danielutils/lombok/__init__.py +1 -0
- danielutils/lombok/builder.py +68 -0
- danielutils/math_/__init__.py +2 -0
- danielutils/math_/constants.py +103 -0
- danielutils/math_/functions.py +34 -0
- danielutils/math_/math_print.py +52 -0
- danielutils/math_/math_symbols.py +125 -0
- danielutils/metaclasses/__init__.py +5 -0
- danielutils/metaclasses/atomic_class_meta.py +36 -0
- danielutils/metaclasses/implicit_data_deleter_meta.py +87 -0
- danielutils/metaclasses/instance_cache_meta.py +66 -0
- danielutils/metaclasses/interface.py +328 -0
- danielutils/metaclasses/overload_meta.py +48 -0
- danielutils/mock_/__init__.py +1 -0
- danielutils/mock_/mock_module.py +27 -0
- danielutils/path.py +55 -0
- danielutils/print_.py +123 -0
- danielutils/progress_bar/__init__.py +3 -0
- danielutils/progress_bar/ascii_progress_bar.py +161 -0
- danielutils/progress_bar/progress_bar.py +75 -0
- danielutils/progress_bar/progress_bar_pool.py +72 -0
- danielutils/protocols/__init__.py +3 -0
- danielutils/protocols/dictable.py +38 -0
- danielutils/protocols/evaluable.py +26 -0
- danielutils/protocols/serializable.py +51 -0
- danielutils/py.typed +0 -0
- danielutils/random_.py +47 -0
- danielutils/reflection/__init__.py +6 -0
- danielutils/reflection/declarations.py +288 -0
- danielutils/reflection/file/__init__.py +1 -0
- danielutils/reflection/file/file_reflection.py +85 -0
- danielutils/reflection/function/__init__.py +1 -0
- danielutils/reflection/function/function_reflections.py +152 -0
- danielutils/reflection/info_classes/__init__.py +6 -0
- danielutils/reflection/info_classes/argument_info.py +278 -0
- danielutils/reflection/info_classes/class_info.py +204 -0
- danielutils/reflection/info_classes/decorator_info.py +54 -0
- danielutils/reflection/info_classes/file_info.py +1180 -0
- danielutils/reflection/info_classes/folder_info.py +592 -0
- danielutils/reflection/info_classes/function_info.py +551 -0
- danielutils/reflection/info_classes/import_info.py +207 -0
- danielutils/reflection/interpreter/__init__.py +9 -0
- danielutils/reflection/interpreter/callstack.py +57 -0
- danielutils/reflection/interpreter/get_traceback.py +27 -0
- danielutils/reflection/interpreter/interpreter.py +41 -0
- danielutils/reflection/interpreter/is_debugging.py +13 -0
- danielutils/reflection/interpreter/os_.py +33 -0
- danielutils/reflection/interpreter/packages.py +33 -0
- danielutils/reflection/interpreter/python_version.py +33 -0
- danielutils/reflection/interpreter/signals.py +18 -0
- danielutils/reflection/interpreter/tracer.py +175 -0
- danielutils/reflection/module/__init__.py +3 -0
- danielutils/reflection/module/lazy_module.py +104 -0
- danielutils/reflection/module/module_reflections.py +34 -0
- danielutils/reflection/module/package_reflection.py +161 -0
- danielutils/retry_executor/__init__.py +3 -0
- danielutils/retry_executor/backoff_strategies/__init__.py +6 -0
- danielutils/retry_executor/backoff_strategies/constant_backoff.py +25 -0
- danielutils/retry_executor/backoff_strategies/exponential_backoff.py +26 -0
- danielutils/retry_executor/backoff_strategies/functional_backoff.py +29 -0
- danielutils/retry_executor/backoff_strategies/linear_backoff.py +23 -0
- danielutils/retry_executor/backoff_strategies/multiplicative_backoff.py +23 -0
- danielutils/retry_executor/backoff_strategies/no_backoff.py +17 -0
- danielutils/retry_executor/backoff_strategy.py +28 -0
- danielutils/retry_executor/retry_executor.py +55 -0
- danielutils/snippets/__init__.py +1 -0
- danielutils/snippets/try_get.py +28 -0
- danielutils/system/__init__.py +3 -0
- danielutils/system/independent.py +197 -0
- danielutils/system/layered_command.py +130 -0
- danielutils/system/windows/__init__.py +2 -0
- danielutils/system/windows/utils/__init__.py +1 -0
- danielutils/system/windows/utils/filetime.py +19 -0
- danielutils/system/windows/win32_ctime.py +183 -0
- danielutils/system/windows/windows.py +78 -0
- danielutils/testing/__init__.py +1 -0
- danielutils/testing/unittest_/__init__.py +2 -0
- danielutils/testing/unittest_/always_teardown_testcase.py +75 -0
- danielutils/testing/unittest_/auto_cwd_testcase.py +114 -0
- danielutils/text.py +169 -0
- danielutils/time.py +64 -0
- danielutils/tools/__init__.py +1 -0
- danielutils/tools/unittest_test_runner/__init__.py +9 -0
- danielutils/tools/unittest_test_runner/cli.py +70 -0
- danielutils/tools/unittest_test_runner/core/__init__.py +17 -0
- danielutils/tools/unittest_test_runner/core/discovery.py +322 -0
- danielutils/tools/unittest_test_runner/core/execution.py +244 -0
- danielutils/tools/unittest_test_runner/core/parser.py +163 -0
- danielutils/tools/unittest_test_runner/core/runner.py +561 -0
- danielutils/tools/unittest_test_runner/models.py +82 -0
- danielutils/university/__init__.py +7 -0
- danielutils/university/computability_and_complexity/__init__.py +2 -0
- danielutils/university/computability_and_complexity/discreate_finite_automaton.py +89 -0
- danielutils/university/computability_and_complexity/languages/__init__.py +0 -0
- danielutils/university/computability_and_complexity/languages/language.py +17 -0
- danielutils/university/computability_and_complexity/languages/sat.py +71 -0
- danielutils/university/computability_and_complexity/turing_machine.py +47 -0
- danielutils/university/databases/__init__.py +1 -0
- danielutils/university/databases/all.py +756 -0
- danielutils/university/image_proccesing/__init__.py +1 -0
- danielutils/university/image_proccesing/encoding/__init__.py +3 -0
- danielutils/university/image_proccesing/encoding/encoding.py +35 -0
- danielutils/university/image_proccesing/encoding/lossless/__init__.py +4 -0
- danielutils/university/image_proccesing/encoding/lossless/huffman.py +0 -0
- danielutils/university/image_proccesing/encoding/lossless/lossless_encoding.py +10 -0
- danielutils/university/image_proccesing/encoding/lossless/lzw.py +0 -0
- danielutils/university/image_proccesing/encoding/lossless/run_length.py +22 -0
- danielutils/university/image_proccesing/encoding/lossy/__init__.py +1 -0
- danielutils/university/image_proccesing/encoding/lossy/lossy_encoding.py +10 -0
- danielutils/university/image_proccesing/tansformations/__init__.py +0 -0
- danielutils/university/image_proccesing/tansformations/gaussian.py +0 -0
- danielutils/university/image_proccesing/tansformations/gradient.py +0 -0
- danielutils/university/image_proccesing/tansformations/hough.py +0 -0
- danielutils/university/image_proccesing/tansformations/laplacian.py +0 -0
- danielutils/university/image_proccesing/tansformations/transformation.py +11 -0
- danielutils/university/linear_algebra/__init__.py +1 -0
- danielutils/university/linear_algebra/matrix.py +404 -0
- danielutils/university/machine_learning/__init__.py +0 -0
- danielutils/university/machine_learning/activation_functions/__init__.py +2 -0
- danielutils/university/machine_learning/activation_functions/activation_function.py +11 -0
- danielutils/university/machine_learning/activation_functions/relu.py +11 -0
- danielutils/university/machine_learning/neuron.py +15 -0
- danielutils/university/oop/__init__.py +1 -0
- danielutils/university/oop/observer.py +31 -0
- danielutils/university/oop/strategy.py +9 -0
- danielutils/university/probability/__init__.py +7 -0
- danielutils/university/probability/conditional_variable/__init__.py +3 -0
- danielutils/university/probability/conditional_variable/conditional_variable.py +138 -0
- danielutils/university/probability/conditional_variable/continuous/__init__.py +0 -0
- danielutils/university/probability/conditional_variable/discrete/__init__.py +8 -0
- danielutils/university/probability/conditional_variable/discrete/bernoulli.py +38 -0
- danielutils/university/probability/conditional_variable/discrete/bernoulli_sum.py +31 -0
- danielutils/university/probability/conditional_variable/discrete/binomial.py +40 -0
- danielutils/university/probability/conditional_variable/discrete/conditional_from_discrete_probability_func.py +36 -0
- danielutils/university/probability/conditional_variable/discrete/discrete.py +42 -0
- danielutils/university/probability/conditional_variable/discrete/geometric.py +43 -0
- danielutils/university/probability/conditional_variable/discrete/poisson.py +32 -0
- danielutils/university/probability/conditional_variable/discrete/uniform.py +33 -0
- danielutils/university/probability/distributions.py +35 -0
- danielutils/university/probability/expressions/__init__.py +2 -0
- danielutils/university/probability/expressions/accumulation_expression.py +291 -0
- danielutils/university/probability/expressions/probability_expression.py +110 -0
- danielutils/university/probability/funcs/__init__.py +4 -0
- danielutils/university/probability/funcs/covariance.py +12 -0
- danielutils/university/probability/funcs/expected_value.py +41 -0
- danielutils/university/probability/funcs/probability_function.py +14 -0
- danielutils/university/probability/funcs/variance.py +17 -0
- danielutils/university/probability/operator.py +79 -0
- danielutils/university/probability/protocols.py +32 -0
- danielutils/university/probability/supp.py +101 -0
- danielutils/university/probability/transformation.py +44 -0
- danielutils/versioned_imports.py +31 -0
- danielutils-1.1.28.dist-info/METADATA +390 -0
- danielutils-1.1.28.dist-info/RECORD +287 -0
- danielutils-1.1.28.dist-info/WHEEL +5 -0
- danielutils-1.1.28.dist-info/licenses/LICENSE +21 -0
- 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)
|