jcl-parser 1.2.1__tar.gz → 1.3.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.
Files changed (70) hide show
  1. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/PKG-INFO +1 -1
  2. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jcl_parser.egg-info/PKG-INFO +1 -1
  3. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jcl_parser.egg-info/SOURCES.txt +11 -0
  4. jcl_parser-1.3.0/jclparser/program_profiles/adrdssu.py +72 -0
  5. jcl_parser-1.3.0/jclparser/program_profiles/binder.py +73 -0
  6. jcl_parser-1.3.0/jclparser/program_profiles/compilers.py +67 -0
  7. jcl_parser-1.3.0/jclparser/program_profiles/ftp.py +59 -0
  8. jcl_parser-1.3.0/jclparser/program_profiles/icetool.py +94 -0
  9. jcl_parser-1.3.0/jclparser/program_profiles/iebdg.py +58 -0
  10. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/program_profiles/iebgener.py +21 -11
  11. jcl_parser-1.3.0/jclparser/program_profiles/iebptpch.py +60 -0
  12. jcl_parser-1.3.0/jclparser/program_profiles/iebupdte.py +76 -0
  13. jcl_parser-1.3.0/jclparser/program_profiles/iehlist.py +54 -0
  14. jcl_parser-1.3.0/jclparser/program_profiles/iehprogm.py +108 -0
  15. jcl_parser-1.3.0/jclparser/program_profiles/irxjcl.py +58 -0
  16. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/program_profiles/registry.py +34 -11
  17. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/pyproject.toml +1 -1
  18. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/LICENSE +0 -0
  19. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/README.md +0 -0
  20. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jcl_parser.egg-info/dependency_links.txt +0 -0
  21. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jcl_parser.egg-info/entry_points.txt +0 -0
  22. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jcl_parser.egg-info/requires.txt +0 -0
  23. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jcl_parser.egg-info/top_level.txt +0 -0
  24. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/__init__.py +0 -0
  25. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/__main__.py +0 -0
  26. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/_job_builder.py +0 -0
  27. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/_model_factories.py +0 -0
  28. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/_post_parse.py +0 -0
  29. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/_statement_parser.py +0 -0
  30. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/builtin_procs.py +0 -0
  31. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/cli.py +0 -0
  32. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/condition_parser.py +0 -0
  33. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/continuation_handler.py +0 -0
  34. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/dataset_lifecycle.py +0 -0
  35. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/errors.py +0 -0
  36. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/explosion.py +0 -0
  37. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/include_loader.py +0 -0
  38. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/jcl_file_type.py +0 -0
  39. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/jes_statements.py +0 -0
  40. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/lint/__init__.py +0 -0
  41. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/lint/base.py +0 -0
  42. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/lint/config.py +0 -0
  43. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/lint/rules.py +0 -0
  44. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/lint/runner.py +0 -0
  45. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/metrics.py +0 -0
  46. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/model.py +0 -0
  47. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/parameter_ast.py +0 -0
  48. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/parameter_validators.py +0 -0
  49. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/parser.py +0 -0
  50. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/plugins/__init__.py +0 -0
  51. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/plugins/base.py +0 -0
  52. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/proc_loader.py +0 -0
  53. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/profiling.py +0 -0
  54. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/program_profiles/__init__.py +0 -0
  55. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/program_profiles/_invoked.py +0 -0
  56. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/program_profiles/base.py +0 -0
  57. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/program_profiles/db2_batch.py +0 -0
  58. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/program_profiles/dfsrrc00.py +0 -0
  59. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/program_profiles/idcams.py +0 -0
  60. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/program_profiles/iefbr14.py +0 -0
  61. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/program_profiles/ikjeft01.py +0 -0
  62. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/program_profiles/sort.py +0 -0
  63. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/py.typed +0 -0
  64. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/rc_analysis.py +0 -0
  65. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/sql_detector.py +0 -0
  66. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/system_symbols.py +0 -0
  67. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/utils.py +0 -0
  68. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/jclparser/validators.py +0 -0
  69. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/setup.cfg +0 -0
  70. {jcl_parser-1.2.1 → jcl_parser-1.3.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jcl-parser
3
- Version: 1.2.1
3
+ Version: 1.3.0
4
4
  Summary: Parse IBM JCL (Job Control Language) scripts into Python objects
5
5
  Author-email: JCL Parser Team <team@jclparser.dev>
6
6
  License: END-USER LICENSE AGREEMENT (EULA)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jcl-parser
3
- Version: 1.2.1
3
+ Version: 1.3.0
4
4
  Summary: Parse IBM JCL (Job Control Language) scripts into Python objects
5
5
  Author-email: JCL Parser Team <team@jclparser.dev>
6
6
  License: END-USER LICENSE AGREEMENT (EULA)
@@ -46,12 +46,23 @@ jclparser/plugins/__init__.py
46
46
  jclparser/plugins/base.py
47
47
  jclparser/program_profiles/__init__.py
48
48
  jclparser/program_profiles/_invoked.py
49
+ jclparser/program_profiles/adrdssu.py
49
50
  jclparser/program_profiles/base.py
51
+ jclparser/program_profiles/binder.py
52
+ jclparser/program_profiles/compilers.py
50
53
  jclparser/program_profiles/db2_batch.py
51
54
  jclparser/program_profiles/dfsrrc00.py
55
+ jclparser/program_profiles/ftp.py
56
+ jclparser/program_profiles/icetool.py
52
57
  jclparser/program_profiles/idcams.py
58
+ jclparser/program_profiles/iebdg.py
53
59
  jclparser/program_profiles/iebgener.py
60
+ jclparser/program_profiles/iebptpch.py
61
+ jclparser/program_profiles/iebupdte.py
54
62
  jclparser/program_profiles/iefbr14.py
63
+ jclparser/program_profiles/iehlist.py
64
+ jclparser/program_profiles/iehprogm.py
55
65
  jclparser/program_profiles/ikjeft01.py
66
+ jclparser/program_profiles/irxjcl.py
56
67
  jclparser/program_profiles/registry.py
57
68
  jclparser/program_profiles/sort.py
@@ -0,0 +1,72 @@
1
+ """Profile for ``ADRDSSU`` — DFSMSdss batch (DUMP/RESTORE/COPY).
2
+
3
+ Control statements are read from ``SYSIN`` and use a keyword-command form,
4
+ e.g. ``DUMP DATASET(INCLUDE(...)) OUTDDNAME(...)``. This is a shallow
5
+ parse: it recognises the leading command keyword and, where present, the
6
+ ``DATASET(INCLUDE(...))`` dataset list, without validating the full
7
+ DFSMSdss grammar (which spans many optional sub-parameters).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import re
13
+ from typing import TYPE_CHECKING
14
+
15
+ from .base import ProgramAnalysis, ProgramProfile, _sysin_text
16
+
17
+ if TYPE_CHECKING:
18
+ from ..model import Step
19
+
20
+ _COMMANDS = (
21
+ "DUMP",
22
+ "RESTORE",
23
+ "COPY",
24
+ "RELEASE",
25
+ "COMPRESS",
26
+ "DEFRAG",
27
+ "CONVERTV",
28
+ "PRINT",
29
+ )
30
+ _RE_COMMAND = re.compile(
31
+ r"^\s*(" + "|".join(_COMMANDS) + r")\b", re.IGNORECASE | re.MULTILINE
32
+ )
33
+ _RE_DATASET_INCLUDE = re.compile(
34
+ r"DATASET\s*\(\s*INCLUDE\s*\(\s*([^)]*)\)", re.IGNORECASE
35
+ )
36
+
37
+
38
+ class ADRDSSUProfile(ProgramProfile):
39
+ program_names = frozenset({"ADRDSSU"})
40
+
41
+ def analyze(self, step: Step) -> ProgramAnalysis:
42
+ text = _sysin_text(step) or ""
43
+ notes: list[str] = []
44
+
45
+ matches = list(_RE_COMMAND.finditer(text))
46
+ for i, m in enumerate(matches):
47
+ command = m.group(1).upper()
48
+ # Scope the INCLUDE search to this command's own statement —
49
+ # up to the next command keyword (or end of text) — so a later
50
+ # command's DATASET(INCLUDE(...)) can't be misattributed here.
51
+ segment_end = matches[i + 1].start() if i + 1 < len(matches) else len(text)
52
+ segment = text[m.end() : segment_end]
53
+ ds_m = _RE_DATASET_INCLUDE.search(segment)
54
+ if ds_m:
55
+ names = [n.strip() for n in ds_m.group(1).split() if n.strip()]
56
+ notes.append(f"{command} DATASET(INCLUDE({' '.join(names)}))")
57
+ else:
58
+ notes.append(command)
59
+
60
+ summary = (
61
+ f"ADRDSSU: {len(notes)} command(s)"
62
+ if notes
63
+ else "ADRDSSU with no recognised commands"
64
+ )
65
+ return ProgramAnalysis(
66
+ program="ADRDSSU",
67
+ step_name=step.name or "",
68
+ summary=summary,
69
+ dataset_events=[],
70
+ notes=notes,
71
+ raw_control=text or None,
72
+ )
@@ -0,0 +1,73 @@
1
+ """Profile for ``IEWL`` / ``HEWL`` — the Binder / Linkage Editor.
2
+
3
+ Control statements are read from ``SYSIN`` (or, when absent, ``SYSLIN``):
4
+ ``INCLUDE`` pulls in an object/load module, ``ENTRY`` sets the entry point,
5
+ ``NAME`` names the output load module member, and ``LIBRARY``/``ALIAS``
6
+ add search libraries and alternate names. Output lands on ``SYSLMOD``.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ from typing import TYPE_CHECKING
13
+
14
+ from .base import ProgramAnalysis, ProgramProfile
15
+
16
+ if TYPE_CHECKING:
17
+ from ..model import Step
18
+
19
+ _RE_INCLUDE = re.compile(
20
+ r"^\s*INCLUDE\s+([A-Za-z0-9$#@]+)", re.IGNORECASE | re.MULTILINE
21
+ )
22
+ _RE_ENTRY = re.compile(r"^\s*ENTRY\s+([A-Za-z0-9$#@]+)", re.IGNORECASE | re.MULTILINE)
23
+ _RE_NAME = re.compile(r"^\s*NAME\s+([A-Za-z0-9$#@]+)", re.IGNORECASE | re.MULTILINE)
24
+ _RE_LIBRARY = re.compile(
25
+ r"^\s*LIBRARY\s+([A-Za-z0-9$#@,()]+)", re.IGNORECASE | re.MULTILINE
26
+ )
27
+ _RE_ALIAS = re.compile(r"^\s*ALIAS\s+([A-Za-z0-9$#@]+)", re.IGNORECASE | re.MULTILINE)
28
+
29
+
30
+ def _control_text(step: Step) -> str:
31
+ for dd_name in ("SYSIN", "SYSLIN"):
32
+ dd = step.get_dd(dd_name)
33
+ if dd and dd.inline_data:
34
+ return dd.inline_data
35
+ return ""
36
+
37
+
38
+ class BinderProfile(ProgramProfile):
39
+ program_names = frozenset({"IEWL", "HEWL"})
40
+
41
+ def analyze(self, step: Step) -> ProgramAnalysis:
42
+ text = _control_text(step)
43
+ notes: list[str] = []
44
+
45
+ includes = [m.group(1) for m in _RE_INCLUDE.finditer(text)]
46
+ if includes:
47
+ notes.append(f"INCLUDE {', '.join(includes)}")
48
+ for m in _RE_ENTRY.finditer(text):
49
+ notes.append(f"ENTRY {m.group(1)}")
50
+ for m in _RE_NAME.finditer(text):
51
+ notes.append(f"NAME {m.group(1)}")
52
+ for m in _RE_LIBRARY.finditer(text):
53
+ notes.append(f"LIBRARY {m.group(1)}")
54
+ for m in _RE_ALIAS.finditer(text):
55
+ notes.append(f"ALIAS {m.group(1)}")
56
+
57
+ syslmod = step.get_dd("SYSLMOD")
58
+ if syslmod and syslmod.dataset_name:
59
+ notes.append(f"output module: {syslmod.dataset_name}")
60
+
61
+ summary = (
62
+ f"{step.program}: link-edits {len(includes)} object module(s)"
63
+ if notes
64
+ else f"{step.program} with no recognised control statements"
65
+ )
66
+ return ProgramAnalysis(
67
+ program=step.program or "IEWL",
68
+ step_name=step.name or "",
69
+ summary=summary,
70
+ dataset_events=[],
71
+ notes=notes,
72
+ raw_control=text or None,
73
+ )
@@ -0,0 +1,67 @@
1
+ """Profile for language-translator (compiler) steps.
2
+
3
+ Covers the common z/OS compilers invoked directly via ``EXEC PGM=``:
4
+ COBOL (``IGYCRCTL``), PL/I (``IEL1CL``, ``IBMZPLI``), and High Level
5
+ Assembler (``ASMA90``). These programs do not read a command-language
6
+ control stream the way utilities do — the "control" is the source itself
7
+ on ``SYSIN`` plus copybook/include libraries on ``SYSLIB``. The profile
8
+ reports which of those DDs are present and where the compiled object
9
+ lands (``SYSLIN``/``SYSPUNCH``), which is enough to model a compile step's
10
+ dataset dependencies without parsing the source language.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import TYPE_CHECKING
16
+
17
+ from .base import ProgramAnalysis, ProgramProfile
18
+
19
+ if TYPE_CHECKING:
20
+ from ..model import Step
21
+
22
+ _COMPILER_LABELS = {
23
+ "IGYCRCTL": "COBOL",
24
+ "IEL1CL": "PL/I",
25
+ "IBMZPLI": "PL/I",
26
+ "ASMA90": "Assembler",
27
+ }
28
+
29
+
30
+ class CompilerProfile(ProgramProfile):
31
+ program_names = frozenset(_COMPILER_LABELS)
32
+
33
+ def analyze(self, step: Step) -> ProgramAnalysis:
34
+ program = step.program or ""
35
+ language = _COMPILER_LABELS.get(program.upper(), "")
36
+ notes: list[str] = []
37
+
38
+ sysin = step.get_dd("SYSIN")
39
+ if sysin and sysin.dataset_name:
40
+ notes.append(f"source: {sysin.dataset_name}")
41
+ elif sysin and sysin.inline_data:
42
+ notes.append("source: in-stream SYSIN")
43
+
44
+ syslib = step.get_dd("SYSLIB")
45
+ if syslib and syslib.dataset_name:
46
+ notes.append(f"copy/include library: {syslib.dataset_name}")
47
+
48
+ for out_dd in ("SYSLIN", "SYSPUNCH"):
49
+ dd = step.get_dd(out_dd)
50
+ if dd and dd.dataset_name:
51
+ notes.append(f"object output ({out_dd}): {dd.dataset_name}")
52
+
53
+ summary = (
54
+ f"{language} compile ({program})" if language else f"compile ({program})"
55
+ )
56
+ if notes:
57
+ summary += f": {len(notes)} relevant DD(s)"
58
+ else:
59
+ summary += " with no recognised SYSIN/SYSLIB/SYSLIN DDs"
60
+
61
+ return ProgramAnalysis(
62
+ program=program or "COMPILER",
63
+ step_name=step.name or "",
64
+ summary=summary,
65
+ dataset_events=[],
66
+ notes=notes,
67
+ )
@@ -0,0 +1,59 @@
1
+ """Profile for batch ``FTP`` (``PGM=FTP``).
2
+
3
+ z/OS batch FTP jobs drive the client from ``SYSIN`` subcommands: ``OPEN``
4
+ names the remote host, ``USER`` authenticates, ``PUT``/``GET``/``MPUT``/
5
+ ``MGET`` transfer files, and ``ASCII``/``BINARY`` set transfer mode. The
6
+ profile surfaces the host and the file operations without attempting to
7
+ resolve remote paths.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import re
13
+ from typing import TYPE_CHECKING
14
+
15
+ from .base import ProgramAnalysis, ProgramProfile, _sysin_text
16
+
17
+ if TYPE_CHECKING:
18
+ from ..model import Step
19
+
20
+ _RE_OPEN = re.compile(r"^\s*OPEN\s+(\S+)", re.IGNORECASE | re.MULTILINE)
21
+ _RE_USER = re.compile(r"^\s*USER\s+(\S+)", re.IGNORECASE | re.MULTILINE)
22
+ _RE_TRANSFER = re.compile(
23
+ r"^\s*(PUT|GET|MPUT|MGET)\s+(\S+)", re.IGNORECASE | re.MULTILINE
24
+ )
25
+ _RE_MODE = re.compile(r"^\s*(ASCII|BINARY)\b", re.IGNORECASE | re.MULTILINE)
26
+
27
+
28
+ class FTPProfile(ProgramProfile):
29
+ program_names = frozenset({"FTP"})
30
+
31
+ def analyze(self, step: Step) -> ProgramAnalysis:
32
+ text = _sysin_text(step) or ""
33
+ notes: list[str] = []
34
+
35
+ host_m = _RE_OPEN.search(text)
36
+ if host_m:
37
+ notes.append(f"OPEN {host_m.group(1)}")
38
+ user_m = _RE_USER.search(text)
39
+ if user_m:
40
+ notes.append(f"USER {user_m.group(1)}")
41
+ for m in _RE_TRANSFER.finditer(text):
42
+ notes.append(f"{m.group(1).upper()} {m.group(2)}")
43
+ mode_m = _RE_MODE.search(text)
44
+ if mode_m:
45
+ notes.append(mode_m.group(1).upper())
46
+
47
+ summary = (
48
+ f"FTP: {len(notes)} subcommand(s)"
49
+ if notes
50
+ else "FTP with no recognised SYSIN subcommands"
51
+ )
52
+ return ProgramAnalysis(
53
+ program="FTP",
54
+ step_name=step.name or "",
55
+ summary=summary,
56
+ dataset_events=[],
57
+ notes=notes,
58
+ raw_control=text or None,
59
+ )
@@ -0,0 +1,94 @@
1
+ """Profile for ``ICETOOL`` — the DFSORT batch toolkit.
2
+
3
+ ICETOOL reads a sequence of operator statements from ``TOOLIN`` (one
4
+ operator per logical statement), each naming its input/output DDs via
5
+ ``FROM(ddname)`` / ``TO(ddname)`` / ``USING(member)``. Common operators:
6
+ ``COPY``, ``SORT``, ``MERGE``, ``COUNT``, ``STATS``, ``SELECT``, ``SPLICE``,
7
+ ``UNIQUE``, ``OCCUR``, ``DISPLAY``, ``DEFAULTS``, ``MODE``, ``RESIZE``,
8
+ ``VERIFY``, ``WHENSAME``, ``WHENDIFF``, ``DEDUP``, ``RANGE``, ``CORR``.
9
+
10
+ Like :mod:`jclparser.program_profiles.sort`, this is a shallow parse: it
11
+ surfaces which operators ran and which DDs they touched without validating
12
+ field specifications.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ from typing import TYPE_CHECKING
19
+
20
+ from .base import ProgramAnalysis, ProgramProfile, _sysin_text
21
+
22
+ if TYPE_CHECKING:
23
+ from ..model import Step
24
+
25
+ _OPERATORS = (
26
+ "COPY",
27
+ "SORT",
28
+ "MERGE",
29
+ "COUNT",
30
+ "STATS",
31
+ "SELECT",
32
+ "SPLICE",
33
+ "UNIQUE",
34
+ "OCCUR",
35
+ "DISPLAY",
36
+ "DEFAULTS",
37
+ "MODE",
38
+ "RESIZE",
39
+ "VERIFY",
40
+ "WHENSAME",
41
+ "WHENDIFF",
42
+ "DEDUP",
43
+ "RANGE",
44
+ "CORR",
45
+ )
46
+ _RE_OPERATOR_LINE = re.compile(
47
+ r"^\s*(" + "|".join(_OPERATORS) + r")\b(.*)$",
48
+ re.IGNORECASE | re.MULTILINE,
49
+ )
50
+ _RE_FROM = re.compile(r"\bFROM\s*\(\s*([A-Za-z0-9$#@]+)\s*\)", re.IGNORECASE)
51
+ _RE_TO = re.compile(r"\bTO\s*\(\s*([A-Za-z0-9$#@]+)\s*\)", re.IGNORECASE)
52
+
53
+
54
+ def _toolin_text(step: Step) -> str:
55
+ dd = step.get_dd("TOOLIN")
56
+ if dd and dd.inline_data:
57
+ return dd.inline_data
58
+ # Some jobs route TOOLIN through SYSIN instead.
59
+ return _sysin_text(step) or ""
60
+
61
+
62
+ class ICETOOLProfile(ProgramProfile):
63
+ program_names = frozenset({"ICETOOL"})
64
+
65
+ def analyze(self, step: Step) -> ProgramAnalysis:
66
+ text = _toolin_text(step)
67
+ notes: list[str] = []
68
+
69
+ for m in _RE_OPERATOR_LINE.finditer(text):
70
+ operator = m.group(1).upper()
71
+ tail = m.group(2)
72
+ froms = _RE_FROM.findall(tail)
73
+ tos = _RE_TO.findall(tail)
74
+ parts = []
75
+ if froms:
76
+ parts.append(f"FROM({','.join(froms)})")
77
+ if tos:
78
+ parts.append(f"TO({','.join(tos)})")
79
+ detail = f" {' '.join(parts)}" if parts else ""
80
+ notes.append(f"{operator}{detail}")
81
+
82
+ summary = (
83
+ f"ICETOOL: {len(notes)} operator(s)"
84
+ if notes
85
+ else "ICETOOL with no recognised TOOLIN operators"
86
+ )
87
+ return ProgramAnalysis(
88
+ program="ICETOOL",
89
+ step_name=step.name or "",
90
+ summary=summary,
91
+ dataset_events=[],
92
+ notes=notes,
93
+ raw_control=text or None,
94
+ )
@@ -0,0 +1,58 @@
1
+ """Profile for ``IEBDG`` — test data generator.
2
+
3
+ Control statements are read from ``SYSIN``: ``DSD`` names the output
4
+ dataset description, ``FD`` describes a field, and ``CREATE`` requests
5
+ record generation (optionally with a ``REPEAT`` count sub-parameter). The
6
+ generated output lands on ``SYSUT2``, referenced by DDNAME= on the DSD
7
+ statement or the step's SYSUT2 DD.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import re
13
+ from typing import TYPE_CHECKING
14
+
15
+ from .base import ProgramAnalysis, ProgramProfile, _sysin_text
16
+
17
+ if TYPE_CHECKING:
18
+ from ..model import Step
19
+
20
+ _RE_DSD = re.compile(
21
+ r"^\s*DSD\b[^\n]*DDNAME=([A-Za-z0-9$#@]+)", re.IGNORECASE | re.MULTILINE
22
+ )
23
+ _RE_FD = re.compile(
24
+ r"^\s*FD\b[^\n]*NAME=([A-Za-z0-9$#@]+)", re.IGNORECASE | re.MULTILINE
25
+ )
26
+ _RE_CREATE = re.compile(
27
+ r"^\s*CREATE\b(?:[^\n]*\bREPEAT=(\d+))?", re.IGNORECASE | re.MULTILINE
28
+ )
29
+
30
+
31
+ class IEBDGProfile(ProgramProfile):
32
+ program_names = frozenset({"IEBDG"})
33
+
34
+ def analyze(self, step: Step) -> ProgramAnalysis:
35
+ text = _sysin_text(step) or ""
36
+ notes: list[str] = []
37
+
38
+ for m in _RE_DSD.finditer(text):
39
+ notes.append(f"DSD DDNAME={m.group(1)}")
40
+ field_names = [m.group(1) for m in _RE_FD.finditer(text)]
41
+ if field_names:
42
+ notes.append(f"FD fields: {', '.join(field_names)}")
43
+ for m in _RE_CREATE.finditer(text):
44
+ notes.append(f"CREATE REPEAT={m.group(1)}" if m.group(1) else "CREATE")
45
+
46
+ summary = (
47
+ f"IEBDG: generates test data ({len(field_names)} field(s))"
48
+ if notes
49
+ else "IEBDG with no recognised control statements"
50
+ )
51
+ return ProgramAnalysis(
52
+ program="IEBDG",
53
+ step_name=step.name or "",
54
+ summary=summary,
55
+ dataset_events=[],
56
+ notes=notes,
57
+ raw_control=text or None,
58
+ )
@@ -1,4 +1,5 @@
1
- """Profiles for simple IBM copy utilities: ``IEBGENER`` and ``IEBCOPY``.
1
+ """Profiles for simple IBM copy utilities: ``IEBGENER``, ``IEBCOPY``, and
2
+ ``ICEGENER`` (the DFSORT-based drop-in replacement for IEBGENER).
2
3
 
3
4
  These programs copy data from one dataset to another. The profile reports
4
5
  SYSUT1 (input) and SYSUT2 (output) as the canonical source/destination pair
@@ -8,27 +9,29 @@ dispositions.
8
9
 
9
10
  from __future__ import annotations
10
11
 
11
- from .base import ProgramProfile, ProgramAnalysis
12
- from ..dataset_lifecycle import DatasetEvent
12
+ from typing import TYPE_CHECKING
13
13
 
14
+ from ..dataset_lifecycle import DatasetEvent, _parse_disp_tokens
15
+ from .base import ProgramAnalysis, ProgramProfile
14
16
 
15
- def _copy_utility_analysis(step, program: str) -> ProgramAnalysis:
17
+ if TYPE_CHECKING:
18
+ from ..model import Step
19
+
20
+
21
+ def _copy_utility_analysis(step: Step, program: str) -> ProgramAnalysis:
16
22
  sysut1 = step.get_dd("SYSUT1")
17
23
  sysut2 = step.get_dd("SYSUT2")
18
24
  events: list[DatasetEvent] = []
19
25
  notes: list[str] = []
20
26
 
21
27
  if sysut1 and sysut1.dataset_name:
28
+ status, _, _ = _parse_disp_tokens(sysut1.disposition)
22
29
  events.append(
23
30
  DatasetEvent(
24
31
  step_name=step.name or "",
25
32
  dd_name="SYSUT1",
26
33
  action="OPEN_SHARED",
27
- disp_status=(sysut1.disposition or "SHR")
28
- .split(",")[0]
29
- .strip("()")
30
- .upper()
31
- or "SHR",
34
+ disp_status=status or "SHR",
32
35
  line_number=sysut1.line_number,
33
36
  )
34
37
  )
@@ -63,12 +66,19 @@ def _copy_utility_analysis(step, program: str) -> ProgramAnalysis:
63
66
  class IEBGENERProfile(ProgramProfile):
64
67
  program_names = frozenset({"IEBGENER"})
65
68
 
66
- def analyze(self, step) -> ProgramAnalysis:
69
+ def analyze(self, step: Step) -> ProgramAnalysis:
67
70
  return _copy_utility_analysis(step, "IEBGENER")
68
71
 
69
72
 
70
73
  class IEBCOPYProfile(ProgramProfile):
71
74
  program_names = frozenset({"IEBCOPY"})
72
75
 
73
- def analyze(self, step) -> ProgramAnalysis:
76
+ def analyze(self, step: Step) -> ProgramAnalysis:
74
77
  return _copy_utility_analysis(step, "IEBCOPY")
78
+
79
+
80
+ class ICEGENERProfile(ProgramProfile):
81
+ program_names = frozenset({"ICEGENER"})
82
+
83
+ def analyze(self, step: Step) -> ProgramAnalysis:
84
+ return _copy_utility_analysis(step, "ICEGENER")
@@ -0,0 +1,60 @@
1
+ """Profile for ``IEBPTPCH`` — sequential/PDS print & punch utility.
2
+
3
+ Control statements are read from ``SYSIN``: ``PRINT`` or ``PUNCH`` selects
4
+ the mode, ``TITLE`` supplies a report heading, ``MEMBER`` selects a PDS
5
+ member, and ``RECORD`` narrows the field range printed/punched. Input
6
+ comes from ``SYSUT1``; output goes to ``SYSUT2``.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ from typing import TYPE_CHECKING
13
+
14
+ from .base import ProgramAnalysis, ProgramProfile, _sysin_text
15
+
16
+ if TYPE_CHECKING:
17
+ from ..model import Step
18
+
19
+ _RE_MODE = re.compile(r"^\s*(PRINT|PUNCH)\b", re.IGNORECASE | re.MULTILINE)
20
+ _RE_MEMBER = re.compile(
21
+ r"^\s*MEMBER\s+NAME=([A-Za-z0-9$#@]+)", re.IGNORECASE | re.MULTILINE
22
+ )
23
+ _RE_TITLE = re.compile(r"^\s*TITLE\b", re.IGNORECASE | re.MULTILINE)
24
+ _RE_RECORD = re.compile(r"^\s*RECORD\b", re.IGNORECASE | re.MULTILINE)
25
+
26
+
27
+ class IEBPTPCHProfile(ProgramProfile):
28
+ program_names = frozenset({"IEBPTPCH"})
29
+
30
+ def analyze(self, step: Step) -> ProgramAnalysis:
31
+ text = _sysin_text(step) or ""
32
+ notes: list[str] = []
33
+
34
+ mode_m = _RE_MODE.search(text)
35
+ if mode_m:
36
+ notes.append(mode_m.group(1).upper())
37
+ for m in _RE_MEMBER.finditer(text):
38
+ notes.append(f"MEMBER {m.group(1)}")
39
+ if _RE_TITLE.search(text):
40
+ notes.append("TITLE")
41
+ if _RE_RECORD.search(text):
42
+ notes.append("RECORD (field selection)")
43
+
44
+ sysut1 = step.get_dd("SYSUT1")
45
+ if sysut1 and sysut1.dataset_name:
46
+ notes.append(f"reads {sysut1.dataset_name}")
47
+
48
+ summary = (
49
+ f"IEBPTPCH: {len(notes)} control clause(s)"
50
+ if notes
51
+ else "IEBPTPCH with no recognised control statements"
52
+ )
53
+ return ProgramAnalysis(
54
+ program="IEBPTPCH",
55
+ step_name=step.name or "",
56
+ summary=summary,
57
+ dataset_events=[],
58
+ notes=notes,
59
+ raw_control=text or None,
60
+ )
@@ -0,0 +1,76 @@
1
+ """Profile for ``IEBUPDTE`` — sequential/PDS member update utility.
2
+
3
+ Control statements in ``SYSIN`` start with ``./`` (a change-control
4
+ statement, not a JCL statement) and use ``./ ADD``, ``./ CHANGE``,
5
+ ``./ REPL``, and ``./ NUMBER``, typically with a ``NAME=member`` operand.
6
+ The profile reports the base input (SYSUT1, when present), the output
7
+ (SYSUT2), and which member operations the control stream requests.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import re
13
+ from typing import TYPE_CHECKING
14
+
15
+ from ..dataset_lifecycle import DatasetEvent, _parse_disp_tokens
16
+ from .base import ProgramAnalysis, ProgramProfile, _sysin_text
17
+
18
+ if TYPE_CHECKING:
19
+ from ..model import Step
20
+
21
+ _RE_CTL = re.compile(
22
+ r"^\./\s*(ADD|CHANGE|REPL|NUMBER)\b(?:[^\n]*\bNAME=([A-Za-z0-9$#@]+))?",
23
+ re.IGNORECASE | re.MULTILINE,
24
+ )
25
+
26
+
27
+ class IEBUPDTEProfile(ProgramProfile):
28
+ program_names = frozenset({"IEBUPDTE"})
29
+
30
+ def analyze(self, step: Step) -> ProgramAnalysis:
31
+ text = _sysin_text(step) or ""
32
+ notes: list[str] = []
33
+
34
+ for m in _RE_CTL.finditer(text):
35
+ action = m.group(1).upper()
36
+ member = m.group(2)
37
+ notes.append(f"{action} NAME={member}" if member else action)
38
+
39
+ events: list[DatasetEvent] = []
40
+ sysut1 = step.get_dd("SYSUT1")
41
+ sysut2 = step.get_dd("SYSUT2")
42
+ if sysut1 and sysut1.dataset_name:
43
+ status, _, _ = _parse_disp_tokens(sysut1.disposition)
44
+ events.append(
45
+ DatasetEvent(
46
+ step_name=step.name or "",
47
+ dd_name="SYSUT1",
48
+ action="OPEN_SHARED",
49
+ disp_status=status or "SHR",
50
+ line_number=sysut1.line_number,
51
+ )
52
+ )
53
+ if sysut2 and sysut2.dataset_name:
54
+ events.append(
55
+ DatasetEvent(
56
+ step_name=step.name or "",
57
+ dd_name="SYSUT2",
58
+ action="ALLOCATE",
59
+ disp_status="NEW",
60
+ line_number=sysut2.line_number,
61
+ )
62
+ )
63
+
64
+ summary = (
65
+ f"IEBUPDTE: {len(notes)} member operation(s)"
66
+ if notes
67
+ else "IEBUPDTE with no recognised control statements"
68
+ )
69
+ return ProgramAnalysis(
70
+ program="IEBUPDTE",
71
+ step_name=step.name or "",
72
+ summary=summary,
73
+ dataset_events=events,
74
+ notes=notes,
75
+ raw_control=text or None,
76
+ )
@@ -0,0 +1,54 @@
1
+ """Profile for ``IEHLIST`` — VTOC / PDS directory listing utility.
2
+
3
+ Control statements are read from ``SYSIN``: ``LISTVTOC``, ``LISTPDS``, and
4
+ ``LISTDG`` (the latter also usable as ``LISTIDG``). IEHLIST is read-only —
5
+ it never mutates a dataset — so the profile only reports which listing
6
+ commands ran and, where present, the volume/dataset they target.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ from typing import TYPE_CHECKING
13
+
14
+ from .base import ProgramAnalysis, ProgramProfile, _sysin_text
15
+
16
+ if TYPE_CHECKING:
17
+ from ..model import Step
18
+
19
+ _RE_LISTVTOC = re.compile(
20
+ r"^\s*LISTVTOC\b[^\n]*VOL=([A-Za-z0-9,=]+)", re.IGNORECASE | re.MULTILINE
21
+ )
22
+ _RE_LISTPDS = re.compile(
23
+ r"^\s*LISTPDS\b[^\n]*DSNAME=([A-Za-z0-9.$#@]+)", re.IGNORECASE | re.MULTILINE
24
+ )
25
+ _RE_LISTDG = re.compile(r"^\s*LIST(?:I)?DG\b", re.IGNORECASE | re.MULTILINE)
26
+
27
+
28
+ class IEHLISTProfile(ProgramProfile):
29
+ program_names = frozenset({"IEHLIST"})
30
+
31
+ def analyze(self, step: Step) -> ProgramAnalysis:
32
+ text = _sysin_text(step) or ""
33
+ notes: list[str] = []
34
+
35
+ for m in _RE_LISTVTOC.finditer(text):
36
+ notes.append(f"LISTVTOC VOL={m.group(1)}")
37
+ for m in _RE_LISTPDS.finditer(text):
38
+ notes.append(f"LISTPDS DSNAME={m.group(1)}")
39
+ for _ in _RE_LISTDG.finditer(text):
40
+ notes.append("LISTDG")
41
+
42
+ summary = (
43
+ f"IEHLIST: {len(notes)} listing command(s)"
44
+ if notes
45
+ else "IEHLIST with no recognised commands"
46
+ )
47
+ return ProgramAnalysis(
48
+ program="IEHLIST",
49
+ step_name=step.name or "",
50
+ summary=summary,
51
+ dataset_events=[],
52
+ notes=notes,
53
+ raw_control=text or None,
54
+ )
@@ -0,0 +1,108 @@
1
+ """Profile for ``IEHPROGM`` — catalog/VTOC maintenance utility.
2
+
3
+ Control statements are read from ``SYSIN``: ``SCRATCH`` (delete),
4
+ ``RENAME``, ``CATLG``, ``UNCATLG``, ``BLDG``, and ``CONNECT``. Unlike the
5
+ read-only listing in IEHLIST, several of these commands mutate the catalog,
6
+ so — like :mod:`jclparser.program_profiles.idcams` — the profile emits
7
+ :class:`~jclparser.dataset_lifecycle.DatasetEvent` entries for
8
+ ``SCRATCH``/``CATLG``/``UNCATLG``.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from typing import TYPE_CHECKING
15
+
16
+ from ..dataset_lifecycle import DatasetEvent
17
+ from .base import ProgramAnalysis, ProgramProfile, _sysin_text
18
+
19
+ if TYPE_CHECKING:
20
+ from ..model import Step
21
+
22
+ _RE_SCRATCH = re.compile(
23
+ r"^\s*SCRATCH\b[^\n]*DSNAME=([A-Za-z0-9.$#@]+)", re.IGNORECASE | re.MULTILINE
24
+ )
25
+ _RE_RENAME = re.compile(
26
+ r"^\s*RENAME\b[^\n]*DSNAME=([A-Za-z0-9.$#@]+)", re.IGNORECASE | re.MULTILINE
27
+ )
28
+ _RE_CATLG = re.compile(
29
+ r"^\s*CATLG\b[^\n]*DSNAME=([A-Za-z0-9.$#@]+)", re.IGNORECASE | re.MULTILINE
30
+ )
31
+ _RE_UNCATLG = re.compile(
32
+ r"^\s*UNCATLG\b[^\n]*DSNAME=([A-Za-z0-9.$#@]+)", re.IGNORECASE | re.MULTILINE
33
+ )
34
+ _RE_BLDG = re.compile(r"^\s*BLDG\b", re.IGNORECASE | re.MULTILINE)
35
+ _RE_CONNECT = re.compile(r"^\s*CONNECT\b", re.IGNORECASE | re.MULTILINE)
36
+
37
+
38
+ class IEHPROGMProfile(ProgramProfile):
39
+ program_names = frozenset({"IEHPROGM"})
40
+
41
+ def analyze(self, step: Step) -> ProgramAnalysis:
42
+ text = _sysin_text(step) or ""
43
+ events: list[DatasetEvent] = []
44
+ notes: list[str] = []
45
+
46
+ for m in _RE_SCRATCH.finditer(text):
47
+ dsn = m.group(1)
48
+ events.append(
49
+ DatasetEvent(
50
+ step_name=step.name or "",
51
+ dd_name="SYSIN",
52
+ action="DELETE",
53
+ disp_status="OLD",
54
+ disp_normal="DELETE",
55
+ line_number=step.line_number,
56
+ )
57
+ )
58
+ notes.append(f"SCRATCH {dsn}")
59
+
60
+ for m in _RE_RENAME.finditer(text):
61
+ notes.append(f"RENAME {m.group(1)}")
62
+
63
+ for m in _RE_CATLG.finditer(text):
64
+ dsn = m.group(1)
65
+ events.append(
66
+ DatasetEvent(
67
+ step_name=step.name or "",
68
+ dd_name="SYSIN",
69
+ action="ALLOCATE",
70
+ disp_status="NEW",
71
+ disp_normal="CATLG",
72
+ line_number=step.line_number,
73
+ )
74
+ )
75
+ notes.append(f"CATLG {dsn}")
76
+
77
+ for m in _RE_UNCATLG.finditer(text):
78
+ dsn = m.group(1)
79
+ events.append(
80
+ DatasetEvent(
81
+ step_name=step.name or "",
82
+ dd_name="SYSIN",
83
+ action="UNCATALOG",
84
+ disp_status="OLD",
85
+ disp_normal="UNCATLG",
86
+ line_number=step.line_number,
87
+ )
88
+ )
89
+ notes.append(f"UNCATLG {dsn}")
90
+
91
+ if _RE_BLDG.search(text):
92
+ notes.append("BLDG (build generation data group index)")
93
+ if _RE_CONNECT.search(text):
94
+ notes.append("CONNECT (add user catalog connection)")
95
+
96
+ summary = (
97
+ f"IEHPROGM: {len(events)} dataset mutations, {len(notes)} commands"
98
+ if events or notes
99
+ else "IEHPROGM with no recognised commands"
100
+ )
101
+ return ProgramAnalysis(
102
+ program="IEHPROGM",
103
+ step_name=step.name or "",
104
+ summary=summary,
105
+ dataset_events=events,
106
+ notes=notes,
107
+ raw_control=text or None,
108
+ )
@@ -0,0 +1,58 @@
1
+ """Profile for ``IRXJCL`` — batch REXX exec runner.
2
+
3
+ IRXJCL runs a single REXX exec named as the first token of ``PARM``; any
4
+ remaining PARM text is passed as the exec's argument string. The exec
5
+ source itself is read from ``SYSEXEC`` (a concatenation of PDS
6
+ exec libraries) rather than SYSIN.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import TYPE_CHECKING
12
+
13
+ from .base import ProgramAnalysis, ProgramProfile
14
+
15
+ if TYPE_CHECKING:
16
+ from ..model import Step
17
+
18
+
19
+ class IRXJCLProfile(ProgramProfile):
20
+ program_names = frozenset({"IRXJCL"})
21
+
22
+ def analyze(self, step: Step) -> ProgramAnalysis:
23
+ notes: list[str] = []
24
+ raw_parm = step.parameters.get("PARM")
25
+ # PARM=(subparm,...) is parsed into a list of strings; PARM=value or
26
+ # PARM='value text' stays a plain string. Normalise both to a string
27
+ # before splitting so a parenthesised PARM can't crash this profile.
28
+ if isinstance(raw_parm, list):
29
+ parm = " ".join(str(p) for p in raw_parm)
30
+ else:
31
+ parm = str(raw_parm) if raw_parm else ""
32
+ parts = parm.split(None, 1)
33
+ exec_name = parts[0].upper() if parts else ""
34
+ exec_args = parts[1] if len(parts) > 1 else ""
35
+
36
+ if exec_name:
37
+ notes.append(f"exec: {exec_name}")
38
+ if exec_args:
39
+ notes.append(f"args: {exec_args}")
40
+
41
+ sysexec = step.get_dd("SYSEXEC")
42
+ if sysexec and sysexec.dataset_name:
43
+ notes.append(f"SYSEXEC library: {sysexec.dataset_name}")
44
+
45
+ summary = (
46
+ f"IRXJCL: runs REXX exec {exec_name}"
47
+ if exec_name
48
+ else "IRXJCL with no exec name in PARM"
49
+ )
50
+ return ProgramAnalysis(
51
+ program="IRXJCL",
52
+ step_name=step.name or "",
53
+ summary=summary,
54
+ dataset_events=[],
55
+ notes=notes,
56
+ raw_control=parm or None,
57
+ invoked_programs=[exec_name] if exec_name else [],
58
+ )
@@ -10,7 +10,7 @@ from __future__ import annotations
10
10
 
11
11
  from typing import TYPE_CHECKING
12
12
 
13
- from .base import ProgramProfile, ProgramAnalysis
13
+ from .base import ProgramAnalysis, ProgramProfile
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from ..model import Job, Step
@@ -45,7 +45,7 @@ def get_profile(program_name: str | None) -> ProgramProfile | None:
45
45
  return None
46
46
 
47
47
 
48
- def analyze_step(step: "Step") -> ProgramAnalysis | None:
48
+ def analyze_step(step: Step) -> ProgramAnalysis | None:
49
49
  """Run the matching profile (if any) against *step*."""
50
50
  profile = get_profile(step.program)
51
51
  if profile is None:
@@ -53,7 +53,7 @@ def analyze_step(step: "Step") -> ProgramAnalysis | None:
53
53
  return profile.analyze(step)
54
54
 
55
55
 
56
- def analyze_job(job: "Job") -> list[ProgramAnalysis]:
56
+ def analyze_job(job: Job) -> list[ProgramAnalysis]:
57
57
  """Run all applicable profiles across every step in *job*."""
58
58
  from ..model import iter_execution_steps
59
59
 
@@ -71,30 +71,53 @@ def analyze_job(job: "Job") -> list[ProgramAnalysis]:
71
71
 
72
72
 
73
73
  def _register_builtins() -> None:
74
- from .iefbr14 import IEFBR14Profile
75
- from .idcams import IDCAMSProfile
76
- from .iebgener import IEBGENERProfile, IEBCOPYProfile
77
- from .sort import SortProfile
78
- from .ikjeft01 import IKJEFT01Profile
74
+ from .adrdssu import ADRDSSUProfile
75
+ from .binder import BinderProfile
76
+ from .compilers import CompilerProfile
79
77
  from .db2_batch import DB2BatchProfile
80
78
  from .dfsrrc00 import DFSRRC00Profile
79
+ from .ftp import FTPProfile
80
+ from .icetool import ICETOOLProfile
81
+ from .idcams import IDCAMSProfile
82
+ from .iebdg import IEBDGProfile
83
+ from .iebgener import ICEGENERProfile, IEBCOPYProfile, IEBGENERProfile
84
+ from .iebptpch import IEBPTPCHProfile
85
+ from .iebupdte import IEBUPDTEProfile
86
+ from .iefbr14 import IEFBR14Profile
87
+ from .iehlist import IEHLISTProfile
88
+ from .iehprogm import IEHPROGMProfile
89
+ from .ikjeft01 import IKJEFT01Profile
90
+ from .irxjcl import IRXJCLProfile
91
+ from .sort import SortProfile
81
92
 
82
93
  register_profile(IEFBR14Profile())
83
94
  register_profile(IDCAMSProfile())
84
95
  register_profile(IEBGENERProfile())
85
96
  register_profile(IEBCOPYProfile())
97
+ register_profile(ICEGENERProfile())
86
98
  register_profile(SortProfile())
87
99
  register_profile(IKJEFT01Profile())
88
100
  register_profile(DB2BatchProfile())
89
101
  register_profile(DFSRRC00Profile())
102
+ register_profile(ICETOOLProfile())
103
+ register_profile(ADRDSSUProfile())
104
+ register_profile(IEHLISTProfile())
105
+ register_profile(IEHPROGMProfile())
106
+ register_profile(IEBUPDTEProfile())
107
+ register_profile(IEBPTPCHProfile())
108
+ register_profile(IEBDGProfile())
109
+ register_profile(BinderProfile())
110
+ register_profile(CompilerProfile())
111
+ register_profile(IRXJCLProfile())
112
+ register_profile(FTPProfile())
90
113
 
91
114
 
92
115
  _register_builtins()
93
116
 
94
117
 
95
118
  __all__ = [
96
- "register_profile",
97
- "get_profile",
98
- "analyze_step",
99
119
  "analyze_job",
120
+ "analyze_step",
121
+ "get_profile",
122
+ "register_profile",
100
123
  ]
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "jcl-parser"
7
- version = "1.2.1"
7
+ version = "1.3.0"
8
8
  description = "Parse IBM JCL (Job Control Language) scripts into Python objects"
9
9
  authors = [
10
10
  {name = "JCL Parser Team", email = "team@jclparser.dev"}
File without changes
File without changes
File without changes
File without changes
File without changes