ofplang-validate 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.
- ofplang/validate/__init__.py +19 -0
- ofplang/validate/__main__.py +9 -0
- ofplang/validate/cli.py +178 -0
- ofplang/validate/contracts.py +531 -0
- ofplang/validate/diagnostics.py +65 -0
- ofplang/validate/duplicates.py +61 -0
- ofplang/validate/entry.py +129 -0
- ofplang/validate/errors.py +176 -0
- ofplang/validate/features.py +126 -0
- ofplang/validate/generics.py +394 -0
- ofplang/validate/identifiers.py +112 -0
- ofplang/validate/imports.py +185 -0
- ofplang/validate/nodes.py +493 -0
- ofplang/validate/objects.py +577 -0
- ofplang/validate/phases.py +87 -0
- ofplang/validate/py.typed +0 -0
- ofplang/validate/references.py +310 -0
- ofplang/validate/scheduling.py +187 -0
- ofplang/validate/script.py +81 -0
- ofplang/validate/shape.py +311 -0
- ofplang/validate/traits.py +59 -0
- ofplang/validate/typecheck.py +132 -0
- ofplang/validate/types.py +226 -0
- ofplang/validate/validator.py +166 -0
- ofplang/validate/views.py +139 -0
- ofplang/validate/yamlnode.py +241 -0
- ofplang_validate-0.1.0.dist-info/METADATA +119 -0
- ofplang_validate-0.1.0.dist-info/RECORD +32 -0
- ofplang_validate-0.1.0.dist-info/WHEEL +5 -0
- ofplang_validate-0.1.0.dist-info/entry_points.txt +2 -0
- ofplang_validate-0.1.0.dist-info/licenses/LICENSE +21 -0
- ofplang_validate-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""ofplang.validate -- validator for Object-flow Programming Language v0."""
|
|
2
|
+
|
|
3
|
+
from ofplang.validate.validator import (
|
|
4
|
+
EXTENSION_TOLERANT,
|
|
5
|
+
MODES,
|
|
6
|
+
STRICT,
|
|
7
|
+
Diagnostic,
|
|
8
|
+
ValidationResult,
|
|
9
|
+
validate,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Diagnostic",
|
|
14
|
+
"ValidationResult",
|
|
15
|
+
"validate",
|
|
16
|
+
"STRICT",
|
|
17
|
+
"EXTENSION_TOLERANT",
|
|
18
|
+
"MODES",
|
|
19
|
+
]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Enable `python -m ofplang <file>...`.
|
|
2
|
+
|
|
3
|
+
Intent: mirror the console-script entry point so the CLI is reachable without an
|
|
4
|
+
installed script, which is convenient in dev checkouts and CI.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ofplang.validate.cli import main
|
|
8
|
+
|
|
9
|
+
raise SystemExit(main())
|
ofplang/validate/cli.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Command-line interface for the ofplang v0 validator.
|
|
2
|
+
|
|
3
|
+
Intent: this is a thin presentation layer over :func:`ofplang.validate.validate` — it
|
|
4
|
+
does no validation itself, it only parses arguments, drives the library over one
|
|
5
|
+
or more files, and renders results. Keeping all logic in the library means the
|
|
6
|
+
CLI cannot drift from the conformance-tested behavior.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
ofp-validate [--mode {strict,extension-tolerant}] [--format {text,json}]
|
|
10
|
+
[-q/--quiet] [--no-color] <file>...
|
|
11
|
+
|
|
12
|
+
Also invocable as `python -m ofplang.validate`, and (once installed) as the
|
|
13
|
+
`validate` subcommand of the umbrella `ofp` CLI.
|
|
14
|
+
|
|
15
|
+
Exit codes (linter convention):
|
|
16
|
+
0 every file is valid
|
|
17
|
+
1 at least one file has validation errors
|
|
18
|
+
2 usage / input error (bad arguments, missing input file)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import json
|
|
25
|
+
import sys
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
from ofplang.validate import validate
|
|
29
|
+
from ofplang.validate.validator import EXTENSION_TOLERANT, STRICT, ValidationResult
|
|
30
|
+
|
|
31
|
+
# Exit codes are part of the CLI contract (scripts/CI depend on them).
|
|
32
|
+
EXIT_OK = 0
|
|
33
|
+
EXIT_INVALID = 1
|
|
34
|
+
EXIT_USAGE = 2
|
|
35
|
+
|
|
36
|
+
# ANSI colors, applied only when writing to a TTY and not disabled.
|
|
37
|
+
_RED = "\033[31m"
|
|
38
|
+
_GREEN = "\033[32m"
|
|
39
|
+
_DIM = "\033[2m"
|
|
40
|
+
_RESET = "\033[0m"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
44
|
+
parser = argparse.ArgumentParser(
|
|
45
|
+
prog="ofp-validate",
|
|
46
|
+
description="Validate ofplang v0 documents.",
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument("paths", nargs="+", metavar="FILE", help="document(s) to validate")
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
"--mode",
|
|
51
|
+
choices=[STRICT, EXTENSION_TOLERANT],
|
|
52
|
+
default=STRICT,
|
|
53
|
+
help="validation mode (default: strict)",
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"--format",
|
|
57
|
+
choices=["text", "json"],
|
|
58
|
+
default="text",
|
|
59
|
+
help="output format (default: text)",
|
|
60
|
+
)
|
|
61
|
+
parser.add_argument(
|
|
62
|
+
"-q",
|
|
63
|
+
"--quiet",
|
|
64
|
+
action="store_true",
|
|
65
|
+
help="suppress per-diagnostic lines; show only the summary",
|
|
66
|
+
)
|
|
67
|
+
parser.add_argument("--no-color", action="store_true", help="disable ANSI color output")
|
|
68
|
+
return parser
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _color_enabled(no_color: bool) -> bool:
|
|
72
|
+
# Color only when explicitly allowed AND attached to a terminal, so piped or
|
|
73
|
+
# redirected output stays clean and parseable.
|
|
74
|
+
return not no_color and sys.stdout.isatty()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _render_text(results: list[tuple[str, ValidationResult]], quiet: bool, color: bool) -> str:
|
|
78
|
+
"""Human-oriented report. One block per file, then a one-line summary."""
|
|
79
|
+
def c(text: str, code: str) -> str:
|
|
80
|
+
return f"{code}{text}{_RESET}" if color else text
|
|
81
|
+
|
|
82
|
+
lines: list[str] = []
|
|
83
|
+
total_errors = 0
|
|
84
|
+
invalid_files = 0
|
|
85
|
+
multi = len(results) > 1
|
|
86
|
+
|
|
87
|
+
for path, result in results:
|
|
88
|
+
if result.ok:
|
|
89
|
+
# Only announce OK files explicitly when validating several, so
|
|
90
|
+
# single-file runs stay terse.
|
|
91
|
+
if not quiet and multi:
|
|
92
|
+
lines.append(f"{path}: {c('OK', _GREEN)}")
|
|
93
|
+
continue
|
|
94
|
+
invalid_files += 1
|
|
95
|
+
total_errors += len(result.diagnostics)
|
|
96
|
+
if multi:
|
|
97
|
+
lines.append(f"{path}:")
|
|
98
|
+
if not quiet:
|
|
99
|
+
for d in result.diagnostics:
|
|
100
|
+
# Prefer a concrete source position as the locator; fall back to
|
|
101
|
+
# the logical path. When both exist, show the path as a dim
|
|
102
|
+
# trailing detail (position primary, path for context).
|
|
103
|
+
if d.location:
|
|
104
|
+
locator = d.location
|
|
105
|
+
detail = f" {c(d.path, _DIM)}" if d.path else ""
|
|
106
|
+
else:
|
|
107
|
+
locator = d.path or "<root>"
|
|
108
|
+
detail = ""
|
|
109
|
+
msg = f" {d.message}" if d.message else ""
|
|
110
|
+
indent = " " if multi else ""
|
|
111
|
+
lines.append(f"{indent}{locator}: {c('error', _RED)} {d.code}{detail}{msg}")
|
|
112
|
+
|
|
113
|
+
if total_errors == 0:
|
|
114
|
+
lines.append(
|
|
115
|
+
c(f"all valid ({len(results)} file{'s' if len(results) != 1 else ''})", _GREEN)
|
|
116
|
+
)
|
|
117
|
+
else:
|
|
118
|
+
lines.append(
|
|
119
|
+
c(f"{total_errors} error{'s' if total_errors != 1 else ''} in "
|
|
120
|
+
f"{invalid_files} of {len(results)} file{'s' if len(results) != 1 else ''}", _RED)
|
|
121
|
+
)
|
|
122
|
+
return "\n".join(lines)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _render_json(results: list[tuple[str, ValidationResult]]) -> str:
|
|
126
|
+
"""Machine-readable report for CI/editor integration."""
|
|
127
|
+
payload = {
|
|
128
|
+
"ok": all(r.ok for _, r in results),
|
|
129
|
+
"results": [
|
|
130
|
+
{
|
|
131
|
+
"file": path,
|
|
132
|
+
"ok": result.ok,
|
|
133
|
+
"diagnostics": [
|
|
134
|
+
{
|
|
135
|
+
"code": d.code,
|
|
136
|
+
"path": d.path,
|
|
137
|
+
"message": d.message,
|
|
138
|
+
"file": d.file,
|
|
139
|
+
"line": d.line,
|
|
140
|
+
"col": d.col,
|
|
141
|
+
}
|
|
142
|
+
for d in result.diagnostics
|
|
143
|
+
],
|
|
144
|
+
}
|
|
145
|
+
for path, result in results
|
|
146
|
+
],
|
|
147
|
+
}
|
|
148
|
+
return json.dumps(payload, indent=2, ensure_ascii=False)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def main(argv: list[str] | None = None) -> int:
|
|
152
|
+
parser = _build_parser()
|
|
153
|
+
args = parser.parse_args(argv)
|
|
154
|
+
|
|
155
|
+
# Pre-check inputs so a mistyped/missing path is a usage error (exit 2)
|
|
156
|
+
# rather than being reported as an in-document diagnostic.
|
|
157
|
+
missing = [p for p in args.paths if not Path(p).is_file()]
|
|
158
|
+
if missing:
|
|
159
|
+
for p in missing:
|
|
160
|
+
print(f"ofp-validate: cannot open {p!r}: no such file", file=sys.stderr)
|
|
161
|
+
return EXIT_USAGE
|
|
162
|
+
|
|
163
|
+
# Validate each file via the library (the sole source of truth).
|
|
164
|
+
results: list[tuple[str, ValidationResult]] = [
|
|
165
|
+
(p, validate(p, mode=args.mode)) for p in args.paths
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
if args.format == "json":
|
|
169
|
+
print(_render_json(results))
|
|
170
|
+
else:
|
|
171
|
+
print(_render_text(results, args.quiet, _color_enabled(args.no_color)))
|
|
172
|
+
|
|
173
|
+
# Exit code reflects the worst outcome across all inputs.
|
|
174
|
+
return EXIT_OK if all(r.ok for _, r in results) else EXIT_INVALID
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if __name__ == "__main__": # pragma: no cover - module entry
|
|
178
|
+
raise SystemExit(main())
|