lange-python 0.3.3__tar.gz → 0.3.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {lange_python-0.3.3 → lange_python-0.3.5}/PKG-INFO +2 -3
  2. {lange_python-0.3.3 → lange_python-0.3.5}/README.md +1 -2
  3. {lange_python-0.3.3 → lange_python-0.3.5}/lange/cli/code/_stats.py +70 -11
  4. {lange_python-0.3.3 → lange_python-0.3.5}/pyproject.toml +1 -1
  5. {lange_python-0.3.3 → lange_python-0.3.5}/lange/__init__.py +0 -0
  6. {lange_python-0.3.3 → lange_python-0.3.5}/lange/__main__.py +0 -0
  7. {lange_python-0.3.3 → lange_python-0.3.5}/lange/_util/__init__.py +0 -0
  8. {lange_python-0.3.3 → lange_python-0.3.5}/lange/_util/_base_client.py +0 -0
  9. {lange_python-0.3.3 → lange_python-0.3.5}/lange/_util/_key_handling.py +0 -0
  10. {lange_python-0.3.3 → lange_python-0.3.5}/lange/cli/__init__.py +0 -0
  11. {lange_python-0.3.3 → lange_python-0.3.5}/lange/cli/build/__init__.py +0 -0
  12. {lange_python-0.3.3 → lange_python-0.3.5}/lange/cli/build/_command.py +0 -0
  13. {lange_python-0.3.3 → lange_python-0.3.5}/lange/cli/build/_discovery.py +0 -0
  14. {lange_python-0.3.3 → lange_python-0.3.5}/lange/cli/build/_docker.py +0 -0
  15. {lange_python-0.3.3 → lange_python-0.3.5}/lange/cli/build/_poetry.py +0 -0
  16. {lange_python-0.3.3 → lange_python-0.3.5}/lange/cli/build/_types.py +0 -0
  17. {lange_python-0.3.3 → lange_python-0.3.5}/lange/cli/code/__init__.py +0 -0
  18. {lange_python-0.3.3 → lange_python-0.3.5}/lange/cli/distribution/__init__.py +0 -0
  19. {lange_python-0.3.3 → lange_python-0.3.5}/lange/cli/distribution/_command.py +0 -0
  20. {lange_python-0.3.3 → lange_python-0.3.5}/lange/distribution/__init__.py +0 -0
  21. {lange_python-0.3.3 → lange_python-0.3.5}/lange/distribution/_client.py +0 -0
  22. {lange_python-0.3.3 → lange_python-0.3.5}/lange/distribution/_util.py +0 -0
  23. {lange_python-0.3.3 → lange_python-0.3.5}/lange/tunnel/__init__.py +0 -0
  24. {lange_python-0.3.3 → lange_python-0.3.5}/lange/tunnel/_client.py +0 -0
  25. {lange_python-0.3.3 → lange_python-0.3.5}/lange/tunnel/_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lange-python
3
- Version: 0.3.3
3
+ Version: 0.3.5
4
4
  Summary: A bundeld set of tools, clients for the lange-suite of tools and more.
5
5
  Author: contact@robertlange.me
6
6
  Requires-Python: >=3.13
@@ -37,8 +37,7 @@ from lange.tunnel import Tunnel
37
37
 
38
38
  tunnel = Tunnel(
39
39
  host="wss://tunnel.lange-labs.com",
40
- secret_key="your-bearer-token",
41
- tunnel_name="dev-edge", # Optional but recommended when one key is linked to multiple tunnels.
40
+ api_key="your-bearer-token",
42
41
  target="http://localhost:3000",
43
42
  )
44
43
 
@@ -22,8 +22,7 @@ from lange.tunnel import Tunnel
22
22
 
23
23
  tunnel = Tunnel(
24
24
  host="wss://tunnel.lange-labs.com",
25
- secret_key="your-bearer-token",
26
- tunnel_name="dev-edge", # Optional but recommended when one key is linked to multiple tunnels.
25
+ api_key="your-bearer-token",
27
26
  target="http://localhost:3000",
28
27
  )
29
28
 
@@ -7,7 +7,7 @@ SUPPORTED_EXTENSIONS: tuple[str, ...] = (
7
7
  # Python
8
8
  ".py",
9
9
  # JavaScript / TypeScript / React
10
- ".js", ".jsx", ".ts", ".tsx",
10
+ ".js", ".jsx", ".test.ts", ".test.tsx", ".ts", ".tsx",
11
11
  # Web (Markup / Styling)
12
12
  ".html", ".css", ".scss", ".sass", ".less",
13
13
  # Shell / Bash
@@ -59,12 +59,13 @@ IGNORED_DIRECTORIES: tuple[str, ...] = (
59
59
  )
60
60
 
61
61
 
62
- def _render_stats_table(stats: dict[str, int]) -> str:
62
+ def render_stats_table(stats: dict[str, int], label: str = "File-Type") -> str:
63
63
  """
64
64
  Render LOC statistics as an ASCII box table.
65
65
 
66
- :param stats: Mapping from file ending to line count.
67
- :returns: Table string with file type, LOC and percentage values.
66
+ :param stats: Mapping from group label to line count.
67
+ :param label: Heading for the first column.
68
+ :returns: Table string with label, LOC and percentage values.
68
69
  """
69
70
  total = sum(stats.values())
70
71
  rows: list[tuple[str, str, str]] = []
@@ -80,7 +81,7 @@ def _render_stats_table(stats: dict[str, int]) -> str:
80
81
  total_percentage = 100.0 if total else 0.0
81
82
  rows.append(("TOTAL", str(total), f"{total_percentage:.2f}%"))
82
83
 
83
- headers = ("File-Type", "LOC", "Percentage")
84
+ headers = (label, "LOC", "Percentage")
84
85
  col_widths = [
85
86
  max(len(headers[index]), max((len(row[index]) for row in rows), default=0))
86
87
  for index in range(3)
@@ -98,7 +99,29 @@ def _render_stats_table(stats: dict[str, int]) -> str:
98
99
  return "\n".join(lines)
99
100
 
100
101
 
101
- def _count_lines_by_extension(root: Path, extensions: Iterable[str]) -> dict[str, int]:
102
+ def _match_supported_extension(file_name: str, extensions: Iterable[str]) -> str | None:
103
+ """
104
+ Resolve the longest supported file ending for a file name.
105
+
106
+ :param file_name: File name that should be matched.
107
+ :param extensions: Allowed file endings, including the leading dot.
108
+ :returns: The longest matching ending, or ``None`` when unsupported.
109
+ """
110
+ normalized_file_name = file_name.lower()
111
+ sorted_extensions = sorted(
112
+ (extension.lower() for extension in extensions),
113
+ key=len,
114
+ reverse=True,
115
+ )
116
+
117
+ for extension in sorted_extensions:
118
+ if normalized_file_name.endswith(extension):
119
+ return extension
120
+
121
+ return None
122
+
123
+
124
+ def count_lines_by_extension(root: Path, extensions: Iterable[str]) -> dict[str, int]:
102
125
  """
103
126
  Count file lines recursively grouped by file ending.
104
127
 
@@ -113,8 +136,8 @@ def _count_lines_by_extension(root: Path, extensions: Iterable[str]) -> dict[str
113
136
  directories[:] = [name for name in directories if name not in IGNORED_DIRECTORIES]
114
137
 
115
138
  for file_name in files:
116
- suffix = Path(file_name).suffix.lower()
117
- if suffix not in counts:
139
+ suffix = _match_supported_extension(file_name, normalized_extensions)
140
+ if suffix is None:
118
141
  continue
119
142
 
120
143
  file_path = Path(current_root) / file_name
@@ -128,6 +151,39 @@ def _count_lines_by_extension(root: Path, extensions: Iterable[str]) -> dict[str
128
151
  return counts
129
152
 
130
153
 
154
+ def count_lines_by_subfolder(root: Path, extensions: Iterable[str]) -> dict[str, int]:
155
+ """
156
+ Count file lines recursively grouped by top-level folder from the root.
157
+
158
+ :param root: Directory that should be scanned recursively.
159
+ :param extensions: Allowed file endings, including the leading dot.
160
+ :returns: Mapping from immediate subfolder name to counted LOC.
161
+ """
162
+ counts: dict[str, int] = {}
163
+
164
+ for current_root, directories, files in os.walk(root, topdown=True):
165
+ directories[:] = [name for name in directories if name not in IGNORED_DIRECTORIES]
166
+
167
+ for file_name in files:
168
+ suffix = _match_supported_extension(file_name, extensions)
169
+ if suffix is None:
170
+ continue
171
+
172
+ file_path = Path(current_root) / file_name
173
+ try:
174
+ with file_path.open("r", encoding="utf-8", errors="ignore") as file_handle:
175
+ line_count = sum(1 for _ in file_handle)
176
+ except Exception:
177
+ # Silently skip files that can't be opened (e.g., permissions issues)
178
+ continue
179
+
180
+ relative_parts = file_path.relative_to(root).parts
181
+ folder_name = relative_parts[0] if len(relative_parts) > 1 else "."
182
+ counts[folder_name] = counts.get(folder_name, 0) + line_count
183
+
184
+ return counts
185
+
186
+
131
187
  @click.command("stats")
132
188
  def code_stats() -> None:
133
189
  """
@@ -135,7 +191,8 @@ def code_stats() -> None:
135
191
 
136
192
  :returns: ``None``.
137
193
  """
138
- stats = _count_lines_by_extension(Path.cwd(), SUPPORTED_EXTENSIONS)
194
+ stats = count_lines_by_extension(Path.cwd(), SUPPORTED_EXTENSIONS)
195
+ subfolder_stats = count_lines_by_subfolder(Path.cwd(), SUPPORTED_EXTENSIONS)
139
196
 
140
197
  # Filter out empty languages for the recognized printout so it's not overwhelming
141
198
  active_extensions = [ext for ext, count in stats.items() if count > 0]
@@ -144,8 +201,10 @@ def code_stats() -> None:
144
201
  click.echo()
145
202
  click.echo(f"Recognized file endings found: {' '.join(active_extensions) if active_extensions else 'None'}")
146
203
  click.echo(f"Ignored folders: {' '.join(IGNORED_DIRECTORIES)}")
147
- click.echo(_render_stats_table(stats))
204
+ click.echo(render_stats_table(stats))
205
+ click.echo()
206
+ click.echo(render_stats_table(subfolder_stats, label="Folder"))
148
207
 
149
208
 
150
209
  if __name__ == '__main__':
151
- code_stats()
210
+ code_stats()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lange-python"
3
- version = "0.3.3"
3
+ version = "0.3.5"
4
4
  description = "A bundeld set of tools, clients for the lange-suite of tools and more."
5
5
  authors = [
6
6
  {name = "contact@robertlange.me"}