zigraf-ir 0.1.0__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.
- graph_ir/__init__.py +35 -0
- graph_ir/cli.py +151 -0
- graph_ir/dag.py +54 -0
- graph_ir/io.py +88 -0
- graph_ir/prettify.py +43 -0
- graph_ir/render.py +116 -0
- graph_ir/schema.py +81 -0
- graph_ir/transforms/__init__.py +6 -0
- graph_ir/transforms/diff.py +55 -0
- graph_ir/transforms/slice.py +26 -0
- graph_ir/validator.py +69 -0
- zigraf_ir-0.1.0.dist-info/METADATA +349 -0
- zigraf_ir-0.1.0.dist-info/RECORD +16 -0
- zigraf_ir-0.1.0.dist-info/WHEEL +4 -0
- zigraf_ir-0.1.0.dist-info/entry_points.txt +2 -0
- zigraf_ir-0.1.0.dist-info/licenses/LICENSE +201 -0
graph_ir/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""graph-ir: the shared graph.yaml intermediate representation.
|
|
2
|
+
|
|
3
|
+
Primitives -> concepts -> applications, with typed dependency edges, a structural
|
|
4
|
+
validator, and graph transforms reused by every ZiNets back-end (spl3, mg, cba, gfc, solver).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .dag import CycleError, ancestors, topological_order
|
|
8
|
+
from .io import dump_graph, load_graph, to_yaml
|
|
9
|
+
from .render import render, to_dot, to_mermaid
|
|
10
|
+
from .schema import EdgeType, Graph, Layer, Node
|
|
11
|
+
from .transforms import GraphDiff, diff_graphs, slice_to
|
|
12
|
+
from .validator import Issue, validate
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"CycleError",
|
|
18
|
+
"EdgeType",
|
|
19
|
+
"Graph",
|
|
20
|
+
"GraphDiff",
|
|
21
|
+
"Issue",
|
|
22
|
+
"Layer",
|
|
23
|
+
"Node",
|
|
24
|
+
"ancestors",
|
|
25
|
+
"diff_graphs",
|
|
26
|
+
"dump_graph",
|
|
27
|
+
"load_graph",
|
|
28
|
+
"render",
|
|
29
|
+
"slice_to",
|
|
30
|
+
"to_dot",
|
|
31
|
+
"to_mermaid",
|
|
32
|
+
"to_yaml",
|
|
33
|
+
"topological_order",
|
|
34
|
+
"validate",
|
|
35
|
+
]
|
graph_ir/cli.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""`gir` command-line tool: validate, diff, slice, render, prettify, and schema for graph.yaml files.
|
|
2
|
+
|
|
3
|
+
Exit codes: 0 success, 1 validation errors, differences found, or a file needing prettify,
|
|
4
|
+
2 usage, load, or render failure.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import difflib
|
|
11
|
+
import sys
|
|
12
|
+
from collections.abc import Sequence
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from . import __version__
|
|
16
|
+
from .io import dump_graph, load_graph, to_yaml
|
|
17
|
+
from .prettify import prettify_file
|
|
18
|
+
from .render import BINARY_FORMATS, FORMATS, render
|
|
19
|
+
from .schema import json_schema_text
|
|
20
|
+
from .transforms import diff_graphs, slice_to
|
|
21
|
+
from .validator import validate
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _cmd_validate(args: argparse.Namespace) -> int:
|
|
25
|
+
graph = load_graph(args.file, resolve_imports=not args.no_imports)
|
|
26
|
+
issues = validate(graph)
|
|
27
|
+
for issue in issues:
|
|
28
|
+
print(f"{args.file}: {issue}")
|
|
29
|
+
if not issues:
|
|
30
|
+
print(f"{args.file}: ok ({len(graph.nodes())} nodes)")
|
|
31
|
+
return 1 if any(issue.severity == "error" for issue in issues) else 0
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _cmd_diff(args: argparse.Namespace) -> int:
|
|
35
|
+
result = diff_graphs(load_graph(args.old), load_graph(args.new))
|
|
36
|
+
if result.is_empty:
|
|
37
|
+
print("no differences")
|
|
38
|
+
return 0
|
|
39
|
+
for node_id in result.added:
|
|
40
|
+
print(f"+ {node_id}")
|
|
41
|
+
for node_id in result.removed:
|
|
42
|
+
print(f"- {node_id}")
|
|
43
|
+
for node_id, delta in result.changed.items():
|
|
44
|
+
for name, (old, new) in delta.items():
|
|
45
|
+
print(f"~ {node_id}.{name}: {old!r} -> {new!r}")
|
|
46
|
+
return 1
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _cmd_slice(args: argparse.Namespace) -> int:
|
|
50
|
+
graph = slice_to(load_graph(args.file), args.target)
|
|
51
|
+
if args.output:
|
|
52
|
+
dump_graph(graph, args.output)
|
|
53
|
+
else:
|
|
54
|
+
sys.stdout.write(to_yaml(graph))
|
|
55
|
+
return 0
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _cmd_render(args: argparse.Namespace) -> int:
|
|
59
|
+
if args.format in BINARY_FORMATS and not args.output:
|
|
60
|
+
raise ValueError(f"--format {args.format} is binary; pass -o FILE")
|
|
61
|
+
graph = load_graph(args.file, resolve_imports=not args.no_imports)
|
|
62
|
+
data = render(graph, args.format)
|
|
63
|
+
if args.output:
|
|
64
|
+
Path(args.output).write_bytes(data)
|
|
65
|
+
else:
|
|
66
|
+
sys.stdout.buffer.write(data)
|
|
67
|
+
return 0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _cmd_prettify(args: argparse.Namespace) -> int:
|
|
71
|
+
result = prettify_file(args.file)
|
|
72
|
+
if not result.changed:
|
|
73
|
+
print(f"{args.file}: already formatted")
|
|
74
|
+
return 0
|
|
75
|
+
if args.write:
|
|
76
|
+
if result.has_comments:
|
|
77
|
+
raise ValueError(f"{args.file} has comments; prettify would drop them, so it was not rewritten")
|
|
78
|
+
Path(args.file).write_text(result.formatted, encoding="utf-8")
|
|
79
|
+
print(f"{args.file}: rewritten")
|
|
80
|
+
return 0
|
|
81
|
+
if result.has_comments:
|
|
82
|
+
print(f"note: {args.file} has comments; --write would drop them", file=sys.stderr)
|
|
83
|
+
sys.stdout.writelines(
|
|
84
|
+
difflib.unified_diff(
|
|
85
|
+
result.original.splitlines(keepends=True),
|
|
86
|
+
result.formatted.splitlines(keepends=True),
|
|
87
|
+
fromfile=args.file,
|
|
88
|
+
tofile=f"{args.file} (prettified)",
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
return 1
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _cmd_schema(args: argparse.Namespace) -> int:
|
|
95
|
+
text = json_schema_text()
|
|
96
|
+
if args.output:
|
|
97
|
+
Path(args.output).write_text(text, encoding="utf-8")
|
|
98
|
+
else:
|
|
99
|
+
sys.stdout.write(text)
|
|
100
|
+
return 0
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
104
|
+
parser = argparse.ArgumentParser(prog="gir", description="graph.yaml intermediate representation tools")
|
|
105
|
+
parser.add_argument("--version", action="version", version=f"gir {__version__}")
|
|
106
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
107
|
+
|
|
108
|
+
p_diff = sub.add_parser("diff", help="show node-level differences between two graphs")
|
|
109
|
+
p_diff.add_argument("old")
|
|
110
|
+
p_diff.add_argument("new")
|
|
111
|
+
p_diff.set_defaults(handler=_cmd_diff)
|
|
112
|
+
|
|
113
|
+
p_prettify = sub.add_parser("prettify", help="show or apply the canonical formatting of a graph.yaml")
|
|
114
|
+
p_prettify.add_argument("file")
|
|
115
|
+
p_prettify.add_argument("--write", action="store_true", help="rewrite the file in place instead of printing a diff")
|
|
116
|
+
p_prettify.set_defaults(handler=_cmd_prettify)
|
|
117
|
+
|
|
118
|
+
p_render = sub.add_parser("render", help="visualize a graph as svg, png, pdf, or mermaid")
|
|
119
|
+
p_render.add_argument("file")
|
|
120
|
+
p_render.add_argument("--format", choices=FORMATS, default="svg", help="output format (default: svg)")
|
|
121
|
+
p_render.add_argument("-o", "--output", help="write to this file instead of stdout")
|
|
122
|
+
p_render.add_argument("--no-imports", action="store_true", help="do not resolve imports")
|
|
123
|
+
p_render.set_defaults(handler=_cmd_render)
|
|
124
|
+
|
|
125
|
+
p_schema = sub.add_parser("schema", help="print the JSON Schema for graph.yaml")
|
|
126
|
+
p_schema.add_argument("-o", "--output", help="write to this file instead of stdout")
|
|
127
|
+
p_schema.set_defaults(handler=_cmd_schema)
|
|
128
|
+
|
|
129
|
+
p_slice = sub.add_parser("slice", help="extract a target node and its prerequisites")
|
|
130
|
+
p_slice.add_argument("file")
|
|
131
|
+
p_slice.add_argument("target")
|
|
132
|
+
p_slice.add_argument("-o", "--output", help="write to this file instead of stdout")
|
|
133
|
+
p_slice.set_defaults(handler=_cmd_slice)
|
|
134
|
+
|
|
135
|
+
p_validate = sub.add_parser("validate", help="check a graph.yaml for structural errors")
|
|
136
|
+
p_validate.add_argument("file")
|
|
137
|
+
p_validate.add_argument("--no-imports", action="store_true", help="do not resolve imports")
|
|
138
|
+
p_validate.set_defaults(handler=_cmd_validate)
|
|
139
|
+
return parser
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
143
|
+
args = _build_parser().parse_args(argv)
|
|
144
|
+
try:
|
|
145
|
+
return args.handler(args)
|
|
146
|
+
except KeyError as exc:
|
|
147
|
+
print(f"gir: error: {exc.args[0]}", file=sys.stderr)
|
|
148
|
+
return 2
|
|
149
|
+
except (OSError, ValueError, RuntimeError) as exc:
|
|
150
|
+
print(f"gir: error: {exc}", file=sys.stderr)
|
|
151
|
+
return 2
|
graph_ir/dag.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""DAG algorithms over an id -> dependency-ids mapping, built on networkx.
|
|
2
|
+
|
|
3
|
+
Kept free of node types so any domain back-end can reuse them.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from collections.abc import Iterable, Mapping
|
|
9
|
+
|
|
10
|
+
import networkx as nx
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CycleError(ValueError):
|
|
14
|
+
"""Raised when a dependency cycle exists. `members` lists nodes stuck in or behind it."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, members: list[str]):
|
|
17
|
+
self.members = members
|
|
18
|
+
super().__init__(f"dependency cycle among: {', '.join(members)}")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _dependency_graph(deps: Mapping[str, Iterable[str]]) -> nx.DiGraph:
|
|
22
|
+
"""Edges run dependency -> dependent, so a topological order lists dependencies first."""
|
|
23
|
+
graph = nx.DiGraph()
|
|
24
|
+
graph.add_nodes_from(deps)
|
|
25
|
+
for node, parents in deps.items():
|
|
26
|
+
graph.add_edges_from((parent, node) for parent in parents)
|
|
27
|
+
return graph
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _cycle_members(graph: nx.DiGraph) -> list[str]:
|
|
31
|
+
"""Nodes that sit on a cycle: members of a multi-node strongly connected component, or self-loops."""
|
|
32
|
+
members = {node for scc in nx.strongly_connected_components(graph) if len(scc) > 1 for node in scc}
|
|
33
|
+
members |= {node for node in graph if graph.has_edge(node, node)}
|
|
34
|
+
return sorted(members)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def topological_order(deps: Mapping[str, Iterable[str]]) -> list[str]:
|
|
38
|
+
"""Return every id with its dependencies before it. Ties break alphabetically.
|
|
39
|
+
|
|
40
|
+
Every dependency id must itself be a key of `deps`; callers filter unknown ids first.
|
|
41
|
+
Raises CycleError when the mapping is not a DAG.
|
|
42
|
+
"""
|
|
43
|
+
graph = _dependency_graph(deps)
|
|
44
|
+
try:
|
|
45
|
+
return list(nx.lexicographical_topological_sort(graph))
|
|
46
|
+
except nx.NetworkXUnfeasible:
|
|
47
|
+
raise CycleError(_cycle_members(graph)) from None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def ancestors(deps: Mapping[str, Iterable[str]], target: str) -> set[str]:
|
|
51
|
+
"""All ids `target` transitively depends on (excluding `target` itself)."""
|
|
52
|
+
if target not in deps:
|
|
53
|
+
raise KeyError(target)
|
|
54
|
+
return set(nx.ancestors(_dependency_graph(deps), target))
|
graph_ir/io.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Load, resolve, and dump graph.yaml files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
from pydantic import ValidationError
|
|
9
|
+
|
|
10
|
+
from .schema import Graph, Layer
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_graph(path: str | Path, resolve_imports: bool = True) -> Graph:
|
|
14
|
+
"""Load a graph.yaml. With `resolve_imports`, merge every file listed under `imports:`.
|
|
15
|
+
|
|
16
|
+
Import paths are relative to the importing file. Imported nodes keep their ids (no
|
|
17
|
+
namespacing); a duplicate id across files is an error. The returned graph has no `imports`.
|
|
18
|
+
This does not run `validate`; call that separately for structural checks.
|
|
19
|
+
"""
|
|
20
|
+
source = Path(path)
|
|
21
|
+
graph = _read(source)
|
|
22
|
+
if resolve_imports:
|
|
23
|
+
graph = _resolve(graph, source, stack=())
|
|
24
|
+
return graph
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _IndentedDumper(yaml.SafeDumper):
|
|
28
|
+
"""Indents list items under their parent key (" - id: a") instead of PyYAML's flush style."""
|
|
29
|
+
|
|
30
|
+
def increase_indent(self, flow: bool = False, indentless: bool = False) -> None:
|
|
31
|
+
return super().increase_indent(flow, False)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def to_yaml(graph: Graph) -> str:
|
|
35
|
+
data = graph.model_dump(mode="json", exclude_defaults=True)
|
|
36
|
+
data = {"version": graph.version, **data} # always written, first, even when default
|
|
37
|
+
return yaml.dump(data, Dumper=_IndentedDumper, sort_keys=False, allow_unicode=True)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def dump_graph(graph: Graph, path: str | Path) -> None:
|
|
41
|
+
Path(path).write_text(to_yaml(graph), encoding="utf-8")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
SUPPORTED_VERSIONS = ("1.0",)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _read(path: Path) -> Graph:
|
|
48
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
49
|
+
if not isinstance(data, dict):
|
|
50
|
+
raise ValueError(f"{path}: top level must be a mapping")
|
|
51
|
+
if str(data.get("version", "1.0")) not in SUPPORTED_VERSIONS:
|
|
52
|
+
raise ValueError(f"{path}: unsupported graph version {data['version']!r}; supported: {', '.join(SUPPORTED_VERSIONS)}")
|
|
53
|
+
try:
|
|
54
|
+
return Graph.model_validate(data)
|
|
55
|
+
except ValidationError as exc:
|
|
56
|
+
raise ValueError(f"{path}: {exc}") from exc
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _resolve(graph: Graph, source: Path, stack: tuple[Path, ...]) -> Graph:
|
|
60
|
+
key = source.resolve()
|
|
61
|
+
if key in stack:
|
|
62
|
+
chain = " -> ".join(str(p) for p in (*stack, key))
|
|
63
|
+
raise ValueError(f"import cycle: {chain}")
|
|
64
|
+
|
|
65
|
+
merged: dict[Layer, list] = {layer: [] for layer in Layer}
|
|
66
|
+
origin: dict[str, Path] = {}
|
|
67
|
+
|
|
68
|
+
def add(layer: Layer, node, where: Path) -> None:
|
|
69
|
+
if node.id in origin:
|
|
70
|
+
raise ValueError(f"duplicate node id {node.id!r}: defined in {origin[node.id]} and {where}")
|
|
71
|
+
origin[node.id] = where
|
|
72
|
+
merged[layer].append(node)
|
|
73
|
+
|
|
74
|
+
base = source.parent
|
|
75
|
+
for imp in graph.imports:
|
|
76
|
+
child_path = base / imp
|
|
77
|
+
child = _resolve(_read(child_path), child_path, stack=(*stack, key))
|
|
78
|
+
for layer, node in child.layered_nodes():
|
|
79
|
+
add(layer, node, child_path)
|
|
80
|
+
for layer, node in graph.layered_nodes():
|
|
81
|
+
add(layer, node, source)
|
|
82
|
+
|
|
83
|
+
return Graph(
|
|
84
|
+
version=graph.version,
|
|
85
|
+
primitives=merged[Layer.PRIMITIVES],
|
|
86
|
+
concepts=merged[Layer.CONCEPTS],
|
|
87
|
+
applications=merged[Layer.APPLICATIONS],
|
|
88
|
+
)
|
graph_ir/prettify.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Canonical formatting for graph.yaml files (`gir prettify`).
|
|
2
|
+
|
|
3
|
+
The canonical form is what `to_yaml` writes: `version` first, layers in order, keys in model
|
|
4
|
+
field order, and defaults omitted. Formatting works on one file only and never resolves imports,
|
|
5
|
+
so imported nodes are not copied into the file being formatted.
|
|
6
|
+
|
|
7
|
+
YAML comments are not preserved, because PyYAML does not expose them. `has_comments` lets the
|
|
8
|
+
CLI refuse to rewrite a file that would lose them.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from .io import load_graph, to_yaml
|
|
18
|
+
|
|
19
|
+
_INLINE_COMMENT = re.compile(r"(^|\s)#")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class Prettified:
|
|
24
|
+
original: str
|
|
25
|
+
formatted: str
|
|
26
|
+
has_comments: bool
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def changed(self) -> bool:
|
|
30
|
+
return self.original != self.formatted
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def has_comments(text: str) -> bool:
|
|
34
|
+
"""True if any line looks like it carries a YAML comment. Conservative: a `#` inside a quoted
|
|
35
|
+
string also counts, which only makes the check refuse more often, never less."""
|
|
36
|
+
return any(_INLINE_COMMENT.search(line) for line in text.splitlines())
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def prettify_file(path: str | Path) -> Prettified:
|
|
40
|
+
source = Path(path)
|
|
41
|
+
original = source.read_text(encoding="utf-8")
|
|
42
|
+
graph = load_graph(source, resolve_imports=False)
|
|
43
|
+
return Prettified(original=original, formatted=to_yaml(graph), has_comments=has_comments(original))
|
graph_ir/render.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Visualization: graph.yaml -> Graphviz DOT, Mermaid, and (via `dot`) SVG / PNG / PDF.
|
|
2
|
+
|
|
3
|
+
Mermaid is pure text and needs nothing installed. SVG, PNG, and PDF shell out to the
|
|
4
|
+
Graphviz `dot` binary, which must be on PATH.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
|
|
12
|
+
from .schema import EdgeType, Graph, Layer
|
|
13
|
+
|
|
14
|
+
FORMATS = ("svg", "png", "pdf", "mermaid")
|
|
15
|
+
BINARY_FORMATS = ("png", "pdf")
|
|
16
|
+
_GRAPHVIZ_FORMATS = ("svg", "png", "pdf")
|
|
17
|
+
|
|
18
|
+
_DOT_SHAPE = {
|
|
19
|
+
Layer.PRIMITIVES: "ellipse",
|
|
20
|
+
Layer.CONCEPTS: "box",
|
|
21
|
+
Layer.APPLICATIONS: "doubleoctagon",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _dot_quote(text: str) -> str:
|
|
26
|
+
return '"' + text.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _mermaid_text(text: str) -> str:
|
|
30
|
+
return text.replace('"', "#quot;")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def to_dot(graph: Graph) -> str:
|
|
34
|
+
"""Graphviz DOT source: one cluster per layer, edges point from dependency to dependent."""
|
|
35
|
+
lines = [
|
|
36
|
+
"digraph graph_ir {",
|
|
37
|
+
" rankdir=LR;",
|
|
38
|
+
' node [fontname="Helvetica"];',
|
|
39
|
+
]
|
|
40
|
+
for layer in Layer:
|
|
41
|
+
nodes = getattr(graph, layer.value)
|
|
42
|
+
if not nodes:
|
|
43
|
+
continue
|
|
44
|
+
lines.append(f' subgraph "cluster_{layer.value}" {{')
|
|
45
|
+
lines.append(f' label={_dot_quote(layer.value)}; style=rounded; color=gray;')
|
|
46
|
+
for node in nodes:
|
|
47
|
+
label = node.label or node.id
|
|
48
|
+
lines.append(f" {_dot_quote(node.id)} [label={_dot_quote(label)} shape={_DOT_SHAPE[layer]}];")
|
|
49
|
+
lines.append(" }")
|
|
50
|
+
|
|
51
|
+
for node in graph.nodes():
|
|
52
|
+
for dep in node.deps:
|
|
53
|
+
kind = node.edge_types.get(dep)
|
|
54
|
+
attrs = f" [label={_dot_quote(kind.value)}]" if kind and kind is not EdgeType.DEPENDS_ON else ""
|
|
55
|
+
lines.append(f" {_dot_quote(dep)} -> {_dot_quote(node.id)}{attrs};")
|
|
56
|
+
lines.append("}")
|
|
57
|
+
return "\n".join(lines) + "\n"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
_MERMAID_BRACKETS = {
|
|
61
|
+
Layer.PRIMITIVES: ("([", "])"),
|
|
62
|
+
Layer.CONCEPTS: ("[", "]"),
|
|
63
|
+
Layer.APPLICATIONS: ("{{", "}}"),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def to_mermaid(graph: Graph) -> str:
|
|
68
|
+
"""Mermaid flowchart (LR), one subgraph per layer. Renders in GitHub and most Markdown viewers."""
|
|
69
|
+
names: dict[str, str] = {}
|
|
70
|
+
for index, (_, node) in enumerate(graph.layered_nodes()):
|
|
71
|
+
names.setdefault(node.id, f"n{index}")
|
|
72
|
+
|
|
73
|
+
lines = ["flowchart LR"]
|
|
74
|
+
for layer in Layer:
|
|
75
|
+
nodes = getattr(graph, layer.value)
|
|
76
|
+
if not nodes:
|
|
77
|
+
continue
|
|
78
|
+
lines.append(f" subgraph {layer.value}")
|
|
79
|
+
open_, close = _MERMAID_BRACKETS[layer]
|
|
80
|
+
for node in nodes:
|
|
81
|
+
label = _mermaid_text(node.label or node.id)
|
|
82
|
+
lines.append(f' {names[node.id]}{open_}"{label}"{close}')
|
|
83
|
+
lines.append(" end")
|
|
84
|
+
|
|
85
|
+
for node in graph.nodes():
|
|
86
|
+
for dep in node.deps:
|
|
87
|
+
if dep not in names: # dangling reference: declare it so the edge still draws
|
|
88
|
+
names[dep] = f"n{len(names)}"
|
|
89
|
+
lines.append(f' {names[dep]}["{_mermaid_text(dep)}"]')
|
|
90
|
+
kind = node.edge_types.get(dep)
|
|
91
|
+
arrow = f"-->|{kind.value}|" if kind and kind is not EdgeType.DEPENDS_ON else "-->"
|
|
92
|
+
lines.append(f" {names[dep]} {arrow} {names[node.id]}")
|
|
93
|
+
return "\n".join(lines) + "\n"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def render(graph: Graph, fmt: str) -> bytes:
|
|
97
|
+
"""Render `graph` in `fmt` and return the raw bytes (UTF-8 text for svg and mermaid)."""
|
|
98
|
+
if fmt == "mermaid":
|
|
99
|
+
return to_mermaid(graph).encode("utf-8")
|
|
100
|
+
if fmt not in _GRAPHVIZ_FORMATS:
|
|
101
|
+
raise ValueError(f"unknown format {fmt!r}; choose from {', '.join(FORMATS)}")
|
|
102
|
+
return _run_dot(to_dot(graph), fmt)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _run_dot(source: str, fmt: str) -> bytes:
|
|
106
|
+
if shutil.which("dot") is None:
|
|
107
|
+
raise RuntimeError("Graphviz 'dot' not found on PATH; install graphviz (e.g. apt install graphviz)")
|
|
108
|
+
proc = subprocess.run(
|
|
109
|
+
["dot", f"-T{fmt}"],
|
|
110
|
+
input=source.encode("utf-8"),
|
|
111
|
+
capture_output=True,
|
|
112
|
+
check=False,
|
|
113
|
+
)
|
|
114
|
+
if proc.returncode != 0:
|
|
115
|
+
raise RuntimeError(f"dot failed: {proc.stderr.decode('utf-8', 'replace').strip()}")
|
|
116
|
+
return proc.stdout
|
graph_ir/schema.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Pydantic models for graph.yaml: a three-layer DAG (primitives -> concepts -> applications)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any, Iterator
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Layer(str, Enum):
|
|
13
|
+
"""Layer a node lives in. Values are the top-level YAML keys."""
|
|
14
|
+
|
|
15
|
+
PRIMITIVES = "primitives"
|
|
16
|
+
CONCEPTS = "concepts"
|
|
17
|
+
APPLICATIONS = "applications"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EdgeType(str, Enum):
|
|
21
|
+
"""Optional semantics for a dependency edge. Unannotated edges are DEPENDS_ON."""
|
|
22
|
+
|
|
23
|
+
DEPENDS_ON = "depends_on"
|
|
24
|
+
DATA_FLOW = "data_flow"
|
|
25
|
+
PREREQUISITE = "prerequisite"
|
|
26
|
+
CONSTRAINT = "constraint"
|
|
27
|
+
INHERITANCE = "inheritance"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Node(BaseModel):
|
|
31
|
+
"""A graph node. `deps` lists the ids it depends on, i.e. its incoming edges."""
|
|
32
|
+
|
|
33
|
+
model_config = ConfigDict(extra="forbid")
|
|
34
|
+
|
|
35
|
+
id: str = Field(min_length=1)
|
|
36
|
+
label: str | None = None
|
|
37
|
+
deps: list[str] = Field(default_factory=list)
|
|
38
|
+
edge_types: dict[str, EdgeType] = Field(default_factory=dict) # dep id -> edge type
|
|
39
|
+
meta: dict[str, Any] = Field(default_factory=dict) # domain payload, opaque to the core
|
|
40
|
+
|
|
41
|
+
@model_validator(mode="after")
|
|
42
|
+
def _typed_edges_must_be_deps(self) -> Node:
|
|
43
|
+
stray = sorted(set(self.edge_types) - set(self.deps))
|
|
44
|
+
if stray:
|
|
45
|
+
raise ValueError(f"edge_types given for non-dependencies: {stray}")
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Graph(BaseModel):
|
|
50
|
+
"""A whole graph.yaml document. Unknown top-level keys are rejected to catch typos."""
|
|
51
|
+
|
|
52
|
+
model_config = ConfigDict(extra="forbid")
|
|
53
|
+
|
|
54
|
+
version: str = "1.0"
|
|
55
|
+
imports: list[str] = Field(default_factory=list) # relative paths, resolved by io.load_graph
|
|
56
|
+
primitives: list[Node] = Field(default_factory=list)
|
|
57
|
+
concepts: list[Node] = Field(default_factory=list)
|
|
58
|
+
applications: list[Node] = Field(default_factory=list)
|
|
59
|
+
|
|
60
|
+
def layered_nodes(self) -> Iterator[tuple[Layer, Node]]:
|
|
61
|
+
"""Yield (layer, node) pairs in layer order, then file order."""
|
|
62
|
+
for layer in Layer:
|
|
63
|
+
for node in getattr(self, layer.value):
|
|
64
|
+
yield layer, node
|
|
65
|
+
|
|
66
|
+
def nodes(self) -> list[Node]:
|
|
67
|
+
return [node for _, node in self.layered_nodes()]
|
|
68
|
+
|
|
69
|
+
def index(self) -> dict[str, tuple[Layer, Node]]:
|
|
70
|
+
"""Map node id -> (layer, node). Raises ValueError on duplicate ids."""
|
|
71
|
+
index: dict[str, tuple[Layer, Node]] = {}
|
|
72
|
+
for layer, node in self.layered_nodes():
|
|
73
|
+
if node.id in index:
|
|
74
|
+
raise ValueError(f"duplicate node id: {node.id!r}")
|
|
75
|
+
index[node.id] = (layer, node)
|
|
76
|
+
return index
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def json_schema_text() -> str:
|
|
80
|
+
"""JSON Schema (draft 2020-12, as generated by Pydantic) for a graph.yaml document."""
|
|
81
|
+
return json.dumps(Graph.model_json_schema(), indent=2, ensure_ascii=False) + "\n"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Semantic diff between two graphs, matched by node id."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..schema import Graph
|
|
9
|
+
|
|
10
|
+
_FIELDS = ("layer", "label", "deps", "edge_types", "meta")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class GraphDiff:
|
|
15
|
+
added: list[str] = field(default_factory=list)
|
|
16
|
+
removed: list[str] = field(default_factory=list)
|
|
17
|
+
# node id -> {field name: (old value, new value)}
|
|
18
|
+
changed: dict[str, dict[str, tuple[Any, Any]]] = field(default_factory=dict)
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def is_empty(self) -> bool:
|
|
22
|
+
return not (self.added or self.removed or self.changed)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def diff_graphs(old: Graph, new: Graph) -> GraphDiff:
|
|
26
|
+
"""Compare two graphs node by node. Dependency order is ignored; only the set matters."""
|
|
27
|
+
before = _describe(old)
|
|
28
|
+
after = _describe(new)
|
|
29
|
+
changed: dict[str, dict[str, tuple[Any, Any]]] = {}
|
|
30
|
+
for node_id in sorted(before.keys() & after.keys()):
|
|
31
|
+
delta = {
|
|
32
|
+
name: (before[node_id][name], after[node_id][name])
|
|
33
|
+
for name in _FIELDS
|
|
34
|
+
if before[node_id][name] != after[node_id][name]
|
|
35
|
+
}
|
|
36
|
+
if delta:
|
|
37
|
+
changed[node_id] = delta
|
|
38
|
+
return GraphDiff(
|
|
39
|
+
added=sorted(after.keys() - before.keys()),
|
|
40
|
+
removed=sorted(before.keys() - after.keys()),
|
|
41
|
+
changed=changed,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _describe(graph: Graph) -> dict[str, dict[str, Any]]:
|
|
46
|
+
return {
|
|
47
|
+
node_id: {
|
|
48
|
+
"layer": layer.value,
|
|
49
|
+
"label": node.label,
|
|
50
|
+
"deps": sorted(node.deps),
|
|
51
|
+
"edge_types": {dep: kind.value for dep, kind in node.edge_types.items()},
|
|
52
|
+
"meta": node.meta,
|
|
53
|
+
}
|
|
54
|
+
for node_id, (layer, node) in graph.index().items()
|
|
55
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Subgraph extraction."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..dag import ancestors
|
|
6
|
+
from ..schema import Graph, Layer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def slice_to(graph: Graph, target: str) -> Graph:
|
|
10
|
+
"""Return the subgraph made of `target` and everything it transitively depends on.
|
|
11
|
+
|
|
12
|
+
For a concept-graph this is the prerequisite closure of the target. Raises KeyError
|
|
13
|
+
if `target` is not a node id.
|
|
14
|
+
"""
|
|
15
|
+
index = graph.index()
|
|
16
|
+
if target not in index:
|
|
17
|
+
raise KeyError(f"unknown node: {target!r}")
|
|
18
|
+
deps = {node_id: node.deps for node_id, (_, node) in index.items()}
|
|
19
|
+
keep = ancestors(deps, target) | {target}
|
|
20
|
+
return Graph(
|
|
21
|
+
version=graph.version,
|
|
22
|
+
**{
|
|
23
|
+
layer.value: [node for node in getattr(graph, layer.value) if node.id in keep]
|
|
24
|
+
for layer in Layer
|
|
25
|
+
},
|
|
26
|
+
)
|
graph_ir/validator.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Structural checks that apply to any domain's graph.yaml, with no domain logic."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import Counter
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Literal
|
|
8
|
+
|
|
9
|
+
from .dag import CycleError, topological_order
|
|
10
|
+
from .schema import Graph, Layer
|
|
11
|
+
|
|
12
|
+
Severity = Literal["error", "warning"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class Issue:
|
|
17
|
+
severity: Severity
|
|
18
|
+
code: str
|
|
19
|
+
message: str
|
|
20
|
+
node: str | None = None
|
|
21
|
+
|
|
22
|
+
def __str__(self) -> str:
|
|
23
|
+
where = f" [{self.node}]" if self.node else ""
|
|
24
|
+
return f"{self.severity}: {self.code}{where}: {self.message}"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def validate(graph: Graph) -> list[Issue]:
|
|
28
|
+
"""Return all structural problems. Errors make a graph invalid; warnings are advisory.
|
|
29
|
+
|
|
30
|
+
Errors: duplicate_id, unknown_dep, primitive_has_deps, layer_violation, cycle.
|
|
31
|
+
Warnings: orphan (a node with no dependencies and no dependents).
|
|
32
|
+
"""
|
|
33
|
+
issues: list[Issue] = []
|
|
34
|
+
nodes = graph.nodes()
|
|
35
|
+
|
|
36
|
+
for node_id, count in Counter(node.id for node in nodes).items():
|
|
37
|
+
if count > 1:
|
|
38
|
+
issues.append(Issue("error", "duplicate_id", f"id appears {count} times", node_id))
|
|
39
|
+
|
|
40
|
+
layer_of: dict[str, Layer] = {}
|
|
41
|
+
for layer, node in graph.layered_nodes():
|
|
42
|
+
layer_of.setdefault(node.id, layer)
|
|
43
|
+
|
|
44
|
+
for layer, node in graph.layered_nodes():
|
|
45
|
+
if layer is Layer.PRIMITIVES and node.deps:
|
|
46
|
+
issues.append(
|
|
47
|
+
Issue("error", "primitive_has_deps", f"primitives are leaves but depend on {node.deps}", node.id)
|
|
48
|
+
)
|
|
49
|
+
for dep in node.deps:
|
|
50
|
+
if dep not in layer_of:
|
|
51
|
+
issues.append(Issue("error", "unknown_dep", f"depends on undefined id {dep!r}", node.id))
|
|
52
|
+
elif layer_of[dep] is Layer.APPLICATIONS:
|
|
53
|
+
issues.append(
|
|
54
|
+
Issue("error", "layer_violation", f"depends on application {dep!r}; applications are capstones", node.id)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
known_deps = {node.id: [dep for dep in node.deps if dep in layer_of] for node in nodes}
|
|
58
|
+
try:
|
|
59
|
+
topological_order(known_deps)
|
|
60
|
+
except CycleError as exc:
|
|
61
|
+
issues.append(Issue("error", "cycle", str(exc)))
|
|
62
|
+
|
|
63
|
+
referenced = {dep for node in nodes for dep in node.deps}
|
|
64
|
+
if len(nodes) > 1:
|
|
65
|
+
for node in nodes:
|
|
66
|
+
if not node.deps and node.id not in referenced:
|
|
67
|
+
issues.append(Issue("warning", "orphan", "not connected to any other node", node.id))
|
|
68
|
+
|
|
69
|
+
return issues
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: zigraf-ir
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: graph.yaml intermediate representation: schema, validator and graph transforms shared across the ZiNets ecosystem
|
|
5
|
+
Project-URL: Homepage, https://github.com/digital-duck/graph-ir
|
|
6
|
+
Project-URL: Repository, https://github.com/digital-duck/graph-ir
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/digital-duck/graph-ir/issues
|
|
8
|
+
Author-email: "Gong, Wen" <lucywang1049@gmail.com>
|
|
9
|
+
License: Apache License
|
|
10
|
+
Version 2.0, January 2004
|
|
11
|
+
http://www.apache.org/licenses/
|
|
12
|
+
|
|
13
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
14
|
+
|
|
15
|
+
1. Definitions.
|
|
16
|
+
|
|
17
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
18
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
19
|
+
|
|
20
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
21
|
+
the copyright owner that is granting the License.
|
|
22
|
+
|
|
23
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
24
|
+
other entities that control, are controlled by, or are under common
|
|
25
|
+
control with that entity. For the purposes of this definition,
|
|
26
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
27
|
+
direction or management of such entity, whether by contract or
|
|
28
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
29
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
30
|
+
|
|
31
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
32
|
+
exercising permissions granted by this License.
|
|
33
|
+
|
|
34
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
35
|
+
including but not limited to software source code, documentation
|
|
36
|
+
source, and configuration files.
|
|
37
|
+
|
|
38
|
+
"Object" form shall mean any form resulting from mechanical
|
|
39
|
+
transformation or translation of a Source form, including but
|
|
40
|
+
not limited to compiled object code, generated documentation,
|
|
41
|
+
and conversions to other media types.
|
|
42
|
+
|
|
43
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
44
|
+
Object form, made available under the License, as indicated by a
|
|
45
|
+
copyright notice that is included in or attached to the work
|
|
46
|
+
(an example is provided in the Appendix below).
|
|
47
|
+
|
|
48
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
49
|
+
form, that is based on (or derived from) the Work and for which the
|
|
50
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
51
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
52
|
+
of this License, Derivative Works shall not include works that remain
|
|
53
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
54
|
+
the Work and Derivative Works thereof.
|
|
55
|
+
|
|
56
|
+
"Contribution" shall mean any work of authorship, including
|
|
57
|
+
the original version of the Work and any modifications or additions
|
|
58
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
59
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
60
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
61
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
62
|
+
means any form of electronic, verbal, or written communication sent
|
|
63
|
+
to the Licensor or its representatives, including but not limited to
|
|
64
|
+
communication on electronic mailing lists, source code control systems,
|
|
65
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
66
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
67
|
+
excluding communication that is conspicuously marked or otherwise
|
|
68
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
69
|
+
|
|
70
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
71
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
72
|
+
subsequently incorporated within the Work.
|
|
73
|
+
|
|
74
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
78
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
79
|
+
Work and such Derivative Works in Source or Object form.
|
|
80
|
+
|
|
81
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
82
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
83
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
84
|
+
(except as stated in this section) patent license to make, have made,
|
|
85
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
86
|
+
where such license applies only to those patent claims licensable
|
|
87
|
+
by such Contributor that are necessarily infringed by their
|
|
88
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
89
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
90
|
+
institute patent litigation against any entity (including a
|
|
91
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
92
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
93
|
+
or contributory patent infringement, then any patent licenses
|
|
94
|
+
granted to You under this License for that Work shall terminate
|
|
95
|
+
as of the date such litigation is filed.
|
|
96
|
+
|
|
97
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
98
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
99
|
+
modifications, and in Source or Object form, provided that You
|
|
100
|
+
meet the following conditions:
|
|
101
|
+
|
|
102
|
+
(a) You must give any other recipients of the Work or
|
|
103
|
+
Derivative Works a copy of this License; and
|
|
104
|
+
|
|
105
|
+
(b) You must cause any modified files to carry prominent notices
|
|
106
|
+
stating that You changed the files; and
|
|
107
|
+
|
|
108
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
109
|
+
that You distribute, all copyright, patent, trademark, and
|
|
110
|
+
attribution notices from the Source form of the Work,
|
|
111
|
+
excluding those notices that do not pertain to any part of
|
|
112
|
+
the Derivative Works; and
|
|
113
|
+
|
|
114
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
115
|
+
distribution, then any Derivative Works that You distribute must
|
|
116
|
+
include a readable copy of the attribution notices contained
|
|
117
|
+
within such NOTICE file, excluding those notices that do not
|
|
118
|
+
pertain to any part of the Derivative Works, in at least one
|
|
119
|
+
of the following places: within a NOTICE text file distributed
|
|
120
|
+
as part of the Derivative Works; within the Source form or
|
|
121
|
+
documentation, if provided along with the Derivative Works; or,
|
|
122
|
+
within a display generated by the Derivative Works, if and
|
|
123
|
+
wherever such third-party notices normally appear. The contents
|
|
124
|
+
of the NOTICE file are for informational purposes only and
|
|
125
|
+
do not modify the License. You may add Your own attribution
|
|
126
|
+
notices within Derivative Works that You distribute, alongside
|
|
127
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
128
|
+
that such additional attribution notices cannot be construed
|
|
129
|
+
as modifying the License.
|
|
130
|
+
|
|
131
|
+
You may add Your own copyright statement to Your modifications and
|
|
132
|
+
may provide additional or different license terms and conditions
|
|
133
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
134
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
135
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
136
|
+
the conditions stated in this License.
|
|
137
|
+
|
|
138
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
139
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
140
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
141
|
+
this License, without any additional terms or conditions.
|
|
142
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
143
|
+
the terms of any separate license agreement you may have executed
|
|
144
|
+
with Licensor regarding such Contributions.
|
|
145
|
+
|
|
146
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
147
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
148
|
+
except as required for reasonable and customary use in describing the
|
|
149
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
150
|
+
|
|
151
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
152
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
153
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
154
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
155
|
+
implied, including, without limitation, any warranties or conditions
|
|
156
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
157
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
158
|
+
appropriateness of using or redistributing the Work and assume any
|
|
159
|
+
risks associated with Your exercise of permissions under this License.
|
|
160
|
+
|
|
161
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
162
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
163
|
+
unless required by applicable law (such as deliberate and grossly
|
|
164
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
165
|
+
liable to You for damages, including any direct, indirect, special,
|
|
166
|
+
incidental, or consequential damages of any character arising as a
|
|
167
|
+
result of this License or out of the use or inability to use the
|
|
168
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
169
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
170
|
+
other commercial damages or losses), even if such Contributor
|
|
171
|
+
has been advised of the possibility of such damages.
|
|
172
|
+
|
|
173
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
174
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
175
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
176
|
+
or other liability obligations and/or rights consistent with this
|
|
177
|
+
License. However, in accepting such obligations, You may act only
|
|
178
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
179
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
180
|
+
defend, and hold each Contributor harmless for any liability
|
|
181
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
182
|
+
of your accepting any such warranty or additional liability.
|
|
183
|
+
|
|
184
|
+
END OF TERMS AND CONDITIONS
|
|
185
|
+
|
|
186
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
187
|
+
|
|
188
|
+
To apply the Apache License to your work, attach the following
|
|
189
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
190
|
+
replaced with your own identifying information. (Don't include
|
|
191
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
192
|
+
comment syntax for the file format. We also recommend that a
|
|
193
|
+
file or class name and description of purpose be included on the
|
|
194
|
+
same "printed page" as the copyright notice for easier
|
|
195
|
+
identification within third-party archives.
|
|
196
|
+
|
|
197
|
+
Copyright [yyyy] [name of copyright owner]
|
|
198
|
+
|
|
199
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
200
|
+
you may not use this file except in compliance with the License.
|
|
201
|
+
You may obtain a copy of the License at
|
|
202
|
+
|
|
203
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
204
|
+
|
|
205
|
+
Unless required by applicable law or agreed to in writing, software
|
|
206
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
207
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
208
|
+
See the License for the specific language governing permissions and
|
|
209
|
+
limitations under the License.
|
|
210
|
+
License-File: LICENSE
|
|
211
|
+
Keywords: dag,graph,intermediate-representation,ir,knowledge-graph,validation,yaml
|
|
212
|
+
Classifier: Development Status :: 3 - Alpha
|
|
213
|
+
Classifier: Intended Audience :: Developers
|
|
214
|
+
Classifier: Intended Audience :: Science/Research
|
|
215
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
216
|
+
Classifier: Operating System :: OS Independent
|
|
217
|
+
Classifier: Programming Language :: Python :: 3
|
|
218
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
219
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
220
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
221
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
222
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
223
|
+
Requires-Python: >=3.10
|
|
224
|
+
Requires-Dist: networkx>=3.2
|
|
225
|
+
Requires-Dist: pydantic>=2.5
|
|
226
|
+
Requires-Dist: pyyaml>=6.0
|
|
227
|
+
Provides-Extra: dev
|
|
228
|
+
Requires-Dist: build; extra == 'dev'
|
|
229
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
230
|
+
Requires-Dist: twine; extra == 'dev'
|
|
231
|
+
Description-Content-Type: text/markdown
|
|
232
|
+
|
|
233
|
+
# graph-ir
|
|
234
|
+
|
|
235
|
+
Graph IR core package shared across SPL, ConceptBook, Graph-Code, Momagrid, and Solver domains.
|
|
236
|
+
|
|
237
|
+
`graph.yaml` is the common intermediate representation of the ZiNets ecosystem, the way LLVM IR
|
|
238
|
+
is for compilers: front-ends emit it, back-ends consume it, and the shared validator and
|
|
239
|
+
transforms live here once.
|
|
240
|
+
|
|
241
|
+
## The format
|
|
242
|
+
|
|
243
|
+
Every graph has three layers, each a list of nodes:
|
|
244
|
+
|
|
245
|
+
```yaml
|
|
246
|
+
version: "1.0"
|
|
247
|
+
imports: [./shared/tools.yaml] # optional; resolved relative to this file
|
|
248
|
+
primitives: # leaves: must not have deps
|
|
249
|
+
- id: x_units
|
|
250
|
+
meta: {type: variable}
|
|
251
|
+
concepts: # composed from primitives and other concepts
|
|
252
|
+
- id: capacity_constraint
|
|
253
|
+
deps: [x_units, max_units]
|
|
254
|
+
edge_types: {max_units: constraint}
|
|
255
|
+
meta: {expression: "x_units <= max_units"}
|
|
256
|
+
applications: # capstones: nothing may depend on these
|
|
257
|
+
- id: minimize_cost
|
|
258
|
+
deps: [capacity_constraint]
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- `deps` are the ids a node depends on (incoming edges).
|
|
262
|
+
- `edge_types` optionally types a dependency: `depends_on` (default), `data_flow`,
|
|
263
|
+
`prerequisite`, `constraint`, `inheritance`.
|
|
264
|
+
- `meta` is an opaque domain payload that the core never interprets.
|
|
265
|
+
|
|
266
|
+
## Install
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
uv venv .venv && uv pip install -p .venv/bin/python -e ".[dev]"
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## CLI: `gir`
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
gir validate graph.yaml # exit 1 on errors; warnings are printed but do not fail
|
|
276
|
+
gir diff old.yaml new.yaml # exit 1 when the graphs differ
|
|
277
|
+
gir slice graph.yaml target_id -o part.yaml # target plus its prerequisite closure
|
|
278
|
+
gir render graph.yaml # svg to stdout (default)
|
|
279
|
+
gir render graph.yaml --format mermaid # mermaid flowchart text
|
|
280
|
+
gir render graph.yaml --format png -o graph.png # png and pdf need -o
|
|
281
|
+
gir prettify graph.yaml # diff against canonical formatting (exit 1 if it would change)
|
|
282
|
+
gir prettify graph.yaml --write # rewrite in place (refuses files with comments)
|
|
283
|
+
gir schema -o schemas/graph.schema.json # JSON Schema for editors and other tools
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
`render` supports `svg`, `png`, `pdf`, and `mermaid`. Mermaid needs nothing installed. The
|
|
287
|
+
other formats need Graphviz's `dot` on PATH (`apt install graphviz`).
|
|
288
|
+
|
|
289
|
+
Exit code 2 means a usage, load, or render error. `prettify` exits 1 when a file is not in
|
|
290
|
+
canonical form, so it can run in CI.
|
|
291
|
+
|
|
292
|
+
## Specification
|
|
293
|
+
|
|
294
|
+
The format is specified in [docs/SPECIFICATION.md](docs/SPECIFICATION.md). The machine-readable
|
|
295
|
+
JSON Schema is [schemas/graph.schema.json](schemas/graph.schema.json), regenerated with
|
|
296
|
+
`gir schema -o schemas/graph.schema.json`. A test fails if the committed schema is out of date.
|
|
297
|
+
|
|
298
|
+
## Python API
|
|
299
|
+
|
|
300
|
+
```python
|
|
301
|
+
from graph_ir import load_graph, validate, diff_graphs, slice_to, dump_graph
|
|
302
|
+
|
|
303
|
+
graph = load_graph("solver_pipeline.yaml") # resolves imports
|
|
304
|
+
for issue in validate(graph):
|
|
305
|
+
print(issue)
|
|
306
|
+
prereqs = slice_to(graph, "minimize_cost")
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Checks
|
|
310
|
+
|
|
311
|
+
| Code | Severity | Meaning |
|
|
312
|
+
|---|---|---|
|
|
313
|
+
| `duplicate_id` | error | an id is defined more than once |
|
|
314
|
+
| `unknown_dep` | error | a dep names an id that does not exist |
|
|
315
|
+
| `primitive_has_deps` | error | a primitive depends on something |
|
|
316
|
+
| `layer_violation` | error | a node depends on an application |
|
|
317
|
+
| `cycle` | error | the dependency graph is not a DAG |
|
|
318
|
+
| `orphan` | warning | a node with no deps and no dependents |
|
|
319
|
+
|
|
320
|
+
## Layout
|
|
321
|
+
|
|
322
|
+
```text
|
|
323
|
+
src/graph_ir/
|
|
324
|
+
schema.py Pydantic models: Graph, Node, Layer, EdgeType
|
|
325
|
+
dag.py topological order, ancestors (domain-agnostic)
|
|
326
|
+
validator.py structural checks -> Issue list
|
|
327
|
+
io.py load_graph (with imports), dump_graph, to_yaml
|
|
328
|
+
prettify.py canonical formatting for `gir prettify`
|
|
329
|
+
transforms/ diff_graphs, slice_to
|
|
330
|
+
render.py DOT and Mermaid generation, svg/png/pdf via `dot`
|
|
331
|
+
cli.py the `gir` command
|
|
332
|
+
docs/SPECIFICATION.md the graph.yaml format specification
|
|
333
|
+
schemas/graph.schema.json generated JSON Schema
|
|
334
|
+
tests/ pytest suite; tests/fixtures holds one graph per domain
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Design decisions
|
|
338
|
+
|
|
339
|
+
- Imports merge nodes without namespacing, so ids must be unique across the whole import tree.
|
|
340
|
+
- `load_graph` does not validate. Run `validate` separately for structural checks.
|
|
341
|
+
- Unknown top-level and node keys are rejected, so typos fail loudly. Domain data goes in `meta`.
|
|
342
|
+
- Only the core checks are here. Domain rules (for example, solver bounds) belong in the back-end.
|
|
343
|
+
|
|
344
|
+
## Not yet implemented
|
|
345
|
+
|
|
346
|
+
- Domain-specific JSON Schema overlays (solver, spl, cba).
|
|
347
|
+
- `graph.html` interactive explorer (belongs to `cba`).
|
|
348
|
+
- Comment-preserving formatting. `prettify --write` refuses files with comments.
|
|
349
|
+
- `spl3` integration, which is the next step: SPL scripts compiling to `GraphIR`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
graph_ir/__init__.py,sha256=jRD-lJfe2hWc01xKM9-ECJMG7JLDEcs9FNJRskqDX10,874
|
|
2
|
+
graph_ir/cli.py,sha256=3rmReKtgfgNK-k24194bcddVl2EI-YCVx6txFzyTkG0,5722
|
|
3
|
+
graph_ir/dag.py,sha256=r9PgURDeR3mmC7x-36CbKsBosjKcmRgp2vN92j9SoKY,2019
|
|
4
|
+
graph_ir/io.py,sha256=Fkf_KE0yWhjrZAe7FohLfuAz_lYkNn-1KUbIHuCfcRg,3126
|
|
5
|
+
graph_ir/prettify.py,sha256=TrqfqoSwgRXwds0Q-nXCtVqWHElobKs-tpEVnUpKvl0,1432
|
|
6
|
+
graph_ir/render.py,sha256=MrVfHhS6QPf7254KeWcug2PByYieq1TbmfAH0OXeVsI,4049
|
|
7
|
+
graph_ir/schema.py,sha256=O9_FTNezcmq0mcEhuj-I_uUeiJoAs17LV0NPOOSn78o,2855
|
|
8
|
+
graph_ir/validator.py,sha256=guPTv0FmEhfIUirQLtcJyebMItaGEFk82j0mb39Vp18,2458
|
|
9
|
+
graph_ir/transforms/__init__.py,sha256=WUlzfblDa1WAoKCfliIRUkhll8vxo57yTzzlBuhbH6A,176
|
|
10
|
+
graph_ir/transforms/diff.py,sha256=4KKZi6H-PsprgqVPbGqQR0u0LmsXfExLMyL77rouh6w,1750
|
|
11
|
+
graph_ir/transforms/slice.py,sha256=z4wE8fNpk4pS2V9b55cNVtCvuYMYv4Hj0Ajiqx6bMC0,820
|
|
12
|
+
zigraf_ir-0.1.0.dist-info/METADATA,sha256=Rd-Ezp1T_BjM9_yULegPeBpGvMzToeDnO4QUNB4GHrM,19135
|
|
13
|
+
zigraf_ir-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
14
|
+
zigraf_ir-0.1.0.dist-info/entry_points.txt,sha256=Ync47gFXz-MpqvTIny11Ht5KkhQqNn-hh0ZTccPiAZk,42
|
|
15
|
+
zigraf_ir-0.1.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
16
|
+
zigraf_ir-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|