sourcecode 2.5.4__py3-none-any.whl → 2.5.5__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.
- sourcecode/__init__.py +1 -1
- sourcecode/architecture_analyzer.py +35 -2
- sourcecode/reconciliation.py +145 -0
- sourcecode/summarizer.py +25 -5
- {sourcecode-2.5.4.dist-info → sourcecode-2.5.5.dist-info}/METADATA +1 -1
- {sourcecode-2.5.4.dist-info → sourcecode-2.5.5.dist-info}/RECORD +9 -9
- {sourcecode-2.5.4.dist-info → sourcecode-2.5.5.dist-info}/WHEEL +0 -0
- {sourcecode-2.5.4.dist-info → sourcecode-2.5.5.dist-info}/entry_points.txt +0 -0
- {sourcecode-2.5.4.dist-info → sourcecode-2.5.5.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
|
@@ -4,6 +4,8 @@ import re
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Literal, Optional
|
|
6
6
|
|
|
7
|
+
from sourcecode.reconciliation import incoherent_layers
|
|
8
|
+
|
|
7
9
|
from sourcecode.schema import (
|
|
8
10
|
ArchitectureAnalysis,
|
|
9
11
|
ArchitectureDomain,
|
|
@@ -139,8 +141,14 @@ LAYER_PATTERNS: dict[str, dict[str, list[str]]] = {
|
|
|
139
141
|
"application": ["application", "usecases", "usecase"],
|
|
140
142
|
"infrastructure": ["infrastructure", "infra", "adapters", "persistence"],
|
|
141
143
|
},
|
|
144
|
+
# P1-D residue (found by measurement, 2026-07-16): "core" belongs with
|
|
145
|
+
# "api"/"store"/"db" — it names a module's importance ("the main module"),
|
|
146
|
+
# never an onion/hexagonal DOMAIN layer, and nearly every large JVM repo has
|
|
147
|
+
# one. With it, keycloak's `core/` (623 Java files) + a
|
|
148
|
+
# `representations/adapters/config` package read "Onion Architecture with
|
|
149
|
+
# domain, adapters layers". Kept are the names that state their role.
|
|
142
150
|
"onion": {
|
|
143
|
-
"domain": ["domain"
|
|
151
|
+
"domain": ["domain"],
|
|
144
152
|
"application": ["application", "usecases"],
|
|
145
153
|
"ports": ["ports", "interfaces"],
|
|
146
154
|
"adapters": ["adapters", "secondary"],
|
|
@@ -173,7 +181,7 @@ LAYER_PATTERNS: dict[str, dict[str, list[str]]] = {
|
|
|
173
181
|
"hexagonal": {
|
|
174
182
|
"port": ["port", "ports", "interface", "interfaces"],
|
|
175
183
|
"adapter": ["adapter", "adapters"],
|
|
176
|
-
"domain": ["domain", "
|
|
184
|
+
"domain": ["domain", "model", "models"], # "core" dropped — see onion
|
|
177
185
|
},
|
|
178
186
|
"monorepo": {
|
|
179
187
|
"apps": ["apps", "applications"],
|
|
@@ -196,6 +204,13 @@ LAYER_PATTERNS: dict[str, dict[str, list[str]]] = {
|
|
|
196
204
|
# it; without it the pattern falls through to layered / a weaker match.
|
|
197
205
|
_PATTERN_REQUIRED_KEYS: dict[str, frozenset[str]] = {
|
|
198
206
|
"mvc": frozenset({"view"}),
|
|
207
|
+
# Same principle as MVC/view — a pattern must show its DEFINING trait, not
|
|
208
|
+
# just any two of its layers. Hexagonal is literally "Ports and Adapters":
|
|
209
|
+
# a `models/` package next to an `adapters/` package is not one, and that
|
|
210
|
+
# pair alone was enough to label keycloak "Hexagonal Architecture". Onion's
|
|
211
|
+
# defining trait is the domain at the centre.
|
|
212
|
+
"hexagonal": frozenset({"port", "adapter"}),
|
|
213
|
+
"onion": frozenset({"domain"}),
|
|
199
214
|
}
|
|
200
215
|
|
|
201
216
|
# Minimum share of source files a matched layer must hold to count as a LAYER
|
|
@@ -646,6 +661,24 @@ class ArchitectureAnalyzer:
|
|
|
646
661
|
continue
|
|
647
662
|
matched[layer_key] = matched_dirs
|
|
648
663
|
layer_files[layer_key] = files
|
|
664
|
+
|
|
665
|
+
# SIM-3: a layer must belong to the same PROGRAM as the rest of the
|
|
666
|
+
# claim. Directory names are language-blind, so on a polyglot
|
|
667
|
+
# monorepo one pattern was assembled across two subsystems: keycloak
|
|
668
|
+
# read "MVC with controller, model, view layers" where `view` was a
|
|
669
|
+
# React SPA (87 .tsx, zero Java) sitting beside a 1088-file Java
|
|
670
|
+
# `model`. Mass cannot catch this — the SPA has genuine mass. The
|
|
671
|
+
# reconciliation rule drops layers owned by a different runtime;
|
|
672
|
+
# template/asset-only layers are untouched, so a real Spring-MVC
|
|
673
|
+
# `templates/` view survives.
|
|
674
|
+
for _bad in incoherent_layers(
|
|
675
|
+
pattern_name,
|
|
676
|
+
{k: [source_paths[i] for i in v] for k, v in layer_files.items()},
|
|
677
|
+
source_paths,
|
|
678
|
+
):
|
|
679
|
+
matched.pop(_bad, None)
|
|
680
|
+
layer_files.pop(_bad, None)
|
|
681
|
+
|
|
649
682
|
# A pattern with unmet required keys cannot qualify (e.g. mvc needs a view).
|
|
650
683
|
required = _PATTERN_REQUIRED_KEYS.get(pattern_name)
|
|
651
684
|
if required and not required.issubset(matched.keys()):
|
sourcecode/reconciliation.py
CHANGED
|
@@ -34,12 +34,21 @@ Rule registry (extraction):
|
|
|
34
34
|
declarations the regex extractor cannot bind, e.g. `public class\n
|
|
35
35
|
Name` split across lines, Unicode type identifiers).
|
|
36
36
|
|
|
37
|
+
Rule registry (architecture):
|
|
38
|
+
layer_language_coherence — a directory-name-matched layer holds NO file in
|
|
39
|
+
the runtime that carries the rest of the claimed pattern, while being
|
|
40
|
+
owned by a DIFFERENT runtime. The layer table and the file-language
|
|
41
|
+
census are contradicting each other: the "architecture" spans two
|
|
42
|
+
subsystems (SIM-3: keycloak's "MVC … view layer" was a React SPA, zero
|
|
43
|
+
Java files, sitting beside a 1088-file Java model).
|
|
44
|
+
|
|
37
45
|
Never raises. Empty evidence → no findings.
|
|
38
46
|
"""
|
|
39
47
|
from __future__ import annotations
|
|
40
48
|
|
|
41
49
|
import re
|
|
42
50
|
from dataclasses import dataclass
|
|
51
|
+
from pathlib import Path
|
|
43
52
|
|
|
44
53
|
|
|
45
54
|
@dataclass(frozen=True)
|
|
@@ -182,6 +191,142 @@ def _rule_parse_coverage(
|
|
|
182
191
|
]
|
|
183
192
|
|
|
184
193
|
|
|
194
|
+
# ---------------------------------------------------------------------------
|
|
195
|
+
# Architecture reconciliation (layer table × file-language census)
|
|
196
|
+
# ---------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
# Source extension → RUNTIME FAMILY. Grouping by runtime, not by extension, is
|
|
199
|
+
# what makes the rule correct: a Kotlin `view/` beside Java `model/` is ONE
|
|
200
|
+
# system (same runtime, direct interop) and must not be called a contradiction,
|
|
201
|
+
# while a TypeScript `view/` beside them is a separate program. Extensions
|
|
202
|
+
# absent here (.html/.ftl/.jsp/.css/.xml …) are deliberately unmapped: a
|
|
203
|
+
# template or asset carries no runtime of its own, so it can never contradict a
|
|
204
|
+
# claim — that carve-out is what keeps a genuine Spring-MVC `templates/` view
|
|
205
|
+
# layer intact.
|
|
206
|
+
_RUNTIME_FAMILY: dict[str, str] = {
|
|
207
|
+
".java": "jvm", ".kt": "jvm", ".kts": "jvm", ".scala": "jvm", ".groovy": "jvm",
|
|
208
|
+
".js": "js", ".jsx": "js", ".ts": "js", ".tsx": "js", ".mjs": "js", ".cjs": "js",
|
|
209
|
+
".py": "python",
|
|
210
|
+
".go": "go",
|
|
211
|
+
".rs": "rust",
|
|
212
|
+
".rb": "ruby",
|
|
213
|
+
".cs": "dotnet",
|
|
214
|
+
".php": "php",
|
|
215
|
+
".swift": "swift",
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# Patterns that describe REPO COMPOSITION rather than one program's internal
|
|
219
|
+
# architecture — their layers are SUPPOSED to span runtimes, so the coherence
|
|
220
|
+
# rule must never fire on them. (mvc/layered/onion/hexagonal/clean are claims
|
|
221
|
+
# about a single program and must hold together in one runtime.)
|
|
222
|
+
_CROSS_RUNTIME_PATTERNS: frozenset[str] = frozenset({
|
|
223
|
+
"fullstack", "microservices", "monorepo",
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def runtime_census(layer_files: dict[str, list[str]]) -> dict[str, dict[str, int]]:
|
|
228
|
+
"""{layer -> {runtime_family -> file count}} — the language evidence view.
|
|
229
|
+
|
|
230
|
+
Files whose extension carries no runtime (templates, assets, config) are
|
|
231
|
+
simply absent from the census; a layer made only of them yields {}.
|
|
232
|
+
"""
|
|
233
|
+
out: dict[str, dict[str, int]] = {}
|
|
234
|
+
for layer, files in layer_files.items():
|
|
235
|
+
counts: dict[str, int] = {}
|
|
236
|
+
for f in files:
|
|
237
|
+
fam = _RUNTIME_FAMILY.get(Path(str(f)).suffix.lower())
|
|
238
|
+
if fam:
|
|
239
|
+
counts[fam] = counts.get(fam, 0) + 1
|
|
240
|
+
out[layer] = counts
|
|
241
|
+
return out
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _rule_layer_language_coherence(
|
|
245
|
+
pattern: str,
|
|
246
|
+
census: dict[str, dict[str, int]],
|
|
247
|
+
repo_runtimes: dict[str, int],
|
|
248
|
+
) -> list[ReconciliationFinding]:
|
|
249
|
+
"""A claimed layer is owned by a different runtime than the repo it describes.
|
|
250
|
+
|
|
251
|
+
The dominant runtime is taken from the WHOLE repo, not from the matched
|
|
252
|
+
layers: which directories happen to match a keyword table is exactly the
|
|
253
|
+
thing under suspicion, so it cannot also be the arbiter. (Measured: on a
|
|
254
|
+
Java backend beside a React SPA, layer-local dominance can invert — the SPA
|
|
255
|
+
out-matches the backend's one matched layer and the JAVA layer gets flagged.)
|
|
256
|
+
|
|
257
|
+
A layer is INCOHERENT iff it holds ZERO files of the repo's runtime AND at
|
|
258
|
+
least one file of another — it is a different program, not a facet of this
|
|
259
|
+
one. A layer with no runtime files at all (templates, assets) is never
|
|
260
|
+
flagged: it does not contradict, it just does not attest.
|
|
261
|
+
|
|
262
|
+
Silent for cross-runtime patterns, whose layers are meant to span runtimes.
|
|
263
|
+
"""
|
|
264
|
+
if pattern in _CROSS_RUNTIME_PATTERNS or len(census) < 2:
|
|
265
|
+
return []
|
|
266
|
+
if len(repo_runtimes) < 2:
|
|
267
|
+
return [] # single-runtime repo — nothing to contradict
|
|
268
|
+
|
|
269
|
+
dominant = max(sorted(repo_runtimes), key=lambda f: repo_runtimes[f])
|
|
270
|
+
|
|
271
|
+
incoherent = sorted(
|
|
272
|
+
layer for layer, counts in census.items()
|
|
273
|
+
if not counts.get(dominant) and any(counts.values())
|
|
274
|
+
)
|
|
275
|
+
if not incoherent:
|
|
276
|
+
return []
|
|
277
|
+
return [
|
|
278
|
+
ReconciliationFinding(
|
|
279
|
+
rule="layer_language_coherence",
|
|
280
|
+
symbols=tuple(incoherent),
|
|
281
|
+
detail=(
|
|
282
|
+
f"{len(incoherent)} layer(s) of the '{pattern}' claim hold no "
|
|
283
|
+
f"{dominant} file while the rest of the pattern is {dominant} — "
|
|
284
|
+
"the claim spans two separate programs, so it does not describe "
|
|
285
|
+
"one architecture."
|
|
286
|
+
),
|
|
287
|
+
)
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def reconcile_architecture_evidence(
|
|
292
|
+
*,
|
|
293
|
+
pattern: str,
|
|
294
|
+
layer_files: dict[str, list[str]],
|
|
295
|
+
repo_files: "list[str]",
|
|
296
|
+
) -> list[ReconciliationFinding]:
|
|
297
|
+
"""Run architecture reconciliation rules. Never raises.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
pattern: candidate pattern name (e.g. "mvc").
|
|
301
|
+
layer_files: {layer -> files matched for that layer}.
|
|
302
|
+
repo_files: every source file considered — supplies the repo's own
|
|
303
|
+
runtime, the arbiter of which layers belong to the claim.
|
|
304
|
+
"""
|
|
305
|
+
findings: list[ReconciliationFinding] = []
|
|
306
|
+
try:
|
|
307
|
+
repo_runtimes = runtime_census({"_repo": list(repo_files)})["_repo"]
|
|
308
|
+
findings.extend(
|
|
309
|
+
_rule_layer_language_coherence(
|
|
310
|
+
pattern, runtime_census(layer_files), repo_runtimes
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
except Exception:
|
|
314
|
+
pass
|
|
315
|
+
return findings
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def incoherent_layers(
|
|
319
|
+
pattern: str, layer_files: dict[str, list[str]], repo_files: "list[str]"
|
|
320
|
+
) -> frozenset[str]:
|
|
321
|
+
"""Layer names the coherence rule rejects for `pattern` (convenience view)."""
|
|
322
|
+
out: set[str] = set()
|
|
323
|
+
for f in reconcile_architecture_evidence(
|
|
324
|
+
pattern=pattern, layer_files=layer_files, repo_files=repo_files
|
|
325
|
+
):
|
|
326
|
+
out.update(f.symbols)
|
|
327
|
+
return frozenset(out)
|
|
328
|
+
|
|
329
|
+
|
|
185
330
|
def reconcile_extraction_evidence(
|
|
186
331
|
*,
|
|
187
332
|
type_decl_files: frozenset[str],
|
sourcecode/summarizer.py
CHANGED
|
@@ -8,7 +8,8 @@ No API calls — templates applied directly over SourceMap.
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
|
-
from sourcecode.architecture_analyzer import _LAYER_MIN_SHARE
|
|
11
|
+
from sourcecode.architecture_analyzer import _LAYER_MIN_SHARE, _PATTERN_REQUIRED_KEYS
|
|
12
|
+
from sourcecode.reconciliation import incoherent_layers
|
|
12
13
|
from sourcecode.detectors.parsers import load_json_file, load_toml_file
|
|
13
14
|
from sourcecode.tree_utils import safe_read_text
|
|
14
15
|
from sourcecode.entrypoint_classifier import is_production_entry_point
|
|
@@ -62,7 +63,9 @@ _ARCH_LAYER_PATTERNS: dict[str, dict[str, list[str]]] = {
|
|
|
62
63
|
"hexagonal": {
|
|
63
64
|
"port": ["ports", "interfaces"],
|
|
64
65
|
"adapter": ["adapters"],
|
|
65
|
-
|
|
66
|
+
# P1-D residue: "core" names a module's importance, not a domain layer
|
|
67
|
+
# (keycloak `core/` = 623 Java files). Keep in step with the analyzer.
|
|
68
|
+
"domain": ["domain", "models"],
|
|
66
69
|
},
|
|
67
70
|
"fullstack": {
|
|
68
71
|
"frontend": ["frontend", "client", "web", "ui", "pages", "components"],
|
|
@@ -415,16 +418,33 @@ class ProjectSummarizer:
|
|
|
415
418
|
dir_files.setdefault(seg.lower(), set()).add(i)
|
|
416
419
|
total = len(file_paths)
|
|
417
420
|
|
|
418
|
-
def
|
|
421
|
+
def _layer_paths(keywords: list[str]) -> list[str]:
|
|
419
422
|
files: set[int] = set()
|
|
420
423
|
for d in keywords:
|
|
421
424
|
files |= dir_files.get(d, set())
|
|
422
|
-
|
|
425
|
+
if not files or (total and len(files) / total < _LAYER_MIN_SHARE):
|
|
426
|
+
return []
|
|
427
|
+
return [file_paths[i] for i in sorted(files)]
|
|
423
428
|
|
|
424
429
|
best_pattern: str | None = None
|
|
425
430
|
best_score = 0
|
|
426
431
|
for pattern_name, layer_keys in _ARCH_LAYER_PATTERNS.items():
|
|
427
|
-
|
|
432
|
+
layer_files: dict[str, list[str]] = {}
|
|
433
|
+
for layer, keywords in layer_keys.items():
|
|
434
|
+
paths = _layer_paths(keywords)
|
|
435
|
+
if paths:
|
|
436
|
+
layer_files[layer] = paths
|
|
437
|
+
# SIM-3: same coherence rule the architecture prose applies — this
|
|
438
|
+
# table is the headline's copy, and a fix applied to only one of the
|
|
439
|
+
# two duplicated tables leaves the other one lying.
|
|
440
|
+
for _bad in incoherent_layers(pattern_name, layer_files, file_paths):
|
|
441
|
+
layer_files.pop(_bad, None)
|
|
442
|
+
# A pattern must show its defining trait here too, or the headline
|
|
443
|
+
# contradicts the architecture prose that already enforces this.
|
|
444
|
+
required = _PATTERN_REQUIRED_KEYS.get(pattern_name)
|
|
445
|
+
if required and not required.issubset(layer_files.keys()):
|
|
446
|
+
continue
|
|
447
|
+
score = len(layer_files)
|
|
428
448
|
if score > best_score:
|
|
429
449
|
best_score = score
|
|
430
450
|
best_pattern = pattern_name
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=RsMXFkrSEd5UuAhMfPgfXfYvqM0A43vYcSfn086K17s,308
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/archetype.py,sha256=d7yoN6Tj4OBj-VgSMPEKg4WF9H8FypnZ5A6AkUh5oIE,34484
|
|
4
|
-
sourcecode/architecture_analyzer.py,sha256=
|
|
4
|
+
sourcecode/architecture_analyzer.py,sha256=vQQzcvmniq4go4bWNB2Xm5HlyIoW65cU7ktJqHIfDpU,53614
|
|
5
5
|
sourcecode/architecture_summary.py,sha256=pkl6lIOnSy-poa9EzW43U0MkXhx2FDfAFh8pDXLpcTo,26630
|
|
6
6
|
sourcecode/ast_extractor.py,sha256=FEsYTsMCXbJfSYQZRk1k-I-PjUNGnKqO1IxMftsgA5U,51351
|
|
7
7
|
sourcecode/cache.py,sha256=1V3vsaODAa2UBJAC0xpvxpmRdriCezQx5Q8JCcfgziE,31892
|
|
@@ -51,7 +51,7 @@ sourcecode/pr_impact.py,sha256=dCDVw83EDbyVf6F9ZmEQmsFz8ruVH7d4mpeKQCIZHM0,16805
|
|
|
51
51
|
sourcecode/prepare_context.py,sha256=PYygAKL_jwz8gGElgM9mroUSih18UKSSjv08ndfl4aA,222877
|
|
52
52
|
sourcecode/progress.py,sha256=qn30sWaHOkjTgXsSBmiPkz7Rsbwc5oSlIe6JNEMYp_k,3149
|
|
53
53
|
sourcecode/ranking_engine.py,sha256=ZAucq_YX2KkWUuAZf4P0lhtQ_38vEFnUhuGtSZd1S0E,12970
|
|
54
|
-
sourcecode/reconciliation.py,sha256=
|
|
54
|
+
sourcecode/reconciliation.py,sha256=L4r_k6c768yq4GSzSOsJHxot2y8bHREA6up68CUkzo4,13853
|
|
55
55
|
sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
|
|
56
56
|
sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
|
|
57
57
|
sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg,12965
|
|
@@ -75,7 +75,7 @@ sourcecode/spring_model.py,sha256=zOAgFmrRbG4a6KLm1TJl55aWMyPNsz3OS3FSczqPG6A,16
|
|
|
75
75
|
sourcecode/spring_security_audit.py,sha256=Rk-aSohezdc7YDYbSoJquVnwpkDB8ty1BCD-4Hc4R5A,22832
|
|
76
76
|
sourcecode/spring_semantic.py,sha256=jteQ1PkY9ArFJv0embg_jBIdbOxqrk9mQ2Xz8OF_FKA,14214
|
|
77
77
|
sourcecode/spring_tx_analyzer.py,sha256=QMXkxa_hrqDVN3D8WJsd0nT7vfdZCT1E8ZWNoFFbvgU,34692
|
|
78
|
-
sourcecode/summarizer.py,sha256=
|
|
78
|
+
sourcecode/summarizer.py,sha256=4IPhFg3Anrqa9U17i_bBKq70Q7eVefsCEK3idFC4VsY,26217
|
|
79
79
|
sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
|
|
80
80
|
sourcecode/type_usage_surface.py,sha256=51IrKRQoIoRnlsiDjHnqpJBn2rc6E59aRhgS0HTzAF0,4428
|
|
81
81
|
sourcecode/validation_inference.py,sha256=UtmYv-d4TEzT1ihGEipAhtXDrwq0XtVtjMpb3i7NsNc,10815
|
|
@@ -137,8 +137,8 @@ sourcecode/telemetry/consent.py,sha256=LIAO9ohJZF8OuZwM4u1VWtALlYfTCCKq4wV3Vwc7i
|
|
|
137
137
|
sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
|
|
138
138
|
sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
|
|
139
139
|
sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
|
|
140
|
-
sourcecode-2.5.
|
|
141
|
-
sourcecode-2.5.
|
|
142
|
-
sourcecode-2.5.
|
|
143
|
-
sourcecode-2.5.
|
|
144
|
-
sourcecode-2.5.
|
|
140
|
+
sourcecode-2.5.5.dist-info/METADATA,sha256=jTeYMssrsUEZUxEVu8WAQq0ihp7c8FtWCT27m2eNeXo,10851
|
|
141
|
+
sourcecode-2.5.5.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
142
|
+
sourcecode-2.5.5.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
|
|
143
|
+
sourcecode-2.5.5.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
144
|
+
sourcecode-2.5.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|