jcl-parser 2.2.1__tar.gz → 2.2.3__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 (80) hide show
  1. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/PKG-INFO +1 -1
  2. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jcl_parser.egg-info/PKG-INFO +1 -1
  3. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/__init__.py +1 -1
  4. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/dataset_lifecycle.py +25 -4
  5. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/lint/rules.py +15 -10
  6. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/model.py +23 -0
  7. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/validators.py +14 -5
  8. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/pyproject.toml +1 -1
  9. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/LICENSE +0 -0
  10. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/README.md +0 -0
  11. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jcl_parser.egg-info/SOURCES.txt +0 -0
  12. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jcl_parser.egg-info/dependency_links.txt +0 -0
  13. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jcl_parser.egg-info/entry_points.txt +0 -0
  14. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jcl_parser.egg-info/requires.txt +0 -0
  15. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jcl_parser.egg-info/top_level.txt +0 -0
  16. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/__main__.py +0 -0
  17. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/_include_origins.py +0 -0
  18. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/_job_builder.py +0 -0
  19. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/_model_factories.py +0 -0
  20. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/_post_parse.py +0 -0
  21. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/_source_scanner.py +0 -0
  22. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/_statement_parser.py +0 -0
  23. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/builtin_procs.py +0 -0
  24. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/checks.py +0 -0
  25. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/cli.py +0 -0
  26. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/condition_parser.py +0 -0
  27. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/continuation_handler.py +0 -0
  28. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/errors.py +0 -0
  29. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/explosion.py +0 -0
  30. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/include_loader.py +0 -0
  31. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/jcl_file_type.py +0 -0
  32. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/jes_statements.py +0 -0
  33. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/lineage.py +0 -0
  34. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/lint/__init__.py +0 -0
  35. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/lint/base.py +0 -0
  36. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/lint/config.py +0 -0
  37. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/lint/runner.py +0 -0
  38. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/lint/suppress.py +0 -0
  39. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/metrics.py +0 -0
  40. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/parameter_ast.py +0 -0
  41. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/parameter_validators.py +0 -0
  42. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/parser.py +0 -0
  43. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/plugins/__init__.py +0 -0
  44. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/plugins/base.py +0 -0
  45. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/proc_loader.py +0 -0
  46. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/profiling.py +0 -0
  47. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/__init__.py +0 -0
  48. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/_control.py +0 -0
  49. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/_invoked.py +0 -0
  50. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/adrdssu.py +0 -0
  51. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/base.py +0 -0
  52. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/binder.py +0 -0
  53. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/bpxbatch.py +0 -0
  54. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/compilers.py +0 -0
  55. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/db2_batch.py +0 -0
  56. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/dfsrrc00.py +0 -0
  57. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/dsnutilb.py +0 -0
  58. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/ftp.py +0 -0
  59. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/icetool.py +0 -0
  60. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/idcams.py +0 -0
  61. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/iebcompr.py +0 -0
  62. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/iebdg.py +0 -0
  63. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/iebgener.py +0 -0
  64. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/iebptpch.py +0 -0
  65. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/iebupdte.py +0 -0
  66. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/iefbr14.py +0 -0
  67. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/iehinitt.py +0 -0
  68. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/iehlist.py +0 -0
  69. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/iehprogm.py +0 -0
  70. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/ikjeft01.py +0 -0
  71. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/irxjcl.py +0 -0
  72. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/registry.py +0 -0
  73. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/program_profiles/sort.py +0 -0
  74. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/py.typed +0 -0
  75. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/rc_analysis.py +0 -0
  76. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/serialization.py +0 -0
  77. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/sql_detector.py +0 -0
  78. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/system_symbols.py +0 -0
  79. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/jclparser/utils.py +0 -0
  80. {jcl_parser-2.2.1 → jcl_parser-2.2.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jcl-parser
3
- Version: 2.2.1
3
+ Version: 2.2.3
4
4
  Summary: Parse IBM JCL (Job Control Language) scripts into Python objects
5
5
  Author-email: Samuel Dion <sam94dion@gmail.com>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jcl-parser
3
- Version: 2.2.1
3
+ Version: 2.2.3
4
4
  Summary: Parse IBM JCL (Job Control Language) scripts into Python objects
5
5
  Author-email: Samuel Dion <sam94dion@gmail.com>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -20,7 +20,7 @@ application, for example::
20
20
  import logging as _logging
21
21
 
22
22
  # Package metadata
23
- __version__ = "2.2.1" # x-release-please-version
23
+ __version__ = "2.2.3" # x-release-please-version
24
24
  __author__ = "JCL Parser Team"
25
25
  __description__ = "Parse IBM JCL scripts into Python objects"
26
26
 
@@ -63,6 +63,11 @@ class DatasetEvent:
63
63
  disp_abnormal: Abnormal termination disposition (same set as normal),
64
64
  or ``""`` if omitted.
65
65
  line_number: Source line number of the DD statement.
66
+ concatenation_member: ``0`` for a named DD; for an unnamed
67
+ concatenation member, its position in the concatenation (the
68
+ named DD is member 1, so the first unnamed card is member 2).
69
+ ``dd_name`` is then the name of the DD the concatenation
70
+ belongs to.
66
71
  """
67
72
 
68
73
  step_name: str
@@ -72,6 +77,14 @@ class DatasetEvent:
72
77
  disp_normal: str = ""
73
78
  disp_abnormal: str = ""
74
79
  line_number: int = 0
80
+ concatenation_member: int = 0
81
+
82
+ @property
83
+ def dd_label(self) -> str:
84
+ """The DD as messages name it: ``IN``, or ``IN, concatenation member 2``."""
85
+ if self.concatenation_member:
86
+ return f"{self.dd_name}, concatenation member {self.concatenation_member}"
87
+ return self.dd_name
75
88
 
76
89
 
77
90
  # ---------------------------------------------------------------------------
@@ -206,7 +219,14 @@ def _step_events(
206
219
  """Record *step*'s events into *lifecycle*; ``None`` when it has none."""
207
220
  step_name = step.name or ""
208
221
  step_events: list[_Event] = []
222
+ # An unnamed DD card continues the concatenation of the named DD before
223
+ # it, so it is reported under that DD's name (as ``Step.steplib`` does).
224
+ owner, member = "", 0
209
225
  for dd in _dataset_dds(step):
226
+ if dd.name:
227
+ owner, member = dd.name, 0
228
+ elif owner:
229
+ member = member + 1 if member else 2
210
230
  if not dd.dataset_name:
211
231
  continue
212
232
  # A backward reference names the dataset of the DD it points at;
@@ -222,12 +242,13 @@ def _step_events(
222
242
 
223
243
  event = DatasetEvent(
224
244
  step_name=step.name or "(unnamed)",
225
- dd_name=dd.name or "",
245
+ dd_name=dd.name or owner,
226
246
  action=_action_from_disp(status),
227
247
  disp_status=status,
228
248
  disp_normal=normal,
229
249
  disp_abnormal=abnormal,
230
250
  line_number=dd.line_number,
251
+ concatenation_member=0 if dd.name else member,
231
252
  )
232
253
  events = lifecycle.setdefault(dsn_key, [])
233
254
  step_events.append((dsn_key, len(events), event))
@@ -460,7 +481,7 @@ class _LifecycleChecker:
460
481
  dsn,
461
482
  pos,
462
483
  f"Dataset '{dsn}' allocated with DISP=NEW,CATLG in step "
463
- f"'{evt.step_name}' (DD {evt.dd_name}) but was already "
484
+ f"'{evt.step_name}' (DD {evt.dd_label}) but was already "
464
485
  f"cataloged in step '{cataloged_by.step_name}' without "
465
486
  f"an intervening DELETE or UNCATLG",
466
487
  evt,
@@ -492,7 +513,7 @@ class _LifecycleChecker:
492
513
  dsn,
493
514
  pos,
494
515
  f"Dataset '{dsn}' opened in step '{evt.step_name}' "
495
- f"(DD {evt.dd_name}) after being uncataloged in step "
516
+ f"(DD {evt.dd_label}) after being uncataloged in step "
496
517
  f"'{uncataloged_by.step_name}'",
497
518
  evt,
498
519
  )
@@ -506,7 +527,7 @@ class _LifecycleChecker:
506
527
  dsn,
507
528
  pos,
508
529
  f"Dataset '{dsn}' opened {disp_label} in step "
509
- f"'{evt.step_name}' (DD {evt.dd_name}) before it "
530
+ f"'{evt.step_name}' (DD {evt.dd_label}) before it "
510
531
  f"is allocated in a later step — verify the dataset "
511
532
  f"exists in the catalog",
512
533
  evt,
@@ -12,7 +12,7 @@ from collections.abc import Iterable
12
12
  from typing import TYPE_CHECKING
13
13
 
14
14
  from ..errors import ErrorSeverity, ValidationError
15
- from ..model import ConditionalBlock, _dataset_dds, iter_execution_steps
15
+ from ..model import ConditionalBlock, _dataset_dds, dd_label, iter_execution_steps
16
16
  from .base import LintRule
17
17
 
18
18
  if TYPE_CHECKING:
@@ -29,7 +29,8 @@ class HardcodedHLQRule(LintRule):
29
29
  defer = config.should_run(LiteralSetValueHlqRule.code)
30
30
  set_values = _qualifier_symbols(job)
31
31
  for step in iter_execution_steps(job.steps):
32
- for dd in _dataset_dds(step):
32
+ dds = _dataset_dds(step)
33
+ for dd in dds:
33
34
  dsn = dd.dataset_name
34
35
  if not dsn or dsn.startswith("&&"):
35
36
  continue
@@ -49,7 +50,7 @@ class HardcodedHLQRule(LintRule):
49
50
  if defer and dsn.split(".", 1)[0].upper() in set_values:
50
51
  continue
51
52
  yield self._err(
52
- f"DD '{dd.name}' in step '{step.name}' uses literal HLQ "
53
+ f"DD '{dd_label(dds, dd)}' in step '{step.name}' uses literal HLQ "
53
54
  f"in dataset '{dsn}' — consider &HLQ",
54
55
  line_number=dd.line_number,
55
56
  config=config,
@@ -63,7 +64,8 @@ class LiteralSysdaUnitRule(LintRule):
63
64
 
64
65
  def check(self, job: Job, config: LintConfig) -> Iterable[ValidationError]:
65
66
  for step in iter_execution_steps(job.steps):
66
- for dd in _dataset_dds(step):
67
+ dds = _dataset_dds(step)
68
+ for dd in dds:
67
69
  unit = dd.parameters.get("UNIT")
68
70
  if not unit:
69
71
  continue
@@ -72,7 +74,7 @@ class LiteralSysdaUnitRule(LintRule):
72
74
  unit_val = unit if isinstance(unit, str) else str(unit[0])
73
75
  if unit_val.strip("()").split(",")[0].strip().upper() == "SYSDA":
74
76
  yield self._err(
75
- f"DD '{dd.name}' in step '{step.name}' uses UNIT=SYSDA",
77
+ f"DD '{dd_label(dds, dd)}' in step '{step.name}' uses UNIT=SYSDA",
76
78
  line_number=dd.line_number,
77
79
  config=config,
78
80
  )
@@ -97,7 +99,8 @@ class CriticalDatasetDeleteRule(LintRule):
97
99
  if not patterns:
98
100
  return
99
101
  for step in iter_execution_steps(job.steps):
100
- for dd in _dataset_dds(step):
102
+ dds = _dataset_dds(step)
103
+ for dd in dds:
101
104
  if not dd.dataset_name:
102
105
  continue
103
106
  upper = dd.dataset_name.upper()
@@ -156,7 +159,8 @@ class NewDatasetWithoutRlseRule(LintRule):
156
159
 
157
160
  def check(self, job: Job, config: LintConfig) -> Iterable[ValidationError]:
158
161
  for step in iter_execution_steps(job.steps):
159
- for dd in _dataset_dds(step):
162
+ dds = _dataset_dds(step)
163
+ for dd in dds:
160
164
  dsn = dd.dataset_name
161
165
  # A temporary dataset is deleted at the end of the job anyway.
162
166
  if not dsn or dsn.startswith("&&"):
@@ -169,7 +173,7 @@ class NewDatasetWithoutRlseRule(LintRule):
169
173
  if space is None or space.rlse:
170
174
  continue
171
175
  yield self._err(
172
- f"DD '{dd.name}' in step '{step.name}' allocates new dataset "
176
+ f"DD '{dd_label(dds, dd)}' in step '{step.name}' allocates new dataset "
173
177
  f"'{dsn}' with SPACE but no RLSE",
174
178
  line_number=dd.line_number,
175
179
  config=config,
@@ -259,7 +263,8 @@ class LiteralSetValueHlqRule(LintRule):
259
263
  if not symbols:
260
264
  return
261
265
  for step in iter_execution_steps(job.steps):
262
- for dd in _dataset_dds(step):
266
+ dds = _dataset_dds(step)
267
+ for dd in dds:
263
268
  dsn = dd.dataset_name
264
269
  # coded_dataset_name is set only when the name used symbols.
265
270
  if not dsn or dd.coded_dataset_name is not None or "&" in dsn:
@@ -270,7 +275,7 @@ class LiteralSetValueHlqRule(LintRule):
270
275
  if symbol is None:
271
276
  continue
272
277
  yield self._err(
273
- f"DD '{dd.name}' in step '{step.name}' codes HLQ "
278
+ f"DD '{dd_label(dds, dd)}' in step '{step.name}' codes HLQ "
274
279
  f"'{dsn.split('.', 1)[0]}' in '{dsn}' literally; "
275
280
  f"SET symbol &{symbol} has that value",
276
281
  line_number=dd.line_number,
@@ -1082,6 +1082,29 @@ def iter_execution_steps(steps: list[Step | ConditionalBlock]) -> Iterator[Step]
1082
1082
  yield from iter_execution_steps(s.else_steps)
1083
1083
 
1084
1084
 
1085
+ def dd_label(dds: list[DDStatement], dd: DDStatement) -> str:
1086
+ """*dd* as messages name it: ``IN``, or ``IN, concatenation member 2``
1087
+ for an unnamed concatenation member coded after ``//IN DD`` in *dds*
1088
+ (the named DD is member 1). The same label ``DatasetEvent.dd_label``
1089
+ gives. An unnamed DD with no named DD before it is ``(unnamed)``.
1090
+ """
1091
+ if dd.name:
1092
+ return dd.name
1093
+ owner, member = "", 0
1094
+ for other in dds:
1095
+ if other.name:
1096
+ owner, member = other.name, 0
1097
+ elif owner:
1098
+ member = member + 1 if member else 2
1099
+ if other is dd:
1100
+ break
1101
+ else:
1102
+ return "(unnamed)"
1103
+ if not owner:
1104
+ return "(unnamed)"
1105
+ return f"{owner}, concatenation member {member}"
1106
+
1107
+
1085
1108
  def _dataset_dds(step: Step) -> list[DDStatement]:
1086
1109
  """The DDs of *step* that can name a dataset, in source order.
1087
1110
 
@@ -15,6 +15,7 @@ from .model import (
15
15
  Job,
16
16
  Step,
17
17
  _dataset_dds,
18
+ dd_label,
18
19
  iter_execution_steps,
19
20
  )
20
21
  from .parameter_validators import DatasetNameValidator, DISPValidator
@@ -408,7 +409,7 @@ def validate_step_structure(step: Step) -> list[ValidationError]:
408
409
 
409
410
  # Validate each DD statement
410
411
  for dd in step.dd_statements:
411
- errors.extend(validate_dd_statement(dd))
412
+ errors.extend(validate_dd_statement(dd, label=dd_label(step.dd_statements, dd)))
412
413
 
413
414
  return errors
414
415
 
@@ -430,17 +431,25 @@ _TEMP_ALLOCATION_KEYS = (
430
431
  )
431
432
 
432
433
 
433
- def validate_dd_statement(dd: DDStatement) -> list[ValidationError]:
434
+ def validate_dd_statement(
435
+ dd: DDStatement, label: str | None = None
436
+ ) -> list[ValidationError]:
434
437
  """
435
438
  Validate DD statement structure and parameters.
436
439
 
437
440
  Args:
438
441
  dd: DDStatement object to validate
442
+ label: How messages name the DD (default ``dd.name``); the step
443
+ check passes ``model.dd_label`` so an unnamed concatenation
444
+ member reads ``IN, concatenation member 2``.
439
445
 
440
446
  Returns:
441
447
  List of validation errors
442
448
  """
443
449
  errors = []
450
+ # How messages name the DD: *label* when the caller knows the DD's place
451
+ # in its step (``IN, concatenation member 2``, see ``model.dd_label``).
452
+ name = label if label is not None else dd.name
444
453
 
445
454
  # DD name validation — empty names are valid for DD concatenation statements
446
455
  if dd.name and not validate_jcl_identifier(dd.name):
@@ -470,7 +479,7 @@ def validate_dd_statement(dd: DDStatement) -> list[ValidationError]:
470
479
  if dd.disposition and not validate_disposition(dd.disposition):
471
480
  errors.append(
472
481
  ValidationError(
473
- f"Invalid DISP parameter '{dd.disposition}' on DD {dd.name}",
482
+ f"Invalid DISP parameter '{dd.disposition}' on DD {name}",
474
483
  ErrorType.VALIDATION_ERROR,
475
484
  line_number=dd.line_number,
476
485
  )
@@ -500,7 +509,7 @@ def validate_dd_statement(dd: DDStatement) -> list[ValidationError]:
500
509
  if conflict_count > 1:
501
510
  errors.append(
502
511
  ValidationError(
503
- f"DD {dd.name} has conflicting data source specifications",
512
+ f"DD {name} has conflicting data source specifications",
504
513
  ErrorType.VALIDATION_ERROR,
505
514
  line_number=dd.line_number,
506
515
  )
@@ -508,7 +517,7 @@ def validate_dd_statement(dd: DDStatement) -> list[ValidationError]:
508
517
  elif source_count == 0:
509
518
  errors.append(
510
519
  ValidationError(
511
- f"DD {dd.name} must specify a data source (DSN, SYSOUT, DUMMY, or inline data)",
520
+ f"DD {name} must specify a data source (DSN, SYSOUT, DUMMY, or inline data)",
512
521
  ErrorType.VALIDATION_ERROR,
513
522
  line_number=dd.line_number,
514
523
  )
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "jcl-parser"
7
- version = "2.2.1"
7
+ version = "2.2.3"
8
8
  description = "Parse IBM JCL (Job Control Language) scripts into Python objects"
9
9
  authors = [
10
10
  {name = "Samuel Dion", email = "sam94dion@gmail.com"}
File without changes
File without changes
File without changes
File without changes