jcl-parser 2.0.2__tar.gz → 2.0.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 (75) hide show
  1. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/PKG-INFO +2 -2
  2. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jcl_parser.egg-info/PKG-INFO +2 -2
  3. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/__init__.py +1 -1
  4. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/_job_builder.py +15 -0
  5. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/explosion.py +69 -19
  6. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/model.py +5 -4
  7. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/sql_detector.py +54 -3
  8. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/pyproject.toml +2 -2
  9. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/LICENSE +0 -0
  10. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/README.md +0 -0
  11. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jcl_parser.egg-info/SOURCES.txt +0 -0
  12. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jcl_parser.egg-info/dependency_links.txt +0 -0
  13. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jcl_parser.egg-info/entry_points.txt +0 -0
  14. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jcl_parser.egg-info/requires.txt +0 -0
  15. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jcl_parser.egg-info/top_level.txt +0 -0
  16. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/__main__.py +0 -0
  17. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/_model_factories.py +0 -0
  18. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/_post_parse.py +0 -0
  19. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/_source_scanner.py +0 -0
  20. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/_statement_parser.py +0 -0
  21. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/builtin_procs.py +0 -0
  22. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/cli.py +0 -0
  23. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/condition_parser.py +0 -0
  24. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/continuation_handler.py +0 -0
  25. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/dataset_lifecycle.py +0 -0
  26. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/errors.py +0 -0
  27. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/include_loader.py +0 -0
  28. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/jcl_file_type.py +0 -0
  29. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/jes_statements.py +0 -0
  30. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/lint/__init__.py +0 -0
  31. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/lint/base.py +0 -0
  32. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/lint/config.py +0 -0
  33. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/lint/rules.py +0 -0
  34. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/lint/runner.py +0 -0
  35. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/metrics.py +0 -0
  36. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/parameter_ast.py +0 -0
  37. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/parameter_validators.py +0 -0
  38. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/parser.py +0 -0
  39. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/plugins/__init__.py +0 -0
  40. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/plugins/base.py +0 -0
  41. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/proc_loader.py +0 -0
  42. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/profiling.py +0 -0
  43. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/__init__.py +0 -0
  44. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/_control.py +0 -0
  45. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/_invoked.py +0 -0
  46. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/adrdssu.py +0 -0
  47. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/base.py +0 -0
  48. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/binder.py +0 -0
  49. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/bpxbatch.py +0 -0
  50. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/compilers.py +0 -0
  51. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/db2_batch.py +0 -0
  52. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/dfsrrc00.py +0 -0
  53. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/dsnutilb.py +0 -0
  54. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/ftp.py +0 -0
  55. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/icetool.py +0 -0
  56. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/idcams.py +0 -0
  57. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iebcompr.py +0 -0
  58. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iebdg.py +0 -0
  59. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iebgener.py +0 -0
  60. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iebptpch.py +0 -0
  61. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iebupdte.py +0 -0
  62. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iefbr14.py +0 -0
  63. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iehinitt.py +0 -0
  64. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iehlist.py +0 -0
  65. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/iehprogm.py +0 -0
  66. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/ikjeft01.py +0 -0
  67. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/irxjcl.py +0 -0
  68. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/registry.py +0 -0
  69. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/program_profiles/sort.py +0 -0
  70. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/py.typed +0 -0
  71. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/rc_analysis.py +0 -0
  72. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/system_symbols.py +0 -0
  73. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/utils.py +0 -0
  74. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/jclparser/validators.py +0 -0
  75. {jcl_parser-2.0.2 → jcl_parser-2.0.3}/setup.cfg +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jcl-parser
3
- Version: 2.0.2
3
+ Version: 2.0.3
4
4
  Summary: Parse IBM JCL (Job Control Language) scripts into Python objects
5
- Author-email: JCL Parser Team <team@jclparser.dev>
5
+ Author-email: Samuel Dion <sam94dion@gmail.com>
6
6
  License: 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
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jcl-parser
3
- Version: 2.0.2
3
+ Version: 2.0.3
4
4
  Summary: Parse IBM JCL (Job Control Language) scripts into Python objects
5
- Author-email: JCL Parser Team <team@jclparser.dev>
5
+ Author-email: Samuel Dion <sam94dion@gmail.com>
6
6
  License: 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
@@ -20,7 +20,7 @@ application, for example::
20
20
  import logging as _logging
21
21
 
22
22
  # Package metadata
23
- __version__ = "2.0.2" # x-release-please-version
23
+ __version__ = "2.0.3" # x-release-please-version
24
24
  __author__ = "JCL Parser Team"
25
25
  __description__ = "Parse IBM JCL scripts into Python objects"
26
26
 
@@ -174,6 +174,10 @@ class JobBuilder:
174
174
  #: only if some SET symbol is redefined after it was first set.
175
175
  self.job_level_steps: list[Step] = []
176
176
  self.set_redefined = False
177
+ #: Steps of in-stream / cataloged procedures with the procedure they
178
+ #: belong to; finish() prunes their ``set_symbols`` snapshot the same
179
+ #: way (against the procedure's own final SET table).
180
+ self.procedure_steps: list[tuple[Procedure, Step]] = []
177
181
  self.pending_exported: set[str] = set()
178
182
  self.pending_jcllib: list[str] = []
179
183
  self.pending_includes: list[str] = []
@@ -315,6 +319,11 @@ class JobBuilder:
315
319
  **self.job.set_statements,
316
320
  }
317
321
  self.job_level_steps.append(self.current_step)
322
+ elif self.current_procedure is not None:
323
+ # A SET inside the procedure is in effect from where it is coded,
324
+ # so this step sees the procedure's SET values as they stand now.
325
+ self.current_step.set_symbols = dict(self.current_procedure.set_statements)
326
+ self.procedure_steps.append((self.current_procedure, self.current_step))
318
327
  self.in_joblib_concat = False # JOBLIB concatenation ends at first EXEC
319
328
  self.concat_override_key = None
320
329
 
@@ -820,6 +829,12 @@ class JobBuilder:
820
829
  for k, v in (step.set_symbols or {}).items()
821
830
  ):
822
831
  step.set_symbols = None
832
+ for procedure, step in self.procedure_steps:
833
+ if all(
834
+ procedure.set_statements.get(k) == v
835
+ for k, v in (step.set_symbols or {}).items()
836
+ ):
837
+ step.set_symbols = None
823
838
 
824
839
  def _warn_orphaned_dds(self, job: Job) -> None:
825
840
  # orphaned_dds is only ever non-empty here when the whole job had no
@@ -569,6 +569,8 @@ def expand_proc_invocation(
569
569
  else:
570
570
  # Clone the step
571
571
  new_step = _clone_step_for_explosion(proc_step)
572
+ step_scope = _proc_step_scope(expansion_scope, proc_step)
573
+ new_step.set_symbols = None
572
574
 
573
575
  # Generate qualified step name
574
576
  if new_step.name:
@@ -584,18 +586,28 @@ def expand_proc_invocation(
584
586
  if new_step.program:
585
587
  new_step.program = substitute_symbols(
586
588
  new_step.program,
587
- expansion_scope,
589
+ step_scope,
588
590
  strict=strict,
589
591
  warnings=warnings,
590
592
  )
591
593
 
592
- # Substitute symbols in step parameters
593
- new_step.parameters = substitute_parameters_dict(
594
- new_step.parameters,
595
- expansion_scope,
596
- strict=strict,
597
- warnings=warnings,
598
- )
594
+ # Substitute symbols in step parameters. A nested
595
+ # ``EXEC procname`` keeps "&&" in its symbolic values, as a
596
+ # top-level one does (TMP='&&WORK' is a temporary dataset).
597
+ if new_step.is_procedure:
598
+ new_step.parameters = _substitute_proc_call_parameters(
599
+ new_step.parameters,
600
+ step_scope,
601
+ strict=strict,
602
+ warnings=warnings,
603
+ )
604
+ else:
605
+ new_step.parameters = substitute_parameters_dict(
606
+ new_step.parameters,
607
+ step_scope,
608
+ strict=strict,
609
+ warnings=warnings,
610
+ )
599
611
  if "COND" in new_step.parameters:
600
612
  new_step.parameters["COND"] = _qualify_cond_parameter(
601
613
  new_step.parameters["COND"], proc_steps, prefix
@@ -605,7 +617,7 @@ def expand_proc_invocation(
605
617
  # for a nested ``EXEC procname``, its PROCSTEP.DDNAME overrides
606
618
  for dd in _step_dds_and_overrides(new_step):
607
619
  substitute_dd_statement(
608
- dd, expansion_scope, strict=strict, warnings=warnings
620
+ dd, step_scope, strict=strict, warnings=warnings
609
621
  )
610
622
 
611
623
  result.append(new_step)
@@ -616,6 +628,24 @@ def expand_proc_invocation(
616
628
  return expanded_steps
617
629
 
618
630
 
631
+ def _proc_step_scope(expansion_scope: SymbolScope, proc_step: Step) -> SymbolScope:
632
+ """Return *expansion_scope* with the PROC SETs in effect at *proc_step*.
633
+
634
+ A SET inside a procedure is in effect from where it is coded: a step read
635
+ before a later SET redefined a symbol keeps the earlier value
636
+ (``Step.set_symbols``). Symbols it has no snapshot for still resolve
637
+ against the procedure's final SET table.
638
+ """
639
+ if proc_step.set_symbols is None:
640
+ return expansion_scope
641
+ return SymbolScope(
642
+ set_symbols={**expansion_scope.set_symbols, **proc_step.set_symbols},
643
+ proc_params=expansion_scope.proc_params,
644
+ exec_overrides=expansion_scope.exec_overrides,
645
+ system_provider=expansion_scope.system_provider,
646
+ )
647
+
648
+
619
649
  _WHOLE_SYMBOL_RE = re.compile(r"&([A-Z0-9@#$]+)\.?")
620
650
 
621
651
 
@@ -1014,6 +1044,33 @@ _EXEC_OVERRIDE_KEYWORDS = frozenset(
1014
1044
  _PARM_KEYWORDS = frozenset({"PARM", "PARMDD"})
1015
1045
 
1016
1046
 
1047
+ def _substitute_proc_call_parameters(
1048
+ parameters: dict[str, Any],
1049
+ scope: SymbolScope,
1050
+ strict: bool = False,
1051
+ allow_undefined: bool = False,
1052
+ warnings: list[str] | None = None,
1053
+ ) -> dict[str, Any]:
1054
+ """Substitute symbols in the parameters of an ``EXEC procname`` statement.
1055
+
1056
+ EXEC keyword overrides (``PARM.PS1='A&&B'``) collapse "&&" to "&" like any
1057
+ other step parameter; a symbolic value keeps it, because it may carry a
1058
+ temporary dataset name (``TMP='&&WORK'``) into the procedure's DDs.
1059
+ """
1060
+ return {
1061
+ key: substitute_parameters_dict(
1062
+ {key: value},
1063
+ scope,
1064
+ strict=strict,
1065
+ allow_undefined=allow_undefined,
1066
+ warnings=warnings,
1067
+ collapse_double_amp=key.upper().partition(".")[0]
1068
+ in _EXEC_OVERRIDE_KEYWORDS,
1069
+ )[key]
1070
+ for key, value in parameters.items()
1071
+ }
1072
+
1073
+
1017
1074
  def _set_exec_keyword(step: Step, keyword: str, value: Any) -> None:
1018
1075
  """Override (or, for an empty value, nullify) *keyword* on *step*."""
1019
1076
  if keyword in _PARM_KEYWORDS:
@@ -1387,16 +1444,9 @@ def explode_job( # noqa: C901 -- complexity baseline, split when touched
1387
1444
  # So are its EXEC keyword overrides (PARM.PS1='&SYSUID') and
1388
1445
  # symbolic values (HLQ=&SYSUID). A symbolic value keeps "&&":
1389
1446
  # it may carry a temporary dataset name (TMP='&&WORK').
1390
- step.parameters = {
1391
- key: substitute_parameters_dict(
1392
- {key: value},
1393
- step_scope,
1394
- allow_undefined=True,
1395
- collapse_double_amp=key.upper().partition(".")[0]
1396
- in _EXEC_OVERRIDE_KEYWORDS,
1397
- )[key]
1398
- for key, value in step.parameters.items()
1399
- }
1447
+ step.parameters = _substitute_proc_call_parameters(
1448
+ step.parameters, step_scope, allow_undefined=True
1449
+ )
1400
1450
  # Expand procedure
1401
1451
  expanded = _explode_step(
1402
1452
  step=step,
@@ -542,10 +542,11 @@ class Step:
542
542
  dd_override_concatenations: dict[str, list[DDStatement]] = field(
543
543
  default_factory=dict
544
544
  )
545
- #: The job's SET symbols as they stood when this step's EXEC was read,
546
- #: kept only when a later ``SET`` redefines one of them (``None``
547
- #: otherwise): a SET value is in effect from the SET statement onward, so
548
- #: this step's ``&SYM`` references must not see the redefinition.
545
+ #: The job's SET symbols as they stood when this step's EXEC was read
546
+ #: (for a step inside a PROC body, the procedure's own SETs), kept only
547
+ #: when a later ``SET`` redefines one of them (``None`` otherwise): a SET
548
+ #: value is in effect from the SET statement onward, so this step's
549
+ #: ``&SYM`` references must not see the redefinition.
549
550
  set_symbols: dict[str, str] | None = field(default=None, repr=False, compare=False)
550
551
 
551
552
  @property
@@ -78,6 +78,15 @@ NON_SQL_CONTROL_PROGRAMS: frozenset[str] = frozenset(
78
78
  }
79
79
  )
80
80
 
81
+ # DB2 online-utility programs. Their in-stream data is utility control
82
+ # statements (``RUNSTATS ... UPDATE ALL``, ``MODIFY RECOVERY ... DELETE AGE(n)``)
83
+ # whose options reuse SQL verbs; only the SQL inside an ``EXEC SQL ...
84
+ # ENDEXEC`` utility statement is SQL.
85
+ DB2_UTILITY_CONTROL_PROGRAMS: frozenset[str] = frozenset({"DSNUTILB"})
86
+
87
+ _EXEC_SQL_RE = re.compile(r"\bEXEC\s+SQL\b", re.IGNORECASE)
88
+ _ENDEXEC_RE = re.compile(r"\bENDEXEC\b", re.IGNORECASE)
89
+
81
90
  # SYSTSIN holds TSO commands (``DELETE``, ``RENAME`` and ``ALTER`` are TSO
82
91
  # commands too); only the DB2 DSN subcommands in it are SQL-related.
83
92
  _TSO_COMMAND_DD = "SYSTSIN"
@@ -178,6 +187,39 @@ def detect_sql_in_text(text: str) -> list[tuple[str, str]]:
178
187
  return results
179
188
 
180
189
 
190
+ def detect_sql_in_utility_statements(text: str) -> list[tuple[str, str]]:
191
+ """Scan DB2 utility control statements for SQL.
192
+
193
+ Only ``EXEC SQL ... ENDEXEC`` utility statements contribute: the
194
+ ``EXEC SQL`` marker itself (``EMBEDDED``) and the SQL statement keywords
195
+ between it and ``ENDEXEC``, which may share a line with either. Every
196
+ other utility statement (``RUNSTATS``, ``REORG``, ``MODIFY RECOVERY``, ...)
197
+ is skipped, so options such as ``UPDATE ALL`` or ``DELETE AGE(90)`` are
198
+ not reported as DML.
199
+ """
200
+ results: list[tuple[str, str]] = []
201
+ in_exec_sql = False
202
+ for raw_line in split_source_lines(text):
203
+ rest = raw_line.strip()
204
+ while rest:
205
+ if not in_exec_sql:
206
+ start = _EXEC_SQL_RE.search(rest)
207
+ if start is None:
208
+ break
209
+ results.append(("EXEC SQL", _KW_CATEGORY["EXEC SQL"]))
210
+ in_exec_sql = True
211
+ rest = rest[start.end() :]
212
+ continue
213
+ end = _ENDEXEC_RE.search(rest)
214
+ body = rest if end is None else rest[: end.start()]
215
+ results.extend(detect_sql_in_text(body))
216
+ if end is None:
217
+ break
218
+ in_exec_sql = False
219
+ rest = rest[end.end() :]
220
+ return results
221
+
222
+
181
223
  def detect_sql_in_dd(dd: DDStatement) -> list[tuple[str, str]]:
182
224
  """Scan a single DD statement's inline data for SQL.
183
225
 
@@ -196,7 +238,10 @@ def detect_sql_in_step(step: Step) -> SQLDetectionResult:
196
238
  the inline data of every DD statement.
197
239
  In-stream data of a program in :data:`NON_SQL_CONTROL_PROGRAMS` is not
198
240
  scanned, and in a ``SYSTSIN`` DD (TSO commands) only DB2 subcommands such
199
- as ``BIND`` count.
241
+ as ``BIND`` count. For a program in
242
+ :data:`DB2_UTILITY_CONTROL_PROGRAMS` only ``EXEC SQL ... ENDEXEC``
243
+ utility statements are scanned
244
+ (:func:`detect_sql_in_utility_statements`).
200
245
  """
201
246
  result = SQLDetectionResult(
202
247
  step_name=step.name,
@@ -204,15 +249,21 @@ def detect_sql_in_step(step: Step) -> SQLDetectionResult:
204
249
  is_db2_program=is_db2_program(step.program)
205
250
  or bool(step.procedure and step.procedure.upper() in DB2_PROCEDURES),
206
251
  )
207
- if step.program and step.program.upper() in NON_SQL_CONTROL_PROGRAMS:
252
+ program = step.program.upper() if step.program else ""
253
+ if program in NON_SQL_CONTROL_PROGRAMS:
208
254
  return result
255
+ utility_control = program in DB2_UTILITY_CONTROL_PROGRAMS
209
256
  is_tso_input = False
210
257
  for dd in step.dd_statements:
211
258
  # An unnamed DD is a concatenation member of the last named one, so
212
259
  # ``// DD *`` after ``//SYSTSIN DD ...`` is TSO input too.
213
260
  if dd.name:
214
261
  is_tso_input = dd.name.upper() == _TSO_COMMAND_DD
215
- for kw, cat in detect_sql_in_dd(dd):
262
+ if utility_control:
263
+ found = detect_sql_in_utility_statements(dd.inline_data or "")
264
+ else:
265
+ found = detect_sql_in_dd(dd)
266
+ for kw, cat in found:
216
267
  if is_tso_input and cat not in _TSO_SQL_CATEGORIES:
217
268
  continue
218
269
  result.sql_statements.append((kw, cat, dd.name))
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "jcl-parser"
7
- version = "2.0.2"
7
+ version = "2.0.3"
8
8
  description = "Parse IBM JCL (Job Control Language) scripts into Python objects"
9
9
  authors = [
10
- {name = "JCL Parser Team", email = "team@jclparser.dev"}
10
+ {name = "Samuel Dion", email = "sam94dion@gmail.com"}
11
11
  ]
12
12
  readme = "README.md"
13
13
  license = {text = "AGPL-3.0-or-later"}
File without changes
File without changes
File without changes
File without changes