jcl-parser 2.0.3__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/PKG-INFO +69 -11
- jcl_parser-2.2.0/README.md +115 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jcl_parser.egg-info/PKG-INFO +69 -11
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jcl_parser.egg-info/SOURCES.txt +5 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jcl_parser.egg-info/entry_points.txt +1 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jcl_parser.egg-info/requires.txt +3 -1
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/__init__.py +45 -5
- jcl_parser-2.2.0/jclparser/_include_origins.py +129 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/_job_builder.py +238 -55
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/_model_factories.py +28 -1
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/_post_parse.py +163 -89
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/_source_scanner.py +109 -38
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/_statement_parser.py +72 -16
- jcl_parser-2.2.0/jclparser/checks.py +382 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/cli.py +486 -200
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/condition_parser.py +73 -76
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/continuation_handler.py +62 -23
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/dataset_lifecycle.py +68 -31
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/errors.py +31 -7
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/explosion.py +493 -248
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/include_loader.py +173 -92
- jcl_parser-2.2.0/jclparser/lineage.py +498 -0
- jcl_parser-2.2.0/jclparser/lint/__init__.py +47 -0
- jcl_parser-2.2.0/jclparser/lint/config.py +189 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/lint/rules.py +119 -9
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/lint/runner.py +62 -36
- jcl_parser-2.2.0/jclparser/lint/suppress.py +81 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/metrics.py +4 -1
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/model.py +79 -4
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/parameter_ast.py +4 -2
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/parameter_validators.py +58 -50
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/parser.py +88 -27
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/plugins/base.py +7 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/proc_loader.py +18 -6
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/profiling.py +5 -1
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/_control.py +6 -3
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/_invoked.py +2 -1
- jcl_parser-2.2.0/jclparser/program_profiles/adrdssu.py +116 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/base.py +8 -3
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/binder.py +24 -5
- jcl_parser-2.2.0/jclparser/program_profiles/ftp.py +187 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/icetool.py +2 -1
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/iefbr14.py +9 -2
- jcl_parser-2.2.0/jclparser/program_profiles/iehlist.py +87 -0
- jcl_parser-2.2.0/jclparser/program_profiles/iehprogm.py +94 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/sort.py +65 -9
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/rc_analysis.py +60 -7
- jcl_parser-2.2.0/jclparser/serialization.py +330 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/sql_detector.py +56 -17
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/utils.py +88 -3
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/validators.py +263 -76
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/pyproject.toml +9 -6
- jcl_parser-2.0.3/README.md +0 -58
- jcl_parser-2.0.3/jclparser/lint/__init__.py +0 -28
- jcl_parser-2.0.3/jclparser/lint/config.py +0 -39
- jcl_parser-2.0.3/jclparser/program_profiles/adrdssu.py +0 -72
- jcl_parser-2.0.3/jclparser/program_profiles/ftp.py +0 -59
- jcl_parser-2.0.3/jclparser/program_profiles/iehlist.py +0 -54
- jcl_parser-2.0.3/jclparser/program_profiles/iehprogm.py +0 -108
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/LICENSE +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jcl_parser.egg-info/dependency_links.txt +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jcl_parser.egg-info/top_level.txt +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/__main__.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/builtin_procs.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/jcl_file_type.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/jes_statements.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/lint/base.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/plugins/__init__.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/__init__.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/bpxbatch.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/compilers.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/db2_batch.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/dfsrrc00.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/dsnutilb.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/idcams.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/iebcompr.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/iebdg.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/iebgener.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/iebptpch.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/iebupdte.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/iehinitt.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/ikjeft01.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/irxjcl.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/program_profiles/registry.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/py.typed +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/jclparser/system_symbols.py +0 -0
- {jcl_parser-2.0.3 → jcl_parser-2.2.0}/setup.cfg +0 -0
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jcl-parser
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Parse IBM JCL (Job Control Language) scripts into Python objects
|
|
5
5
|
Author-email: Samuel Dion <sam94dion@gmail.com>
|
|
6
|
-
License: AGPL-3.0-or-later
|
|
6
|
+
License-Expression: AGPL-3.0-or-later
|
|
7
7
|
Project-URL: Homepage, https://github.com/landparty/jcl-parser
|
|
8
8
|
Project-URL: Repository, https://github.com/landparty/jcl-parser
|
|
9
|
-
Project-URL: Documentation, https://github.com/landparty/jcl-parser/
|
|
9
|
+
Project-URL: Documentation, https://github.com/landparty/jcl-parser/blob/main/.okf/index.md
|
|
10
10
|
Project-URL: Bug Reports, https://github.com/landparty/jcl-parser/issues
|
|
11
11
|
Keywords: jcl,mainframe,parser,ibm,job-control-language
|
|
12
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
15
14
|
Classifier: Operating System :: OS Independent
|
|
16
15
|
Classifier: Programming Language :: Python :: 3
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -28,12 +27,14 @@ Requires-Dist: pytest>=7.0; extra == "dev"
|
|
|
28
27
|
Requires-Dist: coverage[toml]>=7.0; extra == "dev"
|
|
29
28
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
30
29
|
Requires-Dist: pytest-benchmark>=4.0; extra == "dev"
|
|
31
|
-
Requires-Dist: ruff
|
|
30
|
+
Requires-Dist: ruff==0.17.3; extra == "dev"
|
|
32
31
|
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
32
|
+
Requires-Dist: jsonschema>=4.18; extra == "dev"
|
|
33
33
|
Requires-Dist: tomli>=1.1.0; python_version < "3.11" and extra == "dev"
|
|
34
34
|
Provides-Extra: test
|
|
35
35
|
Requires-Dist: pytest>=7.0; extra == "test"
|
|
36
36
|
Requires-Dist: pytest-cov>=4.0; extra == "test"
|
|
37
|
+
Requires-Dist: jsonschema>=4.18; extra == "test"
|
|
37
38
|
Requires-Dist: tomli>=1.1.0; python_version < "3.11" and extra == "test"
|
|
38
39
|
Provides-Extra: profiling
|
|
39
40
|
Requires-Dist: psutil>=5.9; extra == "profiling"
|
|
@@ -50,7 +51,8 @@ Python library for parsing IBM z/OS JCL (Job Control Language) into structured `
|
|
|
50
51
|
## Install
|
|
51
52
|
|
|
52
53
|
```bash
|
|
53
|
-
pip install -
|
|
54
|
+
pip install jcl-parser # from PyPI
|
|
55
|
+
pip install -e ".[dev]" # from a checkout, with the dev tools
|
|
54
56
|
```
|
|
55
57
|
|
|
56
58
|
Requires Python 3.10+. No runtime dependencies.
|
|
@@ -58,11 +60,20 @@ Requires Python 3.10+. No runtime dependencies.
|
|
|
58
60
|
## Quick Start
|
|
59
61
|
|
|
60
62
|
```python
|
|
63
|
+
from pathlib import Path
|
|
64
|
+
|
|
61
65
|
import jclparser
|
|
62
66
|
|
|
63
|
-
job = jclparser.parse_jcl(
|
|
67
|
+
job = jclparser.parse_jcl(Path("PAYROLL.jcl")) # a str is always JCL text, never a path
|
|
64
68
|
print(job.name)
|
|
65
|
-
|
|
69
|
+
for step in jclparser.iter_execution_steps(job.steps): # also walks IF/THEN/ELSE blocks
|
|
70
|
+
print(step.name, step.program or step.procedure)
|
|
71
|
+
|
|
72
|
+
exploded = jclparser.explode_job(job) # PROC expansion is a separate call
|
|
73
|
+
errors = jclparser.validate_jcl(
|
|
74
|
+
Path("PAYROLL.jcl"), level="structure"
|
|
75
|
+
) # list[ParseError]
|
|
76
|
+
print(job.to_json(indent=2)) # schema: jclparser.JOB_JSON_SCHEMA
|
|
66
77
|
```
|
|
67
78
|
|
|
68
79
|
CLI:
|
|
@@ -71,7 +82,52 @@ CLI:
|
|
|
71
82
|
jcl-parser FILE steps # list all steps
|
|
72
83
|
jcl-parser FILE --validate # validate JCL
|
|
73
84
|
jcl-parser FILE lint # run lint rules
|
|
74
|
-
jcl-parser FILE --json # full parse result as JSON
|
|
85
|
+
jcl-parser FILE --json # full parse result as JSON (job.to_dict())
|
|
86
|
+
jcl-parser --json-schema # JSON Schema of that document
|
|
87
|
+
jcl-parser DIR # cross-job dataset lineage of a directory of jobs
|
|
88
|
+
jcl-parser DIR lineage mermaid # ... as a Mermaid graph (or `dot`)
|
|
89
|
+
jcl-parser DIR lint --sarif # lint every job of a directory, as SARIF
|
|
90
|
+
jcl-lint jobs/ --fail-on warning --format sarif -o jcl.sarif # CI / pre-commit
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Lint in CI
|
|
94
|
+
|
|
95
|
+
Configure the rules in `pyproject.toml`, and switch one off for one statement
|
|
96
|
+
with a comment:
|
|
97
|
+
|
|
98
|
+
```toml
|
|
99
|
+
[tool.jcl-parser.lint]
|
|
100
|
+
ignore = ["JCL-LINT-002"]
|
|
101
|
+
fail-on = "warning"
|
|
102
|
+
[tool.jcl-parser.lint.severity]
|
|
103
|
+
"JCL-LINT-004" = "warning"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```jcl
|
|
107
|
+
//* jcl-lint: disable=JCL-LINT-001
|
|
108
|
+
//MASTER DD DSN=PROD.MASTER.FILE,DISP=SHR
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
pre-commit (`.pre-commit-config.yaml`):
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
- repo: https://github.com/landparty/jcl-parser
|
|
115
|
+
rev: v2.2.0 # first release with the hook
|
|
116
|
+
hooks:
|
|
117
|
+
- id: jcl-lint
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
GitHub Actions (findings appear in code scanning):
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
permissions:
|
|
124
|
+
security-events: write
|
|
125
|
+
steps:
|
|
126
|
+
- uses: actions/checkout@v4
|
|
127
|
+
- uses: landparty/jcl-parser@v2.2.0 # first release with the action
|
|
128
|
+
with:
|
|
129
|
+
paths: jcl
|
|
130
|
+
glob: "**/*.jcl"
|
|
75
131
|
```
|
|
76
132
|
|
|
77
133
|
## Commands
|
|
@@ -83,7 +139,7 @@ pytest -m unit
|
|
|
83
139
|
|
|
84
140
|
# Lint / format / type-check
|
|
85
141
|
ruff check . && ruff format .
|
|
86
|
-
mypy
|
|
142
|
+
python scripts/mypy_ratchet.py # mypy strict against the known-error baseline, as CI runs it
|
|
87
143
|
|
|
88
144
|
# Vendor sync (after changes to jclparser/) — rm first: plain `cp -r` nests
|
|
89
145
|
# into dest/jclparser/jclparser/ and leaves stale files when dest exists
|
|
@@ -92,7 +148,9 @@ rm -rf ../backend/_vendor/jcl-parser/jclparser && cp -r jclparser/ ../backend/_v
|
|
|
92
148
|
|
|
93
149
|
## Documentation
|
|
94
150
|
|
|
95
|
-
Full reference: [`.okf/index.md`](.okf/index.md)
|
|
151
|
+
Full reference: [`.okf/index.md`](.okf/index.md). Contributors: start with [`CLAUDE.md`](CLAUDE.md)
|
|
152
|
+
(commands, architecture, invariants) and [`.okf/development/invariants.md`](.okf/development/invariants.md).
|
|
153
|
+
Changes are listed in [`CHANGELOG.md`](CHANGELOG.md) (releases) and [`.okf/log.md`](.okf/log.md).
|
|
96
154
|
|
|
97
155
|
## License
|
|
98
156
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# jcl-parser
|
|
2
|
+
|
|
3
|
+
Python library for parsing IBM z/OS JCL (Job Control Language) into structured `Job`, `Step`, and `DD` objects. Supports PROC expansion, validation, lint rules, SQL detection, and dataset lifecycle analysis.
|
|
4
|
+
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
[](#commands)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install jcl-parser # from PyPI
|
|
13
|
+
pip install -e ".[dev]" # from a checkout, with the dev tools
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Requires Python 3.10+. No runtime dependencies.
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
import jclparser
|
|
24
|
+
|
|
25
|
+
job = jclparser.parse_jcl(Path("PAYROLL.jcl")) # a str is always JCL text, never a path
|
|
26
|
+
print(job.name)
|
|
27
|
+
for step in jclparser.iter_execution_steps(job.steps): # also walks IF/THEN/ELSE blocks
|
|
28
|
+
print(step.name, step.program or step.procedure)
|
|
29
|
+
|
|
30
|
+
exploded = jclparser.explode_job(job) # PROC expansion is a separate call
|
|
31
|
+
errors = jclparser.validate_jcl(
|
|
32
|
+
Path("PAYROLL.jcl"), level="structure"
|
|
33
|
+
) # list[ParseError]
|
|
34
|
+
print(job.to_json(indent=2)) # schema: jclparser.JOB_JSON_SCHEMA
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
CLI:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
jcl-parser FILE steps # list all steps
|
|
41
|
+
jcl-parser FILE --validate # validate JCL
|
|
42
|
+
jcl-parser FILE lint # run lint rules
|
|
43
|
+
jcl-parser FILE --json # full parse result as JSON (job.to_dict())
|
|
44
|
+
jcl-parser --json-schema # JSON Schema of that document
|
|
45
|
+
jcl-parser DIR # cross-job dataset lineage of a directory of jobs
|
|
46
|
+
jcl-parser DIR lineage mermaid # ... as a Mermaid graph (or `dot`)
|
|
47
|
+
jcl-parser DIR lint --sarif # lint every job of a directory, as SARIF
|
|
48
|
+
jcl-lint jobs/ --fail-on warning --format sarif -o jcl.sarif # CI / pre-commit
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Lint in CI
|
|
52
|
+
|
|
53
|
+
Configure the rules in `pyproject.toml`, and switch one off for one statement
|
|
54
|
+
with a comment:
|
|
55
|
+
|
|
56
|
+
```toml
|
|
57
|
+
[tool.jcl-parser.lint]
|
|
58
|
+
ignore = ["JCL-LINT-002"]
|
|
59
|
+
fail-on = "warning"
|
|
60
|
+
[tool.jcl-parser.lint.severity]
|
|
61
|
+
"JCL-LINT-004" = "warning"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```jcl
|
|
65
|
+
//* jcl-lint: disable=JCL-LINT-001
|
|
66
|
+
//MASTER DD DSN=PROD.MASTER.FILE,DISP=SHR
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
pre-commit (`.pre-commit-config.yaml`):
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
- repo: https://github.com/landparty/jcl-parser
|
|
73
|
+
rev: v2.2.0 # first release with the hook
|
|
74
|
+
hooks:
|
|
75
|
+
- id: jcl-lint
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
GitHub Actions (findings appear in code scanning):
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
permissions:
|
|
82
|
+
security-events: write
|
|
83
|
+
steps:
|
|
84
|
+
- uses: actions/checkout@v4
|
|
85
|
+
- uses: landparty/jcl-parser@v2.2.0 # first release with the action
|
|
86
|
+
with:
|
|
87
|
+
paths: jcl
|
|
88
|
+
glob: "**/*.jcl"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Commands
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Tests
|
|
95
|
+
pytest
|
|
96
|
+
pytest -m unit
|
|
97
|
+
|
|
98
|
+
# Lint / format / type-check
|
|
99
|
+
ruff check . && ruff format .
|
|
100
|
+
python scripts/mypy_ratchet.py # mypy strict against the known-error baseline, as CI runs it
|
|
101
|
+
|
|
102
|
+
# Vendor sync (after changes to jclparser/) — rm first: plain `cp -r` nests
|
|
103
|
+
# into dest/jclparser/jclparser/ and leaves stale files when dest exists
|
|
104
|
+
rm -rf ../backend/_vendor/jcl-parser/jclparser && cp -r jclparser/ ../backend/_vendor/jcl-parser/jclparser
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Documentation
|
|
108
|
+
|
|
109
|
+
Full reference: [`.okf/index.md`](.okf/index.md). Contributors: start with [`CLAUDE.md`](CLAUDE.md)
|
|
110
|
+
(commands, architecture, invariants) and [`.okf/development/invariants.md`](.okf/development/invariants.md).
|
|
111
|
+
Changes are listed in [`CHANGELOG.md`](CHANGELOG.md) (releases) and [`.okf/log.md`](.okf/log.md).
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
GNU Affero General Public License v3.0 or later — Copyright © 2026 Samuel Dion. See [LICENSE](LICENSE).
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jcl-parser
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Parse IBM JCL (Job Control Language) scripts into Python objects
|
|
5
5
|
Author-email: Samuel Dion <sam94dion@gmail.com>
|
|
6
|
-
License: AGPL-3.0-or-later
|
|
6
|
+
License-Expression: AGPL-3.0-or-later
|
|
7
7
|
Project-URL: Homepage, https://github.com/landparty/jcl-parser
|
|
8
8
|
Project-URL: Repository, https://github.com/landparty/jcl-parser
|
|
9
|
-
Project-URL: Documentation, https://github.com/landparty/jcl-parser/
|
|
9
|
+
Project-URL: Documentation, https://github.com/landparty/jcl-parser/blob/main/.okf/index.md
|
|
10
10
|
Project-URL: Bug Reports, https://github.com/landparty/jcl-parser/issues
|
|
11
11
|
Keywords: jcl,mainframe,parser,ibm,job-control-language
|
|
12
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
15
14
|
Classifier: Operating System :: OS Independent
|
|
16
15
|
Classifier: Programming Language :: Python :: 3
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -28,12 +27,14 @@ Requires-Dist: pytest>=7.0; extra == "dev"
|
|
|
28
27
|
Requires-Dist: coverage[toml]>=7.0; extra == "dev"
|
|
29
28
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
30
29
|
Requires-Dist: pytest-benchmark>=4.0; extra == "dev"
|
|
31
|
-
Requires-Dist: ruff
|
|
30
|
+
Requires-Dist: ruff==0.17.3; extra == "dev"
|
|
32
31
|
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
32
|
+
Requires-Dist: jsonschema>=4.18; extra == "dev"
|
|
33
33
|
Requires-Dist: tomli>=1.1.0; python_version < "3.11" and extra == "dev"
|
|
34
34
|
Provides-Extra: test
|
|
35
35
|
Requires-Dist: pytest>=7.0; extra == "test"
|
|
36
36
|
Requires-Dist: pytest-cov>=4.0; extra == "test"
|
|
37
|
+
Requires-Dist: jsonschema>=4.18; extra == "test"
|
|
37
38
|
Requires-Dist: tomli>=1.1.0; python_version < "3.11" and extra == "test"
|
|
38
39
|
Provides-Extra: profiling
|
|
39
40
|
Requires-Dist: psutil>=5.9; extra == "profiling"
|
|
@@ -50,7 +51,8 @@ Python library for parsing IBM z/OS JCL (Job Control Language) into structured `
|
|
|
50
51
|
## Install
|
|
51
52
|
|
|
52
53
|
```bash
|
|
53
|
-
pip install -
|
|
54
|
+
pip install jcl-parser # from PyPI
|
|
55
|
+
pip install -e ".[dev]" # from a checkout, with the dev tools
|
|
54
56
|
```
|
|
55
57
|
|
|
56
58
|
Requires Python 3.10+. No runtime dependencies.
|
|
@@ -58,11 +60,20 @@ Requires Python 3.10+. No runtime dependencies.
|
|
|
58
60
|
## Quick Start
|
|
59
61
|
|
|
60
62
|
```python
|
|
63
|
+
from pathlib import Path
|
|
64
|
+
|
|
61
65
|
import jclparser
|
|
62
66
|
|
|
63
|
-
job = jclparser.parse_jcl(
|
|
67
|
+
job = jclparser.parse_jcl(Path("PAYROLL.jcl")) # a str is always JCL text, never a path
|
|
64
68
|
print(job.name)
|
|
65
|
-
|
|
69
|
+
for step in jclparser.iter_execution_steps(job.steps): # also walks IF/THEN/ELSE blocks
|
|
70
|
+
print(step.name, step.program or step.procedure)
|
|
71
|
+
|
|
72
|
+
exploded = jclparser.explode_job(job) # PROC expansion is a separate call
|
|
73
|
+
errors = jclparser.validate_jcl(
|
|
74
|
+
Path("PAYROLL.jcl"), level="structure"
|
|
75
|
+
) # list[ParseError]
|
|
76
|
+
print(job.to_json(indent=2)) # schema: jclparser.JOB_JSON_SCHEMA
|
|
66
77
|
```
|
|
67
78
|
|
|
68
79
|
CLI:
|
|
@@ -71,7 +82,52 @@ CLI:
|
|
|
71
82
|
jcl-parser FILE steps # list all steps
|
|
72
83
|
jcl-parser FILE --validate # validate JCL
|
|
73
84
|
jcl-parser FILE lint # run lint rules
|
|
74
|
-
jcl-parser FILE --json # full parse result as JSON
|
|
85
|
+
jcl-parser FILE --json # full parse result as JSON (job.to_dict())
|
|
86
|
+
jcl-parser --json-schema # JSON Schema of that document
|
|
87
|
+
jcl-parser DIR # cross-job dataset lineage of a directory of jobs
|
|
88
|
+
jcl-parser DIR lineage mermaid # ... as a Mermaid graph (or `dot`)
|
|
89
|
+
jcl-parser DIR lint --sarif # lint every job of a directory, as SARIF
|
|
90
|
+
jcl-lint jobs/ --fail-on warning --format sarif -o jcl.sarif # CI / pre-commit
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Lint in CI
|
|
94
|
+
|
|
95
|
+
Configure the rules in `pyproject.toml`, and switch one off for one statement
|
|
96
|
+
with a comment:
|
|
97
|
+
|
|
98
|
+
```toml
|
|
99
|
+
[tool.jcl-parser.lint]
|
|
100
|
+
ignore = ["JCL-LINT-002"]
|
|
101
|
+
fail-on = "warning"
|
|
102
|
+
[tool.jcl-parser.lint.severity]
|
|
103
|
+
"JCL-LINT-004" = "warning"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```jcl
|
|
107
|
+
//* jcl-lint: disable=JCL-LINT-001
|
|
108
|
+
//MASTER DD DSN=PROD.MASTER.FILE,DISP=SHR
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
pre-commit (`.pre-commit-config.yaml`):
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
- repo: https://github.com/landparty/jcl-parser
|
|
115
|
+
rev: v2.2.0 # first release with the hook
|
|
116
|
+
hooks:
|
|
117
|
+
- id: jcl-lint
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
GitHub Actions (findings appear in code scanning):
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
permissions:
|
|
124
|
+
security-events: write
|
|
125
|
+
steps:
|
|
126
|
+
- uses: actions/checkout@v4
|
|
127
|
+
- uses: landparty/jcl-parser@v2.2.0 # first release with the action
|
|
128
|
+
with:
|
|
129
|
+
paths: jcl
|
|
130
|
+
glob: "**/*.jcl"
|
|
75
131
|
```
|
|
76
132
|
|
|
77
133
|
## Commands
|
|
@@ -83,7 +139,7 @@ pytest -m unit
|
|
|
83
139
|
|
|
84
140
|
# Lint / format / type-check
|
|
85
141
|
ruff check . && ruff format .
|
|
86
|
-
mypy
|
|
142
|
+
python scripts/mypy_ratchet.py # mypy strict against the known-error baseline, as CI runs it
|
|
87
143
|
|
|
88
144
|
# Vendor sync (after changes to jclparser/) — rm first: plain `cp -r` nests
|
|
89
145
|
# into dest/jclparser/jclparser/ and leaves stale files when dest exists
|
|
@@ -92,7 +148,9 @@ rm -rf ../backend/_vendor/jcl-parser/jclparser && cp -r jclparser/ ../backend/_v
|
|
|
92
148
|
|
|
93
149
|
## Documentation
|
|
94
150
|
|
|
95
|
-
Full reference: [`.okf/index.md`](.okf/index.md)
|
|
151
|
+
Full reference: [`.okf/index.md`](.okf/index.md). Contributors: start with [`CLAUDE.md`](CLAUDE.md)
|
|
152
|
+
(commands, architecture, invariants) and [`.okf/development/invariants.md`](.okf/development/invariants.md).
|
|
153
|
+
Changes are listed in [`CHANGELOG.md`](CHANGELOG.md) (releases) and [`.okf/log.md`](.okf/log.md).
|
|
96
154
|
|
|
97
155
|
## License
|
|
98
156
|
|
|
@@ -9,12 +9,14 @@ jcl_parser.egg-info/requires.txt
|
|
|
9
9
|
jcl_parser.egg-info/top_level.txt
|
|
10
10
|
jclparser/__init__.py
|
|
11
11
|
jclparser/__main__.py
|
|
12
|
+
jclparser/_include_origins.py
|
|
12
13
|
jclparser/_job_builder.py
|
|
13
14
|
jclparser/_model_factories.py
|
|
14
15
|
jclparser/_post_parse.py
|
|
15
16
|
jclparser/_source_scanner.py
|
|
16
17
|
jclparser/_statement_parser.py
|
|
17
18
|
jclparser/builtin_procs.py
|
|
19
|
+
jclparser/checks.py
|
|
18
20
|
jclparser/cli.py
|
|
19
21
|
jclparser/condition_parser.py
|
|
20
22
|
jclparser/continuation_handler.py
|
|
@@ -24,6 +26,7 @@ jclparser/explosion.py
|
|
|
24
26
|
jclparser/include_loader.py
|
|
25
27
|
jclparser/jcl_file_type.py
|
|
26
28
|
jclparser/jes_statements.py
|
|
29
|
+
jclparser/lineage.py
|
|
27
30
|
jclparser/metrics.py
|
|
28
31
|
jclparser/model.py
|
|
29
32
|
jclparser/parameter_ast.py
|
|
@@ -33,6 +36,7 @@ jclparser/proc_loader.py
|
|
|
33
36
|
jclparser/profiling.py
|
|
34
37
|
jclparser/py.typed
|
|
35
38
|
jclparser/rc_analysis.py
|
|
39
|
+
jclparser/serialization.py
|
|
36
40
|
jclparser/sql_detector.py
|
|
37
41
|
jclparser/system_symbols.py
|
|
38
42
|
jclparser/utils.py
|
|
@@ -42,6 +46,7 @@ jclparser/lint/base.py
|
|
|
42
46
|
jclparser/lint/config.py
|
|
43
47
|
jclparser/lint/rules.py
|
|
44
48
|
jclparser/lint/runner.py
|
|
49
|
+
jclparser/lint/suppress.py
|
|
45
50
|
jclparser/plugins/__init__.py
|
|
46
51
|
jclparser/plugins/base.py
|
|
47
52
|
jclparser/program_profiles/__init__.py
|
|
@@ -4,8 +4,9 @@ pytest>=7.0
|
|
|
4
4
|
coverage[toml]>=7.0
|
|
5
5
|
pytest-cov>=4.0
|
|
6
6
|
pytest-benchmark>=4.0
|
|
7
|
-
ruff
|
|
7
|
+
ruff==0.17.3
|
|
8
8
|
mypy>=1.0
|
|
9
|
+
jsonschema>=4.18
|
|
9
10
|
|
|
10
11
|
[dev:python_version < "3.11"]
|
|
11
12
|
tomli>=1.1.0
|
|
@@ -16,6 +17,7 @@ psutil>=5.9
|
|
|
16
17
|
[test]
|
|
17
18
|
pytest>=7.0
|
|
18
19
|
pytest-cov>=4.0
|
|
20
|
+
jsonschema>=4.18
|
|
19
21
|
|
|
20
22
|
[test:python_version < "3.11"]
|
|
21
23
|
tomli>=1.1.0
|
|
@@ -20,7 +20,7 @@ application, for example::
|
|
|
20
20
|
import logging as _logging
|
|
21
21
|
|
|
22
22
|
# Package metadata
|
|
23
|
-
__version__ = "2.0
|
|
23
|
+
__version__ = "2.2.0" # x-release-please-version
|
|
24
24
|
__author__ = "JCL Parser Team"
|
|
25
25
|
__description__ = "Parse IBM JCL scripts into Python objects"
|
|
26
26
|
|
|
@@ -66,7 +66,9 @@ from .include_loader import (
|
|
|
66
66
|
DictMemberLoader,
|
|
67
67
|
IncludeMemberLoader,
|
|
68
68
|
JCLLIBMemberLoader,
|
|
69
|
+
LineOrigin,
|
|
69
70
|
resolve_includes,
|
|
71
|
+
resolve_includes_with_origins,
|
|
70
72
|
)
|
|
71
73
|
from .jcl_file_type import (
|
|
72
74
|
FileType,
|
|
@@ -88,6 +90,15 @@ from .jes_statements import (
|
|
|
88
90
|
XmitStatement,
|
|
89
91
|
parse_jes_control,
|
|
90
92
|
)
|
|
93
|
+
from .lineage import (
|
|
94
|
+
DatasetLineage,
|
|
95
|
+
DatasetUse,
|
|
96
|
+
JobDependency,
|
|
97
|
+
JobStream,
|
|
98
|
+
LineageReport,
|
|
99
|
+
build_lineage,
|
|
100
|
+
load_job_stream,
|
|
101
|
+
)
|
|
91
102
|
from .lint import (
|
|
92
103
|
LintConfig,
|
|
93
104
|
LintRule,
|
|
@@ -100,7 +111,16 @@ from .lint import (
|
|
|
100
111
|
to_sarif as lint_to_sarif,
|
|
101
112
|
)
|
|
102
113
|
from .metrics import JobMetrics, compute_metrics
|
|
103
|
-
from .model import
|
|
114
|
+
from .model import (
|
|
115
|
+
ConditionalBlock,
|
|
116
|
+
DDStatement,
|
|
117
|
+
Job,
|
|
118
|
+
OutputStatement,
|
|
119
|
+
Parameter,
|
|
120
|
+
Statement,
|
|
121
|
+
Step,
|
|
122
|
+
iter_execution_steps,
|
|
123
|
+
)
|
|
104
124
|
from .parser import parse_jcl, parse_statement, validate_jcl
|
|
105
125
|
from .plugins import (
|
|
106
126
|
ParserConfig,
|
|
@@ -132,6 +152,12 @@ from .rc_analysis import (
|
|
|
132
152
|
FeasibilityReport,
|
|
133
153
|
compute_feasible_paths,
|
|
134
154
|
)
|
|
155
|
+
from .serialization import (
|
|
156
|
+
JOB_JSON_SCHEMA,
|
|
157
|
+
SCHEMA_VERSION,
|
|
158
|
+
job_to_dict,
|
|
159
|
+
job_to_json,
|
|
160
|
+
)
|
|
135
161
|
from .sql_detector import (
|
|
136
162
|
DB2_PROGRAMS,
|
|
137
163
|
SQLDetectionResult,
|
|
@@ -156,9 +182,6 @@ from .validators import (
|
|
|
156
182
|
# default unless the consuming application configures handlers.
|
|
157
183
|
_logging.getLogger(__name__).addHandler(_logging.NullHandler())
|
|
158
184
|
|
|
159
|
-
# Initialize global plugin registry
|
|
160
|
-
_registry = get_plugin_registry()
|
|
161
|
-
|
|
162
185
|
# Public API - what gets imported with "from jclparser import *"
|
|
163
186
|
__all__ = [ # noqa: RUF022 — grouped by API tier, not alphabetically
|
|
164
187
|
# ── Tier 1: Stable public API ─────────────────────────────────────────────
|
|
@@ -172,6 +195,8 @@ __all__ = [ # noqa: RUF022 — grouped by API tier, not alphabetically
|
|
|
172
195
|
"Parameter",
|
|
173
196
|
"Statement",
|
|
174
197
|
"OutputStatement",
|
|
198
|
+
"ConditionalBlock",
|
|
199
|
+
"iter_execution_steps",
|
|
175
200
|
"JCLParseError",
|
|
176
201
|
"JCLSyntaxError",
|
|
177
202
|
"JCLValidationError",
|
|
@@ -230,7 +255,22 @@ __all__ = [ # noqa: RUF022 — grouped by API tier, not alphabetically
|
|
|
230
255
|
"IncludeMemberLoader",
|
|
231
256
|
"DictMemberLoader",
|
|
232
257
|
"JCLLIBMemberLoader",
|
|
258
|
+
"LineOrigin",
|
|
233
259
|
"resolve_includes",
|
|
260
|
+
"resolve_includes_with_origins",
|
|
261
|
+
# ── Tier 2: JSON export ───────────────────────────────────────────────────
|
|
262
|
+
"job_to_dict",
|
|
263
|
+
"job_to_json",
|
|
264
|
+
"JOB_JSON_SCHEMA",
|
|
265
|
+
"SCHEMA_VERSION",
|
|
266
|
+
# ── Tier 2: Cross-job dataset lineage ─────────────────────────────────────
|
|
267
|
+
"load_job_stream",
|
|
268
|
+
"build_lineage",
|
|
269
|
+
"JobStream",
|
|
270
|
+
"LineageReport",
|
|
271
|
+
"DatasetLineage",
|
|
272
|
+
"DatasetUse",
|
|
273
|
+
"JobDependency",
|
|
234
274
|
# ── Tier 2: Dataset lifecycle analysis ───────────────────────────────────
|
|
235
275
|
"DatasetEvent",
|
|
236
276
|
"analyze_dataset_lifecycle",
|