fromjcl
Parse IBM z/OS JCL into a typed Python model, then serialize it to JSON, YAML, CSV, or back to byte-exact JCL.
fromjcl job.jcl --to json
fromjcl job.jcl --to yaml
fromjcl job.jcl --to csv # one row per (step, DD, dataset)
fromjcl job.jcl --to jcl # byte-exact roundtrip
fromjcl job.jcl --to raw # parse-tree dump
The parser is a pure-Python port of Mike Fulton's
JCLParser (Apache 2.0).
Byte-exact roundtrip is enforced on every commit against an 83-sample
corpus pulled from github.com/IBM/*, github.com/zowe/*, and
hand-authored paraphrases (see
tests/jcl_samples/).
Install
pip install fromjcl
Python 3.12+. Runtime deps are pyyaml and typer. Pure Python, so
the same wheel installs under IBM Open Enterprise Python on z/OS as
well as Linux, macOS, and Windows.
Optional [zoau] extra (experimental)
pip install 'fromjcl[zoau]'
Enables --to zoau and --to mvscmd, which translate each step into
its closest ZOAU shell equivalent (or an mvscmd/mvscmdauth
invocation when no opinionated mapping exists). Pulls in
bashlex to structurally check
every flag in the emitted script against a frozen ZOAU 1.x manpage
table.
The output is best-effort. Every generated script starts with an
EXPERIMENTAL banner, and bashlex catches flag typos but does not
verify semantic equivalence to the source JCL. Review before running
against real datasets.
Quick start
Given test.jcl:
//TESTJOB JOB (ACCT),'TEST',CLASS=A
//STEP01 EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
/* HELLO */
/*
fromjcl test.jcl --to yaml:
name: TESTJOB
account: (ACCT)
programmer: TEST
class_: A
steps:
- name: STEP01
program: IDCAMS
dds:
- name: SYSPRINT
sysout: '*'
- name: SYSIN
instream: ' /* HELLO */'
Querying with jq
# every dataset referenced
fromjcl job.jcl --to json | jq -r '.steps[].dds[].datasets[]?.dsn'
# datasets being created
fromjcl job.jcl --to json | jq -r '.steps[].dds[].datasets[]? | select(.disposition.status=="NEW") | .dsn'
# steps that run a specific program
fromjcl job.jcl --to json | jq -r '.steps[] | select(.program=="IDCAMS") | .name'
Python API
The public surface is what fromjcl/__init__.py re-exports. Anything
under fromjcl._* or fromjcl.converters.* is internal.
from fromjcl import parse, Job, to_yaml, to_jcl, from_dump
# Parse + walk the model.
job = Job.from_parsed(parse("test.jcl"))
for step in job.steps:
print(step.name, step.program, [dd.name for dd in step.dds])
# Forward: serialize the Job to a human-readable format.
print(to_yaml(job)) # YAML
# to_json(job), to_csv(job) also available
# Byte-exact roundtrip preserves comments, column layout, blank lines.
# Pass the parse tree, not the Job (the Job IR is lossy by design).
print(to_jcl(parse("test.jcl")))
# Reverse: take a JSON/YAML/CSV dump and emit JCL (functionally
# equivalent, not byte-exact).
print(from_dump(open("job.yaml").read(), "yaml"))
Available names:
| Symbol | What it is |
|---|---|
parse(path), parse_bytes(data) |
Parser. Returns a dict with full byte-level metadata. |
Job, Step, DD, Dataset, Disposition, Space, DCB |
Dataclasses. Standard equality, asdict(), structural pattern matching. |
Job.from_parsed(tree) |
Build the Job model from a parse tree. |
to_json(job), to_yaml(job), to_csv(job) |
Serialize a Job. |
to_jcl(tree), to_raw(tree) |
Serialize a parse tree (byte-exact / raw dump). |
from_dump(text, fmt) |
Reverse path: text dump back to JCL. |
[zoau] extra adds two private modules accessible via the CLI's
--to zoau / --to mvscmd paths. They are not part of the stable
Python API.
Reverse: re-emit JCL from JSON, YAML, or CSV
fromjcl job.json --rejcl # auto-detects input format
fromjcl job.yaml --rejcl --from yaml
fromjcl job.csv --rejcl --from csv
The reverse path produces functionally equivalent JCL, not
byte-exact. Comments, blank lines, and column layout are lost on the
forward pass through the IR and cannot be reconstructed. The
combinatoric matrix in tests/test_rejcl_matrix.py
asserts that Job → format → JCL → Job is a fixed point under
dataclass equality across the entire IBM/community/ZOAU corpus.
z/OS notes
fromjcl reads EBCDIC-encoded JCL natively. Binary-transferred JCL files
(not auto-converted by the z/OS UNIX shell) are detected by their //
byte signature (EBCDIC 0x61 0x61) and decoded as cp037 by default. All
characters that appear in JCL syntax have identical byte values in cp037,
cp500, and cp1047, so the choice of code page rarely matters for parsing.
If auto-detection picks the wrong code page, pass --encoding explicitly:
fromjcl job.jcl --encoding cp037 # EBCDIC US/Canada (default)
fromjcl job.jcl --encoding cp500 # EBCDIC International
fromjcl job.jcl --encoding cp1047 # z/OS Open Systems (remapped to cp037)
fromjcl job.jcl --encoding ebcdic # alias for cp037
If pip install itself trips over EBCDIC tagging, set
_BPXK_AUTOCVT=ON in the install shell.
Development
uv sync --all-groups
tests/check.sh # ruff format + check, mypy, vulture, pytest
The test group is z/OS-installable (pytest + bashlex, both pure
Python). The dev group adds workstation-only tooling (ruff, mypy,
vulture, radon, interrogate); ruff and uv are Rust binaries with no
z/OS build.
CI runs the same tests/check.sh pipeline on every push and pull
request (see .github/workflows/ci.yml).
Docs
- docs/ARCHITECTURE.md: parser layout, IR shape, and the byte-exact roundtrip contract
- CHANGELOG.md: release notes
- CONTRIBUTING.md: DCO sign-off, PR expectations
- SECURITY.md: private vulnerability reporting
License
Apache-2.0. See LICENSE and NOTICE.
Trademarks
IBM, the IBM logo, z/OS, MVS, and Z Open Automation Utilities (ZOAU) are trademarks or registered trademarks of International Business Machines Corporation, registered in many jurisdictions worldwide. Other product and service names might be trademarks of IBM or other companies. A current list of IBM trademarks is available at https://www.ibm.com/legal/copytrade.
This project is an independent community effort. It is not affiliated with, endorsed by, or sponsored by IBM.
Metadata
Release files for fromjcl 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| fromjcl-0.5.0.tar.gz | 63.8 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fromjcl-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 119.7 kB
Release files / fromjcl-0.5.0.tar.gz
| Download URL | fromjcl-0.5.0.tar.gz |
|---|---|
| Size | 63.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5d6fde87503804680b73b15b42c27d295404fdc3ca16bf861e843beca83ffc2c
|
|
BLAKE2b-256 checksum How to use checksums |
87c7f821976997494d0a85905ce0246adb53568a46ffbb096170ac632f9da773
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|
Release files / fromjcl-0.5.0-py3-none-any.whl
| Download URL | fromjcl-0.5.0-py3-none-any.whl |
|---|---|
| Size | 55.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fd57ad600284858095bcb2626e45c7006b7c78ecab6b5531215eb3020902a368
|
|
BLAKE2b-256 checksum How to use checksums |
21f654232246e863858d555af8b33728a4038451ebe2d6b721dc1f3c6e42fd22
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|
Security for fromjcl 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.