"""JCL Executor — executes parsed JCL steps using platform components.""" from pathlib import Path from jcl.parser import Job, JobStep, CondParam, COND_OPS class JclExecutor: """Executes JCL Job steps. Uses platform CobolRunner for COBOL programs.""" def __init__(self, root_dir: str, cobol_dir: str, copybook_dir: str): self.root_dir = Path(root_dir) self.cobol_dir = Path(cobol_dir) self.copybook_dir = Path(copybook_dir) self.bin_dir = self.root_dir / "bin" self.bin_dir.mkdir(exist_ok=True) self.last_rc: int = 0 self.step_rcs: dict[str, int] = {} self.results: dict[str, dict] = {} def run(self, job: Job) -> int: for step in job.steps: rc = self._execute_step(step) self.last_rc = rc self.step_rcs[step.step_name] = rc return self.last_rc def _execute_step(self, step: JobStep) -> int: # COND check if step.cond and not self._check_cond(step.cond): self.results[step.step_name] = {"status": "SKIPPED", "cond": str(step.cond)} return 0 prog = step.program.upper() if prog == "SORT": return self._run_sort(step) # Use platform's CobolRunner import sys sys.path.insert(0, str(Path(__file__).parent.parent)) from runners.cobol_runner import CobolRunner cbl_path = self.cobol_dir / f"{step.program}.cbl" runner = CobolRunner() build = runner.compile(str(cbl_path)) if not build.success: self.results[step.step_name] = {"status": "COMPILE_ERROR", "log": build.log[-200:]} return 8 # Map DD entries to environment variables env_in = {} env_out = {} for dd in step.dd_entries: name = dd.dd_name.upper() if dd.dsn: path = self._resolve_path(dd.dsn) if name in ("TRANSIN", "MEMBER", "VALIDIN", "RATE", "BILLING"): env_in[name] = str(path) elif name in ("VALIDOUT", "REJECT", "REPORTERR", "CALCOUT", "STMT", "SUMMARY"): env_out[name] = str(path) input_path = env_in.get(list(env_in.keys())[0], "") output_path = env_out.get(list(env_out.keys())[0], str(self.root_dir / "data" / "work" / f"{step.step_name.lower()}_out.bin")) run = runner.run(build.artifact_path, input_path, output_path) self.results[step.step_name] = { "status": "OK" if run.success else "ERROR", "rc": 0 if run.success else 12 } return 0 if run.success else 12 def _check_cond(self, cond: CondParam) -> bool: if cond.step_name: target = self.step_rcs.get(cond.step_name, 0) return not COND_OPS.get(cond.operator, lambda x, y: False)(target, cond.code) return True def _run_sort(self, step: JobStep) -> int: import subprocess infile = None outfile = None for dd in step.dd_entries: n = dd.dd_name.upper() if n == "SORTIN" and dd.dsn: infile = self._resolve_path(dd.dsn) elif n == "SORTOUT" and dd.dsn: outfile = self._resolve_path(dd.dsn) if not infile or not outfile: return 12 if infile.exists(): lines = sorted(infile.read_text().splitlines()) outfile.write_text("\n".join(lines)) self.results[step.step_name] = {"status": "OK", "rc": 0} return 0 def _resolve_path(self, dsn: str) -> Path: import re dsn = re.sub(r"\(\+?\d+\)", "", dsn).strip(".") return (self.root_dir / dsn.lstrip("/")).resolve()