jcl-parser 2.2.2__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.2 → jcl_parser-2.2.3}/PKG-INFO +1 -1
  2. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jcl_parser.egg-info/PKG-INFO +1 -1
  3. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/__init__.py +1 -1
  4. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/lint/rules.py +15 -10
  5. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/model.py +23 -0
  6. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/validators.py +14 -5
  7. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/pyproject.toml +1 -1
  8. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/LICENSE +0 -0
  9. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/README.md +0 -0
  10. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jcl_parser.egg-info/SOURCES.txt +0 -0
  11. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jcl_parser.egg-info/dependency_links.txt +0 -0
  12. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jcl_parser.egg-info/entry_points.txt +0 -0
  13. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jcl_parser.egg-info/requires.txt +0 -0
  14. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jcl_parser.egg-info/top_level.txt +0 -0
  15. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/__main__.py +0 -0
  16. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/_include_origins.py +0 -0
  17. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/_job_builder.py +0 -0
  18. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/_model_factories.py +0 -0
  19. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/_post_parse.py +0 -0
  20. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/_source_scanner.py +0 -0
  21. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/_statement_parser.py +0 -0
  22. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/builtin_procs.py +0 -0
  23. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/checks.py +0 -0
  24. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/cli.py +0 -0
  25. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/condition_parser.py +0 -0
  26. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/continuation_handler.py +0 -0
  27. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/dataset_lifecycle.py +0 -0
  28. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/errors.py +0 -0
  29. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/explosion.py +0 -0
  30. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/include_loader.py +0 -0
  31. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/jcl_file_type.py +0 -0
  32. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/jes_statements.py +0 -0
  33. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/lineage.py +0 -0
  34. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/lint/__init__.py +0 -0
  35. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/lint/base.py +0 -0
  36. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/lint/config.py +0 -0
  37. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/lint/runner.py +0 -0
  38. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/lint/suppress.py +0 -0
  39. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/metrics.py +0 -0
  40. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/parameter_ast.py +0 -0
  41. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/parameter_validators.py +0 -0
  42. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/parser.py +0 -0
  43. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/plugins/__init__.py +0 -0
  44. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/plugins/base.py +0 -0
  45. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/proc_loader.py +0 -0
  46. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/profiling.py +0 -0
  47. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/__init__.py +0 -0
  48. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/_control.py +0 -0
  49. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/_invoked.py +0 -0
  50. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/adrdssu.py +0 -0
  51. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/base.py +0 -0
  52. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/binder.py +0 -0
  53. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/bpxbatch.py +0 -0
  54. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/compilers.py +0 -0
  55. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/db2_batch.py +0 -0
  56. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/dfsrrc00.py +0 -0
  57. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/dsnutilb.py +0 -0
  58. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/ftp.py +0 -0
  59. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/icetool.py +0 -0
  60. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/idcams.py +0 -0
  61. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/iebcompr.py +0 -0
  62. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/iebdg.py +0 -0
  63. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/iebgener.py +0 -0
  64. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/iebptpch.py +0 -0
  65. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/iebupdte.py +0 -0
  66. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/iefbr14.py +0 -0
  67. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/iehinitt.py +0 -0
  68. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/iehlist.py +0 -0
  69. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/iehprogm.py +0 -0
  70. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/ikjeft01.py +0 -0
  71. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/irxjcl.py +0 -0
  72. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/registry.py +0 -0
  73. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/program_profiles/sort.py +0 -0
  74. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/py.typed +0 -0
  75. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/rc_analysis.py +0 -0
  76. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/serialization.py +0 -0
  77. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/sql_detector.py +0 -0
  78. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/system_symbols.py +0 -0
  79. {jcl_parser-2.2.2 → jcl_parser-2.2.3}/jclparser/utils.py +0 -0
  80. {jcl_parser-2.2.2 → 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.2
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.2
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.2" # 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
 
@@ -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.2"
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