diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 310ce62..a2a385f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -21,7 +21,7 @@ jobs: - name: Build and Commit uses: sphinx-notes/pages@v2 with: - requirements_path: ./docs/requirements.txt + requirements_path: requirements.txt - name: Push changes uses: ad-m/github-push-action@master with: diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml new file mode 100644 index 0000000..ce79ac5 --- /dev/null +++ b/.github/workflows/sphinx.yml @@ -0,0 +1,138 @@ +# From: https://github.com/rkdarst/sphinx-actions-test/blob/master/.github/workflows/sphinx-build.yml + +name: sphinx +on: [push, pull_request] + +env: + DEFAULT_BRANCH: "main" + #SPHINXOPTS: "-W --keep-going -T" + # ^-- If these SPHINXOPTS are enabled, then be strict about the builds and fail on any warnings + +jobs: + build-and-deploy: + name: Build and gh-pages + runs-on: ubuntu-latest + steps: + # https://github.com/marketplace/actions/checkout + - uses: actions/checkout@v2 + with: + fetch-depth: 2 + lfs: true + # https://github.com/marketplace/actions/setup-python + # ^-- This gives info on matrix testing. + - name: Install Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + # https://docs.github.com/en/actions/guides/building-and-testing-python#caching-dependencies + # ^-- How to set up caching for pip on Ubuntu + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + # https://docs.github.com/en/actions/guides/building-and-testing-python#installing-dependencies + # ^-- This gives info on installing dependencies with pip + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Debugging information + run: | + echo "github.ref:" ${{github.ref}} + echo "github.event_name:" ${{github.event_name}} + echo "github.head_ref:" ${{github.head_ref}} + echo "github.base_ref:" ${{github.base_ref}} + set -x + git rev-parse --abbrev-ref HEAD + git branch + git branch -a + git remote -v + python -V + pip list --not-required + pip list + # Build + - uses: ammaraskar/sphinx-problem-matcher@master + - name: Build Sphinx docs + run: | + make dirhtml + # This fixes broken copy button icons, as explained in + # https://github.com/coderefinery/sphinx-lesson/issues/50 + # https://github.com/executablebooks/sphinx-copybutton/issues/110 + # This can be removed once these PRs are accepted (but the + # fixes also need to propagate to other themes): + # https://github.com/sphinx-doc/sphinx/pull/8524 + # https://github.com/readthedocs/sphinx_rtd_theme/pull/1025 + sed -i 's/url_root="#"/url_root=""/' _build/dirhtml/index.html || true + # The following supports building all branches and combining on + # gh-pages + + # Clone and set up the old gh-pages branch + - name: Clone old gh-pages + if: ${{ github.event_name == 'push' }} + run: | + set -x + git fetch + ( git branch gh-pages remotes/origin/gh-pages && git clone . --branch=gh-pages _gh-pages/ ) || mkdir _gh-pages + rm -rf _gh-pages/.git/ + mkdir -p _gh-pages/branch/ + # If a push and default branch, copy build to _gh-pages/ as the "main" + # deployment. + - name: Copy new build (default branch) + if: | + contains(github.event_name, 'push') && + contains(github.ref, env.DEFAULT_BRANCH) + run: | + set -x + # Delete everything under _gh-pages/ that is from the + # primary branch deployment. Eicludes the other branches + # _gh-pages/branch-* paths, and not including + # _gh-pages itself. + find _gh-pages/ -mindepth 1 ! -path '_gh-pages/branch*' -delete + rsync -a _build/dirhtml/ _gh-pages/ + # If a push and not on default branch, then copy the build to + # _gh-pages/branch/$brname (transforming '/' into '--') + - name: Copy new build (branch) + if: | + contains(github.event_name, 'push') && + !contains(github.ref, env.DEFAULT_BRANCH) + run: | + set -x + #brname=$(git rev-parse --abbrev-ref HEAD) + brname="${{github.ref}}" + brname="${brname##refs/heads/}" + brdir=${brname//\//--} # replace '/' with '--' + rm -rf _gh-pages/branch/${brdir} + rsync -a _build/dirhtml/ _gh-pages/branch/${brdir} + # Go through each branch in _gh-pages/branch/, if it's not a + # ref, then delete it. + - name: Delete old feature branches + if: ${{ github.event_name == 'push' }} + run: | + set -x + for brdir in `ls _gh-pages/branch/` ; do + brname=${brdir//--/\/} # replace '--' with '/' + if ! git show-ref remotes/origin/$brname ; then + echo "Removing $brdir" + rm -r _gh-pages/branch/$brdir/ + fi + done + # Add the .nojekyll file + - name: nojekyll + if: ${{ github.event_name == 'push' }} + run: | + touch _gh-pages/.nojekyll + # Deploy + # https://github.com/peaceiris/actions-gh-pages + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + if: ${{ github.event_name == 'push' }} + #if: ${{ success() && github.event_name == 'push' && github.ref == 'refs/heads/$defaultBranch' }} + with: + publish_branch: gh-pages + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: _gh-pages/ + force_orphan: true diff --git a/src/csfdock/.DS_Store b/src/csfdock/.DS_Store deleted file mode 100644 index b84a05b..0000000 Binary files a/src/csfdock/.DS_Store and /dev/null differ diff --git a/src/csfdock/DVisualize.py b/src/csfdock/DVisualize.py deleted file mode 100644 index 72b3f17..0000000 --- a/src/csfdock/DVisualize.py +++ /dev/null @@ -1,334 +0,0 @@ -import os - -import py3Dmol -from ipywidgets import Layout, interactive - -from rdkit import Chem -from csfdock.utils import give_id, PDBParse -from rich.console import Console -console = Console() -blue_console = Console(style="white on blue") - - -class DVisualize: - """Grid bix view of the docking pocket - Attributes: - box_center_x (int): Coordinates of box center x-axis. - box_center_y (int): Coordinates of box center y-axis. - box_center_z (int): Coordinates of box center z-axis. - box_size_x (int): Size of x-axis of grid box - box_size_y (int): Size of x-axis of grid box - box_size_z (int): Size of x-axis of grid box - ligand (str): Path of ligand - protein (str): Path of Receptor - """ - - def __init__(self, *args, **kwargs): - self.grid_box_centers = None - self.receptor = kwargs.get("protein", None) - self.ligand = kwargs.get("ligand", None) - self.box_center_x = kwargs.get("box_center_x") - self.box_center_y = kwargs.get("box_center_y") - self.box_center_z = kwargs.get("box_center_z") - self.box_size_x = kwargs.get("box_size_x", 20) - self.box_size_y = kwargs.get("box_size_y", 20) - self.box_size_z = kwargs.get("box_size_z", 20) - self.prot_color = kwargs.get("prot_color", "spectrum") - self.lig_color = kwargs.get("lig_color", "red") - self.membrane = kwargs.get("membrane", None) - self.save = kwargs.get("save", "False") - self.bg_color = kwargs.get("bg_color", "white") - self.mem_color = kwargs.get("mem_color", "blue") - for arg in args: - if isinstance(arg, str): - if self.receptor is None: - self.receptor = arg - elif isinstance(arg, list): - if self.grid_box_centers is None: - self.grid_box_centers = arg - self.box_center_x = arg[0] - self.box_center_y = arg[1] - self.box_center_z = arg[2] - else: - self.grid_box_sizes = arg - self.box_size_x = arg[0] - self.box_size_y = arg[1] - self.box_size_z = arg[2] - - def LoadBox(self, *args, **kwargs): - try: - self.grid_box_centers - except AttributeError: - self.grid_box_centers = None - for arg in args: - if isinstance(arg, str): - if self.receptor is None: - self.receptor = arg - elif isinstance(arg, list): - if self.grid_box_centers is None: - self.grid_box_centers = arg - self.box_center_x = arg[0] - self.box_center_y = arg[1] - self.box_center_z = arg[2] - else: - self.grid_box_sizes = arg - self.box_size_x = arg[0] - self.box_size_y = arg[1] - self.box_size_z = arg[2] - - def __rep__(self): - return f"Complex_Grid: {self.receptor} and {self.ligand}" - - def __str__(self): - return f"Protein: {self.receptor} and \nligand :{self.ligand}" - - def __grid_box(self): - try: - self.vobj.addBox( - { - "center": { - "x": self.box_center_x, - "y": self.box_center_y, - "z": self.box_center_z, - }, - "dimensions": { - "w": self.box_size_x, - "h": self.box_size_y, - "d": self.box_size_z, - }, - "color": "blue", - "opacity": 0.5, - } - ) - except Exception as e: - print("Failed to add Grid") - - def LoadLipid(self, *args, verbose=True,native=False, **kwargs): - lipid = kwargs.get("lipid") - - for arg in args: - lipid = arg - lipid_path = self.LoadReceptor( - lipid, key="Lipid", verbose=verbose, native=native - ) - _, lipid, water, lig = PDBParse(lipid_path) - self.lipid = lipid_path - with open("./temp.pdb", "w+") as f: - for i in lipid: - print(i, end="", file=f) - # m = Chem.MolFromPDBFile("./temp.pdb", sanitize=False) - # print(m) - try: - if self.vobj: - pass - except AttributeError: - self.vobj = py3Dmol.view(width=800, height=600) - lipid_mol = open("./temp.pdb").read() - self.vobj.addModel(lipid_mol, "pdb") - self.vobj.setStyle({"lipid_mol": 2}, {"cartoon": {}}) - try: - os.remove("./temp.pdb") - except Exception: - pass - - # self.vobj.addModel(lipid, "pdb") - # self.vobj.setStyle({"model": 3}, {"cartoon": {}}) - # self.vobj.setStyle({"cartoon": {"color": "spectrum"}}) - - def __complex_view(self): - mol1 = open(self.receptor, "r").read() - file_format = "pdb" - try: - mol2 = open(self.ligand, "r").read() - lig_dir, lig_name, lig_file_format = give_id(self.ligand) - if lig_file_format == "sdf": - file_format = "sdf" - self.vobj.addModel(mol2, f"{file_format}") - self.vobj.setStyle({"model": 1}, {"stick": {}}) - except TypeError as er: - self.mol_view.setStyle( - {"resn": f"{self.resn}"}, {"stick": {"colorscheme": self.lig_color}} - ) - self.vobj.addModel(mol1, "pdb") - self.vobj.setStyle({"cartoon": {"color": self.prot_color}}) - - def __visualize_mol(self): - self.vobj = py3Dmol.view(width=800, height=600) - self.__grid_box() - self.__box_view() - try: - self.LoadLipid(self.lipid, verbose=False, native=False) - except AttributeError as er: - blue_console.print("Lipid maynot be loaded yet") - try: - _ = self.bg_color - except AttributeError: - self.bg_color= "white" - self.vobj.setBackgroundColor(self.bg_color) - self.vobj.rotate(90, {"x": 0, "y": 1, "z": 0}, viewer=(0, 1)) - self.vobj.zoomTo() - return self.vobj.show() - - def ShowMolecules(self, **kwargs): - """Visualize grid box with protein complex - Returns: - py3dmol : 3D Viewer - """ - self.resn = kwargs.get("resn", "LIG") - - grid_obj = interactive(self.__visualize_mol) - return display(grid_obj) - - def __show_ligand( - self, mol_view_object, mol, resn=None, mol_color="blue", style="stick" - ): - _, mol_name, mol_format = give_id(mol) - - try: - mol2 = open(mol, "r").read() - *_, mol_file_format = give_id(self.ligand) - mol_view_object.addModel(mol2, f"{mol_file_format}") - mol_view_object.setStyle({"model": 1}, {"stick": {}}) - except (TypeError, AttributeError) as er: - print( - # "Cannot.." - "Searching name space..." - ) - mol_view_object.setStyle( - {"resn": f"{resn}"}, {f"{style}": {"colorscheme": mol_color}} - ) - # print(er) - return mol_view_object - - def SimpleView(self, **kwargs): - """3d visualization of pdb - Args: - protein (TYPE): protein - ligand (None, optional): small molecule - color (str, optional): color of wish, default: grey - resn (str): Ligand from pdb file. - Returns: - TYPE: structure view. - """ - resn = kwargs.get("resn", "LIG") - self.bg_color = kwargs.get("bg_color", "white") - self.prot_color = kwargs.get("prot_color", "spectrum") - self.lig_color = kwargs.get("lig_color", "red") - self.save = kwargs.get("save", False) - self.show_ligand = kwargs.get("show_ligand", True) - self.show_receptor = kwargs.get("show_receptor", True) - vobj = py3Dmol.view(width=900, height=500) - vobj.setBackgroundColor(self.bg_color) - if self.show_receptor: - structure_dir, structure_name, structure_format = give_id(self.receptor) - if structure_format.lower() == "sdf": - mol = Chem.MolFromMolFile(self.receptor, removeHs=False) - mol = Chem.MolToMolBlock(mol) - vobj.addModel(mol, f"{structure_format}") - self.clean.addModel(mol, f"{structure_format}") - else: - vobj.addModel(open(self.receptor).read()) - self.clean = vobj - vobj.setStyle({"cartoon": {"color": f"{self.prot_color}"}}) - - if self.show_ligand: - try: - self.__show_ligand(vobj, self.ligand, mol_color=self.lig_color) - except AttributeError as er: - print("Ligand not yet added to the project...") - vobj.zoomTo() - if self.save == True: - prefix = "image" - while os.path.exists(f"./images/{prefix}.png"): - suffix += 1 - name = f"{prefix}{suffix}.png" - vobj.save_fig(f"./Images/{name}", dpi=600) - print(f"Successfully saved ./Images/{name} ") - if self.show_ligand is False and self.show_receptor is False: - return "Nothing to visualize.." - - return vobj.show() - - def __box_view(self, **kwargs): - """3d visualization of pdb - Args: - protein (TYPE): protein - ligand (None, optional): small molecule - color (str, optional): color of wish, default: grey - resn (str): Ligand from pdb file. - Returns: - TYPE: structure view. - """ - self.resn = kwargs.get("resn", "LIG") - self.membrane = kwargs.get("membrane", None) - self.lig_color = kwargs.get("resn_color", "yellow") - self.element = kwargs.get("element", None) - self.save = kwargs.get("save", False) - self.mem_color = kwargs.get("mem_color", "blue") - file_format = "pdb" - try: - structure_dir, structure_name, structure_format = give_id(self.receptor) - if structure_format.lower() == "sdf": - mol = Chem.MolFromMolFile(self.receptor, removeHs=False) - mol = Chem.MolToMolBlock(mol) - self.vobj.addModel(mol, f"{structure_format}") - else: - self.vobj.addModel(open(self.receptor).read()) - - self.vobj.setStyle({"cartoon": {"color": "spectrum"}}) - - - except Exception as er: - print("Failed to open protein") - # try: - # if self.ligand and self.resn is not None: - # self.vobj.setStyle( - # {"resn": f"{self.ligand}"}, - # {"stick": {"colorscheme": self.lig_color}}, - # ) - # - # # elif self.ligand is not None and self.resn is None: - # # mol2 = open(self.ligand, 'r').read() - # # lig_dir, lig_name, lig_file_format = give_id(self.ligand) - # # if lig_file_format == "sdf": - # # file_format = "sdf" - # # self.vobj.addModel(mol2, f"{file_format}") - # # self.vobj.setStyle( - # # {'model': 1}, {'stick': {"colorscheme": self.lig_color}} - # # ) - # - # # elif self.ligand is None and self.resn is not None: - # # self.vobj.setStyle( - # # {"resn": f"{self.resn}"}, - # # {"sphere": {"colorscheme": self.lig_color}}, - # # ) - # #except TypeError as er: - # # print("failed to open ligand") - # pass - try: - if self.ligand is not None: - mol2 = open(self.ligand, "r").read() - lig_dir, lig_name, lig_file_format = give_id(self.ligand) - if lig_file_format == "sdf": - file_format = "sdf" - self.vobj.addModel(mol2, f"{file_format}") - self.vobj.setStyle({"model": 1}, {"stick": {}}) - else: - self.vobj.setStyle( - {"resn": f"{self.resn}", "clickable": True}, - {"stick": {"colorscheme": self.lig_color}}, - ) - except Exception as er: - pass - - self.vobj.zoomTo() - # self.vobj.setStyle({"clickable": True}) - if self.save is True: - prefix = "image" - while os.path.exists(f"./images/{prefix}.png"): - suffix += 1 - name = f"{prefix}{suffix}.png" - self.vobj.save_fig(f"./Images/{name}", dpi=600) - print(f"Successfully saved ./Images/{name} ") - # return self.vobj.show() - diff --git a/src/csfdock/DockingTools.py b/src/csfdock/DockingTools.py deleted file mode 100644 index c26752d..0000000 --- a/src/csfdock/DockingTools.py +++ /dev/null @@ -1,994 +0,0 @@ -# Functions invloved in docking using smina - -import itertools -import os -import re -import subprocess - -import ipywidgets -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -import seaborn as sns -from IPython.display import HTML, display -from ipywidgets import ( - FileUpload, - IntSlider, - Layout, - fixed, - interactive, - interactive_output, - widgets, -) -from matplotlib.offsetbox import AnchoredText -from rich.console import Console -from scipy.special import expit -from sklearn.datasets import make_classification -from sklearn.linear_model import LinearRegression, LogisticRegression -from sklearn.metrics import accuracy_score, roc_auc_score, roc_curve -from sklearn.model_selection import KFold, train_test_split - -from csfdock.utils import give_id - -console = Console() - - -def add_hydrogen(list_xyz): - """A function to add hydrogen atoms using - openbabel to a list of molecules. - Creates sub folder "protein_id/poses" in - input file directory and dump there. - Args: - list_xyz (list): Molecules to be added Hydrogen - Returns: - xyz: saves xyz in protein_id/poses with suffix "_addHs.xyz" - """ - # FOR SERVER or use own path.. - OBABEL_PATH = "/share/openbabel-3.1.1/bin/obabel" - for i in list_xyz: - dir_name, id, file_format = give_id(i) - if not os.path.exists(f"{dir_name}/addH/{id[:4]}"): - os.makedirs(f"{dir_name}/addH/{id[:4]}") - command = f"{OBABEL_PATH} {i} -O {dir_name}/addH/{id[:4]}/{id}_addHs.xyz -h" - subprocess.run(command, cwd=f"{dir_name}", shell=True) - return "Successfully Completed." - - -def rmsd_calculator(reference, poses_list, key=None, nomatch=False, verbose=True): - """Calculates RMSD between reference and pose using obrms in server. - Args: - reference (reference molecule): Reference molecule - poses_list (test_poses): List of poses to test with the reference. - key= Any suffix to add to the name of the file. - Returns: - Txt file: Writes reference, poses name and RMSD to a log file. - """ - count = 0 - try: - for ref in reference: - _, ref_name, _dir = give_id(ref) - ref_id = os.path.basename(ref) - ref_dir = os.path.dirname(ref) - ref_dir = os.path.dirname(ref_dir) - for pose in poses_list: - pose_id = os.path.basename(pose) - if ref_id[:4].lower() == pose_id[:4].lower() or (nomatch is True): - command = f"/share/openbabel-3.1.1/bin/obrms {ref} \t {pose}" - pose_id = os.path.basename(pose) - rmsd_out = subprocess.run( - command, - cwd=f"{ref_dir}", - capture_output=True, - text=True, - shell=True, - ) - if not os.path.exists(f"{ref_dir}/result/RMSD"): - os.makedirs(f"{ref_dir}/result/RMSD") - with open( - f"{ref_dir}/result/RMSD/{ref_id[:4]}_RMSD_{key}.txt", "a+" - ) as write_out: - rmsd_result = f"{pose_id.lower()}\t" + str(rmsd_out.stdout) - temp_rmsd = f"{ref_name}\t{pose_id.lower()}\t" + str( - float(rmsd_out.stdout.split()[-1]) - ) - print(rmsd_result, file=write_out) - count += 1 - else: - return "No matched header found" - if verbose: - print(f"Total of {count} rmsd calculated.") - return temp_rmsd - except Exception as er: - print(er) - - -def rmsd_matrix_prep(rmsd_results, print_it=True, return_df=True, only_best=False): - default_scoring_ = {} - ad4_scoring_ = {} - dkoes_fast_scoring_ = {} - dkoes_scoring_old_scoring_ = {} - vina_scoring_ = {} - vinardo_scoring_ = {} - custom_scoring_ = {} - best = {} - with open(rmsd_results[0], "r") as rmsd_results_read: - for count, line in enumerate(rmsd_results_read): - line_info = line.rsplit("/")[-1] - try: - key, value = line_info.split(".pdb") - except Exception as er: - # print(er) - pass - if "_ad4_scoring_" in line: - ad4_scoring_[key] = value.strip() - elif "_default_" in key: - default_scoring_[key] = value.strip() - elif "_dkoes_fast_" in key: - dkoes_fast_scoring_[key] = value.strip() - elif "_dkoes_scoring_old_" in key: - dkoes_scoring_old_scoring_[key] = value.strip() - elif "_vina_" in key: - vina_scoring_[key] = value.strip() - elif "_vinardo_" in key: - vinardo_scoring_[key] = value.strip() - else: - custom_scoring_[key] = value.strip() - # print(count) - try: - ad4_df = pd.DataFrame.from_dict( - ad4_scoring_, orient="index", columns=(["ad4_scoring"]) - ) - default_df = pd.DataFrame.from_dict( - default_scoring_, orient="index", columns=(["default_scoring"]) - ) - dkoes_fast_df = pd.DataFrame.from_dict( - dkoes_fast_scoring_, orient="index", columns=(["dkoes_fast_scoring"]) - ) - dkoes_scoring_old_df = pd.DataFrame.from_dict( - dkoes_fast_scoring_, orient="index", columns=(["dkoes_fast_scoring"]) - ) - vina_df = pd.DataFrame.from_dict( - vina_scoring_, orient="index", columns=(["vina_scoring"]) - ) - vinardo_df = pd.DataFrame.from_dict( - vinardo_scoring_, orient="index", columns=(["vinardo_scoring"]) - ) - custom_df = pd.DataFrame.from_dict( - custom_scoring_, orient="index", columns=(["custom_scoring"]) - ) - return_df = pd.concat( - [ - ad4_df, - default_df, - dkoes_fast_df, - dkoes_scoring_old_df, - vina_df, - vinardo_df, - custom_df, - ] - ) - except Exception as er: - print(f"{er}\n error in data frame") - if print_it: - try: - ad4_best = min(ad4_scoring_.items(), key=lambda x: x[1]) - print("===========BEST RMSD===================") - best[ad4_best[0]] = ad4_best[1] - print(f"{ad4_best[0]} : {ad4_best[1]}") - default_best = min(default_scoring_.items(), key=lambda x: x[1]) - best[default_best[0]] = default_best[1] - print(f"{default_best[0]} : {default_best[1]}") - dkoes_fast_best = min(dkoes_fast_scoring_.items(), key=lambda x: x[1]) - best[dkoes_fast_best[0]] = dkoes_fast_best[1] - print(f"{dkoes_fast_best[0]} : {dkoes_fast_best[1]}") - dkoes_scoring_old_best = min( - dkoes_scoring_old_scoring_.items(), key=lambda x: x[1] - ) - best[dkoes_scoring_old_best[0]] = dkoes_scoring_old_best[1] - print(f"{dkoes_scoring_old_best[0]} :{dkoes_scoring_old_best[1]}") - vina_best = min(vina_scoring_.items(), key=lambda x: x[1]) - best[vina_best[0]] = vina_best[1] - print(f"{vina_best[0]} : {vina_best[1]}") - vinardo_best = min(vinardo_scoring_.items(), key=lambda x: x[1]) - best[vinardo_best[0]] = vinardo_best[1] - print(f"{vinardo_best[0]} : {vinardo_best[-1]}") - custom_best = min(custom_scoring_.items(), key=lambda x: x[1]) - best[custom_best[0]] = custom_best[1] - print(f"{custom_best[0]} : {custom_best[1]}") - except Exception as er: - print(er) - if only_best: - return best - if return_df is True: - return return_df - else: - return ( - default_scoring_, - ad4_scoring_, - dkoes_fast_scoring_, - dkoes_scoring_old_scoring_, - vina_scoring_, - vinardo_scoring_, - custom_scoring_, - ) - - -def conformer_split(filenames, target): - for i in filenames: - file_dir, file_id, file_format = give_id(i) - if not os.path.exists(f"{file_dir}/poses"): - os.makedirs(f"{file_dir}/poses") - command = ( - f"/share/openbabel-3.1.1/bin/obabel {i} -o{target} -O" - f" ./poses/{file_id}_.{target} -m" - ) - subprocess.run(command, cwd=f"{file_dir}", shell=True) - return "Successfully completed." - - -def smina_histogram(sorted_RES, save=False): - """Creates histogram of docking from smina output - Args: - sorted_RES (list): Sorted list of affinity values from smina output. - save (bool, optional): Save plot in ./image/smina_histogram.png - """ - name, benergy = zip(*sorted_RES.items()) - benergy = np.array((benergy), dtype=np.float32) - mean = benergy.mean() - best = benergy.min() - worst = benergy.max() - fig, axs = plt.subplots(1, sharey=False, sharex=False, tight_layout=True) - axs.add_artist( - AnchoredText( - f"Total: {len(name)}\nMean: {mean:.2f}\nBest: {best:.2f}\nWorst:" - f" {worst:.2f}", - loc=1, - ) - ) - axs.hist(benergy) - axs.yaxis.set_label_text("Number of Datasets") - axs.xaxis.set_label_text("Binding Energy Range") - axs.set_title("Distribution of binding energy") - if save: - if not os.path.exists("./images"): - os.makedirs("./images") - plt.savefig("./images/smina_histogram.png", dpi=600) - plt.show() - - -def smina_monitor(smina_output_monitor, plot=False, save=False): - """Display smina process while enter smina stdout - Args: - smina_output_monitor (stdout): stdout from qstat/qsub - Returns: - dict: Displays result in jupyter - """ - RES = {} - count = 0 - for i in smina_output_monitor: - algo_dir, algo_name, algo_format = give_id(i) - if algo_format.lower() == "pdb": - with open(i, "r") as read_smina: - for line in read_smina: - if line[:5] == "MODEL": - number = line[5:].strip() - elif "REMARK" in line: - energy = float(line.rsplit(" ")[-1].strip()) - RES[f"{algo_name}_{number}"] = f"{energy}" - count += 1 - elif algo_format.lower() == "sdf": - pattern_id = r"^[a-zA-Z]\S" - pattern_affinity = r"^>\s<[a-zA-Z]+>" - with open(i, "r") as read_smina: - write_affinity = False - for line in read_smina: - if write_affinity: - energy = float(line.strip()) - # print(energy) - RES[f"{algo_name}_{number}"] = f"{energy}" - count += 1 - write_affinity = False - if re.match(pattern_id, line): - number = line.strip() - elif re.match(pattern_affinity, line): - write_affinity = True - else: - return "File format not supported yet. ['pdb', 'sdf']" - - print(f"Total number of poses generated: {count}") - sorted_RES = dict(sorted(RES.items(), key=lambda x: x[1])) - print("_____________Detail list________________\n") - for key, value in sorted_RES.items(): - print(key, ":", value) - if plot: - smina_histogram(sorted_RES, save=save) - - return sorted_RES - - -def auc_plot(model, X_test, y_test, save=False): - """Plot AUC plot from model, and X_text(data point) y_test(label). - Args: - model (sklearn obj)_: Model object from sklearn trained model. - X_test (pd.DataFrame): Data point for test. - y_test (pd.DataFrame): Label for the data point. - save (bool, optional): Save AUC plot. - """ - # assert isinstance(model, LinearRegression), f"{model} is not a valid model" - # assert isinstance(X_test, pd.DataFrame), f"{X_test} is not a DataFrame" - # assert isinstance(y_test, pd.DataFrame), f"{y_test} is not a DataFrame" - model_regression_probability = model.predict_proba(X_test) - model_regression_probability = model_regression_probability[:, 1] - random_probability = [0 for _ in range(len(y_test))] - random_auc = roc_auc_score(y_test, random_probability) - model_auc = roc_auc_score(y_test, model_regression_probability) - print(f"Random: ROC AUC={random_auc}") - print(f"Model: ROC AUC={model_auc}") - random_false_positive_rate, random_true_positive_rate, _ = roc_curve( - y_test, random_probability - ) - model_false_positive_rate, model_true_positive_rate, _ = roc_curve( - y_test, model_regression_probability - ) - plt.plot( - random_false_positive_rate, - random_true_positive_rate, - linestyle="--", - label="Random", - ) - plt.plot( - model_false_positive_rate, - model_true_positive_rate, - marker=".", - label=f"Model (AUC:{model_auc:.2f})", - ) - plt.xlabel("False Positive Rate") - plt.ylabel("True Positive Rate") - plt.xlim(xmin=0.0) - plt.ylim(ymin=0.0) - plt.title("ROC") - plt.legend() - if save: - prefix = "image" - while os.path.exists(f"./Generated/images/{prefix}.png"): - suffix += 1 - name = f"{prefix}{suffix}.png" - plt.savefig("./Generated/images/{name}", dpi=600) - plt.show() - - -def smina_model_score( - file_path, - num_features=3, - intercept=False, - tsize=0.3, - plot_auc=False, - plot_save=False, -): - """Generates regression model using sklearn. Will Print out coefficients - Args: - file_path (str): csv/excel file path - num_features (int, optional): Number of features to use. Default: 3 - intercept (bool, optional): Mean Error - tsize (float, optional): Percentage of datato use for test. Default 0.3(30%) - plot_auc (bool, optional): Plot ROC AUC curve - plot_save (bool, optional): Save ROC AUC plot - """ - try: - if isinstance(file_path, str): - file_dir, file_name, file_format = give_id(file_path) - # for now | smina result file - supported_file_format = ["csv", "excel"] - assert file_format in supported_file_format, ( - "Note: FileType Error: Not supported file format. Use" - f" {supported_file_format}" - ) - if file_format == "excel": - df = pd.read_excel(file_path) - else: - df = pd.read_csv(file_path) - - except Exception as e: - print(e) - - if isinstance(file_path, pd.DataFrame): - df = file_path - try: - X, y = df.iloc[1:, 1:-2], df.iloc[1:, -1] - X = pd.DataFrame(X) - header = X.iloc[:0, :] - X, y = make_classification(n_features=num_features) - X_train, X_test, y_train, y_test = train_test_split( - X, y, test_size=tsize - ) # TODO //include K-Fold Test - model = LogisticRegression(fit_intercept=intercept) - model.fit(X_train, y_train) # TODOD accept use model input - weight = model.coef_ - weight = [item for i in weight for item in i] - console.print("[bold cyan]Model weights are :~[/bold cyan]\n") - for head, coeff in zip(header, weight): - print(coeff, head, end="\n") - model.predict(X_test) - model.predict_proba(X_test) - score = model.score(X_test, y_test) - print(f"\nModel score: {score}") - except Exception as er: - print(er) - if plot_auc: - auc_plot(model, X_test, y_test, save=plot_save) - # train_plot(model, X, y, X_test, y_test) - return model - - -def train_plot(model, X, y, X_test, y_test): - plt.figure(1, figsize=(4, 3)) - plt.clf() - print(len(X)) - print(len(y)) - plt.scatter(X.ravel(), y, color="black", zorder=20) - # plt.scatter(y_test, X_test.iloc[:,0].values) - loss = expit(X_test * model.coef_ + model.intercept_).ravel() - plt.plot(X_test, loss, color="red", linewidth=3) - - ols = LinearRegression() - ols.fit(X, y) - plt.plot(X_test, ols.coef_ * X_test + ols.intercept_, linewidth=1) - plt.axhline(0.5, color=".5") - - plt.ylabel("y") - plt.xlabel("X") - plt.xticks(range(-5, 10)) - plt.yticks([0, 0.5, 1]) - plt.ylim(-0.25, 1.25) - plt.xlim(-4, 10) - plt.legend( - ("Logistic Regression Model", "Linear Regression Model"), - loc="lower right", - fontsize="small", - ) - plt.tight_layout() - plt.show() - - -def input_custom_scoring(): - """GUI window to enter custom scoring function""" - # initialize some msg and output env - output_csf = widgets.Output() - msg_empty_name = "Enter any name for the file." - warn_empty_name = widgets.HTML(value=f"{msg_empty_name}") - # save the input custom scoring value - - def save_scoring(data): - output_csf.clear_output() - msg_confirm_warn = "Please confirm if the values are right." - information = ipywidgets.widgets.HTML( - value=f"{msg_confirm_warn}" - ) - global scoring_data - global temp_name - temp_name = file_name.value - if not temp_name: - with output_csf: - display(warn_empty_name) - else: - splitted = custom_scoring_area.value.split("\n") - scoring_data = [] - for split in splitted: - split = split.strip() - scoring_data.append(split) - with output_csf: - print(f"Entered file name : {temp_name}") - for line in scoring_data: - if select == "custom_scoring": - if len(line.rstrip()) != 0: - value, item = line.split() - print(f"{value}\t{item}") - else: - print(line) - display(information) - - # writes the save scoring data to a file - def confirm_scoring(data): - output_csf.clear_output() - if not temp_name: - with output_csf: - display(warn_empty_name) - else: - msg_success = "Confirmed and Saved!" - information = ipywidgets.widgets.HTML( - value=f"{msg_success}" - ) - file_name_save = temp_name - file_content = scoring_data - if select == "custom_scoring": - if not os.path.exists("./Generated/custom_function"): - os.makedirs("./Generated/custom_function") - with open( - f"./Generated/custom_function/{file_name_save}_csf.txt", "w+" - ) as write_scoring_function: - for line in file_content: - if len(line.rstrip()) != 0: - value, item = line.split() - print(f"{value}\t{item}", file=write_scoring_function) - - info = ( - f"file save at ./Generated/custom_function/{file_name_save}_csf.txt" - ) - else: - if not os.path.exists("./Generated/smina_input"): - os.makedirs("./Generated/smina_input") - with open( - f"./Generated/smina_input/{file_name_save}_mconfig.txt", "w+" - ) as write_config: - for line in file_content: - print(f"{line}", file=write_config) - - info = ( - f"file save at ./Generated/smina_input/{file_name_save}_mconfig.txt" - ) - with output_csf: - print(info) - display(information) - - # def all_clear(data): - # with output_csf: - # output_csf.clear_output() - # #text area to observe all input text - config_placeholder = ( - "Paste here\n \n Sample config.txt Docking parameters file\n " - " -------------------------------------\n #Inputs\n receptor =" - " ./3L6B_prot.pdbqt\n ligand = ./3L6B_lig.pdbqt\n #Outputs\n " - " out = 3L6B-nowat-Vina.pdbqt\n log = 3L6B-nowat-Vina.log\n " - " #Box center\n center_x = 4.500\n center_y = -2.944\n " - " center_z = -5.250\n #Box size\n size_x = 50\n size_y =" - " 50\n size_z = 50\n #Parameters\n exhaustiveness = 8\n " - " seed = 123456\n" - ) - csf_placeholder = ( - " Paste here\n \n Sample format of custom scoring\n " - " -------------------------------------\n -0.035579 " - " gauss(o=0,_w=0.5,_c=8)\n -0.005156 gauss(o=3,_w=2,_c=8\n " - " 0.840245 repulsion(o=0,_c=8)\n -0.035069 " - " hydrophobic(g=0.5,_b=1.5,_c=8)\n -0.587439 " - " non_dir_h_bond(g=-0.7,_b=0,_c=8)\n 1.923 num_tors_div\n " - " -100.0 atom_type_gaussian(t1=Chlorine,t2=Sulfur,o=0,_w=3,_c=8)\n" - ) - - def evaluate(selected): - output_csf.clear_output() - area_layout = Layout(width="100%", height="400px", flex="row") - global select - select = selected - if selected == "custom_scoring": - global custom_scoring_area - custom_scoring_area = widgets.Textarea( - placeholder=csf_placeholder, - description="Enter:", - disabled=False, - justify_content="space_between", - continuous_update=True, - layout=area_layout, - ) - else: - custom_scoring_area = widgets.Textarea( - placeholder=config_placeholder, - description="Enter:", - disabled=False, - justify_content="space_between", - continuous_update=True, - layout=area_layout, - ) - display(custom_scoring_area) - output_csf.clear_output() - - select_option = widgets.RadioButtons( - options=["custom_scoring", "manual_config"], - value="custom_scoring", - description="What:", - disabled=False, - ) - ui = widgets.HBox([select_option]) - options = widgets.interactive_output(evaluate, {"selected": select_option}) - instruction = ipywidgets.widgets.HTML( - "Copy and Paste the scoring" - " function below and enter" - ) - display(instruction) - # buttons widgets - file_name = widgets.Text(description="Filename:", placeholder="file name ") - save_button = widgets.Button(description="Save") - save_button.style.button_color = "lightgreen" - confirm_button = widgets.Button(description="Confirm") - confirm_button.style.button_color = "salmon" - # clear_button = widgets.Button(description="Clear") - # clear_button.style.button_color = "lightgreen" - display((widgets.VBox([file_name, ui, options])), output_csf) - display((widgets.HBox([save_button, confirm_button]))) - save_button.on_click(save_scoring) - confirm_button.on_click(confirm_scoring) - # clear_button.on_click(all_clear) - - -def xg_model(X, y): - from sklearn.datasets import make_classification - - num_classes = 3 - X, y = make_classification(n_samples=1000, n_informative=5, n_classes=num_classes) - dtrain = xgb.DMatrix(data=X, label=y) - num_parallel_tree = 4 - num_boost_round = 16 - # total number of built trees is num_parallel_tree * num_classes * num_boost_round - - # We build a boosted random forest for classification here. - booster = xgb.train( - {"num_parallel_tree": 4, "subsample": 0.5, "num_class": 3}, - num_boost_round=num_boost_round, - dtrain=dtrain, - ) - - # This is the sliced model, containing [3, 7) forests - # step is also supported with some limitations like negative step is invalid. - sliced: xgb.Booster = booster[3:7] - - return [_ for _ in booster] - - -def xgb_boost(file_train, file_test): - import xgboost as xgb - - CURRENT_DIR = os.path.dirname(__file__) - dtrain = xgb.DMatrix(os.path.join(CURRENT_DIR, "file_train")) - dtest = xgb.DMatrix(os.path.join(CURRENT_DIR, "file_test")) - param = { - "objective": "binary:logistic", - "booster": "gblinear", - "alpha": 0.0001, - "lambda": 1, - } - watchlist = [(dtest, "eval"), (dtrain, "train")] - num_round = 4 - bst = xgb.train(param, dtrain, num_round, watchlist) - preds = bst.predict(dtest) - labels = dtest.get_label() - print( - "error=%f" - % ( - sum(int(preds[i] > 0.5) != labels[i] for i in range(len(preds))) - / float(len(preds)) - ) - ) - - -def run_smina(dir_name_, config_file_name, **kwargs): - """ Creates folder within the cwd with the name id and sub\ - folder run where it will write sh.Also creates a dump folder\ - where error and output log will be dumped. - - """ - mode = kwargs.get("mode", False) - log = kwargs.get("log", "log.txt") - output = kwargs.get("output", "output.sdf") - local = kwargs.get("local", False) - cpu_num = kwargs.get("cpu", 2) - job_name = kwargs.get("job_name", None) - scoring = kwargs.get("scoring") - custom = kwargs.get("custom", False) - enter_output = kwargs.get("enter_output", True) - enter_log = kwargs.get("enter_log", True) - cluster = kwargs.get("cluster", None) - cluster_grp = ["all.q", "gp1", "gp2"] - if (cluster is not None) and (cluster not in cluster_grp): - return f"Invalid cluster name. Available cluster names: {cluster_grp}" - name_id = config_file_name[:4].lower() # FIX - dir_name_cwd = os.getcwd() - dir_name = os.path.dirname(dir_name_) - PATH = kwargs.get("PATH", False) - dir_name = f"{dir_name}" if PATH else f"{dir_name_cwd}" - - if not os.path.exists(f"{dir_name}/Generated/jobs/{name_id}/run"): - os.makedirs(f"{dir_name}/Generated/jobs/{name_id}/run") - if local is False: - SMINA_PATH = "/share/vina/smina" - with open( - f"{dir_name}/Generated/jobs/{name_id}/run/{name_id}_SMina.sh", "w" - ) as out: - if job_name is None: - job_name = name_id - if job_name[0].isdigit(): - job_name = "S" + job_name - print(f"#$ -N {job_name}", file=out) - print("#$ -V", file=out) - print("#$ -S /bin/bash", file=out) - if cluster is not None: - print(f"#$ -q {cluster}", file=out) - print(f"#$ -pe {cpu_num}cpu {cpu_num}", file=out) - if not os.path.exists(f"{dir_name}/Generated/jobs/{name_id}/dump/"): - os.makedirs(f"{dir_name}/Generated/jobs/{name_id}/dump/") - print(f"#$ -o {dir_name}/Generated/jobs/{name_id}/dump/", file=out) - print(f"#$ -e {dir_name}/Generated/jobs/{name_id}/dump/", file=out) - print("#$ -cwd", file=out) - - # Conditional to write log and output - enter_log = f"--log {log}" if enter_log else "" - enter_output = f"--out {output}" if enter_output else "" - if (mode is True) and (custom is False): - print( - f"{SMINA_PATH} --config" - f" {dir_name}/Generated/smina_input/{config_file_name}" - f" --scoring {scoring} --score_only {enter_log} {enter_output}", - file=out, - ) - - elif (mode is True) and (custom is True): - print( - f"{SMINA_PATH} --config" - f" {dir_name}/Generated/smina_input/{config_file_name}" - f" --custom_scoring {scoring} --score_only {enter_output}" - f" {enter_log}", - file=out, - ) - - elif (mode is False) and (custom is False): - print( - f"{SMINA_PATH} --config" - f" {dir_name}/Generated/smina_input/{config_file_name} --scoring" - f" {scoring} {enter_log} {enter_output} ", - file=out, - ) - - elif (mode is False) and (custom is True): - print( - f"{SMINA_PATH} --config" - f" {dir_name}/Generated/smina_input/{config_file_name}" - f" --custom_scoring {scoring} {enter_log} {enter_output} ", - file=out, - ) - - command = f"qsub {dir_name}/Generated/jobs/{name_id}/run/{name_id}_SMina.sh" - else: - if custom is False: - command = ( - f"smina --config {dir_name}/Generated/smina_input/{config_file_name}" - f" --scoring {scoring} {enter_log} {enter_output}" - ) - else: - command = ( - f"smina --config {dir_name}/Generated/smina_input/{config_file_name}" - f" --custom_scoring {scoring} {enter_log} {enter_output}" - ) - - subprocess.run(command, cwd=f"{dir_name}/Generated/jobs/{name_id}", shell=True) - - return "Succesfully completed." - - -# RECORDS OF ALL SCORING FUNCTION -# SF = [ "ad4_scoring", -# "default", -# "dkoes_fast", -# "dkoes_scoring", -# "dkoes_scoring_old", -# "vina", -# "vinardo", -# ] - -# CSF = ["custom"] # for custom scoring and pass custom_scoring_file=PATH to the function -# Otherwise all the other SF will be run but will be calculate with csf -# for numerous time - - -def smina_run(protein_list, ligand_list, **kwargs): - """Prepares config file for smina when enter proterin and ligand - Above sh_run function must be initialezed before in notebook""" - - SF = kwargs.get("SF") - if not isinstance(SF, list): - return "Supplied SF is not a list" - cluster = kwargs.get("cluster", None) - cluster_grp = ["all.q", "gp1", "gp2"] - if (cluster is not None) and (cluster not in cluster_grp): - return f"Invalid cluster name. Available cluster names: {cluster_grp}" - autobox = kwargs.get("autobox", None) - if isinstance(autobox, list): - autobox = autobox[0] - manual_config = kwargs.get("manual_config", None) - if isinstance(manual_config, list): - manual_config = manual_config[0] - run = kwargs.get("run", False) - mode = kwargs.get("mode", False) - local = kwargs.get("local", False) - job_name = kwargs.get("job_name", None) - NUM_MODES = kwargs.get("num_modes", 10) - EXHAUSTIVE = kwargs.get("exhaustive", 50) - ENERGY_RANGE = kwargs.get("energy_range", 10) - SEED = kwargs.get("seed", None) - AUTOBOX_PAD = kwargs.get("pad", 4) - CPU_NUM = kwargs.get("cpu", 8) - nomatch = kwargs.get("match", False) - OUT_FORMAT = kwargs.get("out_format", "sdf") - custom = kwargs.get("custom", False) - - # if CUSTOM_SCORE is not None: - # CSF_FLAG = True - # else: - # CSF_FLAG = False - - def write_config( - receptor, - ligand, - config_file_name, - output_file_name, - log_file_name, - # scoring=None, # Moved to CLI - ): - """Writes config into new files, if already \ - exist append to it.""" - - # dir_name = os.path.dirname(receptor) - dir_name = os.getcwd() - - if not os.path.exists(f"{dir_name}/Generated/smina_input"): - os.makedirs(f"{dir_name}/Generated/smina_input/") - with open( - f"{dir_name}/Generated/smina_input/{config_file_name}", "w+" - ) as config_file: - - # required config arguments - # ------------------------ - print(f"receptor = {receptor} ", file=config_file) - print(f"ligand = {ligand}", file=config_file) - if autobox is not None: - print(f"autobox_ligand = {autobox}", file=config_file) - print(f"autobox_add = {AUTOBOX_PAD}", file=config_file) - - # Optionals con # MOVED TO CLI - # ------------------------------ - print(f"out = {output_file_name}", file=config_file) - print(f"log = {log_file_name}", file=config_file) - # print(f"scoring = {scoring}", file=config_file) ## change to run in CLI - - # Misc(optional) configs - # ----------------------------- - # if CUSTOM_SCORE is not None: ## MOVED TO CLI - # print(f"custom_scoring = {CUSTOM_SCORE}", file=config_file) - # if SMINA_MODE is not None: - # print(f"{SMINA_MODE}", file=config_file) - - print(f"cpu = {CPU_NUM}", file=config_file) - if SEED is not None: - print(f"\n\nseed = {SEED}", file=config_file) - print(f"exhaustiveness = {EXHAUSTIVE}", file=config_file) - # if CUSTOM_SCORE is None: ## change to run in CLI - print(f"num_modes = {NUM_MODES}", file=config_file) - print(f"energy_range = {ENERGY_RANGE }", file=config_file) - - for scoring in SF: - for protein in protein_list: - for ligand in ligand_list: - protein_dir, protein_id, prot_format = give_id(protein) - ligand_dir, ligand_id, lig_format = give_id(ligand) - if custom: - _dir, _name, _format = give_id(scoring) - scoring = _name - if protein_id[:4].lower() == ligand_id[:4].lower() or (nomatch == True): - output_file_name = ligand_id + f"_output_{scoring}.{OUT_FORMAT}" - log_file_name = ligand_id + f"_log_{scoring}.txt" - if manual_config is None: - config_file_name = ligand_id.lower() + f"_config_{scoring}.txt" - enter_output = True - enter_log = True - write_config( - protein_list[0], - ligand, - config_file_name, - output_file_name, - log_file_name, - ) - else: - ( - manual_config_dir, - manual_config_name, - manual_config_format, - ) = give_id(manual_config) - config_file_name = ( - f"{manual_config_name}.{manual_config_format}" - ) - if "output" in open(manual_config).read(): - enter_output = False - else: - enter_output = True - if "log" in open(manual_config).read(): - enter_log = False - else: - enter_log = True - else: - print("Protein and ligand prefix [4 letter] didnt match") - if custom: - scoring = f"{_dir}/{_name}.{_format}" - if run is True and mode is True: - run_smina( - protein_dir, - config_file_name, - scoring=scoring, - mode=mode, - local=local, - # cpu=CPU_NUM, - # job_name=job_name, - custom=custom, - log=log_file_name, - output=output_file_name, - enter_output=enter_output, - enter_log=enter_log, - ) - elif run is True and mode is False: - run_smina( - protein_dir, - config_file_name, - scoring=scoring, - local=local, - cpu=CPU_NUM, - job_name=job_name, - custom=custom, - output=output_file_name, - log=log_file_name, - enter_output=enter_output, - enter_log=enter_log, - PATH=True, - cluster=cluster, - ) - else: - print( - "Run command was not passed so only created the" - " config and sh file but not executed" - ) - # print(base_name) - return "Succesfully completed." - - -def view_affinity(sorted_RES, keyword): - return pd.DataFrame( - [ - (key, value) - for key, value in sorted_RES.items() - if f"{keyword}" in key.lower() - ], - columns=["Pose", "Affinity"], - ) - - -def smina_output_df(sorted_RES): - return pd.DataFrame( - [(key, value) for key, value in sorted_RES.items()], - columns=["Pose", "Affinity"], - ) - - -def rmsd_matrix( - ref, length=2, key="MATRIX", verbose=False, plot=True, save=False, annot=False -): - _rmsd_list = itertools.combinations(ref, length) - cols = ["Reference", "Pose", "RMSD"] - df = pd.DataFrame(columns=cols) - for count, i in enumerate(_rmsd_list): - ref, conf = list([i[0]]), list([i[1]]) - x = rmsd_calculator(ref, conf, nomatch=True, key=key, verbose=False) - if verbose: - print(f"{count}. {x}", sep=" ", flush=True) - x1, x2, x3 = x.split("\t") - df.loc[count] = [x1, x2, x3] - - mdf = df.pivot(index="Reference", columns="Pose", values="RMSD") - mdf.fillna(0) - mdf = mdf.astype(float) - if plot: - # plt.figure(figsize=[15, 8]) - hmap = sns.heatmap(mdf, annot=annot) - hmap.set_title("RMSD MATRIX") - if save: - if not os.path.exists("./Generated/images/"): - os.makedirs("./Generated/images/") - fig = hmap.figure - image = f"./Generated/images/{key}.jpg" - fig.savefig(image, dpi=600) - print(f"Saved ! {image}") - return mdf diff --git a/src/csfdock/KinaseModules.py b/src/csfdock/KinaseModules.py deleted file mode 100644 index a8661e6..0000000 --- a/src/csfdock/KinaseModules.py +++ /dev/null @@ -1,232 +0,0 @@ -import numpy as np -from urllib.request import urlopen -from PIL import Image -import pandas as pd -from typing import Union, Optional -import io -import os -import subprocess - - -# PUBCHEM RELATED - - -class Attributes: - def __init__(self, CID, format="csv"): - - self.CID = CID - self.format = format - self.CID_URL = "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid" - - @property - def image(self): - IMAGE_API = f"{self.CID_URL}/{self.CID}/record/png" - self.image = Image.open(IMAGE_API) - return self.image - - @property - def description(self): - DESC_API = f"{self.CID_URL}/{self.CID}/description/XML" - return pd.read_xml(urlopen(DESC_API).read().decode("utf-8")) - - @property - def formula(self): - FORMULA_API = ( - f"{self.CID_URL}/{self.CID}/property/MolecularFormula/{self.format}" - ) - return pd.read_csv(io.StringIO(urlopen(FORMULA_API).read().decode("utf-8"))) - - @property - def weight(self): - MOL_WEIGHT_API = ( - f"{self.CID_URL}/{self.CID}/property/MolecularWeight/{self.format}" - ) - return pd.read_csv(io.StringIO(urlopen(MOL_WEIGHT_API).read().decode("utf-8"))) - - @property - def xlog(self): - XLOG_API = f"{self.CID_URL}/{self.CID}/property/XLogP/{self.format}" - return pd.read_csv(io.StringIO(urlopen(XLOG_API).read().decode("utf-8"))) - - @property - def smile(self): - ISO_SMILES_API = ( - f"{self.CID_URL}/{self.CID}/property/IsomericSmiles/{self.format}" - ) - return pd.read_csv(io.StringIO(urlopen(ISO_SMILES_API).read().decode("utf-8"))) - - def structure(self, save=False, *args, **kw): - SDF_API = f"{self.CID_URL}/{self.CID}/SDF" - self.structure = urlopen(SDF_API).read().decode("utf-8") - - if save: - try: - _dir = kw.get("dir", None) - _filename = kw.get("filename", None) - _smile_save = kw.get("smile_save", False) - _dir = f"{_dir}" if _dir else "./data/structures" - _filename = ( - f"{_filename}" - if _filename - else "{}".format(self.structure.partition("\n")[0].strip()) - ) - - if not os.path.isdir(_dir): - os.makedirs(_dir) - FileExists = f"{_dir}/{_filename}.sdf" - if _smile_save: - with open("./data/structures/smiles.smi", "a+") as f: - print(self.smile, file=f) - with open(f"{FileExists}", "w") as w: - w.write(self.structure) - w.close() - return f"save success at {_dir} as {_filename}.sdf" - except Exception as error: - print(error) - return self.structure - - # //TODO Method Chaining - - # def protein(self): - # return f"https://pubchem.ncbi.nlm.nih.gov/protein/{self.CID}" - # def gene(self): - # return f"https://pubchem.ncbi.nlm.nih.gov/gene/{self.CID}" - - -def download_CID_structures(UNIQUE_ID): - error_list = [] - print(f"\r=> Calling API and Downloading Structures...") - for label, content in enumerate(UNIQUE_ID): - try: - t = Attributes(content) - t.structure(dir="./data/structures", smile_save=True, save=True) - if not os.path.exists(f"./data/structures/{content}.sdf"): - error_list.append(content) - except Exception as error: - print(error) - error_list.append(content) - print( - f"\r=> => Saved Successfully Verified: {label- len(error_list)} OK šŸ‘Œ Error:" - f" {len(error_list)}", - end="", - flush=True, - ) - return error_list - - -def concatenate_aid_details(AID: [pd.DataFrame, list], download: bool = False): - - print("Time depends on number and size of files...Please be patient... ") - total = len(AID) - aid_list = [] - error_aid_list = [] - DATA = AID["aid"] if isinstance(AID, pd.DataFrame) else AID - for count, _aid in enumerate(DATA): - try: - _aid_exp_detail = extract_aid_detail(f"{_aid}", download=download) - aid_list.append(_aid_exp_detail) - except Exception as error: - # print(f"\ršŸ”“{error}", sep=' ', end='', flush=True) - error_aid_list.append(_aid) - continue - print( - f"\rSuccess: {len(aid_list)}/{total} OK šŸ‘Œ Error:" - f" {len(error_aid_list)}/{total} šŸ”“ " - f" {'Completed' if {count} != {total} else ''}", - sep=" ", - end="", - flush=True, - ) - - print(f"Parsed : {len(aid_list) + len(error_aid_list)} šŸš¦ ") - - detail_data_type = { - "AID": int, - "Panel Member ID": int, - "SID": int, - "CID": int, - "Bioactivity Outcome": str, - "Target GI": int, - "Target GeneID": int, - "Activity Value [uM]": float, - "Activity Name": str, - "Assay Name": str, - "Bioassay Type": str, - "PubMed ID": str, - "RNAi": str, - } - - main_df = pd.DataFrame() - empty_error = [] - for file in aid_list: - try: - main_df = main_df.append( - pd.read_csv(file, dtype=detail_data_type, engine="python") - ) - # print(main_df) - except: - empty_error.append(file) - continue - - # main_df = pd.concat( - # [pd.read_csv(file, dtype=detail_data_type, engine="python", quoting=3, error_bad_lines=False) for file in aid_list if pd.read_csv(file).empty == False], ignore_index=True, sort=False) - - return main_df, error_aid_list - - -def extract_aid_detail(AID: int, download: bool = False, view: bool = False) -> str: - - _API = f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/assay/aid/{AID}/concise/CSV" - if download: - BASE_DIR = os.getcwd() - path = "src/Data/Data_Source/PubChem" - download_path = os.path.join(BASE_DIR, path) - file = f"{AID}.csv" - if not os.path.exists(download_path): - os.makedirs(download_path) - if not os.path.exists(f"{download_path}/{file}"): - command = f"wget -q {_API} -O {download_path}/{file}" - subprocess.run(command, shell=True) - downloaded_aid = f"{download_path}/{file}" - print(f"\rDownloaded AID:{AID}.csv ", sep=" ", end="", flush=True) - if not view: - return downloaded_aid - f = urlopen(_API) - return (f.read().decode("utf-8")), downloaded_aid - # except Exception as error: - # print(f"\t šŸ”“{AID}_Error: {error}", sep=' ', end='', flush=True) - # raise ValueError('A very specific bad thing happened with request.') - # return -1 - - -def check_non_downloaded(AID: pd.DataFrame) -> list: - """AID is the main list of ids to be downloaded""" - - total = len(AID) - downloaded_list = [] - error_aid_list = [] - BASE_DIR = os.getcwd() - for count, _aid in enumerate(AID["aid"]): - if os.path.exists(f"{BASE_DIR}/Data/PubChem/{_aid}.csv"): - downloaded_list.append(_aid) - else: - error_aid_list.append(_aid) - print( - f"\rSuccessfully Downloaded : {len(downloaded_list)}/{total} OK šŸ‘Œ Error:" - f" {len(error_aid_list)}/{total} šŸ”“ ", - sep=" ", - end="", - flush=True, - ) - return error_aid_list - - -def main(input_file: str) -> pd.DataFrame: - print("started.....") - df = pd.read_csv(input_file, low_memory=False) - download_list, error_list = concatenate_aid_details(df, download=True) - - -if __name__ == "__main": - print("started...") - main(sys.argv[1]) diff --git a/src/csfdock/MolView.py b/src/csfdock/MolView.py deleted file mode 100644 index a728237..0000000 --- a/src/csfdock/MolView.py +++ /dev/null @@ -1,514 +0,0 @@ -import ipywidgets -import py3Dmol -from IPython.display import HTML, display -from ipywidgets import ( - FileUpload, - IntSlider, - fixed, - interact, - interactive, - interactive_output, - widgets, - Layout, -) -from rich.console import Console - -console = Console() -from rdkit import Chem -from rdkit.Chem import AllChem -from csfdock.utils import * - - -class MolView: - """3D molecular view - Args: - mol (AllChem Obj): rdkit return object - size (tuple, optional): window size to display 3d view - style (str, optional): "strick | line | ribbon" - surface (bool, optional): surface view - opacity (float, optional): opacity of view - Returns: - py3dmol: 3d visual - """ - - def __init__(self, *args, **kwargs): - self.molecule = kwargs.get("molecule") - self.size = kwargs.get("size", (800, 600)) - self.style = kwargs.get("style", "stick") - self.surface = kwargs.get("surface", False) - self.opacity = kwargs.get("opacity", 0.5) - self.conformers_list = [] - self.all_select = False - self.default_name = False - self.conformer = None - # header information and display section - self.msg_header = "Molecule Visualizer" - self.msg_header_note = "Use upload option if want to import smiles from a file." - self.msg_upload_info = ( - "Note: Upload option will copy your data on the server @" - " ./Generated/smiles folder. " - ) - self.msg_note = "Note: Use 4letter prefix similar to protein for ligand name" - self.header = widgets.HTML( - value=( - "

{self.msg_header}

" - ) - ) - self.header.add_class("header_bg") - self.upload_info = widgets.HTML( - value=f"{self.msg_upload_info}
{self.msg_note}" - ) - self.output = widgets.Output() - self.style_available = ["line", "stick", "sphere", "carton"] - assert self.style in self.style_available, "Style Not Supported yet" - # button sections - self.add_button = widgets.Button(description="Add") - self.add_button.style.button_color = "lightgreen" - self.add_button.on_click(self.add_mol) - self.remove_button = widgets.Button(description="Remove") - self.remove_button.style.button_color = "salmon" - self.remove_button.on_click(self.remove_mol) - self.save_structure = widgets.Button(description="Save") - self.save_structure.style.button_color = "lightblue" - self.save_structure.on_click(self.write_mol) - self.delete_button = widgets.Button(description="Delete") - self.delete_button.style.button_color = "brown" - self.delete_button.on_click(self.delete_mol) - self.caption = widgets.Label(value="File Browser") - self.upload_button = widgets.FileUpload( - accept="", multiple=True, continuous_update=True - ) - self.upload_button.observe( - self.upload, names=["value", "content", "type", "name", "size"] - ) - self.default_checkbox = widgets.Checkbox( - value=False, description="Default names", disabled=False, indent=False - ) - self.default_checkbox.observe(self.default, names="value") - self.all_checkbox = widgets.Checkbox( - value=False, description="Select All", disabled=False, indent=False - ) - self.all_checkbox.observe(self.all_check, names="value") - self.prefix_in = widgets.Text( - placeholder="Enter atleast 4 letter name", - description="Name: ", - disable=False, - ) - self.server_file_selected = widgets.Text( - description="Selected File: ", disable=False - ) - self.server_file_selected.on_submit(self.upload) - self.prefix_in.on_submit(self.prefix_input) - self.smile_in = widgets.Text( - placeholder="Enter smile", description="Smile Code: ", disable=False - ) - self.smile_in.on_submit(self.smile_input) - self.INPUT_FLAG = False - # Link accordian and file upload - - def __str__(self): - return f"Total Molecules = {len(self.conformer)} {self.msg_header}" - - # widgets function section - def smile_input(self, smi): - self.output.clear_output() - self.prefix_in.layout.visibility = None - self.default_checkbox.layout.visibility = None - self.smile_in.layout.visibility = "hidden" - smile_name = self.smile_in.value if not isinstance(smi, list) else smi - max = 0 if self.conformer is None else len(smi) - 1 - self.index_slider = IntSlider( - value=0, - min=0, - max=max, - step=1, - disable=False, - continuous_update=True, - orientation="horizontal", - layout=Layout(width="100%"), - ) - smile_obj = interactive( - self.smi_viewer, - smile=smile_name, - style=self.style_available, - index=self.index_slider, - ) - # self.smile_in.layout.visibility = "hidden" - return display(smile_obj) - - def prefix_input(self, a): - self.output.clear_output() - prefix = self.prefix_in.value - if len(prefix) < 4: - with self.output: - warnings.warn("Should be atleast 4 letter!.") - exit() - with self.output: - print( - f"Entered name: {prefix} .\nIf structure looks" - " OK!\nYou can Save Now.\nElse delete it using Remove" - ) - - def add_mol(self, x): - # self.output.clear_output() - if self.INPUT_FLAG: - self.molecule = self.conformer - # TODO: None when called through infunction call. - try: - if self.molecule in self.conformers_list: - with self.output: - print("Already exist in the list.") - else: - self.conformers_list.append(self.molecule) - with self.output: - # print(self.conformers_list) - console.print("Successfully Added!.") - except: - with self.output: - print("Not able to Add") - - def remove_mol(self, y): - self.output.clear_output() - prefix = self.prefix_in.value - try: - self.conformers_list.remove(self.molecule) - with self.output: - console.print(" Successfully Remove from the temp list to save.!") - except: - with self.output: - print("Molecule was not found!.") - - def delete_mol(self, m): - # self.output.clear_output() - prefix = self.prefix_in.value - try: - os.remove(f"./Generated/data/{prefix}.sdf") - with self.output: - print(f"./Generated/data/{prefix}.sdf Successfully deleted!.") - except: - with self.output: - print(f"./Generated/data/{prefix}.sdf file not found!.") - - def write_all_mol(self, suffix, prefix): - try: - if all(isinstance(i, str) for i in self.conformer): - conformers = [self.smile2conf(x) for x in self.conformer] - elif isinstance(self.conformer, list): - conformers = self.conformer - conformers = list(filter(None, conformers)) - for conf in conformers: - print( - Chem.MolToMolBlock(conf), - file=open(f"./Generated/data/{prefix}{suffix}.sdf", "w+"), - ) - suffix += 1 - with self.output: - print(f"{len(conformers)} molecules save in ./Generated/data folder. ") - self.write = False - except Exception as e: - with self.output: - print( - f"{e}\nCannot write all molecules\n" - "Presiding str maybe in the smiles code." - ) - - def default(self, m): - # self.output.clear_output() - self.default_name = m["new"] - # print(f"Use default name: {default_name}") - - def all_check(self, n): - # self.output.clear_output() - self.all_select = n["new"] - - # Issue : invalid smiles upload saves the default "C" - # TODO : smiles validity check and warn - def upload(self, z): - self.INPUT_FLAG = False - if isinstance(z, str): - with open(z, "r") as input_file_path: - file_content = input_file_path.readlines() - file_content = [x.strip() for x in file_content] - input_file_dir, input_file_name, file_format = give_id(z) - file_detail = f"{input_file_name}.{file_format}" - self.INPUT_FLAG = True - self.output = widgets.Output() - else: - try: - file_detail = next(iter(self.upload_button.value)) - file_name, file_format = file_detail.rsplit(".", 1) - file_content = self.upload_button.data - file_content = [i.decode("utf-8") for i in file_content] - file_content = "".join(str(i) for i in file_content) - file_content = file_content.split() - except StopIteration as er: - input_file_dir, input_file_name, file_format = give_id(z.value) - file_content = open(z.value).readlines() - file_content = [x.strip() for x in file_content] - file_detail = f"{input_file_name}.{file_format}" - # print(file_content) - # print(file_content) - SDF = False - if file_format.lower() == "sdf": - # print(file_content) - m = Chem.MolFromMolBlock(file_content) - smiles = {} - self.smile_in.layout.visibility = "hidden" - self.view(m) - SDF = True - if self.INPUT_FLAG: - smiles = file_content - else: - try: - temp = [file_content.split("\r\n")] - smiles = [ - item - for subitem in temp - for item in subitem - if len(item.rstrip()) is not None - ] - except AttributeError: - smiles = [item for item in file_content] - # print(temp) - - smiles = list(filter(None, smiles)) - if not os.path.exists(f"./Generated/upload/"): - os.makedirs(f"./Generated/upload/") - self.all_checkbox.layout.visibility = None - self.prefix_in.layout.visibility = None - self.default_checkbox.layout.visibility = None - try: - with self.output: - with open( - f"./Generated/upload/{file_detail}", "w+" - ) as server_upload_file: - if SDF: - print(file_content, file=server_upload_file) - return - if isinstance(smiles, list): - for smi in smiles: - print(smi, file=server_upload_file) - self.conformer = [s for s in smiles] - # self.conformer = smiles - self.smile_input(self.conformer) - print(f"{file_detail} successfully uploaded.") - except Exception as e: - print(e) - - # todo // override default or use both custom and default.. - def write_mol(self, z): - try: - # self.output.clear_output() - prefix = self.prefix_in.value - self.write = True - if len(prefix) < 4 and self.default_name is False: - with self.output: - # warnings.warn("Check if name is entered or not and should be atleas 4 letter!.") - print( - "You need to add and enter either name or select default name." - ) - else: - if not os.path.exists("./Generated/data"): - os.makedirs("./Generated/data") - if len(prefix) < 4 and self.default_name is True: - suffix = 0 - prefix = "small_molecule" - while os.path.exists(f"./Generated/data/{prefix}.sdf"): - suffix += 1 - prefix = f"small_molecule{suffix}" - if self.all_select is False: - print( - Chem.MolToMolBlock(self.conformers_list[0]), - file=open(f"./Generated/data/{prefix}.sdf", "w+"), - ) - else: - self.write_all_mol(suffix, prefix) - elif len(prefix) >= 4 and self.default_name is True: - suffix = 0 - prefix = f"{prefix}_small_molecule" - while os.path.exists(f"./Generated/data/{prefix}.sdf"): - suffix += 1 - prefix = f"{prefix}_small_molecule{suffix}" - if self.all_select is False: - suffix += 1 - prefix = f"{prefix}_small_molecule{suffix}" - print( - Chem.MolToMolBlock(self.conformers_list[0]), - file=open(f"./Generated/data/{prefix}.sdf", "w+"), - ) - self.write = False - else: - self.write_all_mol(suffix, prefix) - if self.write: - print( - Chem.MolToMolBlock(self.conformers_list[0]), - file=open(f"./Generated/data/{prefix}.sdf", "w+"), - ) - with self.output: - print(f"{prefix} saved in ./Generated/data/{prefix}.sdf.") - except Exception as er: - with self.output: - print( - f"{er}\nSorry, check input!Name \nTip: Need to Add/Select All" - " first." - ) - - def display(self): - """Displays widgets in jupyter notebook""" - self.output.clear_output() - display(self.header, self.upload_info) - file_browser = ServerPath() - display(widgets.VBox([self.caption, file_browser.accord])) - display(self.server_file_selected) - # Link upload and file browser - display( - ( - widgets.HBox( - ( - self.add_button, - self.remove_button, - self.save_structure, - self.delete_button, - self.upload_button, - self.all_checkbox, - ) - ) - ) - ) - # elf.all_checkbox.layout.visibility = "hidden" - # self.prefix_in.layout.visibility = "hidden" - # self.default_checkbox.layout.visibility = "hidden" - display(widgets.HBox([self.prefix_in, self.default_checkbox, self.smile_in])) - display(self.output) - # display(widgets.HBox([self.upload_button, self.all_checkbox])) - # display(self.smile_in, self.output) - # return self.view(self.molecule) - - def view(self, mol): - """Creates py3dmol view - Args: - mol (rdkit obj): mol object from rdkit - """ - try: - molecular_block = Chem.MolToMolBlock(mol) - viewer = py3Dmol.view(width=self.size[0], height=self.size[1]) - viewer.addModel(molecular_block, "mol") - viewer.setStyle({self.style: {}}) - if self.surface: - viewer.addSurface(py3Dmol.SAS, {"opacity": self.opacity}) - viewer.zoomTo() - viewer.show() - except Exception as er: - with self.output: - print(er, "in view section") - - def smi_viewer(self, smile, *args, **kwargs): - """Converts smile to py3dmol view - Args: - smile (str): Valid smiles codes - *args: Description - **kwargs: style - Returns: - TYPE: Description - """ - self.style = kwargs.get("style", "stick") - index = kwargs.get("index", 0) - # self.entered_smiles = kwargs.get("smiles") - # self.entered_smiles = self.smile_in.value - try: - self.molecule = self.smile2conf(smile) - print("+++++++++++++++++View+++++++++++++++++++++") - print("Note: Hydrogens are added and MMFF Optimized.") - # print(f"{Chem.MolToMolBlock(conf)}") - print("++++++++++++++++++++++++++++++++++++++++++") - # print(AllChem.EmbedMolecule(conf,randomSeed=0xf00d)) - return self.view(self.molecule) - except Exception as er: - with self.output: - print(er, "pp") - - def smile2conf(self, smiles): - """Convert SMILES to rdkit.Mol with 3D coordinates - Args: - smiles (str): smiles code - Returns: - AllChem.Mol: 3d mol object for visualization - """ - try: - mol = Chem.MolFromSmiles(smiles) - mol.SetProp("_Name", f"{smiles}") - if mol is None: - return - mol = Chem.AddHs(mol) - # print(Chem.MolToMolBlock(mol)) - AllChem.EmbedMolecule(mol) - AllChem.MMFFOptimizeMolecule(mol, maxIters=100) - return mol - except Exception as err: - with self.output: - print(err) - - -class ServerPath(MolView): - def __init__(self, start_dir=".", select_file=True): - super().__init__() - self.file = None - self.select_file = select_file - self.cwd = start_dir - self.select = ipywidgets.SelectMultiple( - value=(), rows=10, description="", disabled=False - ) - self.accord = ipywidgets.Accordion(children=[self.select]) - self.accord.selected_index = None # Start closed (showing path only) - self.refresh(".") - self.select.observe(self.on_update, "value") - # widget 1 - - def on_update(self, change): - if len(change["new"]) > 0: - self.refresh(change["new"][0]) - - def refresh(self, item): - path = os.path.abspath(os.path.join(self.cwd, item)) - if os.path.isfile(path): - if self.select_file: - self.accord.set_title(0, path) - self.file = path - self.accord.selected_index = None - else: - self.select.value = () - else: # os.path.isdir(path) - self.file = None - self.cwd = path - # ipywidgets list of files and dirs - keys = ["šŸ“.."] - for item in os.listdir(path): - if item[0] == ".": - continue - elif os.path.isdir(os.path.join(path, item)): - keys.append("šŸ“" + item) - else: - keys.append(item) - # Sort and create list of output values - keys.sort(key=str.lower, reverse=True) - value = [] - for k in keys: - if k[0] == "šŸ“": - value.append(k[1:]) # strip off brackets - else: - value.append(k) - # Update widget - self.accord.set_title(0, path) - self.select.options = list(zip(keys, value)) - with self.select.hold_trait_notifications(): - self.select.value = () - if self.file is not None: - # print(self.file) - self.upload(self.file) - # self.smile_in.layout.visibility = "hidden" - # self.prefix_in.layout.visibility = None - # self.default_checkbox.layout.visibility = None - # return self.file diff --git a/src/csfdock/Project.py b/src/csfdock/Project.py deleted file mode 100644 index dba48fe..0000000 --- a/src/csfdock/Project.py +++ /dev/null @@ -1,350 +0,0 @@ -import re -from collections import Counter -from os.path import join, splitext -from rdkit import Chem -from rich.console import Console -from rich.table import Table - -from csfdock.DVisualize import * -from csfdock.MolView import * -from csfdock.utils import get, PDBParse - -console = Console() -blue_console = Console(style="white on blue") - - -class ProjectStart(MolView, DVisualize): - """Creates a Project in Optimizing Scores and Docking - Args: - *args: Description - **kwargs: Description - """ - - def __init__(self, *args, **kwargs): - self.PROJECT_DIR = kwargs.get("path", os.getcwd()) - super().__init__(*args, **kwargs) - self.AA = [ - "ALA", - "ARG", - "ASN", - "ASP", - "CYS", - "GLN", - "GLU", - "GLY", - "HIS", - "ILE", - "LEU", - "LYS", - "MET", - "PHE", - "PRO", - "SER", - "THR", - "TRP", - "TYR", - "VAL", - ] - - def SetFolders(self, *args, **kwargs): - actual_cwd = os.getcwd() - self.PROJECT_DIR = kwargs.get("path", os.getcwd()) - for i in args: - self.PROJECT_DIR = i - if self.PROJECT_DIR == ".": - return console.print(f"Project Base Directory: {actual_cwd}") - if actual_cwd != self.PROJECT_DIR: - try: - os.chdir(self.PROJECT_DIR) - working_dir = self.PROJECT_DIR - except Exception as err: - working_dir = f"{actual_cwd}/{self.PROJECT_DIR}" - os.chdir(working_dir) - console.print(f"Project Base Directory: {working_dir}") - - def ProjectTree(self, *args, **kwargs): - path = os.getwd() if self.PROJECT_DIR is None else self.PROJECT_DIR - verbose = kwargs.get("verbose",) - self.directory_tree(path, verbose=verbose) - - def __actual_dir_name(self, path, root=None): - """helper function for directory tree generation""" - if root is not None: - path = os.path.join(root, path) - result = os.path.basename(path) - if os.path.islink(path): - realpath = os.readlink(path) - result = f"{os.path.basename(path)} -> {realpath}" - return result - - def directory_tree(self, startpath, verbose=True, depth=-1): - """Tree view of the project directory tree" - directory_tree(path) - """ - supported_file_format = {"txt", "pdb", - "pdbqt", "sdf", "csv", "excel", "pickle"} - console.print( - f"Supported File Format :{supported_file_format}", style="bold green") - table = self.__create_table("bold magenta", "File Type", "Total Files") - c = Counter( - [splitext(i)[1][1:] for i in glob(join(startpath, "**"), - recursive=True) if splitext(i)[1][1:] in supported_file_format] - ) - console.print("============Details of files====================") - for ext, count in c.most_common(): - table.add_row( - f"[bold green]{str(ext)}[/bold green]", f"[red]{str(count)}[/red]" - ) - console.print(table) - if verbose: - console.print("============Directory Tree====================") - prefix = 0 - if startpath != "/": - if startpath.endswith("/"): - startpath = startpath[:-1] - prefix = len(startpath) - for root, dirs, files in os.walk(startpath): - level = root[prefix:].count(os.sep) - if depth > -1 and level > depth: - continue - indent = subindent = "" - if level > 0: - indent = "| " * (level - 1) + "|-- " - subindent = "| " * (level) + "|-- " - print( - f"{indent}šŸ“‚{self.__actual_dir_name(root)}/" - ) # print dir only if symbolic link; otherwise, will be printed as root - for d in dirs: - if not d.startswith("."): - if os.path.islink(os.path.join(root, d)): - print( - f"{subindent}šŸ“ƒ{self.__actual_dir_name(d, root=root)}") - for f in files: - _format = f.rsplit(".")[-1] - if _format in supported_file_format: - print(f"{subindent}šŸ“ƒ{self.__actual_dir_name(f, root=root)}") - else: - pass - - def __receptor_contents_print(self, receptor, receptor_content): - number_of_residues = [] - number_of_membrane_molecules = [] - number_of_water_molecule = 0 - present_ions = [] - number_of_chains = [] - number_of_ligands = [] - number_of_ligands_atoms = 0 - for index, line in enumerate(receptor_content): - if line.startswith("ATOM"): - if line[17:20] in self.AA or line.split()[-1] == "PROA": - number_of_chains.append(line[21]) - number_of_residues.append(line[22:26]) - elif line.split()[-1] == "MEMB": - number_of_membrane_molecules.append(line[22:26]) - elif line.split()[-1] == "TIP3" or line[17:20] == "HOH": - number_of_water_molecule += 1 - elif line.split()[-1] == "HETA": - number_of_ligands.append(line.split()[3]) - else: - present_ions.append(line[17:20]) - elif line.startswith("HETATM"): - if line[17:20] == "HOH": - number_of_water_molecule += 1 - elif len(line[17:20].strip()) < 3: - present_ions.append(line[17:20]) - elif len(line[17:20].strip()) == 3: - # number_of_ligands.append(line.split()[3]) - number_of_ligands.append(line[21]) - number_of_ligands_atoms += 1 - if not present_ions: - max_number_of_single_ions = 0 - else: - max_number_of_single_ions = max( - present_ions, key=present_ions.count) - types_of_ions = set(present_ions) if present_ions else 0 - number_of_membrane_molecules = ( - number_of_membrane_molecules[-1] - if len(number_of_membrane_molecules) > 1 and not None - else 0 - ) - table = self.__create_table("bold blue", "Record", "Counts") - table.add_row("[bold green]Chains:[/]", - f" {len(set(number_of_chains))}") - table.add_row("[bold green]Ligands:[/]", - f"{len(set(number_of_ligands))}") - try: - table.add_row( - "[bold green]Number of ligand atoms :[/]", - f"{number_of_ligands.count(max(number_of_ligands, key=number_of_ligands.count))}", - ) - except ValueError: - table.add_row("[bold green]Number of ligand atoms :[/]", "0") - - table.add_row("[bold green]Protein residues:[/]", - f"{number_of_residues[-1]}") - - try: - table.add_row( - "[bold green]Lipids molecules :[/]", f"{number_of_membrane_molecules}" - ) - except ValueError: - table.add_row("[bold green]Lipids molecules :[/]", "0") - - try: - table.add_row( - "[bold green]Water molecules :[/]", f" {number_of_water_molecule}" - ) - except ValueError: - table.add_row("[bold green]Water molecules :[/]", "0") - try: - table.add_row("[bold green]Ions:[/]", f"{len(present_ions)}") - table.add_row("[bold green]Ion types :[/]", f"{types_of_ions}") - except ValueError: - table.add_row("[bold green]Ions:[/]", "0") - table.add_row("[bold green]Ion types :[/]", "None") - - console.print(f"\nFor[bold red] {receptor}[/]:") - console.print(table) - - def LoadReceptor(self, *args, native=True, verbose=True, **kwargs): - found_what = kwargs.get("key", "Receptor") - - for i in args: - receptor = i - - info = True - if not os.path.exists(receptor): - try: - _receptor = file_search(type="pdb", target=receptor) - if len(_receptor) == 1: - console.print(f"{found_what}: [bold]{_receptor[0]}[/bold]") - info = False - receptor = _receptor[0] - elif len(_receptor) > 1: - print(f"{found_what} {_receptor}") - _receptor_number = int( - input(f"Which {found_what} do you like to select: ") - ) - receptor = _receptor[_receptor_number] - console.print(f"Select {found_what} : {receptor}") - else: - print(f"No {found_what} found in Local directory") - _download = input( - f"Would you like to download the {receptor} from RCSB (Y/n)? " - ) - confirm = ["yes", "y", "YES", "Y"] - if _download in confirm: - download_protein_msg = get(receptor) - # TODO path pass forward - console.print(download_protein_msg) - receptor = f"./Generated/{receptor}.pdb" - else: - console.print( - f"šŸ˜ž {found_what}: [bold red]{receptor} [/]to process" - " further.." - ) - except Exception as er: - print(er) - - if info and (native == False): - console.print(f"{found_what}: [bold]{receptor}[/bold]") - info = False - receptor_content = open(receptor, "r") - receptor_content = receptor_content.readlines() - if verbose: - self.__receptor_contents_print(receptor, receptor_content) - # console.print(self.receptor) - if native: - self.receptor = receptor - return receptor - - def __create_table(self, header_style, arg1, arg2): - result = Table(show_header=True, header_style=header_style) - result.add_column(arg1, style="dim", width=40) - result.add_column(arg2, justify="right") - return result - - def LoadLigand(self, *args, **kwargs): - for arg in args: - self.ligand = arg - *_, _file_format= give_id(self.ligand) - if _file_format.lower() == "sdf": - inf = open(f"{self.ligand}", "rb") - with Chem.ForwardSDMolSupplier(inf) as fsuppl: - for mol in fsuppl: - if mol is None: - continue - console.print(f"{self.ligand} has {mol.GetNumAtoms()} atoms") - elif _file_format.lower() == "pdb": - mol = Chem.MolFromPDBFile(self.ligand, sanitize=False) - console.print(f"{self.ligand} has {mol.GetNumAtoms()} atoms") - else: - return "Unknow ligand file format" - - self.ligand_export = mol - return self.ligand_export - - def SaveComplex(self, **kwargs): - ligand = kwargs.get("lig", None) - receptor = kwargs.get("pro", None) - lipid = kwargs.get("lipid", None) - out_file = kwargs.get("out", "complex_out.pdb") - ligand_mol = Chem.MolToPDBBlock(self.ligand_export, flavor=32) - # print(out_file) - *_, structure_format = give_id(self.receptor) - lipid = lipid if lipid is not None else self.lipid - receptor = receptor if receptor is not None else self.receptor - ligand = ligand if ligand is not None else self.ligand - - try: - if structure_format.lower() == "sdf": - receptor_mol = Chem.MolFromMolFile(receptor, removeHs=False) - elif structure_format.lower() == "pdb": - receptor_mol, *_ = PDBParse(receptor) - - except ValueError as er: - console.print(f"{self.receptor} Error : {er}") - blue_console.print("Not able to parse..") - - write_list = [receptor_mol, ligand_mol] - try: - _prot, lipid_mol, _tip, _lig = PDBParse(lipid) - write_list.append(lipid_mol) - - except Exception as er: - blue_console.print(f"Cannot able to parse {lipid}.\n Error: {er}") - blue_console.print(f"Not writing Lipid in the {out_file}") - - self.__write_to_file(write_list, out_file, check=True) - - # self.__write_to_file(lipid_mol, out_file) - # self.__write_to_file(Chem.MolToPDBBlock(self.ligand_export, flavor=32), out_file) - - def __write_to_file(self, content, filename, check=False): - if check: - count = 0 - _file, _format = filename.rsplit(".") - while os.path.exists(filename): - count += 1 - filename = f"{_file}_{count}.{_format}" - with open(filename, "a+") as f: - if isinstance(content, list): - for _ in content: - for line in _: - print(line, end="", file=f) - else: - for line in content: - print(line, end="", file=f) - console.print(f"{filename} Saved Successfully!", style="bold green") - - def __call__(self): - raise TypeError( - 'Project must be accessed through "instance=ProjectStart()".') - - def __str__(self): - ligand = self.ligand if self.ligand != None else "Not implemented" - receptor = self.receptor if self.receptor != None else "Not implemented" - lipid = self.lipid if self.lipid != None else "Not implemented" - return ( - f"Project : \t\nProtein: {receptor}\t\nligand :{ligand}\t\nLipid : {lipid} " - ) diff --git a/src/csfdock/Rahul-iikwon.sublime-project b/src/csfdock/Rahul-iikwon.sublime-project deleted file mode 100644 index 2e5ea0c..0000000 --- a/src/csfdock/Rahul-iikwon.sublime-project +++ /dev/null @@ -1,8 +0,0 @@ -{ - "folders": - [ - { - "path": "Z:\\home\\lab09\\SPACE\\Rahul-Iikwon" - } - ] -} diff --git a/src/csfdock/Rahul-iikwon.sublime-workspace b/src/csfdock/Rahul-iikwon.sublime-workspace deleted file mode 100644 index c1f132c..0000000 --- a/src/csfdock/Rahul-iikwon.sublime-workspace +++ /dev/null @@ -1,792 +0,0 @@ -{ - "auto_complete": - { - "selected_items": - [ - [ - "Except", - "Exception as e:\n\traise ValueError" - ], - [ - "Da", - "DataFrame" - ], - [ - "file", - "file_path" - ], - [ - "isins", - "isinstance(file_path, str):\n\tfile" - ], - [ - "kwargs", - "kwargs.get(\"match\", False)" - ], - [ - "aut", - "autobox[0" - ], - [ - "verbose", - "verbose = False" - ], - [ - "Data", - "DataFrame" - ], - [ - "iter", - "itertools.combinations" - ], - [ - "write", - "write_affinity" - ], - [ - "patter", - "pattern_id" - ], - [ - "start", - "startswith(\"MODEL\"):\n\ts" - ], - [ - "elif", - "elif algo_format.lower()" - ], - [ - "manu", - "manual_config_format" - ], - [ - "cluster", - "cluster=cluster,\n )\n else:" - ], - [ - "dir_name", - "dir_name_cwd" - ], - [ - "dir", - "dir_name" - ], - [ - "chec", - "check_names(manual_" - ], - [ - "enter_out", - "enter_output}\" if enter_output else \"\"" - ], - [ - "enter", - "enter_output" - ], - [ - "kwaRG", - "kwargs.get(\"" - ], - [ - "output", - "output= output_file_name" - ], - [ - "Inva", - "Invalid cluster name\"\n" - ], - [ - "cl", - "cluster_grp" - ], - [ - "CUSTOM_SCORE", - "CUSTOM_SCORE is not None and" - ], - [ - "C", - "CUSTOM_SCORE" - ], - [ - "Gene", - "Generated" - ], - [ - "Ge", - "Generated/" - ], - [ - "file_", - "file_format = \"" - ], - [ - "Va", - "ValueError:\n\ttable.add_row(\"[bold green]" - ], - [ - "except", - "except ValueError:\n\t table.add_row(" - ], - [ - "add_", - "add_row" - ], - [ - "tab", - "table.add_" - ], - [ - "excep", - "except ValueError:\n\t" - ], - [ - "Mol", - "MolView" - ], - [ - "Ser", - "ServerPath" - ], - [ - "Instacne", - "isinstance" - ], - [ - "Docking", - "DockingTools" - ], - [ - "KinaseMod", - "KinaseModule import *" - ], - [ - "setSty", - "setStyle" - ], - [ - "element", - "element}\"}, {\"stick\": {\"colorscheme\": self.lig_color}" - ], - [ - "Excep", - "Exception as e:\n\t" - ], - [ - "config", - "config_file_name)\n" - ], - [ - "mana", - "manual_config" - ], - [ - "manual", - "manual_config=" - ], - [ - "clear", - "clear_output()" - ], - [ - "msg", - "msg_header" - ], - [ - "default", - "default_checkbox" - ], - [ - "sty", - "style_avaiable" - ], - [ - "view_obj", - "view_object = self.view" - ], - [ - "smi2", - "smi2viewer" - ], - [ - "entered", - "entered_smiles" - ], - [ - "interac", - "interactive" - ], - [ - "get", - "get('size')\n" - ], - [ - "__init", - "__init__" - ], - [ - "self", - "self.box_size_z = 15\nself" - ], - [ - "bo", - "box_size_y" - ], - [ - "box", - "box_size_x" - ], - [ - "el", - "elif key == 'box_size_z':" - ], - [ - "box_cen", - "box_center_z" - ], - [ - "box_center", - "box_center_y" - ], - [ - "ligand", - "ligand = ligand\n" - ], - [ - "inte", - "interactive" - ] - ] - }, - "buffers": - [ - { - "file": "Project.py", - "settings": - { - "buffer_size": 10100, - "encoding": "UTF-8", - "line_ending": "Unix" - } - }, - { - "file": "DockingTools.py", - "settings": - { - "buffer_size": 37840, - "encoding": "UTF-8", - "line_ending": "Windows" - }, - "undo_stack": - [ - [ - 6475, - 1, - "insert", - { - "characters": "\\n" - }, - "AgAAAOw7AAAAAAAA7TsAAAAAAAAAAAAA7TsAAAAAAADuOwAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADsOwAAAAAAAOw7AAAAAAAAAAAAAAAA8L8" - ], - [ - 6479, - 1, - "black", - null, - "AQAAAAAAAAAAAAAAzpMAAAAAAADOkwAAIyBGdW5jdGlvbnMgaW52bG92ZWQgaW4gZG9ja2luZyB1c2luZyBzbWluYQoKaW1wb3J0IHJlCmltcG9ydCBvcwppbXBvcnQgc3VicHJvY2VzcwppbXBvcnQgaXB5d2lkZ2V0cwppbXBvcnQgaXRlcnRvb2xzCmZyb20gaXB5d2lkZ2V0cyBpbXBvcnQgKAogICAgRmlsZVVwbG9hZCwKICAgIEludFNsaWRlciwKICAgIGZpeGVkLAogICAgaW50ZXJhY3RpdmUsCiAgICBpbnRlcmFjdGl2ZV9vdXRwdXQsCiAgICB3aWRnZXRzLAogICAgTGF5b3V0LAopCmZyb20gSVB5dGhvbi5kaXNwbGF5IGltcG9ydCBIVE1MLCBkaXNwbGF5CmltcG9ydCBwYW5kYXMgYXMgcGQKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQKZnJvbSBtYXRwbG90bGliLm9mZnNldGJveCBpbXBvcnQgQW5jaG9yZWRUZXh0CmZyb20gY3NmZG9jay51dGlscyBpbXBvcnQgZ2l2ZV9pZApmcm9tIHNrbGVhcm4uZGF0YXNldHMgaW1wb3J0IG1ha2VfY2xhc3NpZmljYXRpb24KZnJvbSBza2xlYXJuLmxpbmVhcl9tb2RlbCBpbXBvcnQgTGluZWFyUmVncmVzc2lvbiwgTG9naXN0aWNSZWdyZXNzaW9uCmZyb20gc2tsZWFybi5tZXRyaWNzIGltcG9ydCBhY2N1cmFjeV9zY29yZSwgcm9jX2F1Y19zY29yZSwgcm9jX2N1cnZlCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IEtGb2xkLCB0cmFpbl90ZXN0X3NwbGl0CmZyb20gc2NpcHkuc3BlY2lhbCBpbXBvcnQgZXhwaXQKaW1wb3J0IHNlYWJvcm4gYXMgc25zCmZyb20gcmljaC5jb25zb2xlIGltcG9ydCBDb25zb2xlCgpjb25zb2xlID0gQ29uc29sZSgpCgoKZGVmIGFkZF9oeWRyb2dlbihsaXN0X3h5eik6CiAgICAiIiJBIGZ1bmN0aW9uIHRvIGFkZCBoeWRyb2dlbiBhdG9tcyB1c2luZwogICAgb3BlbmJhYmVsIHRvIGEgbGlzdCBvZiBtb2xlY3VsZXMuCiAgICBDcmVhdGVzIHN1YiBmb2xkZXIgInByb3RlaW5faWQvcG9zZXMiIGluCiAgICBpbnB1dCBmaWxlIGRpcmVjdG9yeSBhbmQgZHVtcCB0aGVyZS4KICAgIEFyZ3M6CiAgICAgICAgbGlzdF94eXogKGxpc3QpOiBNb2xlY3VsZXMgdG8gYmUgYWRkZWQgSHlkcm9nZW4KICAgIFJldHVybnM6CiAgICAgICAgeHl6OiBzYXZlcyB4eXogaW4gcHJvdGVpbl9pZC9wb3NlcyB3aXRoIHN1ZmZpeCAiX2FkZEhzLnh5eiIKICAgICIiIgogICAgIyBGT1IgU0VSVkVSIG9yIHVzZSBvd24gcGF0aC4uCiAgICBPQkFCRUxfUEFUSCA9ICIvc2hhcmUvb3BlbmJhYmVsLTMuMS4xL2Jpbi9vYmFiZWwiCiAgICBmb3IgaSBpbiBsaXN0X3h5ejoKICAgICAgICBkaXJfbmFtZSwgaWQsIGZpbGVfZm9ybWF0ID0gZ2l2ZV9pZChpKQogICAgICAgIGlmIG5vdCBvcy5wYXRoLmV4aXN0cyhmIntkaXJfbmFtZX0vYWRkSC97aWRbOjRdfSIpOgogICAgICAgICAgICBvcy5tYWtlZGlycyhmIntkaXJfbmFtZX0vYWRkSC97aWRbOjRdfSIpCiAgICAgICAgY29tbWFuZCA9IGYie09CQUJFTF9QQVRIfSB7aX0gLU8ge2Rpcl9uYW1lfS9hZGRIL3tpZFs6NF19L3tpZH1fYWRkSHMueHl6IC1oIgogICAgICAgIHN1YnByb2Nlc3MucnVuKGNvbW1hbmQsIGN3ZD1mIntkaXJfbmFtZX0iLCBzaGVsbD1UcnVlKQogICAgcmV0dXJuICJTdWNjZXNzZnVsbHkgQ29tcGxldGVkLiIKCgpkZWYgcm1zZF9jYWxjdWxhdG9yKHJlZmVyZW5jZSwgcG9zZXNfbGlzdCwga2V5PU5vbmUsIG5vbWF0Y2g9RmFsc2UsIHZlcmJvc2U9VHJ1ZSk6CiAgICAiIiJDYWxjdWxhdGVzIFJNU0QgYmV0d2VlbiByZWZlcmVuY2UgYW5kIHBvc2UgdXNpbmcgb2JybXMgaW4gc2VydmVyLgogICAgQXJnczoKICAgICAgICByZWZlcmVuY2UgKHJlZmVyZW5jZSBtb2xlY3VsZSk6IFJlZmVyZW5jZSBtb2xlY3VsZQogICAgICAgIHBvc2VzX2xpc3QgKHRlc3RfcG9zZXMpOiBMaXN0IG9mIHBvc2VzIHRvIHRlc3Qgd2l0aCB0aGUgcmVmZXJlbmNlLgogICAgICAgIGtleT0gQW55IHN1ZmZpeCB0byBhZGQgdG8gdGhlIG5hbWUgb2YgdGhlIGZpbGUuCiAgICBSZXR1cm5zOgogICAgICAgIFR4dCBmaWxlOiBXcml0ZXMgcmVmZXJlbmNlLCBwb3NlcyBuYW1lIGFuZCBSTVNEIHRvIGEgbG9nIGZpbGUuCiAgICAiIiIKICAgIGNvdW50ID0gMAogICAgdHJ5OgogICAgICAgIGZvciByZWYgaW4gcmVmZXJlbmNlOgogICAgICAgICAgICBfLCByZWZfbmFtZSwgX2RpciA9IGdpdmVfaWQocmVmKQogICAgICAgICAgICByZWZfaWQgPSBvcy5wYXRoLmJhc2VuYW1lKHJlZikKICAgICAgICAgICAgcmVmX2RpciA9IG9zLnBhdGguZGlybmFtZShyZWYpCiAgICAgICAgICAgIHJlZl9kaXIgPSBvcy5wYXRoLmRpcm5hbWUocmVmX2RpcikKICAgICAgICAgICAgZm9yIHBvc2UgaW4gcG9zZXNfbGlzdDoKICAgICAgICAgICAgICAgIHBvc2VfaWQgPSBvcy5wYXRoLmJhc2VuYW1lKHBvc2UpCiAgICAgICAgICAgICAgICBpZiByZWZfaWRbOjRdLmxvd2VyKCkgPT0gcG9zZV9pZFs6NF0ubG93ZXIoKSBvciAobm9tYXRjaCBpcyBUcnVlKToKICAgICAgICAgICAgICAgICAgICBjb21tYW5kID0gZiIvc2hhcmUvb3BlbmJhYmVsLTMuMS4xL2Jpbi9vYnJtcyB7cmVmfSBcdCB7cG9zZX0iCiAgICAgICAgICAgICAgICAgICAgcG9zZV9pZCA9IG9zLnBhdGguYmFzZW5hbWUocG9zZSkKICAgICAgICAgICAgICAgICAgICBybXNkX291dCA9IHN1YnByb2Nlc3MucnVuKAogICAgICAgICAgICAgICAgICAgICAgICBjb21tYW5kLAogICAgICAgICAgICAgICAgICAgICAgICBjd2Q9ZiJ7cmVmX2Rpcn0iLAogICAgICAgICAgICAgICAgICAgICAgICBjYXB0dXJlX291dHB1dD1UcnVlLAogICAgICAgICAgICAgICAgICAgICAgICB0ZXh0PVRydWUsCiAgICAgICAgICAgICAgICAgICAgICAgIHNoZWxsPVRydWUsCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgIGlmIG5vdCBvcy5wYXRoLmV4aXN0cyhmIntyZWZfZGlyfS9yZXN1bHQvUk1TRCIpOgogICAgICAgICAgICAgICAgICAgICAgICBvcy5tYWtlZGlycyhmIntyZWZfZGlyfS9yZXN1bHQvUk1TRCIpCiAgICAgICAgICAgICAgICAgICAgd2l0aCBvcGVuKAogICAgICAgICAgICAgICAgICAgICAgICBmIntyZWZfZGlyfS9yZXN1bHQvUk1TRC97cmVmX2lkWzo0XX1fUk1TRF97a2V5fS50eHQiLCAiYSsiCiAgICAgICAgICAgICAgICAgICAgKSBhcyB3cml0ZV9vdXQ6CiAgICAgICAgICAgICAgICAgICAgICAgIHJtc2RfcmVzdWx0ID0gZiJ7cG9zZV9pZC5sb3dlcigpfVx0IiArIHN0cihybXNkX291dC5zdGRvdXQpCiAgICAgICAgICAgICAgICAgICAgICAgIHRlbXBfcm1zZCA9IGYie3JlZl9uYW1lfVx0e3Bvc2VfaWQubG93ZXIoKX1cdCIgKyBzdHIoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmbG9hdChybXNkX291dC5zdGRvdXQuc3BsaXQoKVstMV0pCiAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnQocm1zZF9yZXN1bHQsIGZpbGU9d3JpdGVfb3V0KQogICAgICAgICAgICAgICAgICAgIGNvdW50ICs9IDEKICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgcmV0dXJuICJObyBtYXRjaGVkIGhlYWRlciBmb3VuZCIKICAgICAgICBpZiB2ZXJib3NlOgogICAgICAgICAgICBwcmludChmIlRvdGFsIG9mIHtjb3VudH0gcm1zZCBjYWxjdWxhdGVkLiIpCiAgICAgICAgcmV0dXJuIHRlbXBfcm1zZAogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlcjoKICAgICAgICBwcmludChlcikKCgpkZWYgcm1zZF9tYXRyaXhfcHJlcChybXNkX3Jlc3VsdHMsIHByaW50X2l0PVRydWUsIHJldHVybl9kZj1UcnVlLCBvbmx5X2Jlc3Q9RmFsc2UpOgogICAgZGVmYXVsdF9zY29yaW5nXyA9IHt9CiAgICBhZDRfc2NvcmluZ18gPSB7fQogICAgZGtvZXNfZmFzdF9zY29yaW5nXyA9IHt9CiAgICBka29lc19zY29yaW5nX29sZF9zY29yaW5nXyA9IHt9CiAgICB2aW5hX3Njb3JpbmdfID0ge30KICAgIHZpbmFyZG9fc2NvcmluZ18gPSB7fQogICAgY3VzdG9tX3Njb3JpbmdfID0ge30KICAgIGJlc3QgPSB7fQogICAgd2l0aCBvcGVuKHJtc2RfcmVzdWx0c1swXSwgInIiKSBhcyBybXNkX3Jlc3VsdHNfcmVhZDoKICAgICAgICBmb3IgY291bnQsIGxpbmUgaW4gZW51bWVyYXRlKHJtc2RfcmVzdWx0c19yZWFkKToKICAgICAgICAgICAgbGluZV9pbmZvID0gbGluZS5yc3BsaXQoIi8iKVstMV0KICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAga2V5LCB2YWx1ZSA9IGxpbmVfaW5mby5zcGxpdCgiLnBkYiIpCiAgICAgICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXI6CiAgICAgICAgICAgICAgICAjIHByaW50KGVyKQogICAgICAgICAgICAgICAgcGFzcwogICAgICAgICAgICBpZiAiX2FkNF9zY29yaW5nXyIgaW4gbGluZToKICAgICAgICAgICAgICAgIGFkNF9zY29yaW5nX1trZXldID0gdmFsdWUuc3RyaXAoKQogICAgICAgICAgICBlbGlmICJfZGVmYXVsdF8iIGluIGtleToKICAgICAgICAgICAgICAgIGRlZmF1bHRfc2NvcmluZ19ba2V5XSA9IHZhbHVlLnN0cmlwKCkKICAgICAgICAgICAgZWxpZiAiX2Rrb2VzX2Zhc3RfIiBpbiBrZXk6CiAgICAgICAgICAgICAgICBka29lc19mYXN0X3Njb3JpbmdfW2tleV0gPSB2YWx1ZS5zdHJpcCgpCiAgICAgICAgICAgIGVsaWYgIl9ka29lc19zY29yaW5nX29sZF8iIGluIGtleToKICAgICAgICAgICAgICAgIGRrb2VzX3Njb3Jpbmdfb2xkX3Njb3JpbmdfW2tleV0gPSB2YWx1ZS5zdHJpcCgpCiAgICAgICAgICAgIGVsaWYgIl92aW5hXyIgaW4ga2V5OgogICAgICAgICAgICAgICAgdmluYV9zY29yaW5nX1trZXldID0gdmFsdWUuc3RyaXAoKQogICAgICAgICAgICBlbGlmICJfdmluYXJkb18iIGluIGtleToKICAgICAgICAgICAgICAgIHZpbmFyZG9fc2NvcmluZ19ba2V5XSA9IHZhbHVlLnN0cmlwKCkKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIGN1c3RvbV9zY29yaW5nX1trZXldID0gdmFsdWUuc3RyaXAoKQogICAgICAgICMgcHJpbnQoY291bnQpCiAgICB0cnk6CiAgICAgICAgYWQ0X2RmID0gcGQuRGF0YUZyYW1lLmZyb21fZGljdCgKICAgICAgICAgICAgYWQ0X3Njb3JpbmdfLCBvcmllbnQ9ImluZGV4IiwgY29sdW1ucz0oWyJhZDRfc2NvcmluZyJdKQogICAgICAgICkKICAgICAgICBkZWZhdWx0X2RmID0gcGQuRGF0YUZyYW1lLmZyb21fZGljdCgKICAgICAgICAgICAgZGVmYXVsdF9zY29yaW5nXywgb3JpZW50PSJpbmRleCIsIGNvbHVtbnM9KFsiZGVmYXVsdF9zY29yaW5nIl0pCiAgICAgICAgKQogICAgICAgIGRrb2VzX2Zhc3RfZGYgPSBwZC5EYXRhRnJhbWUuZnJvbV9kaWN0KAogICAgICAgICAgICBka29lc19mYXN0X3Njb3JpbmdfLCBvcmllbnQ9ImluZGV4IiwgY29sdW1ucz0oWyJka29lc19mYXN0X3Njb3JpbmciXSkKICAgICAgICApCiAgICAgICAgZGtvZXNfc2NvcmluZ19vbGRfZGYgPSBwZC5EYXRhRnJhbWUuZnJvbV9kaWN0KAogICAgICAgICAgICBka29lc19mYXN0X3Njb3JpbmdfLCBvcmllbnQ9ImluZGV4IiwgY29sdW1ucz0oWyJka29lc19mYXN0X3Njb3JpbmciXSkKICAgICAgICApCiAgICAgICAgdmluYV9kZiA9IHBkLkRhdGFGcmFtZS5mcm9tX2RpY3QoCiAgICAgICAgICAgIHZpbmFfc2NvcmluZ18sIG9yaWVudD0iaW5kZXgiLCBjb2x1bW5zPShbInZpbmFfc2NvcmluZyJdKQogICAgICAgICkKICAgICAgICB2aW5hcmRvX2RmID0gcGQuRGF0YUZyYW1lLmZyb21fZGljdCgKICAgICAgICAgICAgdmluYXJkb19zY29yaW5nXywgb3JpZW50PSJpbmRleCIsIGNvbHVtbnM9KFsidmluYXJkb19zY29yaW5nIl0pCiAgICAgICAgKQogICAgICAgIGN1c3RvbV9kZiA9IHBkLkRhdGFGcmFtZS5mcm9tX2RpY3QoCiAgICAgICAgICAgIGN1c3RvbV9zY29yaW5nXywgb3JpZW50PSJpbmRleCIsIGNvbHVtbnM9KFsiY3VzdG9tX3Njb3JpbmciXSkKICAgICAgICApCiAgICAgICAgcmV0dXJuX2RmID0gcGQuY29uY2F0KAogICAgICAgICAgICBbCiAgICAgICAgICAgICAgICBhZDRfZGYsCiAgICAgICAgICAgICAgICBkZWZhdWx0X2RmLAogICAgICAgICAgICAgICAgZGtvZXNfZmFzdF9kZiwKICAgICAgICAgICAgICAgIGRrb2VzX3Njb3Jpbmdfb2xkX2RmLAogICAgICAgICAgICAgICAgdmluYV9kZiwKICAgICAgICAgICAgICAgIHZpbmFyZG9fZGYsCiAgICAgICAgICAgICAgICBjdXN0b21fZGYsCiAgICAgICAgICAgIF0KICAgICAgICApCiAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGVyOgogICAgICAgIHByaW50KGYie2VyfVxuIGVycm9yIGluIGRhdGEgZnJhbWUiKQogICAgaWYgcHJpbnRfaXQ6CiAgICAgICAgdHJ5OgogICAgICAgICAgICBhZDRfYmVzdCA9IG1pbihhZDRfc2NvcmluZ18uaXRlbXMoKSwga2V5PWxhbWJkYSB4OiB4WzFdKQogICAgICAgICAgICBwcmludCgiPT09PT09PT09PT1CRVNUIFJNU0Q9PT09PT09PT09PT09PT09PT09IikKICAgICAgICAgICAgYmVzdFthZDRfYmVzdFswXV0gPSBhZDRfYmVzdFsxXQogICAgICAgICAgICBwcmludChmInthZDRfYmVzdFswXX0gOiB7YWQ0X2Jlc3RbMV19IikKICAgICAgICAgICAgZGVmYXVsdF9iZXN0ID0gbWluKGRlZmF1bHRfc2NvcmluZ18uaXRlbXMoKSwga2V5PWxhbWJkYSB4OiB4WzFdKQogICAgICAgICAgICBiZXN0W2RlZmF1bHRfYmVzdFswXV0gPSBkZWZhdWx0X2Jlc3RbMV0KICAgICAgICAgICAgcHJpbnQoZiJ7ZGVmYXVsdF9iZXN0WzBdfSA6IHtkZWZhdWx0X2Jlc3RbMV19IikKICAgICAgICAgICAgZGtvZXNfZmFzdF9iZXN0ID0gbWluKGRrb2VzX2Zhc3Rfc2NvcmluZ18uaXRlbXMoKSwga2V5PWxhbWJkYSB4OiB4WzFdKQogICAgICAgICAgICBiZXN0W2Rrb2VzX2Zhc3RfYmVzdFswXV0gPSBka29lc19mYXN0X2Jlc3RbMV0KICAgICAgICAgICAgcHJpbnQoZiJ7ZGtvZXNfZmFzdF9iZXN0WzBdfSA6IHtka29lc19mYXN0X2Jlc3RbMV19IikKICAgICAgICAgICAgZGtvZXNfc2NvcmluZ19vbGRfYmVzdCA9IG1pbigKICAgICAgICAgICAgICAgIGRrb2VzX3Njb3Jpbmdfb2xkX3Njb3JpbmdfLml0ZW1zKCksIGtleT1sYW1iZGEgeDogeFsxXQogICAgICAgICAgICApCiAgICAgICAgICAgIGJlc3RbZGtvZXNfc2NvcmluZ19vbGRfYmVzdFswXV0gPSBka29lc19zY29yaW5nX29sZF9iZXN0WzFdCiAgICAgICAgICAgIHByaW50KGYie2Rrb2VzX3Njb3Jpbmdfb2xkX2Jlc3RbMF19IDp7ZGtvZXNfc2NvcmluZ19vbGRfYmVzdFsxXX0iKQogICAgICAgICAgICB2aW5hX2Jlc3QgPSBtaW4odmluYV9zY29yaW5nXy5pdGVtcygpLCBrZXk9bGFtYmRhIHg6IHhbMV0pCiAgICAgICAgICAgIGJlc3RbdmluYV9iZXN0WzBdXSA9IHZpbmFfYmVzdFsxXQogICAgICAgICAgICBwcmludChmInt2aW5hX2Jlc3RbMF19IDoge3ZpbmFfYmVzdFsxXX0iKQogICAgICAgICAgICB2aW5hcmRvX2Jlc3QgPSBtaW4odmluYXJkb19zY29yaW5nXy5pdGVtcygpLCBrZXk9bGFtYmRhIHg6IHhbMV0pCiAgICAgICAgICAgIGJlc3RbdmluYXJkb19iZXN0WzBdXSA9IHZpbmFyZG9fYmVzdFsxXQogICAgICAgICAgICBwcmludChmInt2aW5hcmRvX2Jlc3RbMF19IDoge3ZpbmFyZG9fYmVzdFstMV19IikKICAgICAgICAgICAgY3VzdG9tX2Jlc3QgPSBtaW4oY3VzdG9tX3Njb3JpbmdfLml0ZW1zKCksIGtleT1sYW1iZGEgeDogeFsxXSkKICAgICAgICAgICAgYmVzdFtjdXN0b21fYmVzdFswXV0gPSBjdXN0b21fYmVzdFsxXQogICAgICAgICAgICBwcmludChmIntjdXN0b21fYmVzdFswXX0gOiB7Y3VzdG9tX2Jlc3RbMV19IikKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGVyOgogICAgICAgICAgICBwcmludChlcikKICAgIGlmIG9ubHlfYmVzdDoKICAgICAgICByZXR1cm4gYmVzdAogICAgaWYgcmV0dXJuX2RmIGlzIFRydWU6CiAgICAgICAgcmV0dXJuIHJldHVybl9kZgogICAgZWxzZToKICAgICAgICByZXR1cm4gKAogICAgICAgICAgICBkZWZhdWx0X3Njb3JpbmdfLAogICAgICAgICAgICBhZDRfc2NvcmluZ18sCiAgICAgICAgICAgIGRrb2VzX2Zhc3Rfc2NvcmluZ18sCiAgICAgICAgICAgIGRrb2VzX3Njb3Jpbmdfb2xkX3Njb3JpbmdfLAogICAgICAgICAgICB2aW5hX3Njb3JpbmdfLAogICAgICAgICAgICB2aW5hcmRvX3Njb3JpbmdfLAogICAgICAgICAgICBjdXN0b21fc2NvcmluZ18sCiAgICAgICAgKQoKCmRlZiBjb25mb3JtZXJfc3BsaXQoZmlsZW5hbWVzLCB0YXJnZXQpOgogICAgZm9yIGkgaW4gZmlsZW5hbWVzOgogICAgICAgIGZpbGVfZGlyLCBmaWxlX2lkLCBmaWxlX2Zvcm1hdCA9IGdpdmVfaWQoaSkKICAgICAgICBpZiBub3Qgb3MucGF0aC5leGlzdHMoZiJ7ZmlsZV9kaXJ9L3Bvc2VzIik6CiAgICAgICAgICAgIG9zLm1ha2VkaXJzKGYie2ZpbGVfZGlyfS9wb3NlcyIpCiAgICAgICAgY29tbWFuZCA9ICgKICAgICAgICAgICAgZiIvc2hhcmUvb3BlbmJhYmVsLTMuMS4xL2Jpbi9vYmFiZWwge2l9IC1ve3RhcmdldH0gLU8iCiAgICAgICAgICAgIGYiIC4vcG9zZXMve2ZpbGVfaWR9Xy57dGFyZ2V0fSAtbSIKICAgICAgICApCiAgICAgICAgc3VicHJvY2Vzcy5ydW4oY29tbWFuZCwgY3dkPWYie2ZpbGVfZGlyfSIsIHNoZWxsPVRydWUpCiAgICByZXR1cm4gIlN1Y2Nlc3NmdWxseSBjb21wbGV0ZWQuIgoKCmRlZiBzbWluYV9oaXN0b2dyYW0oc29ydGVkX1JFUywgc2F2ZT1GYWxzZSk6CiAgICAiIiJDcmVhdGVzIGhpc3RvZ3JhbSBvZiBkb2NraW5nIGZyb20gc21pbmEgb3V0cHV0CiAgICBBcmdzOgogICAgICAgIHNvcnRlZF9SRVMgKGxpc3QpOiBTb3J0ZWQgbGlzdCBvZiBhZmZpbml0eSB2YWx1ZXMgZnJvbSBzbWluYSBvdXRwdXQuCiAgICAgICAgc2F2ZSAoYm9vbCwgb3B0aW9uYWwpOiBTYXZlIHBsb3QgaW4gLi9pbWFnZS9zbWluYV9oaXN0b2dyYW0ucG5nCiAgICAiIiIKICAgIG5hbWUsIGJlbmVyZ3kgPSB6aXAoKnNvcnRlZF9SRVMuaXRlbXMoKSkKICAgIGJlbmVyZ3kgPSBucC5hcnJheSgoYmVuZXJneSksIGR0eXBlPW5wLmZsb2F0MzIpCiAgICBtZWFuID0gYmVuZXJneS5tZWFuKCkKICAgIGJlc3QgPSBiZW5lcmd5Lm1pbigpCiAgICB3b3JzdCA9IGJlbmVyZ3kubWF4KCkKICAgIGZpZywgYXhzID0gcGx0LnN1YnBsb3RzKDEsIHNoYXJleT1GYWxzZSwgc2hhcmV4PUZhbHNlLCB0aWdodF9sYXlvdXQ9VHJ1ZSkKICAgIGF4cy5hZGRfYXJ0aXN0KAogICAgICAgIEFuY2hvcmVkVGV4dCgKICAgICAgICAgICAgZiJUb3RhbDoge2xlbihuYW1lKX1cbk1lYW46IHttZWFuOi4yZn1cbkJlc3Q6IHtiZXN0Oi4yZn1cbldvcnN0OiIKICAgICAgICAgICAgZiIge3dvcnN0Oi4yZn0iLAogICAgICAgICAgICBsb2M9MSwKICAgICAgICApCiAgICApCiAgICBheHMuaGlzdChiZW5lcmd5KQogICAgYXhzLnlheGlzLnNldF9sYWJlbF90ZXh0KCJOdW1iZXIgb2YgRGF0YXNldHMiKQogICAgYXhzLnhheGlzLnNldF9sYWJlbF90ZXh0KCJCaW5kaW5nIEVuZXJneSBSYW5nZSIpCiAgICBheHMuc2V0X3RpdGxlKCJEaXN0cmlidXRpb24gb2YgYmluZGluZyBlbmVyZ3kiKQogICAgaWYgc2F2ZToKICAgICAgICBpZiBub3Qgb3MucGF0aC5leGlzdHMoIi4vaW1hZ2VzIik6CiAgICAgICAgICAgIG9zLm1ha2VkaXJzKCIuL2ltYWdlcyIpCiAgICAgICAgcGx0LnNhdmVmaWcoIi4vaW1hZ2VzL3NtaW5hX2hpc3RvZ3JhbS5wbmciLCBkcGk9NjAwKQogICAgcGx0LnNob3coKQoKCmRlZiBzbWluYV9tb25pdG9yKHNtaW5hX291dHB1dF9tb25pdG9yLCBwbG90PUZhbHNlLCBzYXZlPUZhbHNlKToKICAgICIiIkRpc3BsYXkgc21pbmEgcHJvY2VzcyB3aGlsZSBlbnRlciBzbWluYSBzdGRvdXQKICAgIEFyZ3M6CiAgICAgICAgc21pbmFfb3V0cHV0X21vbml0b3IgKHN0ZG91dCk6IHN0ZG91dCBmcm9tIHFzdGF0L3FzdWIKICAgIFJldHVybnM6CiAgICAgICAgZGljdDogRGlzcGxheXMgcmVzdWx0IGluIGp1cHl0ZXIKICAgICIiIgogICAgUkVTID0ge30KICAgIGNvdW50ID0gMAogICAgZm9yIGkgaW4gc21pbmFfb3V0cHV0X21vbml0b3I6CiAgICAgICAgYWxnb19kaXIsIGFsZ29fbmFtZSwgYWxnb19mb3JtYXQgPSBnaXZlX2lkKGkpCiAgICAgICAgaWYgYWxnb19mb3JtYXQubG93ZXIoKSA9PSAicGRiIjoKICAgICAgICAgICAgd2l0aCBvcGVuKGksICJyIikgYXMgcmVhZF9zbWluYToKICAgICAgICAgICAgICAgIGZvciBsaW5lIGluIHJlYWRfc21pbmE6CiAgICAgICAgICAgICAgICAgICAgaWYgbGluZVs6NV0gPT0gIk1PREVMIjoKICAgICAgICAgICAgICAgICAgICAgICAgbnVtYmVyID0gbGluZVs1Ol0uc3RyaXAoKQogICAgICAgICAgICAgICAgICAgIGVsaWYgIlJFTUFSSyIgaW4gbGluZToKICAgICAgICAgICAgICAgICAgICAgICAgZW5lcmd5ID0gZmxvYXQobGluZS5yc3BsaXQoIiAiKVstMV0uc3RyaXAoKSkKICAgICAgICAgICAgICAgICAgICAgICAgUkVTW2Yie2FsZ29fbmFtZX1fe251bWJlcn0iXSA9IGYie2VuZXJneX0iCiAgICAgICAgICAgICAgICAgICAgICAgIGNvdW50ICs9IDEKICAgICAgICBlbGlmIGFsZ29fZm9ybWF0Lmxvd2VyKCkgPT0gInNkZiI6CiAgICAgICAgICAgIHBhdHRlcm5faWQgPSByIl5bYS16QS1aXVxTIgogICAgICAgICAgICBwYXR0ZXJuX2FmZmluaXR5ID0gciJePlxzPFthLXpBLVpdKz4iCiAgICAgICAgICAgIHdpdGggb3BlbihpLCAiciIpIGFzIHJlYWRfc21pbmE6CiAgICAgICAgICAgICAgICB3cml0ZV9hZmZpbml0eSA9IEZhbHNlCiAgICAgICAgICAgICAgICBmb3IgbGluZSBpbiByZWFkX3NtaW5hOgogICAgICAgICAgICAgICAgICAgIGlmIHdyaXRlX2FmZmluaXR5OgogICAgICAgICAgICAgICAgICAgICAgICBlbmVyZ3kgPSBmbG9hdChsaW5lLnN0cmlwKCkpCiAgICAgICAgICAgICAgICAgICAgICAgICMgcHJpbnQoZW5lcmd5KQogICAgICAgICAgICAgICAgICAgICAgICBSRVNbZiJ7YWxnb19uYW1lfV97bnVtYmVyfSJdID0gZiJ7ZW5lcmd5fSIKICAgICAgICAgICAgICAgICAgICAgICAgY291bnQgKz0gMQogICAgICAgICAgICAgICAgICAgICAgICB3cml0ZV9hZmZpbml0eSA9IEZhbHNlCiAgICAgICAgICAgICAgICAgICAgaWYgcmUubWF0Y2gocGF0dGVybl9pZCwgbGluZSk6CiAgICAgICAgICAgICAgICAgICAgICAgIG51bWJlciA9IGxpbmUuc3RyaXAoKQogICAgICAgICAgICAgICAgICAgIGVsaWYgcmUubWF0Y2gocGF0dGVybl9hZmZpbml0eSwgbGluZSk6CiAgICAgICAgICAgICAgICAgICAgICAgIHdyaXRlX2FmZmluaXR5ID0gVHJ1ZQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiAiRmlsZSBmb3JtYXQgbm90IHN1cHBvcnRlZCB5ZXQuIFsncGRiJywgJ3NkZiddIgoKICAgIHByaW50KGYiVG90YWwgbnVtYmVyIG9mIHBvc2VzIGdlbmVyYXRlZDoge2NvdW50fSIpCiAgICBzb3J0ZWRfUkVTID0gZGljdChzb3J0ZWQoUkVTLml0ZW1zKCksIGtleT1sYW1iZGEgeDogeFsxXSkpCiAgICBwcmludCgiX19fX19fX19fX19fX0RldGFpbCBsaXN0X19fX19fX19fX19fX19fX1xuIikKICAgIGZvciBrZXksIHZhbHVlIGluIHNvcnRlZF9SRVMuaXRlbXMoKToKICAgICAgICBwcmludChrZXksICI6IiwgdmFsdWUpCiAgICBpZiBwbG90OgogICAgICAgIHNtaW5hX2hpc3RvZ3JhbShzb3J0ZWRfUkVTLCBzYXZlPXNhdmUpCgogICAgcmV0dXJuIHNvcnRlZF9SRVMKCgpkZWYgYXVjX3Bsb3QobW9kZWwsIFhfdGVzdCwgeV90ZXN0LCBzYXZlPUZhbHNlKToKICAgICIiIlBsb3QgQVVDIHBsb3QgZnJvbSBtb2RlbCwgYW5kIFhfdGV4dChkYXRhIHBvaW50KSB5X3Rlc3QobGFiZWwpLgogICAgQXJnczoKICAgICAgICBtb2RlbCAoc2tsZWFybiBvYmopXzogTW9kZWwgb2JqZWN0IGZyb20gc2tsZWFybiB0cmFpbmVkIG1vZGVsLgogICAgICAgIFhfdGVzdCAocGQuRGF0YUZyYW1lKTogRGF0YSBwb2ludCBmb3IgdGVzdC4KICAgICAgICB5X3Rlc3QgKHBkLkRhdGFGcmFtZSk6IExhYmVsIGZvciB0aGUgZGF0YSBwb2ludC4KICAgICAgICBzYXZlIChib29sLCBvcHRpb25hbCk6IFNhdmUgQVVDIHBsb3QuCiAgICAiIiIKICAgICMgYXNzZXJ0IGlzaW5zdGFuY2UobW9kZWwsIExpbmVhclJlZ3Jlc3Npb24pLCBmInttb2RlbH0gaXMgbm90IGEgdmFsaWQgbW9kZWwiCiAgICAjIGFzc2VydCBpc2luc3RhbmNlKFhfdGVzdCwgcGQuRGF0YUZyYW1lKSwgZiJ7WF90ZXN0fSBpcyBub3QgYSBEYXRhRnJhbWUiCiAgICAjIGFzc2VydCBpc2luc3RhbmNlKHlfdGVzdCwgcGQuRGF0YUZyYW1lKSwgZiJ7eV90ZXN0fSBpcyBub3QgYSBEYXRhRnJhbWUiCiAgICBtb2RlbF9yZWdyZXNzaW9uX3Byb2JhYmlsaXR5ID0gbW9kZWwucHJlZGljdF9wcm9iYShYX3Rlc3QpCiAgICBtb2RlbF9yZWdyZXNzaW9uX3Byb2JhYmlsaXR5ID0gbW9kZWxfcmVncmVzc2lvbl9wcm9iYWJpbGl0eVs6LCAxXQogICAgcmFuZG9tX3Byb2JhYmlsaXR5ID0gWzAgZm9yIF8gaW4gcmFuZ2UobGVuKHlfdGVzdCkpXQogICAgcmFuZG9tX2F1YyA9IHJvY19hdWNfc2NvcmUoeV90ZXN0LCByYW5kb21fcHJvYmFiaWxpdHkpCiAgICBtb2RlbF9hdWMgPSByb2NfYXVjX3Njb3JlKHlfdGVzdCwgbW9kZWxfcmVncmVzc2lvbl9wcm9iYWJpbGl0eSkKICAgIHByaW50KGYiUmFuZG9tOiBST0MgQVVDPXtyYW5kb21fYXVjfSIpCiAgICBwcmludChmIk1vZGVsOiBST0MgQVVDPXttb2RlbF9hdWN9IikKICAgIHJhbmRvbV9mYWxzZV9wb3NpdGl2ZV9yYXRlLCByYW5kb21fdHJ1ZV9wb3NpdGl2ZV9yYXRlLCBfID0gcm9jX2N1cnZlKAogICAgICAgIHlfdGVzdCwgcmFuZG9tX3Byb2JhYmlsaXR5CiAgICApCiAgICBtb2RlbF9mYWxzZV9wb3NpdGl2ZV9yYXRlLCBtb2RlbF90cnVlX3Bvc2l0aXZlX3JhdGUsIF8gPSByb2NfY3VydmUoCiAgICAgICAgeV90ZXN0LCBtb2RlbF9yZWdyZXNzaW9uX3Byb2JhYmlsaXR5CiAgICApCiAgICBwbHQucGxvdCgKICAgICAgICByYW5kb21fZmFsc2VfcG9zaXRpdmVfcmF0ZSwKICAgICAgICByYW5kb21fdHJ1ZV9wb3NpdGl2ZV9yYXRlLAogICAgICAgIGxpbmVzdHlsZT0iLS0iLAogICAgICAgIGxhYmVsPSJSYW5kb20iLAogICAgKQogICAgcGx0LnBsb3QoCiAgICAgICAgbW9kZWxfZmFsc2VfcG9zaXRpdmVfcmF0ZSwKICAgICAgICBtb2RlbF90cnVlX3Bvc2l0aXZlX3JhdGUsCiAgICAgICAgbWFya2VyPSIuIiwKICAgICAgICBsYWJlbD1mIk1vZGVsIChBVUM6e21vZGVsX2F1YzouMmZ9KSIsCiAgICApCiAgICBwbHQueGxhYmVsKCJGYWxzZSBQb3NpdGl2ZSBSYXRlIikKICAgIHBsdC55bGFiZWwoIlRydWUgUG9zaXRpdmUgUmF0ZSIpCiAgICBwbHQueGxpbSh4bWluPTAuMCkKICAgIHBsdC55bGltKHltaW49MC4wKQogICAgcGx0LnRpdGxlKCJST0MiKQogICAgcGx0LmxlZ2VuZCgpCiAgICBpZiBzYXZlOgogICAgICAgIHByZWZpeCA9ICJpbWFnZSIKICAgICAgICB3aGlsZSBvcy5wYXRoLmV4aXN0cyhmIi4vR2VuZXJhdGVkL2ltYWdlcy97cHJlZml4fS5wbmciKToKICAgICAgICAgICAgc3VmZml4ICs9IDEKICAgICAgICAgICAgbmFtZSA9IGYie3ByZWZpeH17c3VmZml4fS5wbmciCiAgICAgICAgcGx0LnNhdmVmaWcoIi4vR2VuZXJhdGVkL2ltYWdlcy97bmFtZX0iLCBkcGk9NjAwKQogICAgcGx0LnNob3coKQoKCmRlZiBzbWluYV9tb2RlbF9zY29yZSgKICAgIGZpbGVfcGF0aCwKICAgIG51bV9mZWF0dXJlcz0zLAogICAgaW50ZXJjZXB0PUZhbHNlLAogICAgdHNpemU9MC4zLAogICAgcGxvdF9hdWM9RmFsc2UsCiAgICBwbG90X3NhdmU9RmFsc2UsCik6CiAgICAiIiJHZW5lcmF0ZXMgcmVncmVzc2lvbiBtb2RlbCB1c2luZyBza2xlYXJuLiBXaWxsIFByaW50IG91dCBjb2VmZmljaWVudHMKICAgIEFyZ3M6CiAgICAgICAgZmlsZV9wYXRoIChzdHIpOiBjc3YvZXhjZWwgZmlsZSBwYXRoCiAgICAgICAgbnVtX2ZlYXR1cmVzIChpbnQsIG9wdGlvbmFsKTogTnVtYmVyIG9mIGZlYXR1cmVzIHRvIHVzZS4gRGVmYXVsdDogMwogICAgICAgIGludGVyY2VwdCAoYm9vbCwgb3B0aW9uYWwpOiBNZWFuIEVycm9yCiAgICAgICAgdHNpemUgKGZsb2F0LCBvcHRpb25hbCk6IFBlcmNlbnRhZ2Ugb2YgZGF0YXRvIHVzZSBmb3IgdGVzdC4gRGVmYXVsdCAwLjMoMzAlKQogICAgICAgIHBsb3RfYXVjIChib29sLCBvcHRpb25hbCk6IFBsb3QgUk9DIEFVQyBjdXJ2ZQogICAgICAgIHBsb3Rfc2F2ZSAoYm9vbCwgb3B0aW9uYWwpOiBTYXZlIFJPQyBBVUMgcGxvdAogICAgIiIiCiAgICB0cnk6CiAgICAgICAgaWYgaXNpbnN0YW5jZShmaWxlX3BhdGgsIHN0cik6CiAgICAgICAgICAgIGZpbGVfZGlyLCBmaWxlX25hbWUsIGZpbGVfZm9ybWF0ID0gZ2l2ZV9pZChmaWxlX3BhdGgpCiAgICAgICAgICAgIHN1cHBvcnRlZF9maWxlX2Zvcm1hdCA9IFsiY3N2IiwgImV4Y2VsIl0gICMgZm9yIG5vdyB8IHNtaW5hIHJlc3VsdCBmaWxlCiAgICAgICAgICAgIGFzc2VydCBmaWxlX2Zvcm1hdCBpbiBzdXBwb3J0ZWRfZmlsZV9mb3JtYXQsICgKICAgICAgICAgICAgICAgICJOb3RlOiBGaWxlVHlwZSBFcnJvcjogTm90IHN1cHBvcnRlZCBmaWxlIGZvcm1hdC4gVXNlIgogICAgICAgICAgICAgICAgZiIge3N1cHBvcnRlZF9maWxlX2Zvcm1hdH0iCiAgICAgICAgICAgICkKICAgICAgICAgICAgaWYgZmlsZV9mb3JtYXQgPT0gImV4Y2VsIjoKICAgICAgICAgICAgICAgIGRmID0gcGQucmVhZF9leGNlbChmaWxlX3BhdGgpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBkZiA9IHBkLnJlYWRfY3N2KGZpbGVfcGF0aCkKCiAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgcHJpbnQoZSkKCiAgICBpZiBpc2luc3RhbmNlKGZpbGVfcGF0aCwgcGQuRGF0YUZyYW1lKToKICAgICAgICBkZiA9IGZpbGVfcGF0aAogICAgdHJ5OgogICAgICAgIFgsIHkgPSBkZi5pbG9jWzE6LCAxOi0yXSwgZGYuaWxvY1sxOiwgLTFdCiAgICAgICAgWCA9IHBkLkRhdGFGcmFtZShYKQogICAgICAgIGhlYWRlciA9IFguaWxvY1s6MCwgOl0KICAgICAgICBYLCB5ID0gbWFrZV9jbGFzc2lmaWNhdGlvbihuX2ZlYXR1cmVzPW51bV9mZWF0dXJlcykKICAgICAgICBYX3RyYWluLCBYX3Rlc3QsIHlfdHJhaW4sIHlfdGVzdCA9IHRyYWluX3Rlc3Rfc3BsaXQoCiAgICAgICAgICAgIFgsIHksIHRlc3Rfc2l6ZT10c2l6ZQogICAgICAgICkgICMgVE9ETyAvL2luY2x1ZGUgSy1Gb2xkIFRlc3QKICAgICAgICBtb2RlbCA9IExvZ2lzdGljUmVncmVzc2lvbihmaXRfaW50ZXJjZXB0PWludGVyY2VwdCkKICAgICAgICBtb2RlbC5maXQoWF90cmFpbiwgeV90cmFpbikgICMgVE9ET0QgYWNjZXB0IHVzZSBtb2RlbCBpbnB1dAogICAgICAgIHdlaWdodCA9IG1vZGVsLmNvZWZfCiAgICAgICAgd2VpZ2h0ID0gW2l0ZW0gZm9yIGkgaW4gd2VpZ2h0IGZvciBpdGVtIGluIGldCiAgICAgICAgY29uc29sZS5wcmludCgiW2JvbGQgY3lhbl1Nb2RlbCB3ZWlnaHRzIGFyZSA6flsvYm9sZCBjeWFuXVxuIikKICAgICAgICBmb3IgaGVhZCwgY29lZmYgaW4gemlwKGhlYWRlciwgd2VpZ2h0KToKICAgICAgICAgICAgcHJpbnQoY29lZmYsIGhlYWQsIGVuZD0iXG4iKQogICAgICAgIG1vZGVsLnByZWRpY3QoWF90ZXN0KQogICAgICAgIG1vZGVsLnByZWRpY3RfcHJvYmEoWF90ZXN0KQogICAgICAgIHNjb3JlID0gbW9kZWwuc2NvcmUoWF90ZXN0LCB5X3Rlc3QpCiAgICAgICAgcHJpbnQoZiJNb2RlbCBzY29yZToge3Njb3JlfSIpCiAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGVyOgogICAgICAgIHByaW50KGVyKQogICAgaWYgcGxvdF9hdWM6CiAgICAgICAgYXVjX3Bsb3QobW9kZWwsIFhfdGVzdCwgeV90ZXN0LCBzYXZlPXBsb3Rfc2F2ZSkKICAgICAgICAjIHRyYWluX3Bsb3QobW9kZWwsIFgsIHksIFhfdGVzdCwgeV90ZXN0KQogICAgcmV0dXJuIG1vZGVsCgoKZGVmIHRyYWluX3Bsb3QobW9kZWwsIFgsIHksIFhfdGVzdCwgeV90ZXN0KToKICAgIHBsdC5maWd1cmUoMSwgZmlnc2l6ZT0oNCwgMykpCiAgICBwbHQuY2xmKCkKICAgIHByaW50KGxlbihYKSkKICAgIHByaW50KGxlbih5KSkKICAgIHBsdC5zY2F0dGVyKFgucmF2ZWwoKSwgeSwgY29sb3I9ImJsYWNrIiwgem9yZGVyPTIwKQogICAgIyBwbHQuc2NhdHRlcih5X3Rlc3QsIFhfdGVzdC5pbG9jWzosMF0udmFsdWVzKQogICAgbG9zcyA9IGV4cGl0KFhfdGVzdCAqIG1vZGVsLmNvZWZfICsgbW9kZWwuaW50ZXJjZXB0XykucmF2ZWwoKQogICAgcGx0LnBsb3QoWF90ZXN0LCBsb3NzLCBjb2xvcj0icmVkIiwgbGluZXdpZHRoPTMpCgogICAgb2xzID0gTGluZWFyUmVncmVzc2lvbigpCiAgICBvbHMuZml0KFgsIHkpCiAgICBwbHQucGxvdChYX3Rlc3QsIG9scy5jb2VmXyAqIFhfdGVzdCArIG9scy5pbnRlcmNlcHRfLCBsaW5ld2lkdGg9MSkKICAgIHBsdC5heGhsaW5lKDAuNSwgY29sb3I9Ii41IikKCiAgICBwbHQueWxhYmVsKCJ5IikKICAgIHBsdC54bGFiZWwoIlgiKQogICAgcGx0Lnh0aWNrcyhyYW5nZSgtNSwgMTApKQogICAgcGx0Lnl0aWNrcyhbMCwgMC41LCAxXSkKICAgIHBsdC55bGltKC0wLjI1LCAxLjI1KQogICAgcGx0LnhsaW0oLTQsIDEwKQogICAgcGx0LmxlZ2VuZCgKICAgICAgICAoIkxvZ2lzdGljIFJlZ3Jlc3Npb24gTW9kZWwiLCAiTGluZWFyIFJlZ3Jlc3Npb24gTW9kZWwiKSwKICAgICAgICBsb2M9Imxvd2VyIHJpZ2h0IiwKICAgICAgICBmb250c2l6ZT0ic21hbGwiLAogICAgKQogICAgcGx0LnRpZ2h0X2xheW91dCgpCiAgICBwbHQuc2hvdygpCgoKZGVmIGlucHV0X2N1c3RvbV9zY29yaW5nKCk6CiAgICAiIiJHVUkgd2luZG93IHRvIGVudGVyIGN1c3RvbSBzY29yaW5nIGZ1bmN0aW9uIiIiCiAgICAjIGluaXRpYWxpemUgc29tZSBtc2cgYW5kIG91dHB1dCBlbnYKICAgIG91dHB1dF9jc2YgPSB3aWRnZXRzLk91dHB1dCgpCiAgICBtc2dfZW1wdHlfbmFtZSA9ICJFbnRlciBhbnkgbmFtZSBmb3IgdGhlIGZpbGUuIgogICAgd2Fybl9lbXB0eV9uYW1lID0gd2lkZ2V0cy5IVE1MKHZhbHVlPWYiPGI+PGZvbnQgY29sb3I9J3JlZCc+e21zZ19lbXB0eV9uYW1lfTwvYj4iKQogICAgIyBzYXZlIHRoZSBpbnB1dCBjdXN0b20gc2NvcmluZyB2YWx1ZQogICAgZGVmIHNhdmVfc2NvcmluZyhkYXRhKToKICAgICAgICBvdXRwdXRfY3NmLmNsZWFyX291dHB1dCgpCiAgICAgICAgbXNnX2NvbmZpcm1fd2FybiA9ICJQbGVhc2UgY29uZmlybSBpZiB0aGUgdmFsdWVzIGFyZSByaWdodC4iCiAgICAgICAgaW5mb3JtYXRpb24gPSBpcHl3aWRnZXRzLndpZGdldHMuSFRNTCgKICAgICAgICAgICAgdmFsdWU9ZiI8Yj48Zm9udCBjb2xvcj0ncmVkJz57bXNnX2NvbmZpcm1fd2Fybn08L2I+IgogICAgICAgICkKICAgICAgICBnbG9iYWwgc2NvcmluZ19kYXRhCiAgICAgICAgZ2xvYmFsIHRlbXBfbmFtZQogICAgICAgIHRlbXBfbmFtZSA9IGZpbGVfbmFtZS52YWx1ZQogICAgICAgIGlmIG5vdCB0ZW1wX25hbWU6CiAgICAgICAgICAgIHdpdGggb3V0cHV0X2NzZjoKICAgICAgICAgICAgICAgIGRpc3BsYXkod2Fybl9lbXB0eV9uYW1lKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHNwbGl0dGVkID0gY3VzdG9tX3Njb3JpbmdfYXJlYS52YWx1ZS5zcGxpdCgiXG4iKQogICAgICAgICAgICBzY29yaW5nX2RhdGEgPSBbXQogICAgICAgICAgICBmb3Igc3BsaXQgaW4gc3BsaXR0ZWQ6CiAgICAgICAgICAgICAgICBzcGxpdCA9IHNwbGl0LnN0cmlwKCkKICAgICAgICAgICAgICAgIHNjb3JpbmdfZGF0YS5hcHBlbmQoc3BsaXQpCiAgICAgICAgICAgIHdpdGggb3V0cHV0X2NzZjoKICAgICAgICAgICAgICAgIHByaW50KGYiRW50ZXJlZCBmaWxlIG5hbWUgOiB7dGVtcF9uYW1lfSIpCiAgICAgICAgICAgICAgICBmb3IgbGluZSBpbiBzY29yaW5nX2RhdGE6CiAgICAgICAgICAgICAgICAgICAgaWYgc2VsZWN0ID09ICJjdXN0b21fc2NvcmluZyI6CiAgICAgICAgICAgICAgICAgICAgICAgIGlmIGxlbihsaW5lLnJzdHJpcCgpKSAhPSAwOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUsIGl0ZW0gPSBsaW5lLnNwbGl0KCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW50KGYie3ZhbHVlfVx0e2l0ZW19IikKICAgICAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgICAgICBwcmludChsaW5lKQogICAgICAgICAgICAgICAgZGlzcGxheShpbmZvcm1hdGlvbikKCiAgICAjIHdyaXRlcyB0aGUgc2F2ZSBzY29yaW5nIGRhdGEgdG8gYSBmaWxlCiAgICBkZWYgY29uZmlybV9zY29yaW5nKGRhdGEpOgogICAgICAgIG91dHB1dF9jc2YuY2xlYXJfb3V0cHV0KCkKICAgICAgICBpZiBub3QgdGVtcF9uYW1lOgogICAgICAgICAgICB3aXRoIG91dHB1dF9jc2Y6CiAgICAgICAgICAgICAgICBkaXNwbGF5KHdhcm5fZW1wdHlfbmFtZSkKICAgICAgICBlbHNlOgogICAgICAgICAgICBtc2dfc3VjY2VzcyA9ICJDb25maXJtZWQgYW5kIFNhdmVkISIKICAgICAgICAgICAgaW5mb3JtYXRpb24gPSBpcHl3aWRnZXRzLndpZGdldHMuSFRNTCgKICAgICAgICAgICAgICAgIHZhbHVlPWYiPGI+PGZvbnQgY29sb3I9J2dyZWVuJz57bXNnX3N1Y2Nlc3N9PC9iPiIKICAgICAgICAgICAgKQogICAgICAgICAgICBmaWxlX25hbWVfc2F2ZSA9IHRlbXBfbmFtZQogICAgICAgICAgICBmaWxlX2NvbnRlbnQgPSBzY29yaW5nX2RhdGEKICAgICAgICAgICAgaWYgc2VsZWN0ID09ICJjdXN0b21fc2NvcmluZyI6CiAgICAgICAgICAgICAgICBpZiBub3Qgb3MucGF0aC5leGlzdHMoIi4vR2VuZXJhdGVkL2N1c3RvbV9mdW5jdGlvbiIpOgogICAgICAgICAgICAgICAgICAgIG9zLm1ha2VkaXJzKCIuL0dlbmVyYXRlZC9jdXN0b21fZnVuY3Rpb24iKQogICAgICAgICAgICAgICAgd2l0aCBvcGVuKAogICAgICAgICAgICAgICAgICAgIGYiLi9HZW5lcmF0ZWQvY3VzdG9tX2Z1bmN0aW9uL3tmaWxlX25hbWVfc2F2ZX1fY3NmLnR4dCIsICJ3KyIKICAgICAgICAgICAgICAgICkgYXMgd3JpdGVfc2NvcmluZ19mdW5jdGlvbjoKICAgICAgICAgICAgICAgICAgICBmb3IgbGluZSBpbiBmaWxlX2NvbnRlbnQ6CiAgICAgICAgICAgICAgICAgICAgICAgIGlmIGxlbihsaW5lLnJzdHJpcCgpKSAhPSAwOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUsIGl0ZW0gPSBsaW5lLnNwbGl0KCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW50KGYie3ZhbHVlfVx0e2l0ZW19IiwgZmlsZT13cml0ZV9zY29yaW5nX2Z1bmN0aW9uKQoKICAgICAgICAgICAgICAgIGluZm8gPSAoCiAgICAgICAgICAgICAgICAgICAgZiJmaWxlIHNhdmUgYXQgLi9HZW5lcmF0ZWQvY3VzdG9tX2Z1bmN0aW9uL3tmaWxlX25hbWVfc2F2ZX1fY3NmLnR4dCIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIGlmIG5vdCBvcy5wYXRoLmV4aXN0cygiLi9HZW5lcmF0ZWQvc21pbmFfaW5wdXQiKToKICAgICAgICAgICAgICAgICAgICBvcy5tYWtlZGlycygiLi9HZW5lcmF0ZWQvc21pbmFfaW5wdXQiKQogICAgICAgICAgICAgICAgd2l0aCBvcGVuKAogICAgICAgICAgICAgICAgICAgIGYiLi9HZW5lcmF0ZWQvc21pbmFfaW5wdXQve2ZpbGVfbmFtZV9zYXZlfV9tY29uZmlnLnR4dCIsICJ3KyIKICAgICAgICAgICAgICAgICkgYXMgd3JpdGVfY29uZmlnOgogICAgICAgICAgICAgICAgICAgIGZvciBsaW5lIGluIGZpbGVfY29udGVudDoKICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnQoZiJ7bGluZX0iLCBmaWxlPXdyaXRlX2NvbmZpZykKCiAgICAgICAgICAgICAgICBpbmZvID0gKAogICAgICAgICAgICAgICAgICAgIGYiZmlsZSBzYXZlIGF0IC4vR2VuZXJhdGVkL3NtaW5hX2lucHV0L3tmaWxlX25hbWVfc2F2ZX1fbWNvbmZpZy50eHQiCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgIHdpdGggb3V0cHV0X2NzZjoKICAgICAgICAgICAgICAgIHByaW50KGluZm8pCiAgICAgICAgICAgICAgICBkaXNwbGF5KGluZm9ybWF0aW9uKQoKICAgICMgZGVmIGFsbF9jbGVhcihkYXRhKToKICAgICMgICAgd2l0aCBvdXRwdXRfY3NmOgogICAgIyAgICAgICAgb3V0cHV0X2NzZi5jbGVhcl9vdXRwdXQoKQogICAgIyAjdGV4dCBhcmVhIHRvIG9ic2VydmUgYWxsIGlucHV0IHRleHQKICAgIGNvbmZpZ19wbGFjZWhvbGRlciA9ICgKICAgICAgICAiUGFzdGUgaGVyZVxuICAgICAgICBcbiAgICAgICAgU2FtcGxlIGNvbmZpZy50eHQgRG9ja2luZyBwYXJhbWV0ZXJzIGZpbGVcbiAgICAgIgogICAgICAgICIgICAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gICAgICAgICNJbnB1dHNcbiAgICAgICAgcmVjZXB0b3IgPSIKICAgICAgICAiIC4vM0w2Ql9wcm90LnBkYnF0XG4gICAgICAgIGxpZ2FuZCA9IC4vM0w2Ql9saWcucGRicXRcbiAgICAgICAgI091dHB1dHNcbiAgICAgIgogICAgICAgICIgICBvdXQgPSAzTDZCLW5vd2F0LVZpbmEucGRicXRcbiAgICAgICAgbG9nID0gM0w2Qi1ub3dhdC1WaW5hLmxvZ1xuICAgICAgICIKICAgICAgICAiICNCb3ggY2VudGVyXG4gICAgICAgIGNlbnRlcl94ID0gNC41MDBcbiAgICAgICAgY2VudGVyX3kgPSAtMi45NDRcbiAgICAgICAiCiAgICAgICAgIiBjZW50ZXJfeiA9IC01LjI1MFxuICAgICAgICAjQm94IHNpemVcbiAgICAgICAgc2l6ZV94ID0gNTBcbiAgICAgICAgc2l6ZV95ID0iCiAgICAgICAgIiA1MFxuICAgICAgICBzaXplX3ogPSA1MFxuICAgICAgICAjUGFyYW1ldGVyc1xuICAgICAgICBleGhhdXN0aXZlbmVzcyA9IDhcbiAgICIKICAgICAgICAiICAgICBzZWVkID0gMTIzNDU2XG4iCiAgICApCiAgICBjc2ZfcGxhY2Vob2xkZXIgPSAoCiAgICAgICAgIiBQYXN0ZSBoZXJlXG4gICAgICAgIFxuICAgICAgICAgICAgU2FtcGxlIGZvcm1hdCBvZiBjdXN0b20gc2NvcmluZ1xuICAgICAgICIKICAgICAgICAiIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAgICAgICAgLTAuMDM1NTc5ICAgIgogICAgICAgICIgZ2F1c3Mobz0wLF93PTAuNSxfYz04KVxuICAgICAgICAtMC4wMDUxNTYgICAgZ2F1c3Mobz0zLF93PTIsX2M9OFxuICAgICAgICIKICAgICAgICAiIDAuODQwMjQ1ICAgICByZXB1bHNpb24obz0wLF9jPTgpXG4gICAgICAgIC0wLjAzNTA2OSAgICIKICAgICAgICAiIGh5ZHJvcGhvYmljKGc9MC41LF9iPTEuNSxfYz04KVxuICAgICAgICAtMC41ODc0MzkgICAiCiAgICAgICAgIiBub25fZGlyX2hfYm9uZChnPS0wLjcsX2I9MCxfYz04KVxuICAgICAgICAxLjkyMyAgICAgICAgbnVtX3RvcnNfZGl2XG4gICAgICAgIgogICAgICAgICIgLTEwMC4wICAgICAgIGF0b21fdHlwZV9nYXVzc2lhbih0MT1DaGxvcmluZSx0Mj1TdWxmdXIsbz0wLF93PTMsX2M9OClcbiIKICAgICkKCiAgICBkZWYgZXZhbHVhdGUoc2VsZWN0ZWQpOgogICAgICAgIG91dHB1dF9jc2YuY2xlYXJfb3V0cHV0KCkKICAgICAgICBhcmVhX2xheW91dCA9IExheW91dCh3aWR0aD0iMTAwJSIsIGhlaWdodD0iNDAwcHgiLCBmbGV4PSJyb3ciKQogICAgICAgIGdsb2JhbCBzZWxlY3QKICAgICAgICBzZWxlY3QgPSBzZWxlY3RlZAogICAgICAgIGlmIHNlbGVjdGVkID09ICJjdXN0b21fc2NvcmluZyI6CiAgICAgICAgICAgIGdsb2JhbCBjdXN0b21fc2NvcmluZ19hcmVhCiAgICAgICAgICAgIGN1c3RvbV9zY29yaW5nX2FyZWEgPSB3aWRnZXRzLlRleHRhcmVhKAogICAgICAgICAgICAgICAgcGxhY2Vob2xkZXI9Y3NmX3BsYWNlaG9sZGVyLAogICAgICAgICAgICAgICAgZGVzY3JpcHRpb249IkVudGVyOiIsCiAgICAgICAgICAgICAgICBkaXNhYmxlZD1GYWxzZSwKICAgICAgICAgICAgICAgIGp1c3RpZnlfY29udGVudD0ic3BhY2VfYmV0d2VlbiIsCiAgICAgICAgICAgICAgICBjb250aW51b3VzX3VwZGF0ZT1UcnVlLAogICAgICAgICAgICAgICAgbGF5b3V0PWFyZWFfbGF5b3V0LAogICAgICAgICAgICApCiAgICAgICAgZWxzZToKICAgICAgICAgICAgY3VzdG9tX3Njb3JpbmdfYXJlYSA9IHdpZGdldHMuVGV4dGFyZWEoCiAgICAgICAgICAgICAgICBwbGFjZWhvbGRlcj1jb25maWdfcGxhY2Vob2xkZXIsCiAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbj0iRW50ZXI6IiwKICAgICAgICAgICAgICAgIGRpc2FibGVkPUZhbHNlLAogICAgICAgICAgICAgICAganVzdGlmeV9jb250ZW50PSJzcGFjZV9iZXR3ZWVuIiwKICAgICAgICAgICAgICAgIGNvbnRpbnVvdXNfdXBkYXRlPVRydWUsCiAgICAgICAgICAgICAgICBsYXlvdXQ9YXJlYV9sYXlvdXQsCiAgICAgICAgICAgICkKICAgICAgICBkaXNwbGF5KGN1c3RvbV9zY29yaW5nX2FyZWEpCiAgICAgICAgb3V0cHV0X2NzZi5jbGVhcl9vdXRwdXQoKQoKICAgIHNlbGVjdF9vcHRpb24gPSB3aWRnZXRzLlJhZGlvQnV0dG9ucygKICAgICAgICBvcHRpb25zPVsiY3VzdG9tX3Njb3JpbmciLCAibWFudWFsX2NvbmZpZyJdLAogICAgICAgIHZhbHVlPSJjdXN0b21fc2NvcmluZyIsCiAgICAgICAgZGVzY3JpcHRpb249IldoYXQ6IiwKICAgICAgICBkaXNhYmxlZD1GYWxzZSwKICAgICkKICAgIHVpID0gd2lkZ2V0cy5IQm94KFtzZWxlY3Rfb3B0aW9uXSkKICAgIG9wdGlvbnMgPSB3aWRnZXRzLmludGVyYWN0aXZlX291dHB1dChldmFsdWF0ZSwgeyJzZWxlY3RlZCI6IHNlbGVjdF9vcHRpb259KQogICAgaW5zdHJ1Y3Rpb24gPSBpcHl3aWRnZXRzLndpZGdldHMuSFRNTCgKICAgICAgICAiPGZvbnQgc2l6ZSA9IDQ+PGI+PGZvbnQgZmFtaWx5OiBUaW1lcyBOZXcgUm9tYW4+Q29weSBhbmQgUGFzdGUgdGhlIHNjb3JpbmciCiAgICAgICAgIiBmdW5jdGlvbiBiZWxvdyBhbmQgZW50ZXI8L2I+PC9mb250LWZhbWlseT48L2ZvbnQgc2l6ZT4iCiAgICApCiAgICBkaXNwbGF5KGluc3RydWN0aW9uKQogICAgIyBidXR0b25zIHdpZGdldHMKICAgIGZpbGVfbmFtZSA9IHdpZGdldHMuVGV4dChkZXNjcmlwdGlvbj0iRmlsZW5hbWU6IiwgcGxhY2Vob2xkZXI9ImZpbGUgbmFtZSAiKQogICAgc2F2ZV9idXR0b24gPSB3aWRnZXRzLkJ1dHRvbihkZXNjcmlwdGlvbj0iU2F2ZSIpCiAgICBzYXZlX2J1dHRvbi5zdHlsZS5idXR0b25fY29sb3IgPSAibGlnaHRncmVlbiIKICAgIGNvbmZpcm1fYnV0dG9uID0gd2lkZ2V0cy5CdXR0b24oZGVzY3JpcHRpb249IkNvbmZpcm0iKQogICAgY29uZmlybV9idXR0b24uc3R5bGUuYnV0dG9uX2NvbG9yID0gInNhbG1vbiIKICAgICMgY2xlYXJfYnV0dG9uID0gd2lkZ2V0cy5CdXR0b24oZGVzY3JpcHRpb249IkNsZWFyIikKICAgICMgY2xlYXJfYnV0dG9uLnN0eWxlLmJ1dHRvbl9jb2xvciA9ICJsaWdodGdyZWVuIgogICAgZGlzcGxheSgod2lkZ2V0cy5WQm94KFtmaWxlX25hbWUsIHVpLCBvcHRpb25zXSkpLCBvdXRwdXRfY3NmKQogICAgZGlzcGxheSgod2lkZ2V0cy5IQm94KFtzYXZlX2J1dHRvbiwgY29uZmlybV9idXR0b25dKSkpCiAgICBzYXZlX2J1dHRvbi5vbl9jbGljayhzYXZlX3Njb3JpbmcpCiAgICBjb25maXJtX2J1dHRvbi5vbl9jbGljayhjb25maXJtX3Njb3JpbmcpCiAgICAjIGNsZWFyX2J1dHRvbi5vbl9jbGljayhhbGxfY2xlYXIpCgoKZGVmIHhnX21vZGVsKFgsIHkpOgogICAgZnJvbSBza2xlYXJuLmRhdGFzZXRzIGltcG9ydCBtYWtlX2NsYXNzaWZpY2F0aW9uCgogICAgbnVtX2NsYXNzZXMgPSAzCiAgICBYLCB5ID0gbWFrZV9jbGFzc2lmaWNhdGlvbihuX3NhbXBsZXM9MTAwMCwgbl9pbmZvcm1hdGl2ZT01LCBuX2NsYXNzZXM9bnVtX2NsYXNzZXMpCiAgICBkdHJhaW4gPSB4Z2IuRE1hdHJpeChkYXRhPVgsIGxhYmVsPXkpCiAgICBudW1fcGFyYWxsZWxfdHJlZSA9IDQKICAgIG51bV9ib29zdF9yb3VuZCA9IDE2CiAgICAjIHRvdGFsIG51bWJlciBvZiBidWlsdCB0cmVlcyBpcyBudW1fcGFyYWxsZWxfdHJlZSAqIG51bV9jbGFzc2VzICogbnVtX2Jvb3N0X3JvdW5kCgogICAgIyBXZSBidWlsZCBhIGJvb3N0ZWQgcmFuZG9tIGZvcmVzdCBmb3IgY2xhc3NpZmljYXRpb24gaGVyZS4KICAgIGJvb3N0ZXIgPSB4Z2IudHJhaW4oCiAgICAgICAgeyJudW1fcGFyYWxsZWxfdHJlZSI6IDQsICJzdWJzYW1wbGUiOiAwLjUsICJudW1fY2xhc3MiOiAzfSwKICAgICAgICBudW1fYm9vc3Rfcm91bmQ9bnVtX2Jvb3N0X3JvdW5kLAogICAgICAgIGR0cmFpbj1kdHJhaW4sCiAgICApCgogICAgIyBUaGlzIGlzIHRoZSBzbGljZWQgbW9kZWwsIGNvbnRhaW5pbmcgWzMsIDcpIGZvcmVzdHMKICAgICMgc3RlcCBpcyBhbHNvIHN1cHBvcnRlZCB3aXRoIHNvbWUgbGltaXRhdGlvbnMgbGlrZSBuZWdhdGl2ZSBzdGVwIGlzIGludmFsaWQuCiAgICBzbGljZWQ6IHhnYi5Cb29zdGVyID0gYm9vc3RlclszOjddCgogICAgcmV0dXJuIFtfIGZvciBfIGluIGJvb3N0ZXJdCgoKZGVmIHhnYl9ib29zdChmaWxlX3RyYWluLCBmaWxlX3Rlc3QpOgogICAgaW1wb3J0IHhnYm9vc3QgYXMgeGdiCgogICAgQ1VSUkVOVF9ESVIgPSBvcy5wYXRoLmRpcm5hbWUoX19maWxlX18pCiAgICBkdHJhaW4gPSB4Z2IuRE1hdHJpeChvcy5wYXRoLmpvaW4oQ1VSUkVOVF9ESVIsICJmaWxlX3RyYWluIikpCiAgICBkdGVzdCA9IHhnYi5ETWF0cml4KG9zLnBhdGguam9pbihDVVJSRU5UX0RJUiwgImZpbGVfdGVzdCIpKQogICAgcGFyYW0gPSB7CiAgICAgICAgIm9iamVjdGl2ZSI6ICJiaW5hcnk6bG9naXN0aWMiLAogICAgICAgICJib29zdGVyIjogImdibGluZWFyIiwKICAgICAgICAiYWxwaGEiOiAwLjAwMDEsCiAgICAgICAgImxhbWJkYSI6IDEsCiAgICB9CiAgICB3YXRjaGxpc3QgPSBbKGR0ZXN0LCAiZXZhbCIpLCAoZHRyYWluLCAidHJhaW4iKV0KICAgIG51bV9yb3VuZCA9IDQKICAgIGJzdCA9IHhnYi50cmFpbihwYXJhbSwgZHRyYWluLCBudW1fcm91bmQsIHdhdGNobGlzdCkKICAgIHByZWRzID0gYnN0LnByZWRpY3QoZHRlc3QpCiAgICBsYWJlbHMgPSBkdGVzdC5nZXRfbGFiZWwoKQogICAgcHJpbnQoCiAgICAgICAgImVycm9yPSVmIgogICAgICAgICUgKAogICAgICAgICAgICBzdW0oaW50KHByZWRzW2ldID4gMC41KSAhPSBsYWJlbHNbaV0gZm9yIGkgaW4gcmFuZ2UobGVuKHByZWRzKSkpCiAgICAgICAgICAgIC8gZmxvYXQobGVuKHByZWRzKSkKICAgICAgICApCiAgICApCgoKZGVmIHJ1bl9zbWluYShkaXJfbmFtZV8sIGNvbmZpZ19maWxlX25hbWUsICoqa3dhcmdzKToKICAgICIiIiBDcmVhdGVzIGZvbGRlciB3aXRoaW4gdGhlIGN3ZCB3aXRoIHRoZSBuYW1lIGlkIGFuZCBzdWJcCiAgICBmb2xkZXIgcnVuIHdoZXJlIGl0IHdpbGwgd3JpdGUgc2guQWxzbyBjcmVhdGVzIGEgZHVtcCBmb2xkZXJcCiAgICB3aGVyZSBlcnJvciBhbmQgb3V0cHV0IGxvZyB3aWxsIGJlIGR1bXBlZC4KCiAgICAiIiIKICAgIG1vZGUgPSBrd2FyZ3MuZ2V0KCJtb2RlIiwgRmFsc2UpCiAgICBsb2cgPSBrd2FyZ3MuZ2V0KCJsb2ciLCAibG9nLnR4dCIpCiAgICBvdXRwdXQgPSBrd2FyZ3MuZ2V0KCJvdXRwdXQiLCAib3V0cHV0LnNkZiIpCiAgICBsb2NhbCA9IGt3YXJncy5nZXQoImxvY2FsIiwgRmFsc2UpCiAgICBjcHVfbnVtID0ga3dhcmdzLmdldCgiY3B1IiwgMikKICAgIGpvYl9uYW1lID0ga3dhcmdzLmdldCgiam9iX25hbWUiLCBOb25lKQogICAgc2NvcmluZyA9IGt3YXJncy5nZXQoInNjb3JpbmciKQogICAgY3VzdG9tID0ga3dhcmdzLmdldCgiY3VzdG9tIiwgRmFsc2UpCiAgICBlbnRlcl9vdXRwdXQgPSBrd2FyZ3MuZ2V0KCJlbnRlcl9vdXRwdXQiLCBUcnVlKQogICAgZW50ZXJfbG9nID0ga3dhcmdzLmdldCgiZW50ZXJfbG9nIiwgVHJ1ZSkKICAgIGNsdXN0ZXIgPSBrd2FyZ3MuZ2V0KCJjbHVzdGVyIiwgTm9uZSkKICAgIGNsdXN0ZXJfZ3JwID0gWyJhbGwucSIsICJncDEiLCAiZ3AyIl0KICAgIGlmIChjbHVzdGVyIGlzIG5vdCBOb25lKSBhbmQgKGNsdXN0ZXIgbm90IGluIGNsdXN0ZXJfZ3JwKToKICAgICAgICByZXR1cm4gZiJJbnZhbGlkIGNsdXN0ZXIgbmFtZS4gQXZhaWxhYmxlIGNsdXN0ZXIgbmFtZXM6IHtjbHVzdGVyX2dycH0iCiAgICBuYW1lX2lkID0gY29uZmlnX2ZpbGVfbmFtZVs6NF0ubG93ZXIoKSAgIyBGSVgKICAgIGRpcl9uYW1lX2N3ZCA9IG9zLmdldGN3ZCgpCiAgICBkaXJfbmFtZSA9IG9zLnBhdGguZGlybmFtZShkaXJfbmFtZV8pCiAgICBQQVRIID0ga3dhcmdzLmdldCgiUEFUSCIsIEZhbHNlKQogICAgZGlyX25hbWUgPSBmIntkaXJfbmFtZX0iIGlmIFBBVEggZWxzZSBmIntkaXJfbmFtZV9jd2R9IgoKICAgIGlmIG5vdCBvcy5wYXRoLmV4aXN0cyhmIntkaXJfbmFtZX0vR2VuZXJhdGVkL2pvYnMve25hbWVfaWR9L3J1biIpOgogICAgICAgIG9zLm1ha2VkaXJzKGYie2Rpcl9uYW1lfS9HZW5lcmF0ZWQvam9icy97bmFtZV9pZH0vcnVuIikKICAgIGlmIGxvY2FsIGlzIEZhbHNlOgogICAgICAgIFNNSU5BX1BBVEggPSAiL3NoYXJlL3ZpbmEvc21pbmEiCiAgICAgICAgd2l0aCBvcGVuKAogICAgICAgICAgICBmIntkaXJfbmFtZX0vR2VuZXJhdGVkL2pvYnMve25hbWVfaWR9L3J1bi97bmFtZV9pZH1fU01pbmEuc2giLCAidyIKICAgICAgICApIGFzIG91dDoKICAgICAgICAgICAgaWYgam9iX25hbWUgaXMgTm9uZToKICAgICAgICAgICAgICAgIGpvYl9uYW1lID0gbmFtZV9pZAogICAgICAgICAgICBpZiBqb2JfbmFtZVswXS5pc2RpZ2l0KCk6CiAgICAgICAgICAgICAgICBqb2JfbmFtZSA9ICJTIiArIGpvYl9uYW1lCiAgICAgICAgICAgIHByaW50KGYiIyQgLU4ge2pvYl9uYW1lfSIsIGZpbGU9b3V0KQogICAgICAgICAgICBwcmludCgiIyQgLVYiLCBmaWxlPW91dCkKICAgICAgICAgICAgcHJpbnQoIiMkIC1TIC9iaW4vYmFzaCIsIGZpbGU9b3V0KQogICAgICAgICAgICBpZiBjbHVzdGVyIGlzIG5vdCBOb25lOgogICAgICAgICAgICAgICAgcHJpbnQoZiIjJCAtcSB7Y2x1c3Rlcn0iLCBmaWxlPW91dCkKICAgICAgICAgICAgcHJpbnQoZiIjJCAtcGUge2NwdV9udW19Y3B1IHtjcHVfbnVtfSIsIGZpbGU9b3V0KQogICAgICAgICAgICBpZiBub3Qgb3MucGF0aC5leGlzdHMoZiJ7ZGlyX25hbWV9L0dlbmVyYXRlZC9qb2JzL3tuYW1lX2lkfS9kdW1wLyIpOgogICAgICAgICAgICAgICAgb3MubWFrZWRpcnMoZiJ7ZGlyX25hbWV9L0dlbmVyYXRlZC9qb2JzL3tuYW1lX2lkfS9kdW1wLyIpCiAgICAgICAgICAgIHByaW50KGYiIyQgLW8ge2Rpcl9uYW1lfS9HZW5lcmF0ZWQvam9icy97bmFtZV9pZH0vZHVtcC8iLCBmaWxlPW91dCkKICAgICAgICAgICAgcHJpbnQoZiIjJCAtZSAge2Rpcl9uYW1lfS9HZW5lcmF0ZWQvam9icy97bmFtZV9pZH0vZHVtcC8iLCBmaWxlPW91dCkKICAgICAgICAgICAgcHJpbnQoIiMkIC1jd2QiLCBmaWxlPW91dCkKCiAgICAgICAgICAgICMgQ29uZGl0aW9uYWwgdG8gd3JpdGUgbG9nIGFuZCBvdXRwdXQKICAgICAgICAgICAgZW50ZXJfbG9nID0gZiItLWxvZyB7bG9nfSIgaWYgZW50ZXJfbG9nIGVsc2UgIiIKICAgICAgICAgICAgZW50ZXJfb3V0cHV0ID0gZiItLW91dCB7b3V0cHV0fSIgaWYgZW50ZXJfb3V0cHV0IGVsc2UgIiIKICAgICAgICAgICAgaWYgKG1vZGUgaXMgVHJ1ZSkgYW5kIChjdXN0b20gaXMgRmFsc2UpOgogICAgICAgICAgICAgICAgcHJpbnQoCiAgICAgICAgICAgICAgICAgICAgZiJ7U01JTkFfUEFUSH0gLS1jb25maWciCiAgICAgICAgICAgICAgICAgICAgZiIge2Rpcl9uYW1lfS9HZW5lcmF0ZWQvc21pbmFfaW5wdXQve2NvbmZpZ19maWxlX25hbWV9IgogICAgICAgICAgICAgICAgICAgIGYiIC0tc2NvcmluZyB7c2NvcmluZ30gLS1zY29yZV9vbmx5IHtlbnRlcl9sb2d9IHtlbnRlcl9vdXRwdXR9IiwKICAgICAgICAgICAgICAgICAgICBmaWxlPW91dCwKICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgIGVsaWYgKG1vZGUgaXMgVHJ1ZSkgYW5kIChjdXN0b20gaXMgVHJ1ZSk6CiAgICAgICAgICAgICAgICBwcmludCgKICAgICAgICAgICAgICAgICAgICBmIntTTUlOQV9QQVRIfSAtLWNvbmZpZyIKICAgICAgICAgICAgICAgICAgICBmIiB7ZGlyX25hbWV9L0dlbmVyYXRlZC9zbWluYV9pbnB1dC97Y29uZmlnX2ZpbGVfbmFtZX0iCiAgICAgICAgICAgICAgICAgICAgZiIgLS1jdXN0b21fc2NvcmluZyB7c2NvcmluZ30gLS1zY29yZV9vbmx5ICB7ZW50ZXJfb3V0cHV0fSIKICAgICAgICAgICAgICAgICAgICBmIiB7ZW50ZXJfbG9nfSIsCiAgICAgICAgICAgICAgICAgICAgZmlsZT1vdXQsCiAgICAgICAgICAgICAgICApCgogICAgICAgICAgICBlbGlmIChtb2RlIGlzIEZhbHNlKSBhbmQgKGN1c3RvbSBpcyBGYWxzZSk6CiAgICAgICAgICAgICAgICBwcmludCgKICAgICAgICAgICAgICAgICAgICBmIntTTUlOQV9QQVRIfSAtLWNvbmZpZyIKICAgICAgICAgICAgICAgICAgICBmIiB7ZGlyX25hbWV9L0dlbmVyYXRlZC9zbWluYV9pbnB1dC97Y29uZmlnX2ZpbGVfbmFtZX0gLS1zY29yaW5nIgogICAgICAgICAgICAgICAgICAgIGYiIHtzY29yaW5nfSAge2VudGVyX2xvZ30ge2VudGVyX291dHB1dH0gIiwKICAgICAgICAgICAgICAgICAgICBmaWxlPW91dCwKICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgIGVsaWYgKG1vZGUgaXMgRmFsc2UpIGFuZCAoY3VzdG9tIGlzIFRydWUpOgogICAgICAgICAgICAgICAgcHJpbnQoCiAgICAgICAgICAgICAgICAgICAgZiJ7U01JTkFfUEFUSH0gLS1jb25maWciCiAgICAgICAgICAgICAgICAgICAgZiIge2Rpcl9uYW1lfS9HZW5lcmF0ZWQvc21pbmFfaW5wdXQve2NvbmZpZ19maWxlX25hbWV9IgogICAgICAgICAgICAgICAgICAgIGYiIC0tY3VzdG9tX3Njb3Jpbmcge3Njb3Jpbmd9ICB7ZW50ZXJfbG9nfSB7ZW50ZXJfb3V0cHV0fSAiLAogICAgICAgICAgICAgICAgICAgIGZpbGU9b3V0LAogICAgICAgICAgICAgICAgKQoKICAgICAgICBjb21tYW5kID0gZiJxc3ViIHtkaXJfbmFtZX0vR2VuZXJhdGVkL2pvYnMve25hbWVfaWR9L3J1bi97bmFtZV9pZH1fU01pbmEuc2giCiAgICBlbHNlOgogICAgICAgIGlmIGN1c3RvbSBpcyBGYWxzZToKICAgICAgICAgICAgY29tbWFuZCA9ICgKICAgICAgICAgICAgICAgIGYic21pbmEgLS1jb25maWcge2Rpcl9uYW1lfS9HZW5lcmF0ZWQvc21pbmFfaW5wdXQve2NvbmZpZ19maWxlX25hbWV9IgogICAgICAgICAgICAgICAgZiIgLS1zY29yaW5nIHtzY29yaW5nfSB7ZW50ZXJfbG9nfSB7ZW50ZXJfb3V0cHV0fSIKICAgICAgICAgICAgKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGNvbW1hbmQgPSAoCiAgICAgICAgICAgICAgICBmInNtaW5hIC0tY29uZmlnIHtkaXJfbmFtZX0vR2VuZXJhdGVkL3NtaW5hX2lucHV0L3tjb25maWdfZmlsZV9uYW1lfSIKICAgICAgICAgICAgICAgIGYiIC0tY3VzdG9tX3Njb3Jpbmcge3Njb3Jpbmd9ICB7ZW50ZXJfbG9nfSB7ZW50ZXJfb3V0cHV0fSIKICAgICAgICAgICAgKQoKICAgIHN1YnByb2Nlc3MucnVuKGNvbW1hbmQsIGN3ZD1mIntkaXJfbmFtZX0vR2VuZXJhdGVkL2pvYnMve25hbWVfaWR9Iiwgc2hlbGw9VHJ1ZSkKCiAgICByZXR1cm4gIlN1Y2Nlc2Z1bGx5IGNvbXBsZXRlZC4iCgoKIyBSRUNPUkRTIE9GIEFMTCBTQ09SSU5HIEZVTkNUSU9OCiMgU0YgPSBbICAiYWQ0X3Njb3JpbmciLAojICAgICAgICAiZGVmYXVsdCIsCiMgICAgICAgICJka29lc19mYXN0IiwKIyAgICAgICAgImRrb2VzX3Njb3JpbmciLAojICAgICAgICAiZGtvZXNfc2NvcmluZ19vbGQiLAojICAgICAgICAidmluYSIsCiMgICAgICAgICJ2aW5hcmRvIiwKIyAgICBdCgojIENTRiA9IFsiY3VzdG9tIl0gIyBmb3IgY3VzdG9tIHNjb3JpbmcgIGFuZCBwYXNzIGN1c3RvbV9zY29yaW5nX2ZpbGU9UEFUSCB0byB0aGUgZnVuY3Rpb24KIyBPdGhlcndpc2UgYWxsIHRoZSBvdGhlciBTRiB3aWxsIGJlIHJ1biBidXQgd2lsbCBiZSBjYWxjdWxhdGUgd2l0aCBjc2YKIyAgZm9yIG51bWVyb3VzIHRpbWUKCgpkZWYgc21pbmFfcnVuKHByb3RlaW5fbGlzdCwgbGlnYW5kX2xpc3QsICoqa3dhcmdzKToKCiAgICAiIiJQcmVwYXJlcyBjb25maWcgZmlsZSBmb3Igc21pbmEgd2hlbiBlbnRlciBwcm90ZXJpbiBhbmQgbGlnYW5kCiAgICBBYm92ZSBzaF9ydW4gZnVuY3Rpb24gbXVzdCBiZSBpbml0aWFsZXplZCBiZWZvcmUgaW4gbm90ZWJvb2siIiIKCiAgICBTRiA9IGt3YXJncy5nZXQoIlNGIikKICAgIGlmIG5vdCBpc2luc3RhbmNlKFNGLCBsaXN0KToKICAgICAgICByZXR1cm4gIlN1cHBsaWVkIFNGIGlzIG5vdCBhIGxpc3QiCiAgICBjbHVzdGVyID0ga3dhcmdzLmdldCgiY2x1c3RlciIsIE5vbmUpCiAgICBjbHVzdGVyX2dycCA9IFsiYWxsLnEiLCAiZ3AxIiwgImdwMiJdCiAgICBpZiAoY2x1c3RlciBpcyBub3QgTm9uZSkgYW5kIChjbHVzdGVyIG5vdCBpbiBjbHVzdGVyX2dycCk6CiAgICAgICAgcmV0dXJuIGYiSW52YWxpZCBjbHVzdGVyIG5hbWUuIEF2YWlsYWJsZSBjbHVzdGVyIG5hbWVzOiB7Y2x1c3Rlcl9ncnB9IgogICAgYXV0b2JveCA9IGt3YXJncy5nZXQoImF1dG9ib3giLCBOb25lKQogICAgaWYgaXNpbnN0YW5jZShhdXRvYm94LCBsaXN0KToKICAgICAgICBhdXRvYm94ID0gYXV0b2JveFswXQogICAgbWFudWFsX2NvbmZpZyA9IGt3YXJncy5nZXQoIm1hbnVhbF9jb25maWciLCBOb25lKQogICAgaWYgaXNpbnN0YW5jZShtYW51YWxfY29uZmlnLCBsaXN0KToKICAgICAgICBtYW51YWxfY29uZmlnID0gbWFudWFsX2NvbmZpZ1swXQogICAgcnVuID0ga3dhcmdzLmdldCgicnVuIiwgRmFsc2UpCiAgICBtb2RlID0ga3dhcmdzLmdldCgibW9kZSIsIEZhbHNlKQogICAgbG9jYWwgPSBrd2FyZ3MuZ2V0KCJsb2NhbCIsIEZhbHNlKQogICAgam9iX25hbWUgPSBrd2FyZ3MuZ2V0KCJqb2JfbmFtZSIsIE5vbmUpCiAgICBOVU1fTU9ERVMgPSBrd2FyZ3MuZ2V0KCJudW1fbW9kZXMiLCAxMCkKICAgIEVYSEFVU1RJVkUgPSBrd2FyZ3MuZ2V0KCJleGhhdXN0aXZlIiwgNTApCiAgICBFTkVSR1lfUkFOR0UgPSBrd2FyZ3MuZ2V0KCJlbmVyZ3lfcmFuZ2UiLCAxMCkKICAgIFNFRUQgPSBrd2FyZ3MuZ2V0KCJzZWVkIiwgTm9uZSkKICAgIEFVVE9CT1hfUEFEID0ga3dhcmdzLmdldCgicGFkIiwgNCkKICAgIENQVV9OVU0gPSBrd2FyZ3MuZ2V0KCJjcHUiLCA4KQogICAgbm9tYXRjaCA9IGt3YXJncy5nZXQoIm1hdGNoIiwgRmFsc2UpCiAgICBPVVRfRk9STUFUID0ga3dhcmdzLmdldCgib3V0X2Zvcm1hdCIsICJzZGYiKQogICAgY3VzdG9tID0ga3dhcmdzLmdldCgiY3VzdG9tIiwgRmFsc2UpCgogICAgIyBpZiBDVVNUT01fU0NPUkUgaXMgbm90IE5vbmU6CiAgICAjICAgIENTRl9GTEFHID0gVHJ1ZQogICAgIyBlbHNlOgogICAgIyAgICBDU0ZfRkxBRyA9IEZhbHNlCgogICAgZGVmIHdyaXRlX2NvbmZpZygKICAgICAgICByZWNlcHRvciwKICAgICAgICBsaWdhbmQsCiAgICAgICAgY29uZmlnX2ZpbGVfbmFtZSwKICAgICAgICBvdXRwdXRfZmlsZV9uYW1lLAogICAgICAgIGxvZ19maWxlX25hbWUsCiAgICAgICAgIyBzY29yaW5nPU5vbmUsICMgTW92ZWQgdG8gQ0xJCiAgICApOgogICAgICAgICIiIldyaXRlcyBjb25maWcgaW50byBuZXcgZmlsZXMsIGlmIGFscmVhZHkgXAogICAgICAgIGV4aXN0IGFwcGVuZCB0byBpdC4iIiIKCiAgICAgICAgIyBkaXJfbmFtZSA9IG9zLnBhdGguZGlybmFtZShyZWNlcHRvcikKICAgICAgICBkaXJfbmFtZSA9IG9zLmdldGN3ZCgpCgogICAgICAgIGlmIG5vdCBvcy5wYXRoLmV4aXN0cyhmIntkaXJfbmFtZX0vR2VuZXJhdGVkL3NtaW5hX2lucHV0Iik6CiAgICAgICAgICAgIG9zLm1ha2VkaXJzKGYie2Rpcl9uYW1lfS9HZW5lcmF0ZWQvc21pbmFfaW5wdXQvIikKICAgICAgICB3aXRoIG9wZW4oCiAgICAgICAgICAgIGYie2Rpcl9uYW1lfS9HZW5lcmF0ZWQvc21pbmFfaW5wdXQve2NvbmZpZ19maWxlX25hbWV9IiwgIncrIgogICAgICAgICkgYXMgY29uZmlnX2ZpbGU6CgogICAgICAgICAgICAjIHJlcXVpcmVkIGNvbmZpZyBhcmd1bWVudHMKICAgICAgICAgICAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KICAgICAgICAgICAgcHJpbnQoZiJyZWNlcHRvciA9IHtyZWNlcHRvcn0gIiwgZmlsZT1jb25maWdfZmlsZSkKICAgICAgICAgICAgcHJpbnQoZiJsaWdhbmQgPSB7bGlnYW5kfSIsIGZpbGU9Y29uZmlnX2ZpbGUpCiAgICAgICAgICAgIGlmIGF1dG9ib3ggaXMgbm90IE5vbmU6CiAgICAgICAgICAgICAgICBwcmludChmImF1dG9ib3hfbGlnYW5kID0ge2F1dG9ib3h9IiwgZmlsZT1jb25maWdfZmlsZSkKICAgICAgICAgICAgICAgIHByaW50KGYiYXV0b2JveF9hZGQgPSB7QVVUT0JPWF9QQUR9IiwgZmlsZT1jb25maWdfZmlsZSkKCiAgICAgICAgICAgICMgT3B0aW9uYWxzIGNvbiAgIyBNT1ZFRCBUTyBDTEkKICAgICAgICAgICAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KICAgICAgICAgICAgcHJpbnQoZiJvdXQgPSB7b3V0cHV0X2ZpbGVfbmFtZX0iLCBmaWxlPWNvbmZpZ19maWxlKQogICAgICAgICAgICBwcmludChmImxvZyA9IHtsb2dfZmlsZV9uYW1lfSIsIGZpbGU9Y29uZmlnX2ZpbGUpCiAgICAgICAgICAgICMgcHJpbnQoZiJzY29yaW5nID0ge3Njb3Jpbmd9IiwgZmlsZT1jb25maWdfZmlsZSkgICMjIGNoYW5nZSB0byBydW4gaW4gQ0xJCgogICAgICAgICAgICAjIE1pc2Mob3B0aW9uYWwpIGNvbmZpZ3MKICAgICAgICAgICAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogICAgICAgICAgICAjIGlmIENVU1RPTV9TQ09SRSBpcyBub3QgTm9uZTogICMjIE1PVkVEIFRPIENMSQogICAgICAgICAgICAjICAgIHByaW50KGYiY3VzdG9tX3Njb3JpbmcgPSB7Q1VTVE9NX1NDT1JFfSIsIGZpbGU9Y29uZmlnX2ZpbGUpCiAgICAgICAgICAgICMgaWYgU01JTkFfTU9ERSBpcyBub3QgTm9uZToKICAgICAgICAgICAgIyAgICBwcmludChmIntTTUlOQV9NT0RFfSIsIGZpbGU9Y29uZmlnX2ZpbGUpCgogICAgICAgICAgICBwcmludChmImNwdSA9IHtDUFVfTlVNfSIsIGZpbGU9Y29uZmlnX2ZpbGUpCiAgICAgICAgICAgIGlmIFNFRUQgaXMgbm90IE5vbmU6CiAgICAgICAgICAgICAgICBwcmludChmIlxuXG5zZWVkID0ge1NFRUR9IiwgZmlsZT1jb25maWdfZmlsZSkKICAgICAgICAgICAgcHJpbnQoZiJleGhhdXN0aXZlbmVzcyA9IHtFWEhBVVNUSVZFfSIsIGZpbGU9Y29uZmlnX2ZpbGUpCiAgICAgICAgICAgICMgaWYgQ1VTVE9NX1NDT1JFIGlzIE5vbmU6ICMjIGNoYW5nZSB0byBydW4gaW4gQ0xJCiAgICAgICAgICAgIHByaW50KGYibnVtX21vZGVzID0ge05VTV9NT0RFU30iLCBmaWxlPWNvbmZpZ19maWxlKQogICAgICAgICAgICBwcmludChmImVuZXJneV9yYW5nZSA9IHtFTkVSR1lfUkFOR0UgfSIsIGZpbGU9Y29uZmlnX2ZpbGUpCgogICAgZm9yIHNjb3JpbmcgaW4gU0Y6CiAgICAgICAgZm9yIGxpZ2FuZCBpbiBsaWdhbmRfbGlzdDoKICAgICAgICAgICAgcHJvdGVpbl9kaXIsIHByb3RlaW5faWQsIHByb3RfZm9ybWF0ID0gZ2l2ZV9pZChwcm90ZWluX2xpc3RbMF0pCiAgICAgICAgICAgIGxpZ2FuZF9kaXIsIGxpZ2FuZF9pZCwgbGlnX2Zvcm1hdCA9IGdpdmVfaWQobGlnYW5kKQogICAgICAgICAgICBpZiBjdXN0b206CiAgICAgICAgICAgICAgICBfZGlyLCBfbmFtZSwgX2Zvcm1hdCA9IGdpdmVfaWQoc2NvcmluZykKICAgICAgICAgICAgICAgIHNjb3JpbmcgPSBfbmFtZQogICAgICAgICAgICBpZiBwcm90ZWluX2lkWzo0XS5sb3dlcigpID09IGxpZ2FuZF9pZFs6NF0ubG93ZXIoKSBvciAobm9tYXRjaCA9PSBUcnVlKToKICAgICAgICAgICAgICAgIG91dHB1dF9maWxlX25hbWUgPSBsaWdhbmRfaWQgKyBmIl9vdXRwdXRfe3Njb3Jpbmd9LntPVVRfRk9STUFUfSIKICAgICAgICAgICAgICAgIGxvZ19maWxlX25hbWUgPSBsaWdhbmRfaWQgKyBmIl9sb2dfe3Njb3Jpbmd9LnR4dCIKICAgICAgICAgICAgICAgIGlmIG1hbnVhbF9jb25maWcgaXMgTm9uZToKCiAgICAgICAgICAgICAgICAgICAgY29uZmlnX2ZpbGVfbmFtZSA9IGxpZ2FuZF9pZC5sb3dlcigpICsgZiJfY29uZmlnX3tzY29yaW5nfS50eHQiCiAgICAgICAgICAgICAgICAgICAgZW50ZXJfb3V0cHV0ID0gVHJ1ZQogICAgICAgICAgICAgICAgICAgIGVudGVyX2xvZyA9IFRydWUKICAgICAgICAgICAgICAgICAgICB3cml0ZV9jb25maWcoCiAgICAgICAgICAgICAgICAgICAgICAgIHByb3RlaW5fbGlzdFswXSwKICAgICAgICAgICAgICAgICAgICAgICAgbGlnYW5kLAogICAgICAgICAgICAgICAgICAgICAgICBjb25maWdfZmlsZV9uYW1lLAogICAgICAgICAgICAgICAgICAgICAgICBvdXRwdXRfZmlsZV9uYW1lLAogICAgICAgICAgICAgICAgICAgICAgICBsb2dfZmlsZV9uYW1lLAogICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIGVsc2U6CgogICAgICAgICAgICAgICAgICAgICgKICAgICAgICAgICAgICAgICAgICAgICAgbWFudWFsX2NvbmZpZ19kaXIsCiAgICAgICAgICAgICAgICAgICAgICAgIG1hbnVhbF9jb25maWdfbmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgbWFudWFsX2NvbmZpZ19mb3JtYXQsCiAgICAgICAgICAgICAgICAgICAgKSA9IGdpdmVfaWQobWFudWFsX2NvbmZpZykKICAgICAgICAgICAgICAgICAgICBjb25maWdfZmlsZV9uYW1lID0gZiJ7bWFudWFsX2NvbmZpZ19uYW1lfS57bWFudWFsX2NvbmZpZ19mb3JtYXR9IgogICAgICAgICAgICAgICAgICAgIGlmICJvdXRwdXQiIGluIG9wZW4obWFudWFsX2NvbmZpZykucmVhZCgpOgogICAgICAgICAgICAgICAgICAgICAgICBlbnRlcl9vdXRwdXQgPSBGYWxzZQogICAgICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgICAgIGVudGVyX291dHB1dCA9IFRydWUKICAgICAgICAgICAgICAgICAgICBpZiAibG9nIiBpbiBvcGVuKG1hbnVhbF9jb25maWcpLnJlYWQoKToKICAgICAgICAgICAgICAgICAgICAgICAgZW50ZXJfbG9nID0gRmFsc2UKICAgICAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgICAgICBlbnRlcl9sb2cgPSBUcnVlCgogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgcHJpbnQoIlByb3RlaW4gYW5kIGxpZ2FuZCBwcmVmaXggWzQgbGV0dGVyXSBkaWRudCBtYXRjaCIpCiAgICAgICAgICAgIGlmIGN1c3RvbToKICAgICAgICAgICAgICAgIHNjb3JpbmcgPSBmIntfZGlyfS97X25hbWV9LntfZm9ybWF0fSIKICAgICAgICAgICAgaWYgcnVuIGlzIFRydWUgYW5kIG1vZGUgaXMgVHJ1ZToKICAgICAgICAgICAgICAgIHJ1bl9zbWluYSgKICAgICAgICAgICAgICAgICAgICBwcm90ZWluX2RpciwKICAgICAgICAgICAgICAgICAgICBjb25maWdfZmlsZV9uYW1lLAogICAgICAgICAgICAgICAgICAgIHNjb3Jpbmc9c2NvcmluZywKICAgICAgICAgICAgICAgICAgICBtb2RlPW1vZGUsCiAgICAgICAgICAgICAgICAgICAgbG9jYWw9bG9jYWwsCiAgICAgICAgICAgICAgICAgICAgIyBjcHU9Q1BVX05VTSwKICAgICAgICAgICAgICAgICAgICAjIGpvYl9uYW1lPWpvYl9uYW1lLAogICAgICAgICAgICAgICAgICAgIGN1c3RvbT1jdXN0b20sCiAgICAgICAgICAgICAgICAgICAgbG9nPWxvZ19maWxlX25hbWUsCiAgICAgICAgICAgICAgICAgICAgb3V0cHV0PW91dHB1dF9maWxlX25hbWUsCiAgICAgICAgICAgICAgICAgICAgZW50ZXJfb3V0cHV0PWVudGVyX291dHB1dCwKICAgICAgICAgICAgICAgICAgICBlbnRlcl9sb2c9ZW50ZXJfbG9nLAogICAgICAgICAgICAgICAgKQogICAgICAgICAgICBlbGlmIHJ1biBpcyBUcnVlIGFuZCBtb2RlIGlzIEZhbHNlOgogICAgICAgICAgICAgICAgcnVuX3NtaW5hKAogICAgICAgICAgICAgICAgICAgIHByb3RlaW5fZGlyLAogICAgICAgICAgICAgICAgICAgIGNvbmZpZ19maWxlX25hbWUsCiAgICAgICAgICAgICAgICAgICAgc2NvcmluZz1zY29yaW5nLAogICAgICAgICAgICAgICAgICAgIGxvY2FsPWxvY2FsLAogICAgICAgICAgICAgICAgICAgIGNwdT1DUFVfTlVNLAogICAgICAgICAgICAgICAgICAgIGpvYl9uYW1lPWpvYl9uYW1lLAogICAgICAgICAgICAgICAgICAgIGN1c3RvbT1jdXN0b20sCiAgICAgICAgICAgICAgICAgICAgb3V0cHV0PW91dHB1dF9maWxlX25hbWUsCiAgICAgICAgICAgICAgICAgICAgbG9nPWxvZ19maWxlX25hbWUsCiAgICAgICAgICAgICAgICAgICAgZW50ZXJfb3V0cHV0PWVudGVyX291dHB1dCwKICAgICAgICAgICAgICAgICAgICBlbnRlcl9sb2c9ZW50ZXJfbG9nLAogICAgICAgICAgICAgICAgICAgIFBBVEg9VHJ1ZSwKICAgICAgICAgICAgICAgICAgICBjbHVzdGVyPWNsdXN0ZXIsCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBwcmludCgKICAgICAgICAgICAgICAgICAgICAiUnVuIGNvbW1hbmQgd2FzIG5vdCBwYXNzZWQgc28gb25seSBjcmVhdGVkIHRoZSIKICAgICAgICAgICAgICAgICAgICAiIGNvbmZpZyBhbmQgc2ggZmlsZSBidXQgbm90IGV4ZWN1dGVkIgogICAgICAgICAgICAgICAgKQogICAgICAgICMgcHJpbnQoYmFzZV9uYW1lKQogICAgcmV0dXJuICJTdWNjZXNmdWxseSBjb21wbGV0ZWQuIgoKCmRlZiB2aWV3X2FmZmluaXR5KHNvcnRlZF9SRVMsIGtleXdvcmQpOgogICAgZGYgPSBwZC5EYXRhRnJhbWUoCiAgICAgICAgWwogICAgICAgICAgICAoa2V5LCB2YWx1ZSkKICAgICAgICAgICAgZm9yIGtleSwgdmFsdWUgaW4gc29ydGVkX1JFUy5pdGVtcygpCiAgICAgICAgICAgIGlmIGYie2tleXdvcmR9IiBpbiBrZXkubG93ZXIoKQogICAgICAgIF0sCiAgICAgICAgY29sdW1ucz1bIlBvc2UiLCAiQWZmaW5pdHkiXSwKICAgICkKICAgIHJldHVybiBkZgoKCmRlZiBzbWluYV9vdXRwdXRfZGYoc29ydGVkX1JFUyk6CiAgICByZXR1cm4gcGQuRGF0YUZyYW1lKAogICAgICAgIFsoa2V5LCB2YWx1ZSkgZm9yIGtleSwgdmFsdWUgaW4gc29ydGVkX1JFUy5pdGVtcygpXSwKICAgICAgICBjb2x1bW5zPVsiUG9zZSIsICJBZmZpbml0eSJdLAogICAgKQoKCmRlZiBybXNkX21hdHJpeCgKICAgIHJlZiwgbGVuZ3RoPTIsIGtleT0iTUFUUklYIiwgdmVyYm9zZT1GYWxzZSwgcGxvdD1UcnVlLCBzYXZlPUZhbHNlLCBhbm5vdD1GYWxzZQopOgogICAgX3Jtc2RfbGlzdCA9IGl0ZXJ0b29scy5jb21iaW5hdGlvbnMocmVmLCBsZW5ndGgpCiAgICBjb2xzID0gWyJSZWZlcmVuY2UiLCAiUG9zZSIsICJSTVNEIl0KICAgIGRmID0gcGQuRGF0YUZyYW1lKGNvbHVtbnM9Y29scykKICAgIGZvciBjb3VudCwgaSBpbiBlbnVtZXJhdGUoX3Jtc2RfbGlzdCk6CiAgICAgICAgcmVmLCBjb25mID0gbGlzdChbaVswXV0pLCBsaXN0KFtpWzFdXSkKICAgICAgICB4ID0gcm1zZF9jYWxjdWxhdG9yKHJlZiwgY29uZiwgbm9tYXRjaD1UcnVlLCBrZXk9a2V5LCB2ZXJib3NlPUZhbHNlKQogICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgIHByaW50KGYie2NvdW50fS4ge3h9Iiwgc2VwPSIgIiwgZmx1c2g9VHJ1ZSkKICAgICAgICB4MSwgeDIsIHgzID0geC5zcGxpdCgiXHQiKQogICAgICAgIGRmLmxvY1tjb3VudF0gPSBbeDEsIHgyLCB4M10KCiAgICBtZGYgPSBkZi5waXZvdChpbmRleD0iUmVmZXJlbmNlIiwgY29sdW1ucz0iUG9zZSIsIHZhbHVlcz0iUk1TRCIpCiAgICBtZGYuZmlsbG5hKDApCiAgICBtZGYgPSBtZGYuYXN0eXBlKGZsb2F0KQogICAgaWYgcGxvdDoKICAgICAgICAjIHBsdC5maWd1cmUoZmlnc2l6ZT1bMTUsIDhdKQogICAgICAgIGhtYXAgPSBzbnMuaGVhdG1hcChtZGYsIGFubm90PWFubm90KQogICAgICAgIGhtYXAuc2V0X3RpdGxlKCJSTVNEIE1BVFJJWCIpCiAgICAgICAgaWYgc2F2ZToKICAgICAgICAgICAgaWYgbm90IG9zLnBhdGguZXhpc3RzKCIuL0dlbmVyYXRlZC9pbWFnZXMvIik6CiAgICAgICAgICAgICAgICBvcy5tYWtlZGlycygiLi9HZW5lcmF0ZWQvaW1hZ2VzLyIpCiAgICAgICAgICAgIGZpZyA9IGhtYXAuZmlndXJlCiAgICAgICAgICAgIGltYWdlID0gZiIuL0dlbmVyYXRlZC9pbWFnZXMve2tleX0uanBnIgogICAgICAgICAgICBmaWcuc2F2ZWZpZyhpbWFnZSwgZHBpPTYwMCkKICAgICAgICAgICAgcHJpbnQoZiJTYXZlZCAhIHtpbWFnZX0iKQogICAgcmV0dXJuIG1kZgo", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAAASOwAAAAAAABI7AAAAAAAAAAAAAAAA8L8" - ], - [ - 6486, - 1, - "insert", - { - "characters": "\\n" - }, - "AgAAAMg8AAAAAAAAyTwAAAAAAAAAAAAAyTwAAAAAAADKPAAAAAAAAAAAAAA", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAADIPAAAAAAAAMg8AAAAAAAAAAAAAAAA8L8" - ], - [ - 6490, - 1, - "black", - null, - "AQAAAAAAAAAAAAAA0JMAAAAAAADQkwAAIyBGdW5jdGlvbnMgaW52bG92ZWQgaW4gZG9ja2luZyB1c2luZyBzbWluYQoKaW1wb3J0IHJlCmltcG9ydCBvcwppbXBvcnQgc3VicHJvY2VzcwppbXBvcnQgaXB5d2lkZ2V0cwppbXBvcnQgaXRlcnRvb2xzCmZyb20gaXB5d2lkZ2V0cyBpbXBvcnQgKAogICAgRmlsZVVwbG9hZCwKICAgIEludFNsaWRlciwKICAgIGZpeGVkLAogICAgaW50ZXJhY3RpdmUsCiAgICBpbnRlcmFjdGl2ZV9vdXRwdXQsCiAgICB3aWRnZXRzLAogICAgTGF5b3V0LAopCmZyb20gSVB5dGhvbi5kaXNwbGF5IGltcG9ydCBIVE1MLCBkaXNwbGF5CmltcG9ydCBwYW5kYXMgYXMgcGQKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQKZnJvbSBtYXRwbG90bGliLm9mZnNldGJveCBpbXBvcnQgQW5jaG9yZWRUZXh0CmZyb20gY3NmZG9jay51dGlscyBpbXBvcnQgZ2l2ZV9pZApmcm9tIHNrbGVhcm4uZGF0YXNldHMgaW1wb3J0IG1ha2VfY2xhc3NpZmljYXRpb24KZnJvbSBza2xlYXJuLmxpbmVhcl9tb2RlbCBpbXBvcnQgTGluZWFyUmVncmVzc2lvbiwgTG9naXN0aWNSZWdyZXNzaW9uCmZyb20gc2tsZWFybi5tZXRyaWNzIGltcG9ydCBhY2N1cmFjeV9zY29yZSwgcm9jX2F1Y19zY29yZSwgcm9jX2N1cnZlCmZyb20gc2tsZWFybi5tb2RlbF9zZWxlY3Rpb24gaW1wb3J0IEtGb2xkLCB0cmFpbl90ZXN0X3NwbGl0CmZyb20gc2NpcHkuc3BlY2lhbCBpbXBvcnQgZXhwaXQKaW1wb3J0IHNlYWJvcm4gYXMgc25zCmZyb20gcmljaC5jb25zb2xlIGltcG9ydCBDb25zb2xlCgpjb25zb2xlID0gQ29uc29sZSgpCgoKZGVmIGFkZF9oeWRyb2dlbihsaXN0X3h5eik6CiAgICAiIiJBIGZ1bmN0aW9uIHRvIGFkZCBoeWRyb2dlbiBhdG9tcyB1c2luZwogICAgb3BlbmJhYmVsIHRvIGEgbGlzdCBvZiBtb2xlY3VsZXMuCiAgICBDcmVhdGVzIHN1YiBmb2xkZXIgInByb3RlaW5faWQvcG9zZXMiIGluCiAgICBpbnB1dCBmaWxlIGRpcmVjdG9yeSBhbmQgZHVtcCB0aGVyZS4KICAgIEFyZ3M6CiAgICAgICAgbGlzdF94eXogKGxpc3QpOiBNb2xlY3VsZXMgdG8gYmUgYWRkZWQgSHlkcm9nZW4KICAgIFJldHVybnM6CiAgICAgICAgeHl6OiBzYXZlcyB4eXogaW4gcHJvdGVpbl9pZC9wb3NlcyB3aXRoIHN1ZmZpeCAiX2FkZEhzLnh5eiIKICAgICIiIgogICAgIyBGT1IgU0VSVkVSIG9yIHVzZSBvd24gcGF0aC4uCiAgICBPQkFCRUxfUEFUSCA9ICIvc2hhcmUvb3BlbmJhYmVsLTMuMS4xL2Jpbi9vYmFiZWwiCiAgICBmb3IgaSBpbiBsaXN0X3h5ejoKICAgICAgICBkaXJfbmFtZSwgaWQsIGZpbGVfZm9ybWF0ID0gZ2l2ZV9pZChpKQogICAgICAgIGlmIG5vdCBvcy5wYXRoLmV4aXN0cyhmIntkaXJfbmFtZX0vYWRkSC97aWRbOjRdfSIpOgogICAgICAgICAgICBvcy5tYWtlZGlycyhmIntkaXJfbmFtZX0vYWRkSC97aWRbOjRdfSIpCiAgICAgICAgY29tbWFuZCA9IGYie09CQUJFTF9QQVRIfSB7aX0gLU8ge2Rpcl9uYW1lfS9hZGRIL3tpZFs6NF19L3tpZH1fYWRkSHMueHl6IC1oIgogICAgICAgIHN1YnByb2Nlc3MucnVuKGNvbW1hbmQsIGN3ZD1mIntkaXJfbmFtZX0iLCBzaGVsbD1UcnVlKQogICAgcmV0dXJuICJTdWNjZXNzZnVsbHkgQ29tcGxldGVkLiIKCgpkZWYgcm1zZF9jYWxjdWxhdG9yKHJlZmVyZW5jZSwgcG9zZXNfbGlzdCwga2V5PU5vbmUsIG5vbWF0Y2g9RmFsc2UsIHZlcmJvc2U9VHJ1ZSk6CiAgICAiIiJDYWxjdWxhdGVzIFJNU0QgYmV0d2VlbiByZWZlcmVuY2UgYW5kIHBvc2UgdXNpbmcgb2JybXMgaW4gc2VydmVyLgogICAgQXJnczoKICAgICAgICByZWZlcmVuY2UgKHJlZmVyZW5jZSBtb2xlY3VsZSk6IFJlZmVyZW5jZSBtb2xlY3VsZQogICAgICAgIHBvc2VzX2xpc3QgKHRlc3RfcG9zZXMpOiBMaXN0IG9mIHBvc2VzIHRvIHRlc3Qgd2l0aCB0aGUgcmVmZXJlbmNlLgogICAgICAgIGtleT0gQW55IHN1ZmZpeCB0byBhZGQgdG8gdGhlIG5hbWUgb2YgdGhlIGZpbGUuCiAgICBSZXR1cm5zOgogICAgICAgIFR4dCBmaWxlOiBXcml0ZXMgcmVmZXJlbmNlLCBwb3NlcyBuYW1lIGFuZCBSTVNEIHRvIGEgbG9nIGZpbGUuCiAgICAiIiIKICAgIGNvdW50ID0gMAogICAgdHJ5OgogICAgICAgIGZvciByZWYgaW4gcmVmZXJlbmNlOgogICAgICAgICAgICBfLCByZWZfbmFtZSwgX2RpciA9IGdpdmVfaWQocmVmKQogICAgICAgICAgICByZWZfaWQgPSBvcy5wYXRoLmJhc2VuYW1lKHJlZikKICAgICAgICAgICAgcmVmX2RpciA9IG9zLnBhdGguZGlybmFtZShyZWYpCiAgICAgICAgICAgIHJlZl9kaXIgPSBvcy5wYXRoLmRpcm5hbWUocmVmX2RpcikKICAgICAgICAgICAgZm9yIHBvc2UgaW4gcG9zZXNfbGlzdDoKICAgICAgICAgICAgICAgIHBvc2VfaWQgPSBvcy5wYXRoLmJhc2VuYW1lKHBvc2UpCiAgICAgICAgICAgICAgICBpZiByZWZfaWRbOjRdLmxvd2VyKCkgPT0gcG9zZV9pZFs6NF0ubG93ZXIoKSBvciAobm9tYXRjaCBpcyBUcnVlKToKICAgICAgICAgICAgICAgICAgICBjb21tYW5kID0gZiIvc2hhcmUvb3BlbmJhYmVsLTMuMS4xL2Jpbi9vYnJtcyB7cmVmfSBcdCB7cG9zZX0iCiAgICAgICAgICAgICAgICAgICAgcG9zZV9pZCA9IG9zLnBhdGguYmFzZW5hbWUocG9zZSkKICAgICAgICAgICAgICAgICAgICBybXNkX291dCA9IHN1YnByb2Nlc3MucnVuKAogICAgICAgICAgICAgICAgICAgICAgICBjb21tYW5kLAogICAgICAgICAgICAgICAgICAgICAgICBjd2Q9ZiJ7cmVmX2Rpcn0iLAogICAgICAgICAgICAgICAgICAgICAgICBjYXB0dXJlX291dHB1dD1UcnVlLAogICAgICAgICAgICAgICAgICAgICAgICB0ZXh0PVRydWUsCiAgICAgICAgICAgICAgICAgICAgICAgIHNoZWxsPVRydWUsCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgIGlmIG5vdCBvcy5wYXRoLmV4aXN0cyhmIntyZWZfZGlyfS9yZXN1bHQvUk1TRCIpOgogICAgICAgICAgICAgICAgICAgICAgICBvcy5tYWtlZGlycyhmIntyZWZfZGlyfS9yZXN1bHQvUk1TRCIpCiAgICAgICAgICAgICAgICAgICAgd2l0aCBvcGVuKAogICAgICAgICAgICAgICAgICAgICAgICBmIntyZWZfZGlyfS9yZXN1bHQvUk1TRC97cmVmX2lkWzo0XX1fUk1TRF97a2V5fS50eHQiLCAiYSsiCiAgICAgICAgICAgICAgICAgICAgKSBhcyB3cml0ZV9vdXQ6CiAgICAgICAgICAgICAgICAgICAgICAgIHJtc2RfcmVzdWx0ID0gZiJ7cG9zZV9pZC5sb3dlcigpfVx0IiArIHN0cihybXNkX291dC5zdGRvdXQpCiAgICAgICAgICAgICAgICAgICAgICAgIHRlbXBfcm1zZCA9IGYie3JlZl9uYW1lfVx0e3Bvc2VfaWQubG93ZXIoKX1cdCIgKyBzdHIoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmbG9hdChybXNkX291dC5zdGRvdXQuc3BsaXQoKVstMV0pCiAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnQocm1zZF9yZXN1bHQsIGZpbGU9d3JpdGVfb3V0KQogICAgICAgICAgICAgICAgICAgIGNvdW50ICs9IDEKICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgcmV0dXJuICJObyBtYXRjaGVkIGhlYWRlciBmb3VuZCIKICAgICAgICBpZiB2ZXJib3NlOgogICAgICAgICAgICBwcmludChmIlRvdGFsIG9mIHtjb3VudH0gcm1zZCBjYWxjdWxhdGVkLiIpCiAgICAgICAgcmV0dXJuIHRlbXBfcm1zZAogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlcjoKICAgICAgICBwcmludChlcikKCgpkZWYgcm1zZF9tYXRyaXhfcHJlcChybXNkX3Jlc3VsdHMsIHByaW50X2l0PVRydWUsIHJldHVybl9kZj1UcnVlLCBvbmx5X2Jlc3Q9RmFsc2UpOgogICAgZGVmYXVsdF9zY29yaW5nXyA9IHt9CiAgICBhZDRfc2NvcmluZ18gPSB7fQogICAgZGtvZXNfZmFzdF9zY29yaW5nXyA9IHt9CiAgICBka29lc19zY29yaW5nX29sZF9zY29yaW5nXyA9IHt9CiAgICB2aW5hX3Njb3JpbmdfID0ge30KICAgIHZpbmFyZG9fc2NvcmluZ18gPSB7fQogICAgY3VzdG9tX3Njb3JpbmdfID0ge30KICAgIGJlc3QgPSB7fQogICAgd2l0aCBvcGVuKHJtc2RfcmVzdWx0c1swXSwgInIiKSBhcyBybXNkX3Jlc3VsdHNfcmVhZDoKICAgICAgICBmb3IgY291bnQsIGxpbmUgaW4gZW51bWVyYXRlKHJtc2RfcmVzdWx0c19yZWFkKToKICAgICAgICAgICAgbGluZV9pbmZvID0gbGluZS5yc3BsaXQoIi8iKVstMV0KICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAga2V5LCB2YWx1ZSA9IGxpbmVfaW5mby5zcGxpdCgiLnBkYiIpCiAgICAgICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXI6CiAgICAgICAgICAgICAgICAjIHByaW50KGVyKQogICAgICAgICAgICAgICAgcGFzcwogICAgICAgICAgICBpZiAiX2FkNF9zY29yaW5nXyIgaW4gbGluZToKICAgICAgICAgICAgICAgIGFkNF9zY29yaW5nX1trZXldID0gdmFsdWUuc3RyaXAoKQogICAgICAgICAgICBlbGlmICJfZGVmYXVsdF8iIGluIGtleToKICAgICAgICAgICAgICAgIGRlZmF1bHRfc2NvcmluZ19ba2V5XSA9IHZhbHVlLnN0cmlwKCkKICAgICAgICAgICAgZWxpZiAiX2Rrb2VzX2Zhc3RfIiBpbiBrZXk6CiAgICAgICAgICAgICAgICBka29lc19mYXN0X3Njb3JpbmdfW2tleV0gPSB2YWx1ZS5zdHJpcCgpCiAgICAgICAgICAgIGVsaWYgIl9ka29lc19zY29yaW5nX29sZF8iIGluIGtleToKICAgICAgICAgICAgICAgIGRrb2VzX3Njb3Jpbmdfb2xkX3Njb3JpbmdfW2tleV0gPSB2YWx1ZS5zdHJpcCgpCiAgICAgICAgICAgIGVsaWYgIl92aW5hXyIgaW4ga2V5OgogICAgICAgICAgICAgICAgdmluYV9zY29yaW5nX1trZXldID0gdmFsdWUuc3RyaXAoKQogICAgICAgICAgICBlbGlmICJfdmluYXJkb18iIGluIGtleToKICAgICAgICAgICAgICAgIHZpbmFyZG9fc2NvcmluZ19ba2V5XSA9IHZhbHVlLnN0cmlwKCkKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIGN1c3RvbV9zY29yaW5nX1trZXldID0gdmFsdWUuc3RyaXAoKQogICAgICAgICMgcHJpbnQoY291bnQpCiAgICB0cnk6CiAgICAgICAgYWQ0X2RmID0gcGQuRGF0YUZyYW1lLmZyb21fZGljdCgKICAgICAgICAgICAgYWQ0X3Njb3JpbmdfLCBvcmllbnQ9ImluZGV4IiwgY29sdW1ucz0oWyJhZDRfc2NvcmluZyJdKQogICAgICAgICkKICAgICAgICBkZWZhdWx0X2RmID0gcGQuRGF0YUZyYW1lLmZyb21fZGljdCgKICAgICAgICAgICAgZGVmYXVsdF9zY29yaW5nXywgb3JpZW50PSJpbmRleCIsIGNvbHVtbnM9KFsiZGVmYXVsdF9zY29yaW5nIl0pCiAgICAgICAgKQogICAgICAgIGRrb2VzX2Zhc3RfZGYgPSBwZC5EYXRhRnJhbWUuZnJvbV9kaWN0KAogICAgICAgICAgICBka29lc19mYXN0X3Njb3JpbmdfLCBvcmllbnQ9ImluZGV4IiwgY29sdW1ucz0oWyJka29lc19mYXN0X3Njb3JpbmciXSkKICAgICAgICApCiAgICAgICAgZGtvZXNfc2NvcmluZ19vbGRfZGYgPSBwZC5EYXRhRnJhbWUuZnJvbV9kaWN0KAogICAgICAgICAgICBka29lc19mYXN0X3Njb3JpbmdfLCBvcmllbnQ9ImluZGV4IiwgY29sdW1ucz0oWyJka29lc19mYXN0X3Njb3JpbmciXSkKICAgICAgICApCiAgICAgICAgdmluYV9kZiA9IHBkLkRhdGFGcmFtZS5mcm9tX2RpY3QoCiAgICAgICAgICAgIHZpbmFfc2NvcmluZ18sIG9yaWVudD0iaW5kZXgiLCBjb2x1bW5zPShbInZpbmFfc2NvcmluZyJdKQogICAgICAgICkKICAgICAgICB2aW5hcmRvX2RmID0gcGQuRGF0YUZyYW1lLmZyb21fZGljdCgKICAgICAgICAgICAgdmluYXJkb19zY29yaW5nXywgb3JpZW50PSJpbmRleCIsIGNvbHVtbnM9KFsidmluYXJkb19zY29yaW5nIl0pCiAgICAgICAgKQogICAgICAgIGN1c3RvbV9kZiA9IHBkLkRhdGFGcmFtZS5mcm9tX2RpY3QoCiAgICAgICAgICAgIGN1c3RvbV9zY29yaW5nXywgb3JpZW50PSJpbmRleCIsIGNvbHVtbnM9KFsiY3VzdG9tX3Njb3JpbmciXSkKICAgICAgICApCiAgICAgICAgcmV0dXJuX2RmID0gcGQuY29uY2F0KAogICAgICAgICAgICBbCiAgICAgICAgICAgICAgICBhZDRfZGYsCiAgICAgICAgICAgICAgICBkZWZhdWx0X2RmLAogICAgICAgICAgICAgICAgZGtvZXNfZmFzdF9kZiwKICAgICAgICAgICAgICAgIGRrb2VzX3Njb3Jpbmdfb2xkX2RmLAogICAgICAgICAgICAgICAgdmluYV9kZiwKICAgICAgICAgICAgICAgIHZpbmFyZG9fZGYsCiAgICAgICAgICAgICAgICBjdXN0b21fZGYsCiAgICAgICAgICAgIF0KICAgICAgICApCiAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGVyOgogICAgICAgIHByaW50KGYie2VyfVxuIGVycm9yIGluIGRhdGEgZnJhbWUiKQogICAgaWYgcHJpbnRfaXQ6CiAgICAgICAgdHJ5OgogICAgICAgICAgICBhZDRfYmVzdCA9IG1pbihhZDRfc2NvcmluZ18uaXRlbXMoKSwga2V5PWxhbWJkYSB4OiB4WzFdKQogICAgICAgICAgICBwcmludCgiPT09PT09PT09PT1CRVNUIFJNU0Q9PT09PT09PT09PT09PT09PT09IikKICAgICAgICAgICAgYmVzdFthZDRfYmVzdFswXV0gPSBhZDRfYmVzdFsxXQogICAgICAgICAgICBwcmludChmInthZDRfYmVzdFswXX0gOiB7YWQ0X2Jlc3RbMV19IikKICAgICAgICAgICAgZGVmYXVsdF9iZXN0ID0gbWluKGRlZmF1bHRfc2NvcmluZ18uaXRlbXMoKSwga2V5PWxhbWJkYSB4OiB4WzFdKQogICAgICAgICAgICBiZXN0W2RlZmF1bHRfYmVzdFswXV0gPSBkZWZhdWx0X2Jlc3RbMV0KICAgICAgICAgICAgcHJpbnQoZiJ7ZGVmYXVsdF9iZXN0WzBdfSA6IHtkZWZhdWx0X2Jlc3RbMV19IikKICAgICAgICAgICAgZGtvZXNfZmFzdF9iZXN0ID0gbWluKGRrb2VzX2Zhc3Rfc2NvcmluZ18uaXRlbXMoKSwga2V5PWxhbWJkYSB4OiB4WzFdKQogICAgICAgICAgICBiZXN0W2Rrb2VzX2Zhc3RfYmVzdFswXV0gPSBka29lc19mYXN0X2Jlc3RbMV0KICAgICAgICAgICAgcHJpbnQoZiJ7ZGtvZXNfZmFzdF9iZXN0WzBdfSA6IHtka29lc19mYXN0X2Jlc3RbMV19IikKICAgICAgICAgICAgZGtvZXNfc2NvcmluZ19vbGRfYmVzdCA9IG1pbigKICAgICAgICAgICAgICAgIGRrb2VzX3Njb3Jpbmdfb2xkX3Njb3JpbmdfLml0ZW1zKCksIGtleT1sYW1iZGEgeDogeFsxXQogICAgICAgICAgICApCiAgICAgICAgICAgIGJlc3RbZGtvZXNfc2NvcmluZ19vbGRfYmVzdFswXV0gPSBka29lc19zY29yaW5nX29sZF9iZXN0WzFdCiAgICAgICAgICAgIHByaW50KGYie2Rrb2VzX3Njb3Jpbmdfb2xkX2Jlc3RbMF19IDp7ZGtvZXNfc2NvcmluZ19vbGRfYmVzdFsxXX0iKQogICAgICAgICAgICB2aW5hX2Jlc3QgPSBtaW4odmluYV9zY29yaW5nXy5pdGVtcygpLCBrZXk9bGFtYmRhIHg6IHhbMV0pCiAgICAgICAgICAgIGJlc3RbdmluYV9iZXN0WzBdXSA9IHZpbmFfYmVzdFsxXQogICAgICAgICAgICBwcmludChmInt2aW5hX2Jlc3RbMF19IDoge3ZpbmFfYmVzdFsxXX0iKQogICAgICAgICAgICB2aW5hcmRvX2Jlc3QgPSBtaW4odmluYXJkb19zY29yaW5nXy5pdGVtcygpLCBrZXk9bGFtYmRhIHg6IHhbMV0pCiAgICAgICAgICAgIGJlc3RbdmluYXJkb19iZXN0WzBdXSA9IHZpbmFyZG9fYmVzdFsxXQogICAgICAgICAgICBwcmludChmInt2aW5hcmRvX2Jlc3RbMF19IDoge3ZpbmFyZG9fYmVzdFstMV19IikKICAgICAgICAgICAgY3VzdG9tX2Jlc3QgPSBtaW4oY3VzdG9tX3Njb3JpbmdfLml0ZW1zKCksIGtleT1sYW1iZGEgeDogeFsxXSkKICAgICAgICAgICAgYmVzdFtjdXN0b21fYmVzdFswXV0gPSBjdXN0b21fYmVzdFsxXQogICAgICAgICAgICBwcmludChmIntjdXN0b21fYmVzdFswXX0gOiB7Y3VzdG9tX2Jlc3RbMV19IikKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGVyOgogICAgICAgICAgICBwcmludChlcikKICAgIGlmIG9ubHlfYmVzdDoKICAgICAgICByZXR1cm4gYmVzdAogICAgaWYgcmV0dXJuX2RmIGlzIFRydWU6CiAgICAgICAgcmV0dXJuIHJldHVybl9kZgogICAgZWxzZToKICAgICAgICByZXR1cm4gKAogICAgICAgICAgICBkZWZhdWx0X3Njb3JpbmdfLAogICAgICAgICAgICBhZDRfc2NvcmluZ18sCiAgICAgICAgICAgIGRrb2VzX2Zhc3Rfc2NvcmluZ18sCiAgICAgICAgICAgIGRrb2VzX3Njb3Jpbmdfb2xkX3Njb3JpbmdfLAogICAgICAgICAgICB2aW5hX3Njb3JpbmdfLAogICAgICAgICAgICB2aW5hcmRvX3Njb3JpbmdfLAogICAgICAgICAgICBjdXN0b21fc2NvcmluZ18sCiAgICAgICAgKQoKCmRlZiBjb25mb3JtZXJfc3BsaXQoZmlsZW5hbWVzLCB0YXJnZXQpOgogICAgZm9yIGkgaW4gZmlsZW5hbWVzOgogICAgICAgIGZpbGVfZGlyLCBmaWxlX2lkLCBmaWxlX2Zvcm1hdCA9IGdpdmVfaWQoaSkKICAgICAgICBpZiBub3Qgb3MucGF0aC5leGlzdHMoZiJ7ZmlsZV9kaXJ9L3Bvc2VzIik6CiAgICAgICAgICAgIG9zLm1ha2VkaXJzKGYie2ZpbGVfZGlyfS9wb3NlcyIpCiAgICAgICAgY29tbWFuZCA9ICgKICAgICAgICAgICAgZiIvc2hhcmUvb3BlbmJhYmVsLTMuMS4xL2Jpbi9vYmFiZWwge2l9IC1ve3RhcmdldH0gLU8iCiAgICAgICAgICAgIGYiIC4vcG9zZXMve2ZpbGVfaWR9Xy57dGFyZ2V0fSAtbSIKICAgICAgICApCiAgICAgICAgc3VicHJvY2Vzcy5ydW4oY29tbWFuZCwgY3dkPWYie2ZpbGVfZGlyfSIsIHNoZWxsPVRydWUpCiAgICByZXR1cm4gIlN1Y2Nlc3NmdWxseSBjb21wbGV0ZWQuIgoKCmRlZiBzbWluYV9oaXN0b2dyYW0oc29ydGVkX1JFUywgc2F2ZT1GYWxzZSk6CiAgICAiIiJDcmVhdGVzIGhpc3RvZ3JhbSBvZiBkb2NraW5nIGZyb20gc21pbmEgb3V0cHV0CiAgICBBcmdzOgogICAgICAgIHNvcnRlZF9SRVMgKGxpc3QpOiBTb3J0ZWQgbGlzdCBvZiBhZmZpbml0eSB2YWx1ZXMgZnJvbSBzbWluYSBvdXRwdXQuCiAgICAgICAgc2F2ZSAoYm9vbCwgb3B0aW9uYWwpOiBTYXZlIHBsb3QgaW4gLi9pbWFnZS9zbWluYV9oaXN0b2dyYW0ucG5nCiAgICAiIiIKICAgIG5hbWUsIGJlbmVyZ3kgPSB6aXAoKnNvcnRlZF9SRVMuaXRlbXMoKSkKICAgIGJlbmVyZ3kgPSBucC5hcnJheSgoYmVuZXJneSksIGR0eXBlPW5wLmZsb2F0MzIpCiAgICBtZWFuID0gYmVuZXJneS5tZWFuKCkKICAgIGJlc3QgPSBiZW5lcmd5Lm1pbigpCiAgICB3b3JzdCA9IGJlbmVyZ3kubWF4KCkKICAgIGZpZywgYXhzID0gcGx0LnN1YnBsb3RzKDEsIHNoYXJleT1GYWxzZSwgc2hhcmV4PUZhbHNlLCB0aWdodF9sYXlvdXQ9VHJ1ZSkKICAgIGF4cy5hZGRfYXJ0aXN0KAogICAgICAgIEFuY2hvcmVkVGV4dCgKICAgICAgICAgICAgZiJUb3RhbDoge2xlbihuYW1lKX1cbk1lYW46IHttZWFuOi4yZn1cbkJlc3Q6IHtiZXN0Oi4yZn1cbldvcnN0OiIKICAgICAgICAgICAgZiIge3dvcnN0Oi4yZn0iLAogICAgICAgICAgICBsb2M9MSwKICAgICAgICApCiAgICApCiAgICBheHMuaGlzdChiZW5lcmd5KQogICAgYXhzLnlheGlzLnNldF9sYWJlbF90ZXh0KCJOdW1iZXIgb2YgRGF0YXNldHMiKQogICAgYXhzLnhheGlzLnNldF9sYWJlbF90ZXh0KCJCaW5kaW5nIEVuZXJneSBSYW5nZSIpCiAgICBheHMuc2V0X3RpdGxlKCJEaXN0cmlidXRpb24gb2YgYmluZGluZyBlbmVyZ3kiKQogICAgaWYgc2F2ZToKICAgICAgICBpZiBub3Qgb3MucGF0aC5leGlzdHMoIi4vaW1hZ2VzIik6CiAgICAgICAgICAgIG9zLm1ha2VkaXJzKCIuL2ltYWdlcyIpCiAgICAgICAgcGx0LnNhdmVmaWcoIi4vaW1hZ2VzL3NtaW5hX2hpc3RvZ3JhbS5wbmciLCBkcGk9NjAwKQogICAgcGx0LnNob3coKQoKCmRlZiBzbWluYV9tb25pdG9yKHNtaW5hX291dHB1dF9tb25pdG9yLCBwbG90PUZhbHNlLCBzYXZlPUZhbHNlKToKICAgICIiIkRpc3BsYXkgc21pbmEgcHJvY2VzcyB3aGlsZSBlbnRlciBzbWluYSBzdGRvdXQKICAgIEFyZ3M6CiAgICAgICAgc21pbmFfb3V0cHV0X21vbml0b3IgKHN0ZG91dCk6IHN0ZG91dCBmcm9tIHFzdGF0L3FzdWIKICAgIFJldHVybnM6CiAgICAgICAgZGljdDogRGlzcGxheXMgcmVzdWx0IGluIGp1cHl0ZXIKICAgICIiIgogICAgUkVTID0ge30KICAgIGNvdW50ID0gMAogICAgZm9yIGkgaW4gc21pbmFfb3V0cHV0X21vbml0b3I6CiAgICAgICAgYWxnb19kaXIsIGFsZ29fbmFtZSwgYWxnb19mb3JtYXQgPSBnaXZlX2lkKGkpCiAgICAgICAgaWYgYWxnb19mb3JtYXQubG93ZXIoKSA9PSAicGRiIjoKICAgICAgICAgICAgd2l0aCBvcGVuKGksICJyIikgYXMgcmVhZF9zbWluYToKICAgICAgICAgICAgICAgIGZvciBsaW5lIGluIHJlYWRfc21pbmE6CiAgICAgICAgICAgICAgICAgICAgaWYgbGluZVs6NV0gPT0gIk1PREVMIjoKICAgICAgICAgICAgICAgICAgICAgICAgbnVtYmVyID0gbGluZVs1Ol0uc3RyaXAoKQogICAgICAgICAgICAgICAgICAgIGVsaWYgIlJFTUFSSyIgaW4gbGluZToKICAgICAgICAgICAgICAgICAgICAgICAgZW5lcmd5ID0gZmxvYXQobGluZS5yc3BsaXQoIiAiKVstMV0uc3RyaXAoKSkKICAgICAgICAgICAgICAgICAgICAgICAgUkVTW2Yie2FsZ29fbmFtZX1fe251bWJlcn0iXSA9IGYie2VuZXJneX0iCiAgICAgICAgICAgICAgICAgICAgICAgIGNvdW50ICs9IDEKICAgICAgICBlbGlmIGFsZ29fZm9ybWF0Lmxvd2VyKCkgPT0gInNkZiI6CiAgICAgICAgICAgIHBhdHRlcm5faWQgPSByIl5bYS16QS1aXVxTIgogICAgICAgICAgICBwYXR0ZXJuX2FmZmluaXR5ID0gciJePlxzPFthLXpBLVpdKz4iCiAgICAgICAgICAgIHdpdGggb3BlbihpLCAiciIpIGFzIHJlYWRfc21pbmE6CiAgICAgICAgICAgICAgICB3cml0ZV9hZmZpbml0eSA9IEZhbHNlCiAgICAgICAgICAgICAgICBmb3IgbGluZSBpbiByZWFkX3NtaW5hOgogICAgICAgICAgICAgICAgICAgIGlmIHdyaXRlX2FmZmluaXR5OgogICAgICAgICAgICAgICAgICAgICAgICBlbmVyZ3kgPSBmbG9hdChsaW5lLnN0cmlwKCkpCiAgICAgICAgICAgICAgICAgICAgICAgICMgcHJpbnQoZW5lcmd5KQogICAgICAgICAgICAgICAgICAgICAgICBSRVNbZiJ7YWxnb19uYW1lfV97bnVtYmVyfSJdID0gZiJ7ZW5lcmd5fSIKICAgICAgICAgICAgICAgICAgICAgICAgY291bnQgKz0gMQogICAgICAgICAgICAgICAgICAgICAgICB3cml0ZV9hZmZpbml0eSA9IEZhbHNlCiAgICAgICAgICAgICAgICAgICAgaWYgcmUubWF0Y2gocGF0dGVybl9pZCwgbGluZSk6CiAgICAgICAgICAgICAgICAgICAgICAgIG51bWJlciA9IGxpbmUuc3RyaXAoKQogICAgICAgICAgICAgICAgICAgIGVsaWYgcmUubWF0Y2gocGF0dGVybl9hZmZpbml0eSwgbGluZSk6CiAgICAgICAgICAgICAgICAgICAgICAgIHdyaXRlX2FmZmluaXR5ID0gVHJ1ZQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiAiRmlsZSBmb3JtYXQgbm90IHN1cHBvcnRlZCB5ZXQuIFsncGRiJywgJ3NkZiddIgoKICAgIHByaW50KGYiVG90YWwgbnVtYmVyIG9mIHBvc2VzIGdlbmVyYXRlZDoge2NvdW50fSIpCiAgICBzb3J0ZWRfUkVTID0gZGljdChzb3J0ZWQoUkVTLml0ZW1zKCksIGtleT1sYW1iZGEgeDogeFsxXSkpCiAgICBwcmludCgiX19fX19fX19fX19fX0RldGFpbCBsaXN0X19fX19fX19fX19fX19fX1xuIikKICAgIGZvciBrZXksIHZhbHVlIGluIHNvcnRlZF9SRVMuaXRlbXMoKToKICAgICAgICBwcmludChrZXksICI6IiwgdmFsdWUpCiAgICBpZiBwbG90OgogICAgICAgIHNtaW5hX2hpc3RvZ3JhbShzb3J0ZWRfUkVTLCBzYXZlPXNhdmUpCgogICAgcmV0dXJuIHNvcnRlZF9SRVMKCgpkZWYgYXVjX3Bsb3QobW9kZWwsIFhfdGVzdCwgeV90ZXN0LCBzYXZlPUZhbHNlKToKICAgICIiIlBsb3QgQVVDIHBsb3QgZnJvbSBtb2RlbCwgYW5kIFhfdGV4dChkYXRhIHBvaW50KSB5X3Rlc3QobGFiZWwpLgogICAgQXJnczoKICAgICAgICBtb2RlbCAoc2tsZWFybiBvYmopXzogTW9kZWwgb2JqZWN0IGZyb20gc2tsZWFybiB0cmFpbmVkIG1vZGVsLgogICAgICAgIFhfdGVzdCAocGQuRGF0YUZyYW1lKTogRGF0YSBwb2ludCBmb3IgdGVzdC4KICAgICAgICB5X3Rlc3QgKHBkLkRhdGFGcmFtZSk6IExhYmVsIGZvciB0aGUgZGF0YSBwb2ludC4KICAgICAgICBzYXZlIChib29sLCBvcHRpb25hbCk6IFNhdmUgQVVDIHBsb3QuCiAgICAiIiIKICAgICMgYXNzZXJ0IGlzaW5zdGFuY2UobW9kZWwsIExpbmVhclJlZ3Jlc3Npb24pLCBmInttb2RlbH0gaXMgbm90IGEgdmFsaWQgbW9kZWwiCiAgICAjIGFzc2VydCBpc2luc3RhbmNlKFhfdGVzdCwgcGQuRGF0YUZyYW1lKSwgZiJ7WF90ZXN0fSBpcyBub3QgYSBEYXRhRnJhbWUiCiAgICAjIGFzc2VydCBpc2luc3RhbmNlKHlfdGVzdCwgcGQuRGF0YUZyYW1lKSwgZiJ7eV90ZXN0fSBpcyBub3QgYSBEYXRhRnJhbWUiCiAgICBtb2RlbF9yZWdyZXNzaW9uX3Byb2JhYmlsaXR5ID0gbW9kZWwucHJlZGljdF9wcm9iYShYX3Rlc3QpCiAgICBtb2RlbF9yZWdyZXNzaW9uX3Byb2JhYmlsaXR5ID0gbW9kZWxfcmVncmVzc2lvbl9wcm9iYWJpbGl0eVs6LCAxXQogICAgcmFuZG9tX3Byb2JhYmlsaXR5ID0gWzAgZm9yIF8gaW4gcmFuZ2UobGVuKHlfdGVzdCkpXQogICAgcmFuZG9tX2F1YyA9IHJvY19hdWNfc2NvcmUoeV90ZXN0LCByYW5kb21fcHJvYmFiaWxpdHkpCiAgICBtb2RlbF9hdWMgPSByb2NfYXVjX3Njb3JlKHlfdGVzdCwgbW9kZWxfcmVncmVzc2lvbl9wcm9iYWJpbGl0eSkKICAgIHByaW50KGYiUmFuZG9tOiBST0MgQVVDPXtyYW5kb21fYXVjfSIpCiAgICBwcmludChmIk1vZGVsOiBST0MgQVVDPXttb2RlbF9hdWN9IikKICAgIHJhbmRvbV9mYWxzZV9wb3NpdGl2ZV9yYXRlLCByYW5kb21fdHJ1ZV9wb3NpdGl2ZV9yYXRlLCBfID0gcm9jX2N1cnZlKAogICAgICAgIHlfdGVzdCwgcmFuZG9tX3Byb2JhYmlsaXR5CiAgICApCiAgICBtb2RlbF9mYWxzZV9wb3NpdGl2ZV9yYXRlLCBtb2RlbF90cnVlX3Bvc2l0aXZlX3JhdGUsIF8gPSByb2NfY3VydmUoCiAgICAgICAgeV90ZXN0LCBtb2RlbF9yZWdyZXNzaW9uX3Byb2JhYmlsaXR5CiAgICApCiAgICBwbHQucGxvdCgKICAgICAgICByYW5kb21fZmFsc2VfcG9zaXRpdmVfcmF0ZSwKICAgICAgICByYW5kb21fdHJ1ZV9wb3NpdGl2ZV9yYXRlLAogICAgICAgIGxpbmVzdHlsZT0iLS0iLAogICAgICAgIGxhYmVsPSJSYW5kb20iLAogICAgKQogICAgcGx0LnBsb3QoCiAgICAgICAgbW9kZWxfZmFsc2VfcG9zaXRpdmVfcmF0ZSwKICAgICAgICBtb2RlbF90cnVlX3Bvc2l0aXZlX3JhdGUsCiAgICAgICAgbWFya2VyPSIuIiwKICAgICAgICBsYWJlbD1mIk1vZGVsIChBVUM6e21vZGVsX2F1YzouMmZ9KSIsCiAgICApCiAgICBwbHQueGxhYmVsKCJGYWxzZSBQb3NpdGl2ZSBSYXRlIikKICAgIHBsdC55bGFiZWwoIlRydWUgUG9zaXRpdmUgUmF0ZSIpCiAgICBwbHQueGxpbSh4bWluPTAuMCkKICAgIHBsdC55bGltKHltaW49MC4wKQogICAgcGx0LnRpdGxlKCJST0MiKQogICAgcGx0LmxlZ2VuZCgpCiAgICBpZiBzYXZlOgogICAgICAgIHByZWZpeCA9ICJpbWFnZSIKICAgICAgICB3aGlsZSBvcy5wYXRoLmV4aXN0cyhmIi4vR2VuZXJhdGVkL2ltYWdlcy97cHJlZml4fS5wbmciKToKICAgICAgICAgICAgc3VmZml4ICs9IDEKICAgICAgICAgICAgbmFtZSA9IGYie3ByZWZpeH17c3VmZml4fS5wbmciCiAgICAgICAgcGx0LnNhdmVmaWcoIi4vR2VuZXJhdGVkL2ltYWdlcy97bmFtZX0iLCBkcGk9NjAwKQogICAgcGx0LnNob3coKQoKCmRlZiBzbWluYV9tb2RlbF9zY29yZSgKICAgIGZpbGVfcGF0aCwKICAgIG51bV9mZWF0dXJlcz0zLAogICAgaW50ZXJjZXB0PUZhbHNlLAogICAgdHNpemU9MC4zLAogICAgcGxvdF9hdWM9RmFsc2UsCiAgICBwbG90X3NhdmU9RmFsc2UsCik6CiAgICAiIiJHZW5lcmF0ZXMgcmVncmVzc2lvbiBtb2RlbCB1c2luZyBza2xlYXJuLiBXaWxsIFByaW50IG91dCBjb2VmZmljaWVudHMKICAgIEFyZ3M6CiAgICAgICAgZmlsZV9wYXRoIChzdHIpOiBjc3YvZXhjZWwgZmlsZSBwYXRoCiAgICAgICAgbnVtX2ZlYXR1cmVzIChpbnQsIG9wdGlvbmFsKTogTnVtYmVyIG9mIGZlYXR1cmVzIHRvIHVzZS4gRGVmYXVsdDogMwogICAgICAgIGludGVyY2VwdCAoYm9vbCwgb3B0aW9uYWwpOiBNZWFuIEVycm9yCiAgICAgICAgdHNpemUgKGZsb2F0LCBvcHRpb25hbCk6IFBlcmNlbnRhZ2Ugb2YgZGF0YXRvIHVzZSBmb3IgdGVzdC4gRGVmYXVsdCAwLjMoMzAlKQogICAgICAgIHBsb3RfYXVjIChib29sLCBvcHRpb25hbCk6IFBsb3QgUk9DIEFVQyBjdXJ2ZQogICAgICAgIHBsb3Rfc2F2ZSAoYm9vbCwgb3B0aW9uYWwpOiBTYXZlIFJPQyBBVUMgcGxvdAogICAgIiIiCiAgICB0cnk6CiAgICAgICAgaWYgaXNpbnN0YW5jZShmaWxlX3BhdGgsIHN0cik6CiAgICAgICAgICAgIGZpbGVfZGlyLCBmaWxlX25hbWUsIGZpbGVfZm9ybWF0ID0gZ2l2ZV9pZChmaWxlX3BhdGgpCiAgICAgICAgICAgIHN1cHBvcnRlZF9maWxlX2Zvcm1hdCA9IFsiY3N2IiwgImV4Y2VsIl0gICMgZm9yIG5vdyB8IHNtaW5hIHJlc3VsdCBmaWxlCiAgICAgICAgICAgIGFzc2VydCBmaWxlX2Zvcm1hdCBpbiBzdXBwb3J0ZWRfZmlsZV9mb3JtYXQsICgKICAgICAgICAgICAgICAgICJOb3RlOiBGaWxlVHlwZSBFcnJvcjogTm90IHN1cHBvcnRlZCBmaWxlIGZvcm1hdC4gVXNlIgogICAgICAgICAgICAgICAgZiIge3N1cHBvcnRlZF9maWxlX2Zvcm1hdH0iCiAgICAgICAgICAgICkKICAgICAgICAgICAgaWYgZmlsZV9mb3JtYXQgPT0gImV4Y2VsIjoKICAgICAgICAgICAgICAgIGRmID0gcGQucmVhZF9leGNlbChmaWxlX3BhdGgpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBkZiA9IHBkLnJlYWRfY3N2KGZpbGVfcGF0aCkKCiAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgcHJpbnQoZSkKCiAgICBpZiBpc2luc3RhbmNlKGZpbGVfcGF0aCwgcGQuRGF0YUZyYW1lKToKICAgICAgICBkZiA9IGZpbGVfcGF0aAogICAgdHJ5OgogICAgICAgIFgsIHkgPSBkZi5pbG9jWzE6LCAxOi0yXSwgZGYuaWxvY1sxOiwgLTFdCiAgICAgICAgWCA9IHBkLkRhdGFGcmFtZShYKQogICAgICAgIGhlYWRlciA9IFguaWxvY1s6MCwgOl0KICAgICAgICBYLCB5ID0gbWFrZV9jbGFzc2lmaWNhdGlvbihuX2ZlYXR1cmVzPW51bV9mZWF0dXJlcykKICAgICAgICBYX3RyYWluLCBYX3Rlc3QsIHlfdHJhaW4sIHlfdGVzdCA9IHRyYWluX3Rlc3Rfc3BsaXQoCiAgICAgICAgICAgIFgsIHksIHRlc3Rfc2l6ZT10c2l6ZQogICAgICAgICkgICMgVE9ETyAvL2luY2x1ZGUgSy1Gb2xkIFRlc3QKICAgICAgICBtb2RlbCA9IExvZ2lzdGljUmVncmVzc2lvbihmaXRfaW50ZXJjZXB0PWludGVyY2VwdCkKICAgICAgICBtb2RlbC5maXQoWF90cmFpbiwgeV90cmFpbikgICMgVE9ET0QgYWNjZXB0IHVzZSBtb2RlbCBpbnB1dAogICAgICAgIHdlaWdodCA9IG1vZGVsLmNvZWZfCiAgICAgICAgd2VpZ2h0ID0gW2l0ZW0gZm9yIGkgaW4gd2VpZ2h0IGZvciBpdGVtIGluIGldCiAgICAgICAgY29uc29sZS5wcmludCgiW2JvbGQgY3lhbl1Nb2RlbCB3ZWlnaHRzIGFyZSA6flsvYm9sZCBjeWFuXVxuIikKICAgICAgICBmb3IgaGVhZCwgY29lZmYgaW4gemlwKGhlYWRlciwgd2VpZ2h0KToKICAgICAgICAgICAgcHJpbnQoY29lZmYsIGhlYWQsIGVuZD0iXG4iKQogICAgICAgIG1vZGVsLnByZWRpY3QoWF90ZXN0KQogICAgICAgIG1vZGVsLnByZWRpY3RfcHJvYmEoWF90ZXN0KQogICAgICAgIHNjb3JlID0gbW9kZWwuc2NvcmUoWF90ZXN0LCB5X3Rlc3QpCiAgICAgICAgcHJpbnQoZiJcbk1vZGVsIHNjb3JlOiB7c2NvcmV9IikKICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXI6CiAgICAgICAgcHJpbnQoZXIpCiAgICBpZiBwbG90X2F1YzoKICAgICAgICBhdWNfcGxvdChtb2RlbCwgWF90ZXN0LCB5X3Rlc3QsIHNhdmU9cGxvdF9zYXZlKQogICAgICAgICMgdHJhaW5fcGxvdChtb2RlbCwgWCwgeSwgWF90ZXN0LCB5X3Rlc3QpCiAgICByZXR1cm4gbW9kZWwKCgpkZWYgdHJhaW5fcGxvdChtb2RlbCwgWCwgeSwgWF90ZXN0LCB5X3Rlc3QpOgogICAgcGx0LmZpZ3VyZSgxLCBmaWdzaXplPSg0LCAzKSkKICAgIHBsdC5jbGYoKQogICAgcHJpbnQobGVuKFgpKQogICAgcHJpbnQobGVuKHkpKQogICAgcGx0LnNjYXR0ZXIoWC5yYXZlbCgpLCB5LCBjb2xvcj0iYmxhY2siLCB6b3JkZXI9MjApCiAgICAjIHBsdC5zY2F0dGVyKHlfdGVzdCwgWF90ZXN0Lmlsb2NbOiwwXS52YWx1ZXMpCiAgICBsb3NzID0gZXhwaXQoWF90ZXN0ICogbW9kZWwuY29lZl8gKyBtb2RlbC5pbnRlcmNlcHRfKS5yYXZlbCgpCiAgICBwbHQucGxvdChYX3Rlc3QsIGxvc3MsIGNvbG9yPSJyZWQiLCBsaW5ld2lkdGg9MykKCiAgICBvbHMgPSBMaW5lYXJSZWdyZXNzaW9uKCkKICAgIG9scy5maXQoWCwgeSkKICAgIHBsdC5wbG90KFhfdGVzdCwgb2xzLmNvZWZfICogWF90ZXN0ICsgb2xzLmludGVyY2VwdF8sIGxpbmV3aWR0aD0xKQogICAgcGx0LmF4aGxpbmUoMC41LCBjb2xvcj0iLjUiKQoKICAgIHBsdC55bGFiZWwoInkiKQogICAgcGx0LnhsYWJlbCgiWCIpCiAgICBwbHQueHRpY2tzKHJhbmdlKC01LCAxMCkpCiAgICBwbHQueXRpY2tzKFswLCAwLjUsIDFdKQogICAgcGx0LnlsaW0oLTAuMjUsIDEuMjUpCiAgICBwbHQueGxpbSgtNCwgMTApCiAgICBwbHQubGVnZW5kKAogICAgICAgICgiTG9naXN0aWMgUmVncmVzc2lvbiBNb2RlbCIsICJMaW5lYXIgUmVncmVzc2lvbiBNb2RlbCIpLAogICAgICAgIGxvYz0ibG93ZXIgcmlnaHQiLAogICAgICAgIGZvbnRzaXplPSJzbWFsbCIsCiAgICApCiAgICBwbHQudGlnaHRfbGF5b3V0KCkKICAgIHBsdC5zaG93KCkKCgpkZWYgaW5wdXRfY3VzdG9tX3Njb3JpbmcoKToKICAgICIiIkdVSSB3aW5kb3cgdG8gZW50ZXIgY3VzdG9tIHNjb3JpbmcgZnVuY3Rpb24iIiIKICAgICMgaW5pdGlhbGl6ZSBzb21lIG1zZyBhbmQgb3V0cHV0IGVudgogICAgb3V0cHV0X2NzZiA9IHdpZGdldHMuT3V0cHV0KCkKICAgIG1zZ19lbXB0eV9uYW1lID0gIkVudGVyIGFueSBuYW1lIGZvciB0aGUgZmlsZS4iCiAgICB3YXJuX2VtcHR5X25hbWUgPSB3aWRnZXRzLkhUTUwodmFsdWU9ZiI8Yj48Zm9udCBjb2xvcj0ncmVkJz57bXNnX2VtcHR5X25hbWV9PC9iPiIpCiAgICAjIHNhdmUgdGhlIGlucHV0IGN1c3RvbSBzY29yaW5nIHZhbHVlCiAgICBkZWYgc2F2ZV9zY29yaW5nKGRhdGEpOgogICAgICAgIG91dHB1dF9jc2YuY2xlYXJfb3V0cHV0KCkKICAgICAgICBtc2dfY29uZmlybV93YXJuID0gIlBsZWFzZSBjb25maXJtIGlmIHRoZSB2YWx1ZXMgYXJlIHJpZ2h0LiIKICAgICAgICBpbmZvcm1hdGlvbiA9IGlweXdpZGdldHMud2lkZ2V0cy5IVE1MKAogICAgICAgICAgICB2YWx1ZT1mIjxiPjxmb250IGNvbG9yPSdyZWQnPnttc2dfY29uZmlybV93YXJufTwvYj4iCiAgICAgICAgKQogICAgICAgIGdsb2JhbCBzY29yaW5nX2RhdGEKICAgICAgICBnbG9iYWwgdGVtcF9uYW1lCiAgICAgICAgdGVtcF9uYW1lID0gZmlsZV9uYW1lLnZhbHVlCiAgICAgICAgaWYgbm90IHRlbXBfbmFtZToKICAgICAgICAgICAgd2l0aCBvdXRwdXRfY3NmOgogICAgICAgICAgICAgICAgZGlzcGxheSh3YXJuX2VtcHR5X25hbWUpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgc3BsaXR0ZWQgPSBjdXN0b21fc2NvcmluZ19hcmVhLnZhbHVlLnNwbGl0KCJcbiIpCiAgICAgICAgICAgIHNjb3JpbmdfZGF0YSA9IFtdCiAgICAgICAgICAgIGZvciBzcGxpdCBpbiBzcGxpdHRlZDoKICAgICAgICAgICAgICAgIHNwbGl0ID0gc3BsaXQuc3RyaXAoKQogICAgICAgICAgICAgICAgc2NvcmluZ19kYXRhLmFwcGVuZChzcGxpdCkKICAgICAgICAgICAgd2l0aCBvdXRwdXRfY3NmOgogICAgICAgICAgICAgICAgcHJpbnQoZiJFbnRlcmVkIGZpbGUgbmFtZSA6IHt0ZW1wX25hbWV9IikKICAgICAgICAgICAgICAgIGZvciBsaW5lIGluIHNjb3JpbmdfZGF0YToKICAgICAgICAgICAgICAgICAgICBpZiBzZWxlY3QgPT0gImN1c3RvbV9zY29yaW5nIjoKICAgICAgICAgICAgICAgICAgICAgICAgaWYgbGVuKGxpbmUucnN0cmlwKCkpICE9IDA6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSwgaXRlbSA9IGxpbmUuc3BsaXQoKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnQoZiJ7dmFsdWV9XHR7aXRlbX0iKQogICAgICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgICAgIHByaW50KGxpbmUpCiAgICAgICAgICAgICAgICBkaXNwbGF5KGluZm9ybWF0aW9uKQoKICAgICMgd3JpdGVzIHRoZSBzYXZlIHNjb3JpbmcgZGF0YSB0byBhIGZpbGUKICAgIGRlZiBjb25maXJtX3Njb3JpbmcoZGF0YSk6CiAgICAgICAgb3V0cHV0X2NzZi5jbGVhcl9vdXRwdXQoKQogICAgICAgIGlmIG5vdCB0ZW1wX25hbWU6CiAgICAgICAgICAgIHdpdGggb3V0cHV0X2NzZjoKICAgICAgICAgICAgICAgIGRpc3BsYXkod2Fybl9lbXB0eV9uYW1lKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIG1zZ19zdWNjZXNzID0gIkNvbmZpcm1lZCBhbmQgU2F2ZWQhIgogICAgICAgICAgICBpbmZvcm1hdGlvbiA9IGlweXdpZGdldHMud2lkZ2V0cy5IVE1MKAogICAgICAgICAgICAgICAgdmFsdWU9ZiI8Yj48Zm9udCBjb2xvcj0nZ3JlZW4nPnttc2dfc3VjY2Vzc308L2I+IgogICAgICAgICAgICApCiAgICAgICAgICAgIGZpbGVfbmFtZV9zYXZlID0gdGVtcF9uYW1lCiAgICAgICAgICAgIGZpbGVfY29udGVudCA9IHNjb3JpbmdfZGF0YQogICAgICAgICAgICBpZiBzZWxlY3QgPT0gImN1c3RvbV9zY29yaW5nIjoKICAgICAgICAgICAgICAgIGlmIG5vdCBvcy5wYXRoLmV4aXN0cygiLi9HZW5lcmF0ZWQvY3VzdG9tX2Z1bmN0aW9uIik6CiAgICAgICAgICAgICAgICAgICAgb3MubWFrZWRpcnMoIi4vR2VuZXJhdGVkL2N1c3RvbV9mdW5jdGlvbiIpCiAgICAgICAgICAgICAgICB3aXRoIG9wZW4oCiAgICAgICAgICAgICAgICAgICAgZiIuL0dlbmVyYXRlZC9jdXN0b21fZnVuY3Rpb24ve2ZpbGVfbmFtZV9zYXZlfV9jc2YudHh0IiwgIncrIgogICAgICAgICAgICAgICAgKSBhcyB3cml0ZV9zY29yaW5nX2Z1bmN0aW9uOgogICAgICAgICAgICAgICAgICAgIGZvciBsaW5lIGluIGZpbGVfY29udGVudDoKICAgICAgICAgICAgICAgICAgICAgICAgaWYgbGVuKGxpbmUucnN0cmlwKCkpICE9IDA6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSwgaXRlbSA9IGxpbmUuc3BsaXQoKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnQoZiJ7dmFsdWV9XHR7aXRlbX0iLCBmaWxlPXdyaXRlX3Njb3JpbmdfZnVuY3Rpb24pCgogICAgICAgICAgICAgICAgaW5mbyA9ICgKICAgICAgICAgICAgICAgICAgICBmImZpbGUgc2F2ZSBhdCAuL0dlbmVyYXRlZC9jdXN0b21fZnVuY3Rpb24ve2ZpbGVfbmFtZV9zYXZlfV9jc2YudHh0IgogICAgICAgICAgICAgICAgKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgaWYgbm90IG9zLnBhdGguZXhpc3RzKCIuL0dlbmVyYXRlZC9zbWluYV9pbnB1dCIpOgogICAgICAgICAgICAgICAgICAgIG9zLm1ha2VkaXJzKCIuL0dlbmVyYXRlZC9zbWluYV9pbnB1dCIpCiAgICAgICAgICAgICAgICB3aXRoIG9wZW4oCiAgICAgICAgICAgICAgICAgICAgZiIuL0dlbmVyYXRlZC9zbWluYV9pbnB1dC97ZmlsZV9uYW1lX3NhdmV9X21jb25maWcudHh0IiwgIncrIgogICAgICAgICAgICAgICAgKSBhcyB3cml0ZV9jb25maWc6CiAgICAgICAgICAgICAgICAgICAgZm9yIGxpbmUgaW4gZmlsZV9jb250ZW50OgogICAgICAgICAgICAgICAgICAgICAgICBwcmludChmIntsaW5lfSIsIGZpbGU9d3JpdGVfY29uZmlnKQoKICAgICAgICAgICAgICAgIGluZm8gPSAoCiAgICAgICAgICAgICAgICAgICAgZiJmaWxlIHNhdmUgYXQgLi9HZW5lcmF0ZWQvc21pbmFfaW5wdXQve2ZpbGVfbmFtZV9zYXZlfV9tY29uZmlnLnR4dCIKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgd2l0aCBvdXRwdXRfY3NmOgogICAgICAgICAgICAgICAgcHJpbnQoaW5mbykKICAgICAgICAgICAgICAgIGRpc3BsYXkoaW5mb3JtYXRpb24pCgogICAgIyBkZWYgYWxsX2NsZWFyKGRhdGEpOgogICAgIyAgICB3aXRoIG91dHB1dF9jc2Y6CiAgICAjICAgICAgICBvdXRwdXRfY3NmLmNsZWFyX291dHB1dCgpCiAgICAjICN0ZXh0IGFyZWEgdG8gb2JzZXJ2ZSBhbGwgaW5wdXQgdGV4dAogICAgY29uZmlnX3BsYWNlaG9sZGVyID0gKAogICAgICAgICJQYXN0ZSBoZXJlXG4gICAgICAgIFxuICAgICAgICBTYW1wbGUgY29uZmlnLnR4dCBEb2NraW5nIHBhcmFtZXRlcnMgZmlsZVxuICAgICAiCiAgICAgICAgIiAgIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAgICAgICAgI0lucHV0c1xuICAgICAgICByZWNlcHRvciA9IgogICAgICAgICIgLi8zTDZCX3Byb3QucGRicXRcbiAgICAgICAgbGlnYW5kID0gLi8zTDZCX2xpZy5wZGJxdFxuICAgICAgICAjT3V0cHV0c1xuICAgICAiCiAgICAgICAgIiAgIG91dCA9IDNMNkItbm93YXQtVmluYS5wZGJxdFxuICAgICAgICBsb2cgPSAzTDZCLW5vd2F0LVZpbmEubG9nXG4gICAgICAgIgogICAgICAgICIgI0JveCBjZW50ZXJcbiAgICAgICAgY2VudGVyX3ggPSA0LjUwMFxuICAgICAgICBjZW50ZXJfeSA9IC0yLjk0NFxuICAgICAgICIKICAgICAgICAiIGNlbnRlcl96ID0gLTUuMjUwXG4gICAgICAgICNCb3ggc2l6ZVxuICAgICAgICBzaXplX3ggPSA1MFxuICAgICAgICBzaXplX3kgPSIKICAgICAgICAiIDUwXG4gICAgICAgIHNpemVfeiA9IDUwXG4gICAgICAgICNQYXJhbWV0ZXJzXG4gICAgICAgIGV4aGF1c3RpdmVuZXNzID0gOFxuICAgIgogICAgICAgICIgICAgIHNlZWQgPSAxMjM0NTZcbiIKICAgICkKICAgIGNzZl9wbGFjZWhvbGRlciA9ICgKICAgICAgICAiIFBhc3RlIGhlcmVcbiAgICAgICAgXG4gICAgICAgICAgICBTYW1wbGUgZm9ybWF0IG9mIGN1c3RvbSBzY29yaW5nXG4gICAgICAgIgogICAgICAgICIgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICAgICAgICAtMC4wMzU1NzkgICAiCiAgICAgICAgIiBnYXVzcyhvPTAsX3c9MC41LF9jPTgpXG4gICAgICAgIC0wLjAwNTE1NiAgICBnYXVzcyhvPTMsX3c9MixfYz04XG4gICAgICAgIgogICAgICAgICIgMC44NDAyNDUgICAgIHJlcHVsc2lvbihvPTAsX2M9OClcbiAgICAgICAgLTAuMDM1MDY5ICAgIgogICAgICAgICIgaHlkcm9waG9iaWMoZz0wLjUsX2I9MS41LF9jPTgpXG4gICAgICAgIC0wLjU4NzQzOSAgICIKICAgICAgICAiIG5vbl9kaXJfaF9ib25kKGc9LTAuNyxfYj0wLF9jPTgpXG4gICAgICAgIDEuOTIzICAgICAgICBudW1fdG9yc19kaXZcbiAgICAgICAiCiAgICAgICAgIiAtMTAwLjAgICAgICAgYXRvbV90eXBlX2dhdXNzaWFuKHQxPUNobG9yaW5lLHQyPVN1bGZ1cixvPTAsX3c9MyxfYz04KVxuIgogICAgKQoKICAgIGRlZiBldmFsdWF0ZShzZWxlY3RlZCk6CiAgICAgICAgb3V0cHV0X2NzZi5jbGVhcl9vdXRwdXQoKQogICAgICAgIGFyZWFfbGF5b3V0ID0gTGF5b3V0KHdpZHRoPSIxMDAlIiwgaGVpZ2h0PSI0MDBweCIsIGZsZXg9InJvdyIpCiAgICAgICAgZ2xvYmFsIHNlbGVjdAogICAgICAgIHNlbGVjdCA9IHNlbGVjdGVkCiAgICAgICAgaWYgc2VsZWN0ZWQgPT0gImN1c3RvbV9zY29yaW5nIjoKICAgICAgICAgICAgZ2xvYmFsIGN1c3RvbV9zY29yaW5nX2FyZWEKICAgICAgICAgICAgY3VzdG9tX3Njb3JpbmdfYXJlYSA9IHdpZGdldHMuVGV4dGFyZWEoCiAgICAgICAgICAgICAgICBwbGFjZWhvbGRlcj1jc2ZfcGxhY2Vob2xkZXIsCiAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbj0iRW50ZXI6IiwKICAgICAgICAgICAgICAgIGRpc2FibGVkPUZhbHNlLAogICAgICAgICAgICAgICAganVzdGlmeV9jb250ZW50PSJzcGFjZV9iZXR3ZWVuIiwKICAgICAgICAgICAgICAgIGNvbnRpbnVvdXNfdXBkYXRlPVRydWUsCiAgICAgICAgICAgICAgICBsYXlvdXQ9YXJlYV9sYXlvdXQsCiAgICAgICAgICAgICkKICAgICAgICBlbHNlOgogICAgICAgICAgICBjdXN0b21fc2NvcmluZ19hcmVhID0gd2lkZ2V0cy5UZXh0YXJlYSgKICAgICAgICAgICAgICAgIHBsYWNlaG9sZGVyPWNvbmZpZ19wbGFjZWhvbGRlciwKICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uPSJFbnRlcjoiLAogICAgICAgICAgICAgICAgZGlzYWJsZWQ9RmFsc2UsCiAgICAgICAgICAgICAgICBqdXN0aWZ5X2NvbnRlbnQ9InNwYWNlX2JldHdlZW4iLAogICAgICAgICAgICAgICAgY29udGludW91c191cGRhdGU9VHJ1ZSwKICAgICAgICAgICAgICAgIGxheW91dD1hcmVhX2xheW91dCwKICAgICAgICAgICAgKQogICAgICAgIGRpc3BsYXkoY3VzdG9tX3Njb3JpbmdfYXJlYSkKICAgICAgICBvdXRwdXRfY3NmLmNsZWFyX291dHB1dCgpCgogICAgc2VsZWN0X29wdGlvbiA9IHdpZGdldHMuUmFkaW9CdXR0b25zKAogICAgICAgIG9wdGlvbnM9WyJjdXN0b21fc2NvcmluZyIsICJtYW51YWxfY29uZmlnIl0sCiAgICAgICAgdmFsdWU9ImN1c3RvbV9zY29yaW5nIiwKICAgICAgICBkZXNjcmlwdGlvbj0iV2hhdDoiLAogICAgICAgIGRpc2FibGVkPUZhbHNlLAogICAgKQogICAgdWkgPSB3aWRnZXRzLkhCb3goW3NlbGVjdF9vcHRpb25dKQogICAgb3B0aW9ucyA9IHdpZGdldHMuaW50ZXJhY3RpdmVfb3V0cHV0KGV2YWx1YXRlLCB7InNlbGVjdGVkIjogc2VsZWN0X29wdGlvbn0pCiAgICBpbnN0cnVjdGlvbiA9IGlweXdpZGdldHMud2lkZ2V0cy5IVE1MKAogICAgICAgICI8Zm9udCBzaXplID0gND48Yj48Zm9udCBmYW1pbHk6IFRpbWVzIE5ldyBSb21hbj5Db3B5IGFuZCBQYXN0ZSB0aGUgc2NvcmluZyIKICAgICAgICAiIGZ1bmN0aW9uIGJlbG93IGFuZCBlbnRlcjwvYj48L2ZvbnQtZmFtaWx5PjwvZm9udCBzaXplPiIKICAgICkKICAgIGRpc3BsYXkoaW5zdHJ1Y3Rpb24pCiAgICAjIGJ1dHRvbnMgd2lkZ2V0cwogICAgZmlsZV9uYW1lID0gd2lkZ2V0cy5UZXh0KGRlc2NyaXB0aW9uPSJGaWxlbmFtZToiLCBwbGFjZWhvbGRlcj0iZmlsZSBuYW1lICIpCiAgICBzYXZlX2J1dHRvbiA9IHdpZGdldHMuQnV0dG9uKGRlc2NyaXB0aW9uPSJTYXZlIikKICAgIHNhdmVfYnV0dG9uLnN0eWxlLmJ1dHRvbl9jb2xvciA9ICJsaWdodGdyZWVuIgogICAgY29uZmlybV9idXR0b24gPSB3aWRnZXRzLkJ1dHRvbihkZXNjcmlwdGlvbj0iQ29uZmlybSIpCiAgICBjb25maXJtX2J1dHRvbi5zdHlsZS5idXR0b25fY29sb3IgPSAic2FsbW9uIgogICAgIyBjbGVhcl9idXR0b24gPSB3aWRnZXRzLkJ1dHRvbihkZXNjcmlwdGlvbj0iQ2xlYXIiKQogICAgIyBjbGVhcl9idXR0b24uc3R5bGUuYnV0dG9uX2NvbG9yID0gImxpZ2h0Z3JlZW4iCiAgICBkaXNwbGF5KCh3aWRnZXRzLlZCb3goW2ZpbGVfbmFtZSwgdWksIG9wdGlvbnNdKSksIG91dHB1dF9jc2YpCiAgICBkaXNwbGF5KCh3aWRnZXRzLkhCb3goW3NhdmVfYnV0dG9uLCBjb25maXJtX2J1dHRvbl0pKSkKICAgIHNhdmVfYnV0dG9uLm9uX2NsaWNrKHNhdmVfc2NvcmluZykKICAgIGNvbmZpcm1fYnV0dG9uLm9uX2NsaWNrKGNvbmZpcm1fc2NvcmluZykKICAgICMgY2xlYXJfYnV0dG9uLm9uX2NsaWNrKGFsbF9jbGVhcikKCgpkZWYgeGdfbW9kZWwoWCwgeSk6CiAgICBmcm9tIHNrbGVhcm4uZGF0YXNldHMgaW1wb3J0IG1ha2VfY2xhc3NpZmljYXRpb24KCiAgICBudW1fY2xhc3NlcyA9IDMKICAgIFgsIHkgPSBtYWtlX2NsYXNzaWZpY2F0aW9uKG5fc2FtcGxlcz0xMDAwLCBuX2luZm9ybWF0aXZlPTUsIG5fY2xhc3Nlcz1udW1fY2xhc3NlcykKICAgIGR0cmFpbiA9IHhnYi5ETWF0cml4KGRhdGE9WCwgbGFiZWw9eSkKICAgIG51bV9wYXJhbGxlbF90cmVlID0gNAogICAgbnVtX2Jvb3N0X3JvdW5kID0gMTYKICAgICMgdG90YWwgbnVtYmVyIG9mIGJ1aWx0IHRyZWVzIGlzIG51bV9wYXJhbGxlbF90cmVlICogbnVtX2NsYXNzZXMgKiBudW1fYm9vc3Rfcm91bmQKCiAgICAjIFdlIGJ1aWxkIGEgYm9vc3RlZCByYW5kb20gZm9yZXN0IGZvciBjbGFzc2lmaWNhdGlvbiBoZXJlLgogICAgYm9vc3RlciA9IHhnYi50cmFpbigKICAgICAgICB7Im51bV9wYXJhbGxlbF90cmVlIjogNCwgInN1YnNhbXBsZSI6IDAuNSwgIm51bV9jbGFzcyI6IDN9LAogICAgICAgIG51bV9ib29zdF9yb3VuZD1udW1fYm9vc3Rfcm91bmQsCiAgICAgICAgZHRyYWluPWR0cmFpbiwKICAgICkKCiAgICAjIFRoaXMgaXMgdGhlIHNsaWNlZCBtb2RlbCwgY29udGFpbmluZyBbMywgNykgZm9yZXN0cwogICAgIyBzdGVwIGlzIGFsc28gc3VwcG9ydGVkIHdpdGggc29tZSBsaW1pdGF0aW9ucyBsaWtlIG5lZ2F0aXZlIHN0ZXAgaXMgaW52YWxpZC4KICAgIHNsaWNlZDogeGdiLkJvb3N0ZXIgPSBib29zdGVyWzM6N10KCiAgICByZXR1cm4gW18gZm9yIF8gaW4gYm9vc3Rlcl0KCgpkZWYgeGdiX2Jvb3N0KGZpbGVfdHJhaW4sIGZpbGVfdGVzdCk6CiAgICBpbXBvcnQgeGdib29zdCBhcyB4Z2IKCiAgICBDVVJSRU5UX0RJUiA9IG9zLnBhdGguZGlybmFtZShfX2ZpbGVfXykKICAgIGR0cmFpbiA9IHhnYi5ETWF0cml4KG9zLnBhdGguam9pbihDVVJSRU5UX0RJUiwgImZpbGVfdHJhaW4iKSkKICAgIGR0ZXN0ID0geGdiLkRNYXRyaXgob3MucGF0aC5qb2luKENVUlJFTlRfRElSLCAiZmlsZV90ZXN0IikpCiAgICBwYXJhbSA9IHsKICAgICAgICAib2JqZWN0aXZlIjogImJpbmFyeTpsb2dpc3RpYyIsCiAgICAgICAgImJvb3N0ZXIiOiAiZ2JsaW5lYXIiLAogICAgICAgICJhbHBoYSI6IDAuMDAwMSwKICAgICAgICAibGFtYmRhIjogMSwKICAgIH0KICAgIHdhdGNobGlzdCA9IFsoZHRlc3QsICJldmFsIiksIChkdHJhaW4sICJ0cmFpbiIpXQogICAgbnVtX3JvdW5kID0gNAogICAgYnN0ID0geGdiLnRyYWluKHBhcmFtLCBkdHJhaW4sIG51bV9yb3VuZCwgd2F0Y2hsaXN0KQogICAgcHJlZHMgPSBic3QucHJlZGljdChkdGVzdCkKICAgIGxhYmVscyA9IGR0ZXN0LmdldF9sYWJlbCgpCiAgICBwcmludCgKICAgICAgICAiZXJyb3I9JWYiCiAgICAgICAgJSAoCiAgICAgICAgICAgIHN1bShpbnQocHJlZHNbaV0gPiAwLjUpICE9IGxhYmVsc1tpXSBmb3IgaSBpbiByYW5nZShsZW4ocHJlZHMpKSkKICAgICAgICAgICAgLyBmbG9hdChsZW4ocHJlZHMpKQogICAgICAgICkKICAgICkKCgpkZWYgcnVuX3NtaW5hKGRpcl9uYW1lXywgY29uZmlnX2ZpbGVfbmFtZSwgKiprd2FyZ3MpOgogICAgIiIiIENyZWF0ZXMgZm9sZGVyIHdpdGhpbiB0aGUgY3dkIHdpdGggdGhlIG5hbWUgaWQgYW5kIHN1YlwKICAgIGZvbGRlciBydW4gd2hlcmUgaXQgd2lsbCB3cml0ZSBzaC5BbHNvIGNyZWF0ZXMgYSBkdW1wIGZvbGRlclwKICAgIHdoZXJlIGVycm9yIGFuZCBvdXRwdXQgbG9nIHdpbGwgYmUgZHVtcGVkLgoKICAgICIiIgogICAgbW9kZSA9IGt3YXJncy5nZXQoIm1vZGUiLCBGYWxzZSkKICAgIGxvZyA9IGt3YXJncy5nZXQoImxvZyIsICJsb2cudHh0IikKICAgIG91dHB1dCA9IGt3YXJncy5nZXQoIm91dHB1dCIsICJvdXRwdXQuc2RmIikKICAgIGxvY2FsID0ga3dhcmdzLmdldCgibG9jYWwiLCBGYWxzZSkKICAgIGNwdV9udW0gPSBrd2FyZ3MuZ2V0KCJjcHUiLCAyKQogICAgam9iX25hbWUgPSBrd2FyZ3MuZ2V0KCJqb2JfbmFtZSIsIE5vbmUpCiAgICBzY29yaW5nID0ga3dhcmdzLmdldCgic2NvcmluZyIpCiAgICBjdXN0b20gPSBrd2FyZ3MuZ2V0KCJjdXN0b20iLCBGYWxzZSkKICAgIGVudGVyX291dHB1dCA9IGt3YXJncy5nZXQoImVudGVyX291dHB1dCIsIFRydWUpCiAgICBlbnRlcl9sb2cgPSBrd2FyZ3MuZ2V0KCJlbnRlcl9sb2ciLCBUcnVlKQogICAgY2x1c3RlciA9IGt3YXJncy5nZXQoImNsdXN0ZXIiLCBOb25lKQogICAgY2x1c3Rlcl9ncnAgPSBbImFsbC5xIiwgImdwMSIsICJncDIiXQogICAgaWYgKGNsdXN0ZXIgaXMgbm90IE5vbmUpIGFuZCAoY2x1c3RlciBub3QgaW4gY2x1c3Rlcl9ncnApOgogICAgICAgIHJldHVybiBmIkludmFsaWQgY2x1c3RlciBuYW1lLiBBdmFpbGFibGUgY2x1c3RlciBuYW1lczoge2NsdXN0ZXJfZ3JwfSIKICAgIG5hbWVfaWQgPSBjb25maWdfZmlsZV9uYW1lWzo0XS5sb3dlcigpICAjIEZJWAogICAgZGlyX25hbWVfY3dkID0gb3MuZ2V0Y3dkKCkKICAgIGRpcl9uYW1lID0gb3MucGF0aC5kaXJuYW1lKGRpcl9uYW1lXykKICAgIFBBVEggPSBrd2FyZ3MuZ2V0KCJQQVRIIiwgRmFsc2UpCiAgICBkaXJfbmFtZSA9IGYie2Rpcl9uYW1lfSIgaWYgUEFUSCBlbHNlIGYie2Rpcl9uYW1lX2N3ZH0iCgogICAgaWYgbm90IG9zLnBhdGguZXhpc3RzKGYie2Rpcl9uYW1lfS9HZW5lcmF0ZWQvam9icy97bmFtZV9pZH0vcnVuIik6CiAgICAgICAgb3MubWFrZWRpcnMoZiJ7ZGlyX25hbWV9L0dlbmVyYXRlZC9qb2JzL3tuYW1lX2lkfS9ydW4iKQogICAgaWYgbG9jYWwgaXMgRmFsc2U6CiAgICAgICAgU01JTkFfUEFUSCA9ICIvc2hhcmUvdmluYS9zbWluYSIKICAgICAgICB3aXRoIG9wZW4oCiAgICAgICAgICAgIGYie2Rpcl9uYW1lfS9HZW5lcmF0ZWQvam9icy97bmFtZV9pZH0vcnVuL3tuYW1lX2lkfV9TTWluYS5zaCIsICJ3IgogICAgICAgICkgYXMgb3V0OgogICAgICAgICAgICBpZiBqb2JfbmFtZSBpcyBOb25lOgogICAgICAgICAgICAgICAgam9iX25hbWUgPSBuYW1lX2lkCiAgICAgICAgICAgIGlmIGpvYl9uYW1lWzBdLmlzZGlnaXQoKToKICAgICAgICAgICAgICAgIGpvYl9uYW1lID0gIlMiICsgam9iX25hbWUKICAgICAgICAgICAgcHJpbnQoZiIjJCAtTiB7am9iX25hbWV9IiwgZmlsZT1vdXQpCiAgICAgICAgICAgIHByaW50KCIjJCAtViIsIGZpbGU9b3V0KQogICAgICAgICAgICBwcmludCgiIyQgLVMgL2Jpbi9iYXNoIiwgZmlsZT1vdXQpCiAgICAgICAgICAgIGlmIGNsdXN0ZXIgaXMgbm90IE5vbmU6CiAgICAgICAgICAgICAgICBwcmludChmIiMkIC1xIHtjbHVzdGVyfSIsIGZpbGU9b3V0KQogICAgICAgICAgICBwcmludChmIiMkIC1wZSB7Y3B1X251bX1jcHUge2NwdV9udW19IiwgZmlsZT1vdXQpCiAgICAgICAgICAgIGlmIG5vdCBvcy5wYXRoLmV4aXN0cyhmIntkaXJfbmFtZX0vR2VuZXJhdGVkL2pvYnMve25hbWVfaWR9L2R1bXAvIik6CiAgICAgICAgICAgICAgICBvcy5tYWtlZGlycyhmIntkaXJfbmFtZX0vR2VuZXJhdGVkL2pvYnMve25hbWVfaWR9L2R1bXAvIikKICAgICAgICAgICAgcHJpbnQoZiIjJCAtbyB7ZGlyX25hbWV9L0dlbmVyYXRlZC9qb2JzL3tuYW1lX2lkfS9kdW1wLyIsIGZpbGU9b3V0KQogICAgICAgICAgICBwcmludChmIiMkIC1lICB7ZGlyX25hbWV9L0dlbmVyYXRlZC9qb2JzL3tuYW1lX2lkfS9kdW1wLyIsIGZpbGU9b3V0KQogICAgICAgICAgICBwcmludCgiIyQgLWN3ZCIsIGZpbGU9b3V0KQoKICAgICAgICAgICAgIyBDb25kaXRpb25hbCB0byB3cml0ZSBsb2cgYW5kIG91dHB1dAogICAgICAgICAgICBlbnRlcl9sb2cgPSBmIi0tbG9nIHtsb2d9IiBpZiBlbnRlcl9sb2cgZWxzZSAiIgogICAgICAgICAgICBlbnRlcl9vdXRwdXQgPSBmIi0tb3V0IHtvdXRwdXR9IiBpZiBlbnRlcl9vdXRwdXQgZWxzZSAiIgogICAgICAgICAgICBpZiAobW9kZSBpcyBUcnVlKSBhbmQgKGN1c3RvbSBpcyBGYWxzZSk6CiAgICAgICAgICAgICAgICBwcmludCgKICAgICAgICAgICAgICAgICAgICBmIntTTUlOQV9QQVRIfSAtLWNvbmZpZyIKICAgICAgICAgICAgICAgICAgICBmIiB7ZGlyX25hbWV9L0dlbmVyYXRlZC9zbWluYV9pbnB1dC97Y29uZmlnX2ZpbGVfbmFtZX0iCiAgICAgICAgICAgICAgICAgICAgZiIgLS1zY29yaW5nIHtzY29yaW5nfSAtLXNjb3JlX29ubHkge2VudGVyX2xvZ30ge2VudGVyX291dHB1dH0iLAogICAgICAgICAgICAgICAgICAgIGZpbGU9b3V0LAogICAgICAgICAgICAgICAgKQoKICAgICAgICAgICAgZWxpZiAobW9kZSBpcyBUcnVlKSBhbmQgKGN1c3RvbSBpcyBUcnVlKToKICAgICAgICAgICAgICAgIHByaW50KAogICAgICAgICAgICAgICAgICAgIGYie1NNSU5BX1BBVEh9IC0tY29uZmlnIgogICAgICAgICAgICAgICAgICAgIGYiIHtkaXJfbmFtZX0vR2VuZXJhdGVkL3NtaW5hX2lucHV0L3tjb25maWdfZmlsZV9uYW1lfSIKICAgICAgICAgICAgICAgICAgICBmIiAtLWN1c3RvbV9zY29yaW5nIHtzY29yaW5nfSAtLXNjb3JlX29ubHkgIHtlbnRlcl9vdXRwdXR9IgogICAgICAgICAgICAgICAgICAgIGYiIHtlbnRlcl9sb2d9IiwKICAgICAgICAgICAgICAgICAgICBmaWxlPW91dCwKICAgICAgICAgICAgICAgICkKCiAgICAgICAgICAgIGVsaWYgKG1vZGUgaXMgRmFsc2UpIGFuZCAoY3VzdG9tIGlzIEZhbHNlKToKICAgICAgICAgICAgICAgIHByaW50KAogICAgICAgICAgICAgICAgICAgIGYie1NNSU5BX1BBVEh9IC0tY29uZmlnIgogICAgICAgICAgICAgICAgICAgIGYiIHtkaXJfbmFtZX0vR2VuZXJhdGVkL3NtaW5hX2lucHV0L3tjb25maWdfZmlsZV9uYW1lfSAtLXNjb3JpbmciCiAgICAgICAgICAgICAgICAgICAgZiIge3Njb3Jpbmd9ICB7ZW50ZXJfbG9nfSB7ZW50ZXJfb3V0cHV0fSAiLAogICAgICAgICAgICAgICAgICAgIGZpbGU9b3V0LAogICAgICAgICAgICAgICAgKQoKICAgICAgICAgICAgZWxpZiAobW9kZSBpcyBGYWxzZSkgYW5kIChjdXN0b20gaXMgVHJ1ZSk6CiAgICAgICAgICAgICAgICBwcmludCgKICAgICAgICAgICAgICAgICAgICBmIntTTUlOQV9QQVRIfSAtLWNvbmZpZyIKICAgICAgICAgICAgICAgICAgICBmIiB7ZGlyX25hbWV9L0dlbmVyYXRlZC9zbWluYV9pbnB1dC97Y29uZmlnX2ZpbGVfbmFtZX0iCiAgICAgICAgICAgICAgICAgICAgZiIgLS1jdXN0b21fc2NvcmluZyB7c2NvcmluZ30gIHtlbnRlcl9sb2d9IHtlbnRlcl9vdXRwdXR9ICIsCiAgICAgICAgICAgICAgICAgICAgZmlsZT1vdXQsCiAgICAgICAgICAgICAgICApCgogICAgICAgIGNvbW1hbmQgPSBmInFzdWIge2Rpcl9uYW1lfS9HZW5lcmF0ZWQvam9icy97bmFtZV9pZH0vcnVuL3tuYW1lX2lkfV9TTWluYS5zaCIKICAgIGVsc2U6CiAgICAgICAgaWYgY3VzdG9tIGlzIEZhbHNlOgogICAgICAgICAgICBjb21tYW5kID0gKAogICAgICAgICAgICAgICAgZiJzbWluYSAtLWNvbmZpZyB7ZGlyX25hbWV9L0dlbmVyYXRlZC9zbWluYV9pbnB1dC97Y29uZmlnX2ZpbGVfbmFtZX0iCiAgICAgICAgICAgICAgICBmIiAtLXNjb3Jpbmcge3Njb3Jpbmd9IHtlbnRlcl9sb2d9IHtlbnRlcl9vdXRwdXR9IgogICAgICAgICAgICApCiAgICAgICAgZWxzZToKICAgICAgICAgICAgY29tbWFuZCA9ICgKICAgICAgICAgICAgICAgIGYic21pbmEgLS1jb25maWcge2Rpcl9uYW1lfS9HZW5lcmF0ZWQvc21pbmFfaW5wdXQve2NvbmZpZ19maWxlX25hbWV9IgogICAgICAgICAgICAgICAgZiIgLS1jdXN0b21fc2NvcmluZyB7c2NvcmluZ30gIHtlbnRlcl9sb2d9IHtlbnRlcl9vdXRwdXR9IgogICAgICAgICAgICApCgogICAgc3VicHJvY2Vzcy5ydW4oY29tbWFuZCwgY3dkPWYie2Rpcl9uYW1lfS9HZW5lcmF0ZWQvam9icy97bmFtZV9pZH0iLCBzaGVsbD1UcnVlKQoKICAgIHJldHVybiAiU3VjY2VzZnVsbHkgY29tcGxldGVkLiIKCgojIFJFQ09SRFMgT0YgQUxMIFNDT1JJTkcgRlVOQ1RJT04KIyBTRiA9IFsgICJhZDRfc2NvcmluZyIsCiMgICAgICAgICJkZWZhdWx0IiwKIyAgICAgICAgImRrb2VzX2Zhc3QiLAojICAgICAgICAiZGtvZXNfc2NvcmluZyIsCiMgICAgICAgICJka29lc19zY29yaW5nX29sZCIsCiMgICAgICAgICJ2aW5hIiwKIyAgICAgICAgInZpbmFyZG8iLAojICAgIF0KCiMgQ1NGID0gWyJjdXN0b20iXSAjIGZvciBjdXN0b20gc2NvcmluZyAgYW5kIHBhc3MgY3VzdG9tX3Njb3JpbmdfZmlsZT1QQVRIIHRvIHRoZSBmdW5jdGlvbgojIE90aGVyd2lzZSBhbGwgdGhlIG90aGVyIFNGIHdpbGwgYmUgcnVuIGJ1dCB3aWxsIGJlIGNhbGN1bGF0ZSB3aXRoIGNzZgojICBmb3IgbnVtZXJvdXMgdGltZQoKCmRlZiBzbWluYV9ydW4ocHJvdGVpbl9saXN0LCBsaWdhbmRfbGlzdCwgKiprd2FyZ3MpOgoKICAgICIiIlByZXBhcmVzIGNvbmZpZyBmaWxlIGZvciBzbWluYSB3aGVuIGVudGVyIHByb3RlcmluIGFuZCBsaWdhbmQKICAgIEFib3ZlIHNoX3J1biBmdW5jdGlvbiBtdXN0IGJlIGluaXRpYWxlemVkIGJlZm9yZSBpbiBub3RlYm9vayIiIgoKICAgIFNGID0ga3dhcmdzLmdldCgiU0YiKQogICAgaWYgbm90IGlzaW5zdGFuY2UoU0YsIGxpc3QpOgogICAgICAgIHJldHVybiAiU3VwcGxpZWQgU0YgaXMgbm90IGEgbGlzdCIKICAgIGNsdXN0ZXIgPSBrd2FyZ3MuZ2V0KCJjbHVzdGVyIiwgTm9uZSkKICAgIGNsdXN0ZXJfZ3JwID0gWyJhbGwucSIsICJncDEiLCAiZ3AyIl0KICAgIGlmIChjbHVzdGVyIGlzIG5vdCBOb25lKSBhbmQgKGNsdXN0ZXIgbm90IGluIGNsdXN0ZXJfZ3JwKToKICAgICAgICByZXR1cm4gZiJJbnZhbGlkIGNsdXN0ZXIgbmFtZS4gQXZhaWxhYmxlIGNsdXN0ZXIgbmFtZXM6IHtjbHVzdGVyX2dycH0iCiAgICBhdXRvYm94ID0ga3dhcmdzLmdldCgiYXV0b2JveCIsIE5vbmUpCiAgICBpZiBpc2luc3RhbmNlKGF1dG9ib3gsIGxpc3QpOgogICAgICAgIGF1dG9ib3ggPSBhdXRvYm94WzBdCiAgICBtYW51YWxfY29uZmlnID0ga3dhcmdzLmdldCgibWFudWFsX2NvbmZpZyIsIE5vbmUpCiAgICBpZiBpc2luc3RhbmNlKG1hbnVhbF9jb25maWcsIGxpc3QpOgogICAgICAgIG1hbnVhbF9jb25maWcgPSBtYW51YWxfY29uZmlnWzBdCiAgICBydW4gPSBrd2FyZ3MuZ2V0KCJydW4iLCBGYWxzZSkKICAgIG1vZGUgPSBrd2FyZ3MuZ2V0KCJtb2RlIiwgRmFsc2UpCiAgICBsb2NhbCA9IGt3YXJncy5nZXQoImxvY2FsIiwgRmFsc2UpCiAgICBqb2JfbmFtZSA9IGt3YXJncy5nZXQoImpvYl9uYW1lIiwgTm9uZSkKICAgIE5VTV9NT0RFUyA9IGt3YXJncy5nZXQoIm51bV9tb2RlcyIsIDEwKQogICAgRVhIQVVTVElWRSA9IGt3YXJncy5nZXQoImV4aGF1c3RpdmUiLCA1MCkKICAgIEVORVJHWV9SQU5HRSA9IGt3YXJncy5nZXQoImVuZXJneV9yYW5nZSIsIDEwKQogICAgU0VFRCA9IGt3YXJncy5nZXQoInNlZWQiLCBOb25lKQogICAgQVVUT0JPWF9QQUQgPSBrd2FyZ3MuZ2V0KCJwYWQiLCA0KQogICAgQ1BVX05VTSA9IGt3YXJncy5nZXQoImNwdSIsIDgpCiAgICBub21hdGNoID0ga3dhcmdzLmdldCgibWF0Y2giLCBGYWxzZSkKICAgIE9VVF9GT1JNQVQgPSBrd2FyZ3MuZ2V0KCJvdXRfZm9ybWF0IiwgInNkZiIpCiAgICBjdXN0b20gPSBrd2FyZ3MuZ2V0KCJjdXN0b20iLCBGYWxzZSkKCiAgICAjIGlmIENVU1RPTV9TQ09SRSBpcyBub3QgTm9uZToKICAgICMgICAgQ1NGX0ZMQUcgPSBUcnVlCiAgICAjIGVsc2U6CiAgICAjICAgIENTRl9GTEFHID0gRmFsc2UKCiAgICBkZWYgd3JpdGVfY29uZmlnKAogICAgICAgIHJlY2VwdG9yLAogICAgICAgIGxpZ2FuZCwKICAgICAgICBjb25maWdfZmlsZV9uYW1lLAogICAgICAgIG91dHB1dF9maWxlX25hbWUsCiAgICAgICAgbG9nX2ZpbGVfbmFtZSwKICAgICAgICAjIHNjb3Jpbmc9Tm9uZSwgIyBNb3ZlZCB0byBDTEkKICAgICk6CiAgICAgICAgIiIiV3JpdGVzIGNvbmZpZyBpbnRvIG5ldyBmaWxlcywgaWYgYWxyZWFkeSBcCiAgICAgICAgZXhpc3QgYXBwZW5kIHRvIGl0LiIiIgoKICAgICAgICAjIGRpcl9uYW1lID0gb3MucGF0aC5kaXJuYW1lKHJlY2VwdG9yKQogICAgICAgIGRpcl9uYW1lID0gb3MuZ2V0Y3dkKCkKCiAgICAgICAgaWYgbm90IG9zLnBhdGguZXhpc3RzKGYie2Rpcl9uYW1lfS9HZW5lcmF0ZWQvc21pbmFfaW5wdXQiKToKICAgICAgICAgICAgb3MubWFrZWRpcnMoZiJ7ZGlyX25hbWV9L0dlbmVyYXRlZC9zbWluYV9pbnB1dC8iKQogICAgICAgIHdpdGggb3BlbigKICAgICAgICAgICAgZiJ7ZGlyX25hbWV9L0dlbmVyYXRlZC9zbWluYV9pbnB1dC97Y29uZmlnX2ZpbGVfbmFtZX0iLCAidysiCiAgICAgICAgKSBhcyBjb25maWdfZmlsZToKCiAgICAgICAgICAgICMgcmVxdWlyZWQgY29uZmlnIGFyZ3VtZW50cwogICAgICAgICAgICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogICAgICAgICAgICBwcmludChmInJlY2VwdG9yID0ge3JlY2VwdG9yfSAiLCBmaWxlPWNvbmZpZ19maWxlKQogICAgICAgICAgICBwcmludChmImxpZ2FuZCA9IHtsaWdhbmR9IiwgZmlsZT1jb25maWdfZmlsZSkKICAgICAgICAgICAgaWYgYXV0b2JveCBpcyBub3QgTm9uZToKICAgICAgICAgICAgICAgIHByaW50KGYiYXV0b2JveF9saWdhbmQgPSB7YXV0b2JveH0iLCBmaWxlPWNvbmZpZ19maWxlKQogICAgICAgICAgICAgICAgcHJpbnQoZiJhdXRvYm94X2FkZCA9IHtBVVRPQk9YX1BBRH0iLCBmaWxlPWNvbmZpZ19maWxlKQoKICAgICAgICAgICAgIyBPcHRpb25hbHMgY29uICAjIE1PVkVEIFRPIENMSQogICAgICAgICAgICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQogICAgICAgICAgICBwcmludChmIm91dCA9IHtvdXRwdXRfZmlsZV9uYW1lfSIsIGZpbGU9Y29uZmlnX2ZpbGUpCiAgICAgICAgICAgIHByaW50KGYibG9nID0ge2xvZ19maWxlX25hbWV9IiwgZmlsZT1jb25maWdfZmlsZSkKICAgICAgICAgICAgIyBwcmludChmInNjb3JpbmcgPSB7c2NvcmluZ30iLCBmaWxlPWNvbmZpZ19maWxlKSAgIyMgY2hhbmdlIHRvIHJ1biBpbiBDTEkKCiAgICAgICAgICAgICMgTWlzYyhvcHRpb25hbCkgY29uZmlncwogICAgICAgICAgICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgICAgICAgICAgICMgaWYgQ1VTVE9NX1NDT1JFIGlzIG5vdCBOb25lOiAgIyMgTU9WRUQgVE8gQ0xJCiAgICAgICAgICAgICMgICAgcHJpbnQoZiJjdXN0b21fc2NvcmluZyA9IHtDVVNUT01fU0NPUkV9IiwgZmlsZT1jb25maWdfZmlsZSkKICAgICAgICAgICAgIyBpZiBTTUlOQV9NT0RFIGlzIG5vdCBOb25lOgogICAgICAgICAgICAjICAgIHByaW50KGYie1NNSU5BX01PREV9IiwgZmlsZT1jb25maWdfZmlsZSkKCiAgICAgICAgICAgIHByaW50KGYiY3B1ID0ge0NQVV9OVU19IiwgZmlsZT1jb25maWdfZmlsZSkKICAgICAgICAgICAgaWYgU0VFRCBpcyBub3QgTm9uZToKICAgICAgICAgICAgICAgIHByaW50KGYiXG5cbnNlZWQgPSB7U0VFRH0iLCBmaWxlPWNvbmZpZ19maWxlKQogICAgICAgICAgICBwcmludChmImV4aGF1c3RpdmVuZXNzID0ge0VYSEFVU1RJVkV9IiwgZmlsZT1jb25maWdfZmlsZSkKICAgICAgICAgICAgIyBpZiBDVVNUT01fU0NPUkUgaXMgTm9uZTogIyMgY2hhbmdlIHRvIHJ1biBpbiBDTEkKICAgICAgICAgICAgcHJpbnQoZiJudW1fbW9kZXMgPSB7TlVNX01PREVTfSIsIGZpbGU9Y29uZmlnX2ZpbGUpCiAgICAgICAgICAgIHByaW50KGYiZW5lcmd5X3JhbmdlID0ge0VORVJHWV9SQU5HRSB9IiwgZmlsZT1jb25maWdfZmlsZSkKCiAgICBmb3Igc2NvcmluZyBpbiBTRjoKICAgICAgICBmb3IgbGlnYW5kIGluIGxpZ2FuZF9saXN0OgogICAgICAgICAgICBwcm90ZWluX2RpciwgcHJvdGVpbl9pZCwgcHJvdF9mb3JtYXQgPSBnaXZlX2lkKHByb3RlaW5fbGlzdFswXSkKICAgICAgICAgICAgbGlnYW5kX2RpciwgbGlnYW5kX2lkLCBsaWdfZm9ybWF0ID0gZ2l2ZV9pZChsaWdhbmQpCiAgICAgICAgICAgIGlmIGN1c3RvbToKICAgICAgICAgICAgICAgIF9kaXIsIF9uYW1lLCBfZm9ybWF0ID0gZ2l2ZV9pZChzY29yaW5nKQogICAgICAgICAgICAgICAgc2NvcmluZyA9IF9uYW1lCiAgICAgICAgICAgIGlmIHByb3RlaW5faWRbOjRdLmxvd2VyKCkgPT0gbGlnYW5kX2lkWzo0XS5sb3dlcigpIG9yIChub21hdGNoID09IFRydWUpOgogICAgICAgICAgICAgICAgb3V0cHV0X2ZpbGVfbmFtZSA9IGxpZ2FuZF9pZCArIGYiX291dHB1dF97c2NvcmluZ30ue09VVF9GT1JNQVR9IgogICAgICAgICAgICAgICAgbG9nX2ZpbGVfbmFtZSA9IGxpZ2FuZF9pZCArIGYiX2xvZ197c2NvcmluZ30udHh0IgogICAgICAgICAgICAgICAgaWYgbWFudWFsX2NvbmZpZyBpcyBOb25lOgoKICAgICAgICAgICAgICAgICAgICBjb25maWdfZmlsZV9uYW1lID0gbGlnYW5kX2lkLmxvd2VyKCkgKyBmIl9jb25maWdfe3Njb3Jpbmd9LnR4dCIKICAgICAgICAgICAgICAgICAgICBlbnRlcl9vdXRwdXQgPSBUcnVlCiAgICAgICAgICAgICAgICAgICAgZW50ZXJfbG9nID0gVHJ1ZQogICAgICAgICAgICAgICAgICAgIHdyaXRlX2NvbmZpZygKICAgICAgICAgICAgICAgICAgICAgICAgcHJvdGVpbl9saXN0WzBdLAogICAgICAgICAgICAgICAgICAgICAgICBsaWdhbmQsCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbmZpZ19maWxlX25hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dF9maWxlX25hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgIGxvZ19maWxlX25hbWUsCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZWxzZToKCiAgICAgICAgICAgICAgICAgICAgKAogICAgICAgICAgICAgICAgICAgICAgICBtYW51YWxfY29uZmlnX2RpciwKICAgICAgICAgICAgICAgICAgICAgICAgbWFudWFsX2NvbmZpZ19uYW1lLAogICAgICAgICAgICAgICAgICAgICAgICBtYW51YWxfY29uZmlnX2Zvcm1hdCwKICAgICAgICAgICAgICAgICAgICApID0gZ2l2ZV9pZChtYW51YWxfY29uZmlnKQogICAgICAgICAgICAgICAgICAgIGNvbmZpZ19maWxlX25hbWUgPSBmInttYW51YWxfY29uZmlnX25hbWV9LnttYW51YWxfY29uZmlnX2Zvcm1hdH0iCiAgICAgICAgICAgICAgICAgICAgaWYgIm91dHB1dCIgaW4gb3BlbihtYW51YWxfY29uZmlnKS5yZWFkKCk6CiAgICAgICAgICAgICAgICAgICAgICAgIGVudGVyX291dHB1dCA9IEZhbHNlCiAgICAgICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICAgICAgZW50ZXJfb3V0cHV0ID0gVHJ1ZQogICAgICAgICAgICAgICAgICAgIGlmICJsb2ciIGluIG9wZW4obWFudWFsX2NvbmZpZykucmVhZCgpOgogICAgICAgICAgICAgICAgICAgICAgICBlbnRlcl9sb2cgPSBGYWxzZQogICAgICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgICAgIGVudGVyX2xvZyA9IFRydWUKCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBwcmludCgiUHJvdGVpbiBhbmQgbGlnYW5kIHByZWZpeCBbNCBsZXR0ZXJdIGRpZG50IG1hdGNoIikKICAgICAgICAgICAgaWYgY3VzdG9tOgogICAgICAgICAgICAgICAgc2NvcmluZyA9IGYie19kaXJ9L3tfbmFtZX0ue19mb3JtYXR9IgogICAgICAgICAgICBpZiBydW4gaXMgVHJ1ZSBhbmQgbW9kZSBpcyBUcnVlOgogICAgICAgICAgICAgICAgcnVuX3NtaW5hKAogICAgICAgICAgICAgICAgICAgIHByb3RlaW5fZGlyLAogICAgICAgICAgICAgICAgICAgIGNvbmZpZ19maWxlX25hbWUsCiAgICAgICAgICAgICAgICAgICAgc2NvcmluZz1zY29yaW5nLAogICAgICAgICAgICAgICAgICAgIG1vZGU9bW9kZSwKICAgICAgICAgICAgICAgICAgICBsb2NhbD1sb2NhbCwKICAgICAgICAgICAgICAgICAgICAjIGNwdT1DUFVfTlVNLAogICAgICAgICAgICAgICAgICAgICMgam9iX25hbWU9am9iX25hbWUsCiAgICAgICAgICAgICAgICAgICAgY3VzdG9tPWN1c3RvbSwKICAgICAgICAgICAgICAgICAgICBsb2c9bG9nX2ZpbGVfbmFtZSwKICAgICAgICAgICAgICAgICAgICBvdXRwdXQ9b3V0cHV0X2ZpbGVfbmFtZSwKICAgICAgICAgICAgICAgICAgICBlbnRlcl9vdXRwdXQ9ZW50ZXJfb3V0cHV0LAogICAgICAgICAgICAgICAgICAgIGVudGVyX2xvZz1lbnRlcl9sb2csCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgIGVsaWYgcnVuIGlzIFRydWUgYW5kIG1vZGUgaXMgRmFsc2U6CiAgICAgICAgICAgICAgICBydW5fc21pbmEoCiAgICAgICAgICAgICAgICAgICAgcHJvdGVpbl9kaXIsCiAgICAgICAgICAgICAgICAgICAgY29uZmlnX2ZpbGVfbmFtZSwKICAgICAgICAgICAgICAgICAgICBzY29yaW5nPXNjb3JpbmcsCiAgICAgICAgICAgICAgICAgICAgbG9jYWw9bG9jYWwsCiAgICAgICAgICAgICAgICAgICAgY3B1PUNQVV9OVU0sCiAgICAgICAgICAgICAgICAgICAgam9iX25hbWU9am9iX25hbWUsCiAgICAgICAgICAgICAgICAgICAgY3VzdG9tPWN1c3RvbSwKICAgICAgICAgICAgICAgICAgICBvdXRwdXQ9b3V0cHV0X2ZpbGVfbmFtZSwKICAgICAgICAgICAgICAgICAgICBsb2c9bG9nX2ZpbGVfbmFtZSwKICAgICAgICAgICAgICAgICAgICBlbnRlcl9vdXRwdXQ9ZW50ZXJfb3V0cHV0LAogICAgICAgICAgICAgICAgICAgIGVudGVyX2xvZz1lbnRlcl9sb2csCiAgICAgICAgICAgICAgICAgICAgUEFUSD1UcnVlLAogICAgICAgICAgICAgICAgICAgIGNsdXN0ZXI9Y2x1c3RlciwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHByaW50KAogICAgICAgICAgICAgICAgICAgICJSdW4gY29tbWFuZCB3YXMgbm90IHBhc3NlZCBzbyBvbmx5IGNyZWF0ZWQgdGhlIgogICAgICAgICAgICAgICAgICAgICIgY29uZmlnIGFuZCBzaCBmaWxlIGJ1dCBub3QgZXhlY3V0ZWQiCiAgICAgICAgICAgICAgICApCiAgICAgICAgIyBwcmludChiYXNlX25hbWUpCiAgICByZXR1cm4gIlN1Y2Nlc2Z1bGx5IGNvbXBsZXRlZC4iCgoKZGVmIHZpZXdfYWZmaW5pdHkoc29ydGVkX1JFUywga2V5d29yZCk6CiAgICBkZiA9IHBkLkRhdGFGcmFtZSgKICAgICAgICBbCiAgICAgICAgICAgIChrZXksIHZhbHVlKQogICAgICAgICAgICBmb3Iga2V5LCB2YWx1ZSBpbiBzb3J0ZWRfUkVTLml0ZW1zKCkKICAgICAgICAgICAgaWYgZiJ7a2V5d29yZH0iIGluIGtleS5sb3dlcigpCiAgICAgICAgXSwKICAgICAgICBjb2x1bW5zPVsiUG9zZSIsICJBZmZpbml0eSJdLAogICAgKQogICAgcmV0dXJuIGRmCgoKZGVmIHNtaW5hX291dHB1dF9kZihzb3J0ZWRfUkVTKToKICAgIHJldHVybiBwZC5EYXRhRnJhbWUoCiAgICAgICAgWyhrZXksIHZhbHVlKSBmb3Iga2V5LCB2YWx1ZSBpbiBzb3J0ZWRfUkVTLml0ZW1zKCldLAogICAgICAgIGNvbHVtbnM9WyJQb3NlIiwgIkFmZmluaXR5Il0sCiAgICApCgoKZGVmIHJtc2RfbWF0cml4KAogICAgcmVmLCBsZW5ndGg9Miwga2V5PSJNQVRSSVgiLCB2ZXJib3NlPUZhbHNlLCBwbG90PVRydWUsIHNhdmU9RmFsc2UsIGFubm90PUZhbHNlCik6CiAgICBfcm1zZF9saXN0ID0gaXRlcnRvb2xzLmNvbWJpbmF0aW9ucyhyZWYsIGxlbmd0aCkKICAgIGNvbHMgPSBbIlJlZmVyZW5jZSIsICJQb3NlIiwgIlJNU0QiXQogICAgZGYgPSBwZC5EYXRhRnJhbWUoY29sdW1ucz1jb2xzKQogICAgZm9yIGNvdW50LCBpIGluIGVudW1lcmF0ZShfcm1zZF9saXN0KToKICAgICAgICByZWYsIGNvbmYgPSBsaXN0KFtpWzBdXSksIGxpc3QoW2lbMV1dKQogICAgICAgIHggPSBybXNkX2NhbGN1bGF0b3IocmVmLCBjb25mLCBub21hdGNoPVRydWUsIGtleT1rZXksIHZlcmJvc2U9RmFsc2UpCiAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgcHJpbnQoZiJ7Y291bnR9LiB7eH0iLCBzZXA9IiAiLCBmbHVzaD1UcnVlKQogICAgICAgIHgxLCB4MiwgeDMgPSB4LnNwbGl0KCJcdCIpCiAgICAgICAgZGYubG9jW2NvdW50XSA9IFt4MSwgeDIsIHgzXQoKICAgIG1kZiA9IGRmLnBpdm90KGluZGV4PSJSZWZlcmVuY2UiLCBjb2x1bW5zPSJQb3NlIiwgdmFsdWVzPSJSTVNEIikKICAgIG1kZi5maWxsbmEoMCkKICAgIG1kZiA9IG1kZi5hc3R5cGUoZmxvYXQpCiAgICBpZiBwbG90OgogICAgICAgICMgcGx0LmZpZ3VyZShmaWdzaXplPVsxNSwgOF0pCiAgICAgICAgaG1hcCA9IHNucy5oZWF0bWFwKG1kZiwgYW5ub3Q9YW5ub3QpCiAgICAgICAgaG1hcC5zZXRfdGl0bGUoIlJNU0QgTUFUUklYIikKICAgICAgICBpZiBzYXZlOgogICAgICAgICAgICBpZiBub3Qgb3MucGF0aC5leGlzdHMoIi4vR2VuZXJhdGVkL2ltYWdlcy8iKToKICAgICAgICAgICAgICAgIG9zLm1ha2VkaXJzKCIuL0dlbmVyYXRlZC9pbWFnZXMvIikKICAgICAgICAgICAgZmlnID0gaG1hcC5maWd1cmUKICAgICAgICAgICAgaW1hZ2UgPSBmIi4vR2VuZXJhdGVkL2ltYWdlcy97a2V5fS5qcGciCiAgICAgICAgICAgIGZpZy5zYXZlZmlnKGltYWdlLCBkcGk9NjAwKQogICAgICAgICAgICBwcmludChmIlNhdmVkICEge2ltYWdlfSIpCiAgICByZXR1cm4gbWRmCg", - "AgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPC/AAAAAAEAAACLPAAAAAAAAIs8AAAAAAAAAAAAAAAA8L8" - ] - ] - } - ], - "build_system": "", - "build_system_choices": - [ - [ - [ - [ - "Packages/Python/Python.sublime-build", - "" - ], - [ - "Packages/Python/Python.sublime-build", - "Syntax Check" - ] - ], - [ - "Packages/Python/Python.sublime-build", - "Syntax Check" - ] - ] - ], - "build_varint": "", - "command_palette": - { - "height": 0.0, - "last_filter": "", - "selected_items": - [ - [ - "doc", - "AutoDocstring: Current" - ], - [ - "install", - "Package Control: Install Package" - ], - [ - "fix", - "Python Fix Imports" - ], - [ - "inde", - "Indentation: Convert to Spaces" - ] - ], - "width": 0.0 - }, - "console": - { - "height": 0.0, - "history": - [ - ] - }, - "distraction_free": - { - "menu_visible": true, - "show_minimap": false, - "show_open_files": false, - "show_tabs": false, - "side_bar_visible": false, - "status_bar_visible": false - }, - "expanded_folders": - [ - "/Z/home/lab09/SPACE/Rahul-Iikwon", - "/Z/home/lab09/SPACE/Rahul-Iikwon/csfdock" - ], - "file_history": - [ - "/Z/home/lab09/SPACE/Rahul-Iikwon/Generated/jobs/3eml/3eml_ligand_output_dkoes_scoring_old.pdb", - "/Z/home/lab09/SPACE/Rahul-Iikwon/Generated/jobs/3eml/run/3eml_SMina.sh", - "/Z/home/lab09/SPACE/Rahul-Iikwon/csfdock/DVisualize.py", - "/Z/home/lab09/SPACE/Rahul-Iikwon/csfdock/Project.py", - "/Z/home/lab09/SPACE/Rahul-Iikwon/csfdock/DockingTools.py", - "/Z/home/lab09/DOCKER/CSF/DATA/Actives/6nzp_active_complete.sdf", - "/Z/home/lab09/POSTGRES/docker-compose.yml", - "/Z/home/lab09/DOCKER/CSF/DATA/Inactive/6nzp_decoy_complete.sdf", - "/Z/home/lab09/DOCKER/CSF/DATA/Inactive/6nzp_decoy_complete_fix.sdf", - "/Z/home/lab09/DOCKER/CSF/data/Inactive/dude-decoys/decoys/6nzp_all_decoys.smi", - "/Z/home/lab09/DOCKER/CSF/scores.txt", - "/Z/home/lab09/DOCKER/6NZP/Generated/data/6nzp_best.pdbqt", - "/Z/home/lab09/DOCKER/gpu-jupyter/data/6NZP/Generated/2rgp.pdb", - "/Z/home/lab09/DOCKER/gpu-jupyter/data/6NZP/DATA/membrane.pdb", - "/Z/home/lab09/DOCKER/gpu-jupyter/data/6NZP/Scripts/utils.py", - "/Z/home/lab09/DOCKER/gpu-jupyter/data/6NZP/Scripts/ar2a_v3.py", - "/Z/home/lab09/DOCKER/gpu-jupyter/data/6NZP/Scripts/MolView.py", - "/Z/home/lab09/DOCKER/gpu-jupyter/data/6NZP/Scripts/ServerPath.py", - "/Z/home/lab09/DOCKER/gpu-jupyter/data/6NZP/Scripts/DVisualize.py", - "/Z/home/lab09/DOCKER/gpu-jupyter/data/6NZP/Scripts/DockingTools.py", - "/Z/home/lab09/DOCKER/gpu-jupyter/data/6NZP/Scripts/__init__.py", - "/Z/home/lab09/DOCKER/gpu-jupyter/data/6NZP/Scripts/main.py", - "/Z/home/lab09/DOCKER/gpu-jupyter/data/6NZP/ar2a_v3.py", - "/Z/home/lab09/DOCKER/gpu-jupyter/data/6NZP/DockingTools.py", - "/Z/home/lab09/DOCKER/gpu-jupyter/data/6NZP/Utility.py", - "/Z/home/lab09/DOCKER/gpu-jupyter/.build/start.sh", - "/Z/home/lab09/DOCKER/gpu-jupyter/build_push_all.sh", - "/Z/home/lab09/DOCKER/gpu-jupyter/.build/docker-stacks/base-notebook/start.sh", - "/Z/home/lab09/DOCKER/gpu-jupyter/.build/docker-stacks/base-notebook/Dockerfile.ppc64le.patch", - "/Z/home/lab09/DOCKER/gpu-jupyter/.build/docker-stacks/base-notebook/Dockerfile", - "/Z/home/lab09/DOCKER/gpu-jupyter/.build/Dockerfile", - "/Z/home/lab09/DOCKER/Rahul-IIkwon/ar2a_v3.py", - "/Z/home/lab09/DOCKER/Rahul-IIkwon/KinaseModules.py", - "/Z/home/lab09/DOCKER/Rahul-IIkwon/ar2r.py", - "/Z/home/lab09/DOCKER/DEV/Kinase_CLassifier/KC/KinaseModules.py", - "/Z/home/lab09/DOCKER/Rahul-IIkwon/RawData/smiles/Conformer3D_CID_2244.sdf" - ], - "find": - { - "height": 27.0 - }, - "find_in_files": - { - "height": 0.0, - "where_history": - [ - ] - }, - "find_state": - { - "case_sensitive": false, - "find_history": - [ - "obabel", - "Nomatch", - "dir_name_" - ], - "highlight": true, - "in_selection": false, - "preserve_case": false, - "regex": false, - "replace_history": - [ - "nomatch", - "dir_name" - ], - "reverse": false, - "scrollbar_highlights": true, - "show_context": true, - "use_buffer2": true, - "use_gitignore": true, - "whole_word": false, - "wrap": true - }, - "groups": - [ - { - "sheets": - [ - { - "buffer": 0, - "file": "Project.py", - "semi_transient": true, - "settings": - { - "buffer_size": 10100, - "regions": - { - }, - "selection": - [ - [ - 0, - 0 - ] - ], - "settings": - { - "bracket_highlighter.busy": false, - "bracket_highlighter.locations": - { - "close": - { - }, - "icon": - { - }, - "open": - { - }, - "unmatched": - { - } - }, - "bracket_highlighter.regions": - [ - "bh_double_quote", - "bh_double_quote_center", - "bh_double_quote_open", - "bh_double_quote_close", - "bh_double_quote_content", - "bh_square", - "bh_square_center", - "bh_square_open", - "bh_square_close", - "bh_square_content", - "bh_default", - "bh_default_center", - "bh_default_open", - "bh_default_close", - "bh_default_content", - "bh_curly", - "bh_curly_center", - "bh_curly_open", - "bh_curly_close", - "bh_curly_content", - "bh_c_define", - "bh_c_define_center", - "bh_c_define_open", - "bh_c_define_close", - "bh_c_define_content", - "bh_angle", - "bh_angle_center", - "bh_angle_open", - "bh_angle_close", - "bh_angle_content", - "bh_tag", - "bh_tag_center", - "bh_tag_open", - "bh_tag_close", - "bh_tag_content", - "bh_unmatched", - "bh_unmatched_center", - "bh_unmatched_open", - "bh_unmatched_close", - "bh_unmatched_content", - "bh_round", - "bh_round_center", - "bh_round_open", - "bh_round_close", - "bh_round_content", - "bh_regex", - "bh_regex_center", - "bh_regex_open", - "bh_regex_close", - "bh_regex_content", - "bh_single_quote", - "bh_single_quote_center", - "bh_single_quote_open", - "bh_single_quote_close", - "bh_single_quote_content" - ], - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 0.0, - "zoom_level": 1.0 - }, - "stack_index": 1, - "stack_multiselect": false, - "type": "text" - }, - { - "buffer": 1, - "file": "DockingTools.py", - "selected": true, - "semi_transient": false, - "settings": - { - "buffer_size": 37840, - "regions": - { - }, - "selection": - [ - [ - 13647, - 13644 - ] - ], - "settings": - { - "auto_complete": false, - "bracket_highlighter.busy": false, - "bracket_highlighter.locations": - { - "close": - { - "1": - [ - 13645, - 13646 - ] - }, - "icon": - { - "1": - [ - "Packages/BracketHighlighter/icons/round_bracket.png", - "region.yellowish" - ] - }, - "open": - { - "1": - [ - 13531, - 13532 - ] - }, - "unmatched": - { - } - }, - "bracket_highlighter.regions": - [ - "bh_double_quote", - "bh_double_quote_center", - "bh_double_quote_open", - "bh_double_quote_close", - "bh_double_quote_content", - "bh_square", - "bh_square_center", - "bh_square_open", - "bh_square_close", - "bh_square_content", - "bh_default", - "bh_default_center", - "bh_default_open", - "bh_default_close", - "bh_default_content", - "bh_curly", - "bh_curly_center", - "bh_curly_open", - "bh_curly_close", - "bh_curly_content", - "bh_c_define", - "bh_c_define_center", - "bh_c_define_open", - "bh_c_define_close", - "bh_c_define_content", - "bh_angle", - "bh_angle_center", - "bh_angle_open", - "bh_angle_close", - "bh_angle_content", - "bh_tag", - "bh_tag_center", - "bh_tag_open", - "bh_tag_close", - "bh_tag_content", - "bh_unmatched", - "bh_unmatched_center", - "bh_unmatched_open", - "bh_unmatched_close", - "bh_unmatched_content", - "bh_round", - "bh_round_center", - "bh_round_open", - "bh_round_close", - "bh_round_content", - "bh_regex", - "bh_regex_center", - "bh_regex_open", - "bh_regex_close", - "bh_regex_content", - "bh_single_quote", - "bh_single_quote_center", - "bh_single_quote_open", - "bh_single_quote_close", - "bh_single_quote_content" - ], - "syntax": "Packages/Python/Python.sublime-syntax", - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "translation.x": 0.0, - "translation.y": 14940.0, - "zoom_level": 1.0 - }, - "stack_index": 0, - "stack_multiselect": false, - "type": "text" - } - ] - } - ], - "incremental_find": - { - "height": 27.0 - }, - "input": - { - "height": 39.0 - }, - "layout": - { - "cells": - [ - [ - 0, - 0, - 1, - 1 - ] - ], - "cols": - [ - 0.0, - 1.0 - ], - "rows": - [ - 0.0, - 1.0 - ] - }, - "menu_visible": true, - "output.black": - { - "height": 126.0 - }, - "output.exec": - { - "height": 78.0 - }, - "output.find_results": - { - "height": 0.0 - }, - "output.mdpopups": - { - "height": 0.0 - }, - "pinned_build_system": "", - "project": "Rahul-iikwon.sublime-project", - "replace": - { - "height": 50.0 - }, - "save_all_on_build": true, - "select_file": - { - "height": 0.0, - "last_filter": "", - "selected_items": - [ - ], - "width": 0.0 - }, - "select_project": - { - "height": 0.0, - "last_filter": "", - "selected_items": - [ - ], - "width": 0.0 - }, - "select_symbol": - { - "height": 0.0, - "last_filter": "", - "selected_items": - [ - ], - "width": 0.0 - }, - "selected_group": 0, - "settings": - { - }, - "show_minimap": true, - "show_open_files": true, - "show_tabs": true, - "side_bar_visible": true, - "side_bar_width": 121.0, - "status_bar_visible": true, - "template_settings": - { - } -} diff --git a/src/csfdock/__main__.py b/src/csfdock/__main__.py deleted file mode 100644 index 96c7bfc..0000000 --- a/src/csfdock/__main__.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Command-line interface.""" -import click - - -@click.command() -@click.version_option() -def main() : - """CsfDock.""" - - -if __name__ == "__main__": - main(prog_name="csfdock") # pragma: no cover diff --git a/src/csfdock/ar2a_v3.py b/src/csfdock/ar2a_v3.py deleted file mode 100644 index ade3344..0000000 --- a/src/csfdock/ar2a_v3.py +++ /dev/null @@ -1,186 +0,0 @@ -import re -import sys -from collections import Counter - -import ipywidgets -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -import py3Dmol -from IPython.display import HTML, display -from ipywidgets import ( - FileUpload, - IntSlider, - fixed, - interactive, - interactive_output, - widgets, - Layout, -) -from ipywidgets.embed import embed_minimal_html -from matplotlib.offsetbox import AnchoredText -from rdkit import Chem -from rdkit.Chem import AllChem -from rich.console import Console -from rich.table import Table - -console = Console() -# from rich import print - -from sklearn.datasets import make_classification -from sklearn.linear_model import LinearRegression, LogisticRegression -from sklearn.metrics import accuracy_score, roc_auc_score, roc_curve -from sklearn.model_selection import KFold, train_test_split - -from csfdock.DockingTools import * -from csfdock.DVisualize import * -from csfdock.KinaseModules import * -from csfdock.MolView import * -from csfdock.Project import * -from csfdock.utils import * -from csfdock.xg_mod import * - -# - - -def view(structure, ligand=None, color="grey", save=False): - """3d visualization of pdb - Args: - structure (TYPE): Description - ligand (None, optional): small molecule - color (str, optional): color of wish, default: grey - Returns: - TYPE: structure view. - """ - structure_dir, structure_name, structure_format = give_id(structure) - v = py3Dmol.view(width=900, height=500) - if structure_format.lower() == "sdf": - mol = Chem.MolFromMolFile(structure, removeHs=False) - mol = Chem.MolToMolBlock(mol) - v.addModel(mol, f"{structure_format}") - else: - v.addModel(open(structure).read()) - v.setStyle({"cartoon": {"color": f"{color}"}}) - if ligand is not None: - v.setStyle({"resn": f"{ligand}"}, {"stick": {"colorscheme": "greenCarbon"}}) - v.zoomTo() - v.show() - if save: - prefix = "image" - while os.path.exists(f"./images/{prefix}.png"): - suffix += 1 - name = f"{prefix}{suffix}.png" - v.save_fig(f"./Images/{name}", dpi=600) - return structure - - -def update_exp_data(new_data): - """Enter new data to already generated experimental data - Args: - new_data (list|dict): New experimental data - Returns: - pd.DataFrame: Latest data - """ - try: - order_list = ["Elec", "Vdw", "exp"] - if not new_data: - return "Enter valid data" - if isinstance(new_data, list): - CONFIRMED = input(f"Is the list in order(yes|no)\n{order_list}: ") - if CONFIRMED.lower() != "yes": - return "Enter valid order experimental data" - df = pd.DataFrame(data=new_data) - df = df.T - new_columns = {0: "Elec", 1: "Vdw", 2: "exp"} - df.rename(columns=new_columns, inplace=True) - else: - df = pd.DataFrame(data=new_data) - print( - "[bold magenta]Staged for updating previous data with[/bold magenta]" - f" \n{df}\n" - ) - old_df = pd.read_pickle("./DATA/experimental_data.pickle") - latest_df = pd.concat([old_df, df], ignore_index=True) - print( - "[bold green]Successfully save!! [/bold green]\n\nlatest experimental_data" - f" :\n {latest_df}" - ) - latest_df.to_pickle("./DATA/experimental_data.pickle") - return latest_df - except Exception as e: - print(e) - return - - -def parse_log(file): - """Parse LIE log file in return delta Vwd and Elec - Args: - file (str): Log file path - Returns: - pd.DataFrame: Vdw and Elect DataFrame. - """ - try: - with open(file, "r") as file: - info = [] - lines = file.readlines() - extract = False - for index, line in enumerate(lines): - # print(f" {line.strip()}" ) - if line[:6].strip() == "Delta": - extract = True - if extract and ( - line[:6].strip() == "Vdw" or line[:7].strip() == "Elec" - ): - info.append(line.split()) - except Exception as e: - print(e) - df = pd.DataFrame(info) - df = df.T.reset_index(drop=True) - df.columns = df.iloc[0] - df.drop(df.index[0], inplace=True) - return df - - -def exp_model_score(file_path, num_features=2, intercept=False, tsize=0.3, plot=False): - """Generates regression model using sklearn. Will Print out coefficients - Args: - file_path (str): csv/excel file path - num_features (int, optional): Number of features to use. Default: 3 - intercept (bool, optional): Mean Error - tsize (float, optional): Percentage of datato use for test. Default 0.3(30%) - plot_auc (bool, optional): Plot ROC AUC curve - plot_save (bool, optional): Save ROC AUC plot - """ - data, file_name, file_format = give_id(file_path) - supported_file_format = ["csv", "excel"] # for now | smina result file - assert ( - file_format in supported_file_format - ), f"Note: FileType Error: Not supported file format. Use {supported_file_format}" - try: - if file_format == "excel": - df = pd.read_excel(file_path) - else: - df = pd.read_csv(file_path) - print(df) - X, y = df.iloc[:, :-1], df.iloc[:, -1] - X = pd.DataFrame(X) - header = X.iloc[:0, :] - # print(f"----------\n {X}") - # X, y = make_classification(n_samples=df.shape()[0],n_features=num_features) - # TODO //include K-Fold Test - model = LinearRegression(fit_intercept=intercept) - model.fit(X, y) # TODOD accept use model input - print(f"Alpha: {model.coef_[-1]}, Beta= {model.coef_[0]}") - # weight = model.coef_ - # weight = [item for i in weight for item in i] - # for head, coeff in zip(header, weight): - # print(coeff, head, end="\n") - except Exception as er: - print(er) - if plot: - plt.scatter(X, y, color="black") - plt.plot(X, y, color="blue", linewidth=3) - plt.xticks(()) - plt.yticks(()) - plt.show() - return model diff --git a/src/csfdock/py.typed b/src/csfdock/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/src/csfdock/utils.py b/src/csfdock/utils.py deleted file mode 100644 index 3f3969f..0000000 --- a/src/csfdock/utils.py +++ /dev/null @@ -1,146 +0,0 @@ -# Some often used utilities.. -from glob import glob -import os -import subprocess - - -def file_search(type=None, target="*", specific=None): - """searches files in sub dir - Args: - type (str, optional): Search file format - target (str, optional): Identifier to search - specific (str, optional): Specific folder to search - Returns: - list: Search result - """ - BASE_DIR = os.getcwd() - try: - if specific is None: - return sorted(glob(f"{BASE_DIR}/**/{target}.{type}", recursive=True)) - else: - return sorted( - glob(f"{BASE_DIR}/**/{specific}/{target}.{type}", recursive=True) - ) - except Exception as error: - print(f"{error} \n File not found anywhere.") - - -def give_id(input_file): - """Function to return the main file name excluding "." extension. - Args: - file (list): Name of file with "." extension. - Returns: - Name: Name without extension. - """ - file_name = os.path.basename(input_file) - file_name, file_format = file_name.rsplit(".") - file_dir = os.path.dirname(input_file) - return file_dir, file_name, file_format - - -def get(id, molecule="protein", prot_id="", type_="pdb"): - """Downloads structure from RCSB and save in Generated sub folder. - Args: - id (TYPE): PDB ID - molecule (str, optional): default:Protein or Small Molecule - prot_id (str, optional): Description - type_ (str, optional): Structure type to download. - Returns: - pdb/sdf/**: 3D coordinate file - """ - try: - assert molecule in [ - "protein", - "ligand", - ], 'Note: molecule parameter must be either "protein" or "ligand" only' - if not os.path.exists("./Generated/"): - os.makedirs("./Generated/") - if molecule.lower() == "protein": - assert type_ in [ - "pdb" - ], "Note: \n Only PDB format supported for protein for now." - command = ( - f"wget https://files.rcsb.org/download/{id}.{type_} -q -P ./Generated/" - ) - msg = f"downloading of {id}.{type_}" - elif molecule.lower() == "ligand": - command = ( - f"wget -c -O https://files.rcsb.org/ligands/download/{id}_ideal.{type_}" - f" > ./Generated/{prot_id}_{id}_ligand.{type_}" - ) - msg = f"downloading of {prot_id}_{id}_ligand.{type_}" - if os.path.exists(f"./Generated/{id}.{type_}"): - msg = "but not downloaded as it already exists" - else: - subprocess.run(command, shell=True) - return f"Succcesfully executed {msg} in ./Generated folder." - except Exception as er: - print(er) - - -def search_gui(): - output = widgets.Output() - - def f(File_type): - global file_type - file_type = File_type - - def search(file): - output.clear_output() - try: - target = target_in.value - if len(target) == 0: - with output: - print("** Cannot be empty target!") - else: - global search_result - specific = folder_in.value - search_result = folder_search( - type=file_type, target=target, specific=specific - ) - with output: - print(f"Total files found: {len(search_result)}") - print(search_result) - return search_result - except: - with output: - print("something wrong") - - usage_information = widgets.HTML( - "Enter target file type and target name and specify" - " the folder." - ) - display(usage_information) - interact(f, File_type=["pdb", "sdf", "xyz", "txt"]) - target_in = widgets.Text( - placeholder="Enter name", description="Target: ", disable=False - ) - folder_in = widgets.Text( - placeholder="specific folder name(optional)", - description="Folder: ", - disable=False, - ) - search_button = widgets.Button(description="Search") - search_button.style.button_color = "lightgreen" - display(widgets.HBox([target_in, folder_in, search_button]), output) - search_button.on_click(search) - - -def PDBParse(target): - protein = [] - membrane = [] - tip3 = [] - ligand = [] - with open(target, "r") as target: - temp = target.readlines() - for line in temp: - if line.startswith("ATOM") and line[21:22].strip() == "P": - protein.append(line) - elif line.startswith("ATOM") and line[21:22].strip() == "M": - membrane.append(line) - elif line.startswith("ATOM") and line[21:22].strip() == "T": - tip3.append(line) - elif line.startswith("HETATM"): - ligand.append(line) - - return protein, membrane, tip3, ligand diff --git a/src/csfdock/xg_mod.py b/src/csfdock/xg_mod.py deleted file mode 100644 index 8d89c49..0000000 --- a/src/csfdock/xg_mod.py +++ /dev/null @@ -1,99 +0,0 @@ -from pandas import read_csv -from numpy import absolute -from matplotlib import pyplot -from numpy import mean -from numpy import std -from sklearn.datasets import make_regression -from sklearn.model_selection import cross_val_score -from sklearn.model_selection import RepeatedKFold -from xgboost import XGBRegressor -from sklearn.datasets import make_classification -from sklearn.model_selection import RepeatedStratifiedKFold -from xgboost import XGBClassifier -from matplotlib import pyplot - - -def ensamble(X, y): - # define dataset - X, y = make_regression( - n_samples=1000, n_features=5, n_informative=5, noise=0.1, random_state=7 - ) - # define the model - model = XGBRegressor() - model.fit(X, y) - # evaluate the model - cv = RepeatedKFold(n_splits=10, n_repeats=3, random_state=1) - n_scores = cross_val_score( - model, - X, - y, - scoring="neg_mean_absolute_error", - cv=cv, - n_jobs=-1, - error_score="raise", - ) - # report performance - print("MAE: %.3f (%.3f)" % (mean(n_scores), std(n_scores))) - - -def xg(file, ensemble=True, params=None): - - dataframe = read_csv(file, header=None) - data = dataframe.values - # split data into input and output columns - X, y = data[1:, 1:-1], data[1:, -1] - # define model - model = XGBRegressor() - model.fit(X, y) - # define model evaluation method - cv = RepeatedKFold(n_splits=10, n_repeats=3, random_state=10) - # evaluate model - scores = cross_val_score( - model, X, y, scoring="neg_mean_absolute_error", cv=cv, n_jobs=-1 - ) - # force scores to be positive - scores = absolute(scores) - print("Mean MAE: %.3f (%.3f)" % (scores.mean(), scores.std())) - - if ensemble: - ensamble(X, y) - if params is not None: - xg_param(X, y) - - -# explore xgboost number of trees effect on performance -def xg_param(X, y): - def get_dataset(): - X, y = make_classification( - n_samples=1000, - n_features=5, - n_informative=15, - n_redundant=5, - random_state=712, - ) - return X, y - - # get a list of models to evaluate - def get_models(): - trees = [10, 50, 100, 500, 1000, 5000] - return {str(n): XGBClassifier(n_estimators=n) for n in trees} - - # evaluate a give model using cross-validation - def evaluate_model(model): - cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) - return cross_val_score(model, X, y, scoring="accuracy", cv=cv, n_jobs=-1) - - # define dataset - X, y = get_dataset() - # get the models to evaluate - models = get_models() - # evaluate the models and store results - results, names = list(), list() - for name, model in models.items(): - scores = evaluate_model(model) - results.append(scores) - names.append(name) - print(">%s Accuracy: %.3f[mean] %.3f[std]" % (name, mean(scores), std(scores))) - # plot model performance for comparison - pyplot.boxplot(results, labels=names, showmeans=True) - pyplot.show()