jcl-parser 2.1.0__tar.gz → 2.2.1__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.1.0 → jcl_parser-2.2.1}/PKG-INFO +69 -11
- jcl_parser-2.2.1/README.md +115 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jcl_parser.egg-info/PKG-INFO +69 -11
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jcl_parser.egg-info/SOURCES.txt +4 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jcl_parser.egg-info/entry_points.txt +1 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jcl_parser.egg-info/requires.txt +3 -1
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/__init__.py +29 -4
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/_include_origins.py +3 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/_job_builder.py +94 -5
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/_source_scanner.py +95 -6
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/_statement_parser.py +0 -9
- jcl_parser-2.2.1/jclparser/checks.py +419 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/cli.py +458 -200
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/condition_parser.py +55 -64
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/continuation_handler.py +17 -3
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/errors.py +18 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/explosion.py +301 -90
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/include_loader.py +6 -2
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/jes_statements.py +3 -0
- jcl_parser-2.2.1/jclparser/lineage.py +527 -0
- jcl_parser-2.2.1/jclparser/lint/__init__.py +47 -0
- jcl_parser-2.2.1/jclparser/lint/config.py +189 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/lint/rules.py +114 -4
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/lint/runner.py +102 -38
- jcl_parser-2.2.1/jclparser/lint/suppress.py +109 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/model.py +71 -4
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/parameter_ast.py +4 -2
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/parameter_validators.py +58 -50
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/parser.py +32 -6
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/proc_loader.py +3 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/profiling.py +5 -1
- jcl_parser-2.2.1/jclparser/program_profiles/adrdssu.py +116 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/binder.py +22 -4
- jcl_parser-2.2.1/jclparser/program_profiles/bpxbatch.py +116 -0
- jcl_parser-2.2.1/jclparser/program_profiles/dsnutilb.py +163 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/icetool.py +27 -17
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/iefbr14.py +9 -2
- jcl_parser-2.2.1/jclparser/program_profiles/iehlist.py +87 -0
- jcl_parser-2.2.1/jclparser/program_profiles/iehprogm.py +94 -0
- jcl_parser-2.2.1/jclparser/serialization.py +330 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/utils.py +19 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/validators.py +128 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/pyproject.toml +9 -6
- jcl_parser-2.1.0/README.md +0 -58
- jcl_parser-2.1.0/jclparser/lint/__init__.py +0 -28
- jcl_parser-2.1.0/jclparser/lint/config.py +0 -39
- jcl_parser-2.1.0/jclparser/program_profiles/adrdssu.py +0 -72
- jcl_parser-2.1.0/jclparser/program_profiles/bpxbatch.py +0 -68
- jcl_parser-2.1.0/jclparser/program_profiles/dsnutilb.py +0 -79
- jcl_parser-2.1.0/jclparser/program_profiles/iehlist.py +0 -54
- jcl_parser-2.1.0/jclparser/program_profiles/iehprogm.py +0 -108
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/LICENSE +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jcl_parser.egg-info/dependency_links.txt +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jcl_parser.egg-info/top_level.txt +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/__main__.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/_model_factories.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/_post_parse.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/builtin_procs.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/dataset_lifecycle.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/jcl_file_type.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/lint/base.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/metrics.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/plugins/__init__.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/plugins/base.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/__init__.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/_control.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/_invoked.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/base.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/compilers.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/db2_batch.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/dfsrrc00.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/ftp.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/idcams.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/iebcompr.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/iebdg.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/iebgener.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/iebptpch.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/iebupdte.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/iehinitt.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/ikjeft01.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/irxjcl.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/registry.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/program_profiles/sort.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/py.typed +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/rc_analysis.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/sql_detector.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/jclparser/system_symbols.py +0 -0
- {jcl_parser-2.1.0 → jcl_parser-2.2.1}/setup.cfg +0 -0
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jcl-parser
|
|
3
|
-
Version: 2.1
|
|
3
|
+
Version: 2.2.1
|
|
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.1
|
|
3
|
+
Version: 2.2.1
|
|
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
|
|
|
@@ -16,6 +16,7 @@ jclparser/_post_parse.py
|
|
|
16
16
|
jclparser/_source_scanner.py
|
|
17
17
|
jclparser/_statement_parser.py
|
|
18
18
|
jclparser/builtin_procs.py
|
|
19
|
+
jclparser/checks.py
|
|
19
20
|
jclparser/cli.py
|
|
20
21
|
jclparser/condition_parser.py
|
|
21
22
|
jclparser/continuation_handler.py
|
|
@@ -25,6 +26,7 @@ jclparser/explosion.py
|
|
|
25
26
|
jclparser/include_loader.py
|
|
26
27
|
jclparser/jcl_file_type.py
|
|
27
28
|
jclparser/jes_statements.py
|
|
29
|
+
jclparser/lineage.py
|
|
28
30
|
jclparser/metrics.py
|
|
29
31
|
jclparser/model.py
|
|
30
32
|
jclparser/parameter_ast.py
|
|
@@ -34,6 +36,7 @@ jclparser/proc_loader.py
|
|
|
34
36
|
jclparser/profiling.py
|
|
35
37
|
jclparser/py.typed
|
|
36
38
|
jclparser/rc_analysis.py
|
|
39
|
+
jclparser/serialization.py
|
|
37
40
|
jclparser/sql_detector.py
|
|
38
41
|
jclparser/system_symbols.py
|
|
39
42
|
jclparser/utils.py
|
|
@@ -43,6 +46,7 @@ jclparser/lint/base.py
|
|
|
43
46
|
jclparser/lint/config.py
|
|
44
47
|
jclparser/lint/rules.py
|
|
45
48
|
jclparser/lint/runner.py
|
|
49
|
+
jclparser/lint/suppress.py
|
|
46
50
|
jclparser/plugins/__init__.py
|
|
47
51
|
jclparser/plugins/base.py
|
|
48
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.1
|
|
23
|
+
__version__ = "2.2.1" # x-release-please-version
|
|
24
24
|
__author__ = "JCL Parser Team"
|
|
25
25
|
__description__ = "Parse IBM JCL scripts into Python objects"
|
|
26
26
|
|
|
@@ -90,6 +90,15 @@ from .jes_statements import (
|
|
|
90
90
|
XmitStatement,
|
|
91
91
|
parse_jes_control,
|
|
92
92
|
)
|
|
93
|
+
from .lineage import (
|
|
94
|
+
DatasetLineage,
|
|
95
|
+
DatasetUse,
|
|
96
|
+
JobDependency,
|
|
97
|
+
JobStream,
|
|
98
|
+
LineageReport,
|
|
99
|
+
build_lineage,
|
|
100
|
+
load_job_stream,
|
|
101
|
+
)
|
|
93
102
|
from .lint import (
|
|
94
103
|
LintConfig,
|
|
95
104
|
LintRule,
|
|
@@ -143,6 +152,12 @@ from .rc_analysis import (
|
|
|
143
152
|
FeasibilityReport,
|
|
144
153
|
compute_feasible_paths,
|
|
145
154
|
)
|
|
155
|
+
from .serialization import (
|
|
156
|
+
JOB_JSON_SCHEMA,
|
|
157
|
+
SCHEMA_VERSION,
|
|
158
|
+
job_to_dict,
|
|
159
|
+
job_to_json,
|
|
160
|
+
)
|
|
146
161
|
from .sql_detector import (
|
|
147
162
|
DB2_PROGRAMS,
|
|
148
163
|
SQLDetectionResult,
|
|
@@ -167,9 +182,6 @@ from .validators import (
|
|
|
167
182
|
# default unless the consuming application configures handlers.
|
|
168
183
|
_logging.getLogger(__name__).addHandler(_logging.NullHandler())
|
|
169
184
|
|
|
170
|
-
# Initialize global plugin registry
|
|
171
|
-
_registry = get_plugin_registry()
|
|
172
|
-
|
|
173
185
|
# Public API - what gets imported with "from jclparser import *"
|
|
174
186
|
__all__ = [ # noqa: RUF022 — grouped by API tier, not alphabetically
|
|
175
187
|
# ── Tier 1: Stable public API ─────────────────────────────────────────────
|
|
@@ -246,6 +258,19 @@ __all__ = [ # noqa: RUF022 — grouped by API tier, not alphabetically
|
|
|
246
258
|
"LineOrigin",
|
|
247
259
|
"resolve_includes",
|
|
248
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",
|
|
249
274
|
# ── Tier 2: Dataset lifecycle analysis ───────────────────────────────────
|
|
250
275
|
"DatasetEvent",
|
|
251
276
|
"analyze_dataset_lifecycle",
|
|
@@ -90,6 +90,9 @@ class IncludeLineMap:
|
|
|
90
90
|
(step_name, ref, self.translate(line))
|
|
91
91
|
for step_name, ref, line in job.unresolved_output_refs
|
|
92
92
|
]
|
|
93
|
+
job.conditional_errors[:] = [
|
|
94
|
+
(self.translate(line), message) for line, message in job.conditional_errors
|
|
95
|
+
]
|
|
93
96
|
|
|
94
97
|
def _relabel_object(self, obj: Any, seen: set[int]) -> None:
|
|
95
98
|
"""Translate ``line_number`` on *obj* and every model object under it."""
|