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()