{
  "version": "1.8.2",
  "script": "import subprocess\nimport threading\n\ndef _stream_logs(logger, is_stderr, stream):\n    # iter() reads until '' or EOF\n    for line in iter(stream.readline, \"\"):\n        line = line.rstrip()\n        if is_stderr:\n            logger.error(line)\n        else:\n            logger.info(line)\n\n\ndef run(js7Step, cmd):\n    if not cmd:\n        raise ValueError(\"No command specified: 'cmd' must be a non-empty list\")\n\n    process = subprocess.Popen(\n        cmd,\n        stdout=subprocess.PIPE,\n        stderr=subprocess.PIPE,\n        text=True,\n        bufsize=1\n    )\n\n    js7Step.setCancelableResource(process)\n\n    # Defines the thread for live stdout\n    stdout_thread = threading.Thread(\n        target=_stream_logs,\n        args=(js7Step.getLogger(), False, process.stdout),\n        daemon=False\n    )\n\n    # Defines the thread for live stderr\n    stderr_thread = threading.Thread(\n        target=_stream_logs,\n        args=(js7Step.getLogger(), True, process.stderr),\n        daemon=False\n    )\n\n    stdout_thread.start()\n    stderr_thread.start()      \n\n    # Waits until the process is finish\n    rc = process.wait()\n    \n    # Ensures threads have finished reading\n    stdout_thread.join()\n    stderr_thread.join()\n\n    return rc\n\n\ndef cancel(js7Step):\n    if js7Step is None:\n        return\n        \n    process = js7Step.getCancelableResource()\n    if process is None:\n        return\n    \n    try:\n        if process.poll() is None:\n            js7Step.getLogger().info(f\"[ManagedProcess.cancel][kill]{process}...\")\n            process.kill()\n    except Exception as e:\n        js7Step.getLogger().error(f\"[ManagedProcess.cancel][{process}]{e}\")\n\n    return process.wait()"
}