staticer 0.0.1__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.
staticer/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from .staticer import main
File without changes
@@ -0,0 +1,116 @@
1
+ """Format-agnostic dependency graph: resolution, transitive exclusion, and
2
+ dependency-first init order, driven entirely by caller-supplied
3
+ ``list_imports``/``resolve`` callables.
4
+
5
+ This module knows nothing about PE or ELF; those formats supply their own
6
+ ``list_imports``/``resolve`` implementations (see ``pe_embedder``/
7
+ ``elf_embedder``) and call into ``build_dependency_graph``/``embed_order``.
8
+ """
9
+
10
+ from dataclasses import field
11
+ from pathlib import Path
12
+ from typing import Callable, Dict, List, Optional, Set
13
+
14
+ from firstclass import firstclass
15
+
16
+
17
+ @firstclass
18
+ class DependencyNode:
19
+ name: str
20
+ path: Path
21
+ embeddable: bool
22
+ # Names of this node's own resolved imports (edges), keyed the same
23
+ # way as the graph dict. Populated by build_dependency_graph;
24
+ # embed_order walks it to produce a dependencies-first order. Not
25
+ # part of the original interface's three fields, but required for
26
+ # embed_order to have any edge information to sort over, since the
27
+ # graph dict is its only input.
28
+ imports: List[str] = field(default_factory=list)
29
+
30
+
31
+ class CyclicDependencyError(Exception):
32
+ pass
33
+
34
+
35
+ def build_dependency_graph(
36
+ root_path: Path,
37
+ list_imports: Callable[[Path], List[str]],
38
+ resolve: Callable[[str, List[Path]], Optional[Path]],
39
+ search_dirs: List[Path],
40
+ lib_dir: Optional[Path],
41
+ exclude: Set[str],
42
+ ) -> Dict[str, DependencyNode]:
43
+ nodes: Dict[str, DependencyNode] = {}
44
+
45
+ def in_scope(path: Path) -> bool:
46
+ return lib_dir is None or path.is_relative_to(lib_dir)
47
+
48
+ def visit(name: str, path: Path, force_excluded: bool) -> None:
49
+ is_excluded = force_excluded or (name in exclude) or not in_scope(path)
50
+ if name in nodes:
51
+ node = nodes[name]
52
+ # Only cascade when this visit newly excludes the node (guards
53
+ # against both redundant re-cascading on repeat revisits and,
54
+ # critically, infinite recursion around cycles: once a node is
55
+ # already False, re-walking its recorded imports again is a
56
+ # no-op).
57
+ if is_excluded and node.embeddable:
58
+ node.embeddable = False
59
+ for child_name in node.imports:
60
+ child = nodes.get(child_name)
61
+ if child is not None:
62
+ visit(child_name, child.path, force_excluded=True)
63
+ return
64
+ node = DependencyNode(name=name, path=path, embeddable=not is_excluded)
65
+ nodes[name] = node
66
+ for imp_name in list_imports(path):
67
+ resolved = resolve(imp_name, search_dirs)
68
+ if resolved is None:
69
+ continue
70
+ node.imports.append(imp_name)
71
+ visit(imp_name, resolved, force_excluded=is_excluded)
72
+
73
+ for imp_name in list_imports(root_path):
74
+ resolved = resolve(imp_name, search_dirs)
75
+ if resolved is not None:
76
+ visit(imp_name, resolved, force_excluded=False)
77
+ return nodes
78
+
79
+
80
+ def embed_order(graph: Dict[str, DependencyNode]) -> List[DependencyNode]:
81
+ """Dependencies-first topological order over the embeddable subset of
82
+ ``graph``. Raises CyclicDependencyError, naming the cycle, if a cycle
83
+ exists among embeddable nodes."""
84
+ order: List[DependencyNode] = []
85
+ visited: Set[str] = set()
86
+ in_progress: List[str] = []
87
+ in_progress_set: Set[str] = set()
88
+
89
+ def visit(name: str) -> None:
90
+ if name in visited:
91
+ return
92
+ if name in in_progress_set:
93
+ cycle = in_progress[in_progress.index(name):] + [name]
94
+ raise CyclicDependencyError(
95
+ "cyclic dependency detected: " + " -> ".join(cycle)
96
+ )
97
+
98
+ node = graph[name]
99
+ in_progress.append(name)
100
+ in_progress_set.add(name)
101
+
102
+ for imp_name in node.imports:
103
+ dep = graph.get(imp_name)
104
+ if dep is not None and dep.embeddable:
105
+ visit(imp_name)
106
+
107
+ in_progress.pop()
108
+ in_progress_set.discard(name)
109
+ visited.add(name)
110
+ order.append(node)
111
+
112
+ for name, node in graph.items():
113
+ if node.embeddable:
114
+ visit(name)
115
+
116
+ return order