1 Background

This notebook reformats the original P.evermanni GFF (Porites_evermanni_v1.annot.gff), which is not compliant with the GFF standard (GitHub page). The GFF is lacking the gene feature, which may (or may not) be needed/useful for downstream processing. This notebook adds a gene feature. Additionally, it is lacking the exon feature. We’ve decided to insert exon features which cover UTR and CDS features.

Finally, despite the naming convention, there aren’t any actual annotations in that GFF, beyond the feature designations (i.e. no gene ontology, no SwissProt IDs, gene names, etc.). This notebook does not address those shortcomings.

Unlike other scripts, this will output to E-Peve/data, instead of an output directory in ../output.

1.1 Software requirements

Requires genometools (GitHub repo) to be installed and in the system $PATH.

Requires AGAT to be installed via conda/mamba for conversion to GTF.

2 Create a Bash variables file

This allows usage of Bash variables across R Markdown chunks.

{
echo "#### Assign Variables ####"
echo ""

echo "# Data directories"
echo 'export repo_dir=~/gitrepos/urol-e5/timeseries_molecular'
echo 'export data_dir=${repo_dir}/E-Peve/data'
echo ""

echo "# Input files"
echo 'export original_gff="Porites_evermanni_v1.annot.gff"'
echo 'export intermediate_gff="intermediate.gff"'
echo 'export validated_gff="Porites_evermanni_validated.gff3"'
echo ""

echo "# Output files"
echo 'export gtf="Porites_evermanni_validated.gtf"'
echo ""

echo "# Programs"
echo 'export conda_path="/home/sam/programs/miniforge3-24.7.1-0/bin/mamba"'
echo 'export conda_env_name="agat_env"'
echo ""

echo "# Print formatting"
echo 'export line="--------------------------------------------------------"'
echo ""
} > .bashvars

cat .bashvars
#### Assign Variables ####

# Data directories
export repo_dir=~/gitrepos/urol-e5/timeseries_molecular
export data_dir=${repo_dir}/E-Peve/data

# Input files
export original_gff="Porites_evermanni_v1.annot.gff"
export intermediate_gff="intermediate.gff"
export validated_gff="Porites_evermanni_validated.gff3"

# Output files
export gtf="Porites_evermanni_validated.gtf"

# Programs
export conda_path="/home/sam/programs/miniforge3-24.7.1-0/bin/mamba"
export conda_env_name="agat_env"

# Print formatting
export line="--------------------------------------------------------"

3 Peak at original GFF

source .bashvars

head "${data_dir}/${original_gff}"
Porites_evermani_scaffold_1 Gmove   mRNA    3107    4488    543 -   .   ID=Peve_00000001;Name=Peve_00000001;start=0;stop=1;cds_size=543
Porites_evermani_scaffold_1 Gmove   CDS 3107    3444    .   -   .   Parent=Peve_00000001
Porites_evermani_scaffold_1 Gmove   CDS 4284    4488    .   -   .   Parent=Peve_00000001
Porites_evermani_scaffold_1 Gmove   mRNA    424479  429034  2439.63 -   .   ID=Peve_00000002;Name=Peve_00000002;start=1;stop=1;cds_size=2019
Porites_evermani_scaffold_1 Gmove   CDS 424479  425361  .   -   .   Parent=Peve_00000002
Porites_evermani_scaffold_1 Gmove   CDS 426181  426735  .   -   .   Parent=Peve_00000002
Porites_evermani_scaffold_1 Gmove   CDS 427013  427140  .   -   .   Parent=Peve_00000002
Porites_evermani_scaffold_1 Gmove   CDS 427665  427724  .   -   .   Parent=Peve_00000002
Porites_evermani_scaffold_1 Gmove   CDS 428642  429034  .   -   .   Parent=Peve_00000002
Porites_evermani_scaffold_1 Gmove   mRNA    429394  438909  1570.66 +   .   ID=Peve_00000003;Name=Peve_00000003;start=1;stop=1;cds_size=1458

3.1 Check features

source .bashvars

awk '{print $3}' "${data_dir}/${original_gff}" | sort --unique
CDS
mRNA
UTR

4 Fix GFF

source .bashvars

awk '
BEGIN { OFS="\t"; mrna_count = 0; utr_count = 0; gene_count = 0; cds_count = 0; exon_count = 0 }
{
    if ($3 == "mRNA") {
        split($9, attributes, ";")
        for (i in attributes) {
            if (attributes[i] ~ /^ID=/) {
                original_id = substr(attributes[i], 4)
                gene_id = "ID=gene-" original_id
                parent_id = "Parent=gene-" original_id
                break
            }
        }
        
        # Increment the global mRNA counter
        mrna_count++
        
        new_mrna_id = "ID=mrna-" sprintf("%05d", mrna_count)
        
        # Store the mapping of original mRNA ID to new mRNA ID
        mrna_map[original_id] = "mrna-" sprintf("%05d", mrna_count)
        
        # Replace the old ID with the new mRNA ID
        for (i in attributes) {
            if (attributes[i] ~ /^ID=/) {
                attributes[i] = new_mrna_id
                break
            }
        }
        $9 = attributes[1]
        for (i = 2; i <= length(attributes); i++) {
            $9 = $9 ";" attributes[i]
        }
        $9 = $9 ";" parent_id
        print $1, $2, "gene", $4, $5, ".", $7, $8, gene_id
        print $0  # Print the original mRNA feature
        
        # Increment the gene counter and reset the CDS counter for each new gene
        gene_count++
    } else if ($3 == "UTR" || $3 == "CDS") {
        split($9, attributes, ";")
        id_set = 0
        parent_set = 0
        for (i in attributes) {
            if (attributes[i] ~ /^ID=/) {
                id_set = 1
                if ($3 == "UTR") {
                    attributes[i] = "ID=utr-" sprintf("%05d", ++utr_count)
                } else if ($3 == "CDS") {
                    attributes[i] = "ID=cds-" sprintf("%05d", ++cds_count)
                }
            }
            if (attributes[i] ~ /^Parent=/) {
                parent_set = 1
                original_parent_id = substr(attributes[i], 8)
                if (original_parent_id in mrna_map) {
                    attributes[i] = "Parent=" mrna_map[original_parent_id]
                }
            }
        }
        if (id_set == 0) {
            if ($3 == "UTR") {
                attributes[1] = "ID=utr-" sprintf("%05d", ++utr_count) ";" attributes[1]
            } else if ($3 == "CDS") {
                attributes[1] = "ID=cds-" sprintf("%05d", ++cds_count) ";" attributes[1]
            }
        }
        if (parent_set == 0) {
            attributes[length(attributes) + 1] = "Parent=" mrna_map[original_id]
        }
        $9 = attributes[1]
        for (i = 2; i <= length(attributes); i++) {
            $9 = $9 ";" attributes[i]
        }
        print $1, $2, $3, $4, $5, $6, $7, $8, $9  # Print the original UTR or CDS feature
        
        # Add exon feature
        exon_count++
        new_exon_id = "ID=exon-" sprintf("%06d", exon_count)
        exon_attributes = new_exon_id
        for (i = 1; i <= length(attributes); i++) {
            if (attributes[i] !~ /^ID=/) {
                exon_attributes = exon_attributes ";" attributes[i]
            }
        }
        # Ensure Parent attribute is included in exon attributes
        if (parent_set == 1) {
            exon_attributes = exon_attributes ";Parent=" mrna_map[original_parent_id]
        }
        print $1, $2, "exon", $4, $5, $6, $7, $8, exon_attributes
    }
}
' "${data_dir}/${original_gff}" > "${data_dir}/${intermediate_gff}"

4.1 Inspect intermediate GFF

source .bashvars

head "${data_dir}"/"${intermediate_gff}"
Porites_evermani_scaffold_1 Gmove   gene    3107    4488    .   -   .   ID=gene-Peve_00000001
Porites_evermani_scaffold_1 Gmove   mRNA    3107    4488    543 -   .   ID=mrna-00001;Name=Peve_00000001;start=0;stop=1;cds_size=543;Parent=gene-Peve_00000001
Porites_evermani_scaffold_1 Gmove   CDS 3107    3444    .   -   .   ID=cds-00001;Parent=mrna-00001
Porites_evermani_scaffold_1 Gmove   exon    3107    3444    .   -   .   ID=exon-000001;Parent=mrna-00001
Porites_evermani_scaffold_1 Gmove   CDS 4284    4488    .   -   .   ID=cds-00002;Parent=mrna-00001
Porites_evermani_scaffold_1 Gmove   exon    4284    4488    .   -   .   ID=exon-000002;Parent=mrna-00001
Porites_evermani_scaffold_1 Gmove   gene    424479  429034  .   -   .   ID=gene-Peve_00000002
Porites_evermani_scaffold_1 Gmove   mRNA    424479  429034  2439.63 -   .   ID=mrna-00002;Name=Peve_00000002;start=1;stop=1;cds_size=2019;Parent=gene-Peve_00000002
Porites_evermani_scaffold_1 Gmove   CDS 424479  425361  .   -   .   ID=cds-00003;Parent=mrna-00002
Porites_evermani_scaffold_1 Gmove   exon    424479  425361  .   -   .   ID=exon-000003;Parent=mrna-00002

5 Validate GFF

Validate GFF using genometools gff3.

  • -tidy: Attempts to clean/fix any potential issues.

  • -checkids: Checks IDs.

  • -retainids: Retains IDs from input GFF instead of assigning new ones.

source .bashvars

gt gff3 \
-tidy \
-checkids \
-retainids \
-sort \
"${data_dir}"/"${intermediate_gff}" \
> "${data_dir}"/"${validated_gff}" \
2> "${data_dir}"/gt_gff3_validation_errors.log

5.1 Check for error(s) in validation

Process would stop if error occurred, so only need to check end of file.

Warnings are expected, as they generally indicated modifications to the GFF to bring it into compliance.

source .bashvars

tail "${data_dir}"/gt_gff3_validation_errors.log
warning: CDS feature on line 81189 in file "/home/sam/gitrepos/urol-e5/timeseries_molecular/E-Peve/data/intermediate.gff" has the wrong phase . -> correcting it to 0
warning: CDS feature on line 81191 in file "/home/sam/gitrepos/urol-e5/timeseries_molecular/E-Peve/data/intermediate.gff" has the wrong phase . -> correcting it to 2
warning: CDS feature on line 81193 in file "/home/sam/gitrepos/urol-e5/timeseries_molecular/E-Peve/data/intermediate.gff" has the wrong phase . -> correcting it to 2
warning: CDS feature on line 81197 in file "/home/sam/gitrepos/urol-e5/timeseries_molecular/E-Peve/data/intermediate.gff" has the wrong phase . -> correcting it to 0
warning: CDS feature on line 81199 in file "/home/sam/gitrepos/urol-e5/timeseries_molecular/E-Peve/data/intermediate.gff" has the wrong phase . -> correcting it to 0
warning: CDS feature on line 81203 in file "/home/sam/gitrepos/urol-e5/timeseries_molecular/E-Peve/data/intermediate.gff" has the wrong phase . -> correcting it to 0
warning: CDS feature on line 81205 in file "/home/sam/gitrepos/urol-e5/timeseries_molecular/E-Peve/data/intermediate.gff" has the wrong phase . -> correcting it to 0
warning: CDS feature on line 81207 in file "/home/sam/gitrepos/urol-e5/timeseries_molecular/E-Peve/data/intermediate.gff" has the wrong phase . -> correcting it to 2
warning: CDS feature on line 81209 in file "/home/sam/gitrepos/urol-e5/timeseries_molecular/E-Peve/data/intermediate.gff" has the wrong phase . -> correcting it to 0
warning: CDS feature on line 81211 in file "/home/sam/gitrepos/urol-e5/timeseries_molecular/E-Peve/data/intermediate.gff" has the wrong phase . -> correcting it to 0

5.2 Inspect Validated GFF

source .bashvars

head "${data_dir}"/"${validated_gff}"
##gff-version 3
##sequence-region   Porites_evermani_scaffold_1 3107 1802489
##sequence-region   Porites_evermani_scaffold_10 16486 1155879
##sequence-region   Porites_evermani_scaffold_100 2448 527357
##sequence-region   Porites_evermani_scaffold_1000 5675 163688
##sequence-region   Porites_evermani_scaffold_1001 5840 151448
##sequence-region   Porites_evermani_scaffold_1002 3372 125467
##sequence-region   Porites_evermani_scaffold_1003 139 162450
##sequence-region   Porites_evermani_scaffold_1004 10061 159112
##sequence-region   Porites_evermani_scaffold_1005 28090 152503

6 Remove intermediate GFF

source .bashvars

rm "${data_dir}"/"${intermediate_gff}"

7 Convert to GTF

source .bashvars

# Sends progress to /dev/null to avoid crashing Rmd notebook
"${conda_path}" run -n ${conda_env_name} \
agat_convert_sp_gff2gtf.pl \
--gff "${data_dir}"/"${validated_gff}" \
--output "${data_dir}"/${gtf} \
1> "${data_dir}"/agat.log \
2> /dev/null

7.1 Check AGAT GTF Log

source .bashvars

cat "${data_dir}"/agat.log
converting to GTF3
********************************************************************************
*                              - Start parsing -                               *
********************************************************************************
-------------------------- parse options and metadata --------------------------
=> Accessing the feature level json files
    Using standard /home/sam/programs/miniforge3-24.7.1-0/envs/agat_env/lib/site_perl/5.26.2/auto/share/dist/AGAT/features_level1.json file
    Using standard /home/sam/programs/miniforge3-24.7.1-0/envs/agat_env/lib/site_perl/5.26.2/auto/share/dist/AGAT/features_level2.json file
    Using standard /home/sam/programs/miniforge3-24.7.1-0/envs/agat_env/lib/site_perl/5.26.2/auto/share/dist/AGAT/features_level3.json file
    Using standard /home/sam/programs/miniforge3-24.7.1-0/envs/agat_env/lib/site_perl/5.26.2/auto/share/dist/AGAT/features_spread.json file
=> Attribute used to group features when no Parent/ID relationship exists:
    * locus_tag
    * gene_id
=> merge_loci option deactivated
=> Accessing Ontology
    No ontology accessible from the gff file header!
    We use the SOFA ontology distributed with AGAT:
        /home/sam/programs/miniforge3-24.7.1-0/envs/agat_env/lib/site_perl/5.26.2/auto/share/dist/AGAT/so.obo
    Read ontology /home/sam/programs/miniforge3-24.7.1-0/envs/agat_env/lib/site_perl/5.26.2/auto/share/dist/AGAT/so.obo:
        4 root terms, and 2472 total terms, and 1436 leaf terms
    Filtering ontology:
        We found 1757 terms that are sequence_feature or is_a child of it.
-------------------------------- parse features --------------------------------
=> GFF parser version used: 3

********************************************************************************
*                               - End parsing -                                *
*                             done in 109 seconds                              *
********************************************************************************

********************************************************************************
*                               - Start checks -                               *
********************************************************************************
---------------------------- Check1: feature types -----------------------------
----------------------------------- ontology -----------------------------------
All feature types in agreement with the Ontology.
------------------------------------- agat -------------------------------------
AGAT can deal with all the encountered feature types (3rd column)
------------------------------ done in 0 seconds -------------------------------

------------------------------ Check2: duplicates ------------------------------
None found
------------------------------ done in 0 seconds -------------------------------

-------------------------- Check3: sequential bucket ---------------------------
Nothing to check as sequential bucket!
------------------------------ done in 0 seconds -------------------------------

--------------------------- Check4: l2 linked to l3 ----------------------------
No problem found
------------------------------ done in 0 seconds -------------------------------

--------------------------- Check5: l1 linked to l2 ----------------------------
No problem found
------------------------------ done in 0 seconds -------------------------------

--------------------------- Check6: remove orphan l1 ---------------------------
We remove only those not supposed to be orphan
None found
------------------------------ done in 1 seconds -------------------------------

------------------------------ Check7: check cds -------------------------------
No problem found
------------------------------ done in 0 seconds -------------------------------

----------------------------- Check8: check exons ------------------------------
No exons created
No exons locations modified
10475 exons removed that were supernumerary
No level2 locations modified
------------------------------ done in 11 seconds ------------------------------

------------------------------ Check9: check utrs ------------------------------
No UTRs created
No UTRs locations modified
No supernumerary UTRs removed
------------------------------ done in 6 seconds -------------------------------

------------------------ Check10: all level2 locations -------------------------
No problem found
------------------------------ done in 5 seconds -------------------------------

------------------------ Check11: all level1 locations -------------------------
No problem found
------------------------------ done in 1 seconds -------------------------------

---------------------- Check12: remove identical isoforms ----------------------
None found
------------------------------ done in 0 seconds -------------------------------
********************************************************************************
*                                - End checks -                                *
*                              done in 24 seconds                              *
********************************************************************************

=> OmniscientI total time: 133 seconds
Bye Bye

7.2 Inspect GTF

source .bashvars

head "${data_dir}"/"${gtf}"
##gtf-version 3
##sequence-region   Porites_evermani_scaffold_1 3107 1802489
##sequence-region   Porites_evermani_scaffold_10 16486 1155879
##sequence-region   Porites_evermani_scaffold_100 2448 527357
##sequence-region   Porites_evermani_scaffold_1000 5675 163688
##sequence-region   Porites_evermani_scaffold_1001 5840 151448
##sequence-region   Porites_evermani_scaffold_1002 3372 125467
##sequence-region   Porites_evermani_scaffold_1003 139 162450
##sequence-region   Porites_evermani_scaffold_1004 10061 159112
##sequence-region   Porites_evermani_scaffold_1005 28090 152503
LS0tCnRpdGxlOiAiUC5ldmVybWFubmkgR0ZGIHJlZm9ybWF0dGluZyIKYXV0aG9yOiAiU2FtIFdoaXRlIgpkYXRlOiAiMjAyNS0wMS0xMCIKb3V0cHV0OiAKICBib29rZG93bjo6aHRtbF9kb2N1bWVudDI6CiAgICB0aGVtZTogY29zbW8KICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogIGdpdGh1Yl9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgaHRtbF9kb2N1bWVudDoKICAgIHRoZW1lOiBjb3NtbwogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQoKIyBCYWNrZ3JvdW5kCgpUaGlzIG5vdGVib29rIHJlZm9ybWF0cyB0aGUgb3JpZ2luYWwgX1AuZXZlcm1hbm5pXyBHRkYgKFtQb3JpdGVzX2V2ZXJtYW5uaV92MS5hbm5vdC5nZmZdKC4uL2RhdGEvUG9yaXRlc19ldmVybWFubmlfdjEuYW5ub3QuZ2ZmKSksIHdoaWNoIGlzIG5vdCBjb21wbGlhbnQgd2l0aCBbdGhlIEdGRiBzdGFuZGFyZF0oaHR0cHM6Ly9naXRodWIuY29tL3RoZS1zZXF1ZW5jZS1vbnRvbG9neS9zcGVjaWZpY2F0aW9ucy9ibG9iL21hc3Rlci9nZmYzLm1kKSAoR2l0SHViIHBhZ2UpLiBUaGUgR0ZGIGlzIGxhY2tpbmcgdGhlIGBnZW5lYCBmZWF0dXJlLCB3aGljaCBtYXkgKG9yIG1heSBub3QpIGJlIG5lZWRlZC91c2VmdWwgZm9yIGRvd25zdHJlYW0gcHJvY2Vzc2luZy4gVGhpcyBub3RlYm9vayBhZGRzIGEgYGdlbmVgIGZlYXR1cmUuIEFkZGl0aW9uYWxseSwgaXQgaXMgbGFja2luZyB0aGUgYGV4b25gIGZlYXR1cmUuIFdlJ3ZlIGRlY2lkZWQgdG8gaW5zZXJ0IGBleG9uYCBmZWF0dXJlcyB3aGljaCBjb3ZlciBgVVRSYCBhbmQgYENEU2AgZmVhdHVyZXMuCgpGaW5hbGx5LCBkZXNwaXRlIHRoZSBuYW1pbmcgY29udmVudGlvbiwgdGhlcmUgYXJlbid0IGFueSBhY3R1YWwgYW5ub3RhdGlvbnMgaW4gdGhhdCBHRkYsIGJleW9uZCB0aGUgZmVhdHVyZSBkZXNpZ25hdGlvbnMgKGkuZS4gbm8gZ2VuZSBvbnRvbG9neSwgbm8gU3dpc3NQcm90IElEcywgZ2VuZSBuYW1lcywgZXRjLikuIFRoaXMgbm90ZWJvb2sgZG9lcyBfbm90XyBhZGRyZXNzIHRob3NlIHNob3J0Y29taW5ncy4KCjo6OiB7LmNhbGxvdXQtbm90ZX0KVW5saWtlIG90aGVyIHNjcmlwdHMsIHRoaXMgd2lsbCBvdXRwdXQgdG8gW0UtUGV2ZS9kYXRhXSguLi9kYXRhKSwgaW5zdGVhZCBvZiBhbiBvdXRwdXQgZGlyZWN0b3J5IGluIGAuLi9vdXRwdXRgLgo6OjoKCiMjIFNvZnR3YXJlIHJlcXVpcmVtZW50cwoKUmVxdWlyZXMgW2dlbm9tZXRvb2xzXShodHRwczovL2dpdGh1Yi5jb20vZ2Vub21ldG9vbHMvZ2Vub21ldG9vbHMpIChHaXRIdWIgcmVwbykgdG8gYmUgaW5zdGFsbGVkIGFuZCBpbiB0aGUgc3lzdGVtIGAkUEFUSGAuCgpSZXF1aXJlcyBbQUdBVF0oaHR0cHM6Ly9naXRodWIuY29tL05CSVN3ZWRlbi9BR0FUKSB0byBiZSBpbnN0YWxsZWQgdmlhIGNvbmRhL21hbWJhIGZvcgpjb252ZXJzaW9uIHRvIEdURi4KCiMjIElucHV0cwoKLSBbUG9yaXRlc19ldmVybWFubmlfdjEuYW5ub3QuZ2ZmXSguLi9kYXRhL1Bvcml0ZXNfZXZlcm1hbm5pX3YxLmFubm90LmdmZikKCiMjIE91dHB1dHMKCi0gW1Bvcml0ZXNfZXZlcm1hbm5pX3ZhbGlkYXRlZC5nZmYzXSguLi9kYXRhL1Bvcml0ZXNfZXZlcm1hbm5pX3ZhbGlkYXRlZC5nZmYzKQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmxpYnJhcnkoa25pdHIpCmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBlY2hvID0gVFJVRSwgICAgICAgICAjIERpc3BsYXkgY29kZSBjaHVua3MKICBldmFsID0gRkFMU0UsICAgICAgICAjIEV2YWx1YXRlIGNvZGUgY2h1bmtzCiAgd2FybmluZyA9IEZBTFNFLCAgICAgIyBIaWRlIHdhcm5pbmdzCiAgbWVzc2FnZSA9IEZBTFNFLCAgICAgIyBIaWRlIG1lc3NhZ2VzCiAgY29tbWVudCA9ICIiICAgICAgICAgIyBQcmV2ZW50cyBhcHBlbmRpbmcgJyMjJyB0byBiZWdpbm5pbmcgb2YgbGluZXMgaW4gY29kZSBvdXRwdXQKKQpgYGAKCgojIENyZWF0ZSBhIEJhc2ggdmFyaWFibGVzIGZpbGUKClRoaXMgYWxsb3dzIHVzYWdlIG9mIEJhc2ggdmFyaWFibGVzIGFjcm9zcyBSIE1hcmtkb3duIGNodW5rcy4KCmBgYHtyIHNhdmUtYmFzaC12YXJpYWJsZXMtdG8tcnZhcnMtZmlsZSwgZW5naW5lPSdiYXNoJywgZXZhbD1UUlVFfQp7CmVjaG8gIiMjIyMgQXNzaWduIFZhcmlhYmxlcyAjIyMjIgplY2hvICIiCgplY2hvICIjIERhdGEgZGlyZWN0b3JpZXMiCmVjaG8gJ2V4cG9ydCByZXBvX2Rpcj1+L2dpdHJlcG9zL3Vyb2wtZTUvdGltZXNlcmllc19tb2xlY3VsYXInCmVjaG8gJ2V4cG9ydCBkYXRhX2Rpcj0ke3JlcG9fZGlyfS9FLVBldmUvZGF0YScKZWNobyAiIgoKZWNobyAiIyBJbnB1dCBmaWxlcyIKZWNobyAnZXhwb3J0IG9yaWdpbmFsX2dmZj0iUG9yaXRlc19ldmVybWFubmlfdjEuYW5ub3QuZ2ZmIicKZWNobyAnZXhwb3J0IGludGVybWVkaWF0ZV9nZmY9ImludGVybWVkaWF0ZS5nZmYiJwplY2hvICdleHBvcnQgdmFsaWRhdGVkX2dmZj0iUG9yaXRlc19ldmVybWFubmlfdmFsaWRhdGVkLmdmZjMiJwplY2hvICIiCgplY2hvICIjIE91dHB1dCBmaWxlcyIKZWNobyAnZXhwb3J0IGd0Zj0iUG9yaXRlc19ldmVybWFubmlfdmFsaWRhdGVkLmd0ZiInCmVjaG8gIiIKCmVjaG8gIiMgUHJvZ3JhbXMiCmVjaG8gJ2V4cG9ydCBjb25kYV9wYXRoPSIvaG9tZS9zYW0vcHJvZ3JhbXMvbWluaWZvcmdlMy0yNC43LjEtMC9iaW4vbWFtYmEiJwplY2hvICdleHBvcnQgY29uZGFfZW52X25hbWU9ImFnYXRfZW52IicKZWNobyAiIgoKZWNobyAiIyBQcmludCBmb3JtYXR0aW5nIgplY2hvICdleHBvcnQgbGluZT0iLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0iJwplY2hvICIiCn0gPiAuYmFzaHZhcnMKCmNhdCAuYmFzaHZhcnMKYGBgCgoKIyBQZWFrIGF0IG9yaWdpbmFsIEdGRgpgYGB7ciB2aWV3LW9yaWdpbmFsLUdGRiwgZW5naW5lPSdiYXNoJywgZXZhbD1UUlVFfQpzb3VyY2UgLmJhc2h2YXJzCgpoZWFkICIke2RhdGFfZGlyfS8ke29yaWdpbmFsX2dmZn0iCmBgYAojIyBDaGVjayBmZWF0dXJlcwpgYGB7ciBmZWF0dXJlcy1vcmlnaW5hbC1HRkYsIGVuZ2luZT0nYmFzaCcsIGV2YWw9VFJVRX0Kc291cmNlIC5iYXNodmFycwoKYXdrICd7cHJpbnQgJDN9JyAiJHtkYXRhX2Rpcn0vJHtvcmlnaW5hbF9nZmZ9IiB8IHNvcnQgLS11bmlxdWUKYGBgCgojIEZpeCBHRkYKCgpgYGB7ciBmaXgtZ2ZmLCBlbmdpbmU9J2Jhc2gnLCBldmFsPVRSVUV9CnNvdXJjZSAuYmFzaHZhcnMKCmF3ayAnCkJFR0lOIHsgT0ZTPSJcdCI7IG1ybmFfY291bnQgPSAwOyB1dHJfY291bnQgPSAwOyBnZW5lX2NvdW50ID0gMDsgY2RzX2NvdW50ID0gMDsgZXhvbl9jb3VudCA9IDAgfQp7CiAgICBpZiAoJDMgPT0gIm1STkEiKSB7CiAgICAgICAgc3BsaXQoJDksIGF0dHJpYnV0ZXMsICI7IikKICAgICAgICBmb3IgKGkgaW4gYXR0cmlidXRlcykgewogICAgICAgICAgICBpZiAoYXR0cmlidXRlc1tpXSB+IC9eSUQ9LykgewogICAgICAgICAgICAgICAgb3JpZ2luYWxfaWQgPSBzdWJzdHIoYXR0cmlidXRlc1tpXSwgNCkKICAgICAgICAgICAgICAgIGdlbmVfaWQgPSAiSUQ9Z2VuZS0iIG9yaWdpbmFsX2lkCiAgICAgICAgICAgICAgICBwYXJlbnRfaWQgPSAiUGFyZW50PWdlbmUtIiBvcmlnaW5hbF9pZAogICAgICAgICAgICAgICAgYnJlYWsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICAKICAgICAgICAjIEluY3JlbWVudCB0aGUgZ2xvYmFsIG1STkEgY291bnRlcgogICAgICAgIG1ybmFfY291bnQrKwogICAgICAgIAogICAgICAgIG5ld19tcm5hX2lkID0gIklEPW1ybmEtIiBzcHJpbnRmKCIlMDVkIiwgbXJuYV9jb3VudCkKICAgICAgICAKICAgICAgICAjIFN0b3JlIHRoZSBtYXBwaW5nIG9mIG9yaWdpbmFsIG1STkEgSUQgdG8gbmV3IG1STkEgSUQKICAgICAgICBtcm5hX21hcFtvcmlnaW5hbF9pZF0gPSAibXJuYS0iIHNwcmludGYoIiUwNWQiLCBtcm5hX2NvdW50KQogICAgICAgIAogICAgICAgICMgUmVwbGFjZSB0aGUgb2xkIElEIHdpdGggdGhlIG5ldyBtUk5BIElECiAgICAgICAgZm9yIChpIGluIGF0dHJpYnV0ZXMpIHsKICAgICAgICAgICAgaWYgKGF0dHJpYnV0ZXNbaV0gfiAvXklEPS8pIHsKICAgICAgICAgICAgICAgIGF0dHJpYnV0ZXNbaV0gPSBuZXdfbXJuYV9pZAogICAgICAgICAgICAgICAgYnJlYWsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICAkOSA9IGF0dHJpYnV0ZXNbMV0KICAgICAgICBmb3IgKGkgPSAyOyBpIDw9IGxlbmd0aChhdHRyaWJ1dGVzKTsgaSsrKSB7CiAgICAgICAgICAgICQ5ID0gJDkgIjsiIGF0dHJpYnV0ZXNbaV0KICAgICAgICB9CiAgICAgICAgJDkgPSAkOSAiOyIgcGFyZW50X2lkCiAgICAgICAgcHJpbnQgJDEsICQyLCAiZ2VuZSIsICQ0LCAkNSwgIi4iLCAkNywgJDgsIGdlbmVfaWQKICAgICAgICBwcmludCAkMCAgIyBQcmludCB0aGUgb3JpZ2luYWwgbVJOQSBmZWF0dXJlCiAgICAgICAgCiAgICAgICAgIyBJbmNyZW1lbnQgdGhlIGdlbmUgY291bnRlciBhbmQgcmVzZXQgdGhlIENEUyBjb3VudGVyIGZvciBlYWNoIG5ldyBnZW5lCiAgICAgICAgZ2VuZV9jb3VudCsrCiAgICB9IGVsc2UgaWYgKCQzID09ICJVVFIiIHx8ICQzID09ICJDRFMiKSB7CiAgICAgICAgc3BsaXQoJDksIGF0dHJpYnV0ZXMsICI7IikKICAgICAgICBpZF9zZXQgPSAwCiAgICAgICAgcGFyZW50X3NldCA9IDAKICAgICAgICBmb3IgKGkgaW4gYXR0cmlidXRlcykgewogICAgICAgICAgICBpZiAoYXR0cmlidXRlc1tpXSB+IC9eSUQ9LykgewogICAgICAgICAgICAgICAgaWRfc2V0ID0gMQogICAgICAgICAgICAgICAgaWYgKCQzID09ICJVVFIiKSB7CiAgICAgICAgICAgICAgICAgICAgYXR0cmlidXRlc1tpXSA9ICJJRD11dHItIiBzcHJpbnRmKCIlMDVkIiwgKyt1dHJfY291bnQpCiAgICAgICAgICAgICAgICB9IGVsc2UgaWYgKCQzID09ICJDRFMiKSB7CiAgICAgICAgICAgICAgICAgICAgYXR0cmlidXRlc1tpXSA9ICJJRD1jZHMtIiBzcHJpbnRmKCIlMDVkIiwgKytjZHNfY291bnQpCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgaWYgKGF0dHJpYnV0ZXNbaV0gfiAvXlBhcmVudD0vKSB7CiAgICAgICAgICAgICAgICBwYXJlbnRfc2V0ID0gMQogICAgICAgICAgICAgICAgb3JpZ2luYWxfcGFyZW50X2lkID0gc3Vic3RyKGF0dHJpYnV0ZXNbaV0sIDgpCiAgICAgICAgICAgICAgICBpZiAob3JpZ2luYWxfcGFyZW50X2lkIGluIG1ybmFfbWFwKSB7CiAgICAgICAgICAgICAgICAgICAgYXR0cmlidXRlc1tpXSA9ICJQYXJlbnQ9IiBtcm5hX21hcFtvcmlnaW5hbF9wYXJlbnRfaWRdCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgaWYgKGlkX3NldCA9PSAwKSB7CiAgICAgICAgICAgIGlmICgkMyA9PSAiVVRSIikgewogICAgICAgICAgICAgICAgYXR0cmlidXRlc1sxXSA9ICJJRD11dHItIiBzcHJpbnRmKCIlMDVkIiwgKyt1dHJfY291bnQpICI7IiBhdHRyaWJ1dGVzWzFdCiAgICAgICAgICAgIH0gZWxzZSBpZiAoJDMgPT0gIkNEUyIpIHsKICAgICAgICAgICAgICAgIGF0dHJpYnV0ZXNbMV0gPSAiSUQ9Y2RzLSIgc3ByaW50ZigiJTA1ZCIsICsrY2RzX2NvdW50KSAiOyIgYXR0cmlidXRlc1sxXQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIGlmIChwYXJlbnRfc2V0ID09IDApIHsKICAgICAgICAgICAgYXR0cmlidXRlc1tsZW5ndGgoYXR0cmlidXRlcykgKyAxXSA9ICJQYXJlbnQ9IiBtcm5hX21hcFtvcmlnaW5hbF9pZF0KICAgICAgICB9CiAgICAgICAgJDkgPSBhdHRyaWJ1dGVzWzFdCiAgICAgICAgZm9yIChpID0gMjsgaSA8PSBsZW5ndGgoYXR0cmlidXRlcyk7IGkrKykgewogICAgICAgICAgICAkOSA9ICQ5ICI7IiBhdHRyaWJ1dGVzW2ldCiAgICAgICAgfQogICAgICAgIHByaW50ICQxLCAkMiwgJDMsICQ0LCAkNSwgJDYsICQ3LCAkOCwgJDkgICMgUHJpbnQgdGhlIG9yaWdpbmFsIFVUUiBvciBDRFMgZmVhdHVyZQogICAgICAgIAogICAgICAgICMgQWRkIGV4b24gZmVhdHVyZQogICAgICAgIGV4b25fY291bnQrKwogICAgICAgIG5ld19leG9uX2lkID0gIklEPWV4b24tIiBzcHJpbnRmKCIlMDZkIiwgZXhvbl9jb3VudCkKICAgICAgICBleG9uX2F0dHJpYnV0ZXMgPSBuZXdfZXhvbl9pZAogICAgICAgIGZvciAoaSA9IDE7IGkgPD0gbGVuZ3RoKGF0dHJpYnV0ZXMpOyBpKyspIHsKICAgICAgICAgICAgaWYgKGF0dHJpYnV0ZXNbaV0gIX4gL15JRD0vKSB7CiAgICAgICAgICAgICAgICBleG9uX2F0dHJpYnV0ZXMgPSBleG9uX2F0dHJpYnV0ZXMgIjsiIGF0dHJpYnV0ZXNbaV0KICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICAjIEVuc3VyZSBQYXJlbnQgYXR0cmlidXRlIGlzIGluY2x1ZGVkIGluIGV4b24gYXR0cmlidXRlcwogICAgICAgIGlmIChwYXJlbnRfc2V0ID09IDEpIHsKICAgICAgICAgICAgZXhvbl9hdHRyaWJ1dGVzID0gZXhvbl9hdHRyaWJ1dGVzICI7UGFyZW50PSIgbXJuYV9tYXBbb3JpZ2luYWxfcGFyZW50X2lkXQogICAgICAgIH0KICAgICAgICBwcmludCAkMSwgJDIsICJleG9uIiwgJDQsICQ1LCAkNiwgJDcsICQ4LCBleG9uX2F0dHJpYnV0ZXMKICAgIH0KfQonICIke2RhdGFfZGlyfS8ke29yaWdpbmFsX2dmZn0iID4gIiR7ZGF0YV9kaXJ9LyR7aW50ZXJtZWRpYXRlX2dmZn0iCmBgYAoKIyMgSW5zcGVjdCBpbnRlcm1lZGlhdGUgR0ZGCmBgYHtyIGludGVybWVkaWF0ZS1nZmYsIGVuZ2luZT0nYmFzaCcsIGV2YWw9VFJVRX0Kc291cmNlIC5iYXNodmFycwoKaGVhZCAiJHtkYXRhX2Rpcn0iLyIke2ludGVybWVkaWF0ZV9nZmZ9IgpgYGAKCiMgVmFsaWRhdGUgR0ZGCgpWYWxpZGF0ZSBHRkYgdXNpbmcgZ2Vub21ldG9vbHMgYGdmZjNgLgoKLSBgLXRpZHlgOiBBdHRlbXB0cyB0byBjbGVhbi9maXggYW55IHBvdGVudGlhbCBpc3N1ZXMuCgotIGAtY2hlY2tpZHNgOiBDaGVja3MgSURzLgoKLSBgLXJldGFpbmlkc2A6IFJldGFpbnMgSURzIGZyb20gaW5wdXQgR0ZGIGluc3RlYWQgb2YgYXNzaWduaW5nIG5ldyBvbmVzLgoKYGBge3IgdmFsaWRhdGUtZ2ZmLCBlbmdpbmU9J2Jhc2gnLCBldmFsPUZBTFNFfQpzb3VyY2UgLmJhc2h2YXJzCgpndCBnZmYzIFwKLXRpZHkgXAotY2hlY2tpZHMgXAotcmV0YWluaWRzIFwKLXNvcnQgXAoiJHtkYXRhX2Rpcn0iLyIke2ludGVybWVkaWF0ZV9nZmZ9IiBcCj4gIiR7ZGF0YV9kaXJ9Ii8iJHt2YWxpZGF0ZWRfZ2ZmfSIgXAoyPiAiJHtkYXRhX2Rpcn0iL2d0X2dmZjNfdmFsaWRhdGlvbl9lcnJvcnMubG9nCmBgYAoKIyMgQ2hlY2sgZm9yIGVycm9yKHMpIGluIHZhbGlkYXRpb24KClByb2Nlc3Mgd291bGQgc3RvcCBpZiBlcnJvciBvY2N1cnJlZCwgc28gb25seSBuZWVkIHRvIGNoZWNrIGVuZCBvZiBmaWxlLgoKOjo6IHsuY2FsbG91dC1ub3RlfQpXYXJuaW5ncyBhcmUgZXhwZWN0ZWQsIGFzIHRoZXkgZ2VuZXJhbGx5IGluZGljYXRlZCBtb2RpZmljYXRpb25zIHRvIHRoZSBHRkYgdG8gYnJpbmcgaXQgaW50byBjb21wbGlhbmNlLgo6OjoKCmBgYHtyIHZhbGlkYXRlLWdmZi1lcnJvci1jaGVjaywgZW5naW5lPSdiYXNoJywgZXZhbD1UUlVFfQpzb3VyY2UgLmJhc2h2YXJzCgp0YWlsICIke2RhdGFfZGlyfSIvZ3RfZ2ZmM192YWxpZGF0aW9uX2Vycm9ycy5sb2cKYGBgCgojIyBJbnNwZWN0IFZhbGlkYXRlZCBHRkYKYGBge3IgdmFsaWRhdGVkLWdmZiwgZW5naW5lPSdiYXNoJywgZXZhbD1UUlVFfQpzb3VyY2UgLmJhc2h2YXJzCgpoZWFkICIke2RhdGFfZGlyfSIvIiR7dmFsaWRhdGVkX2dmZn0iCmBgYAoKIyBSZW1vdmUgaW50ZXJtZWRpYXRlIEdGRgpgYGB7ciBybS1pbnRlcm1lZGlhdGUtZ2ZmLCBlbmdpbmU9J2Jhc2gnLCBldmFsPVRSVUV9CnNvdXJjZSAuYmFzaHZhcnMKCnJtICIke2RhdGFfZGlyfSIvIiR7aW50ZXJtZWRpYXRlX2dmZn0iCmBgYAoKIyBDb252ZXJ0IHRvIEdURiAKCmBgYHtyIGNyZWF0ZS1HVEYsIGVuZ2luZT0nYmFzaCcsIGV2YWw9VFJVRX0Kc291cmNlIC5iYXNodmFycwoKIyBTZW5kcyBwcm9ncmVzcyB0byAvZGV2L251bGwgdG8gYXZvaWQgY3Jhc2hpbmcgUm1kIG5vdGVib29rCiIke2NvbmRhX3BhdGh9IiBydW4gLW4gJHtjb25kYV9lbnZfbmFtZX0gXAphZ2F0X2NvbnZlcnRfc3BfZ2ZmMmd0Zi5wbCBcCi0tZ2ZmICIke2RhdGFfZGlyfSIvIiR7dmFsaWRhdGVkX2dmZn0iIFwKLS1vdXRwdXQgIiR7ZGF0YV9kaXJ9Ii8ke2d0Zn0gXAoxPiAiJHtkYXRhX2Rpcn0iL2FnYXQubG9nIFwKMj4gL2Rldi9udWxsCmBgYAoKIyMgQ2hlY2sgQUdBVCBHVEYgTG9nCmBgYHtyIGluc3BlY3QtQUdBVC1sb2csIGVuZ2luZT0nYmFzaCcsIGV2YWw9VFJVRX0Kc291cmNlIC5iYXNodmFycwoKY2F0ICIke2RhdGFfZGlyfSIvYWdhdC5sb2cKYGBgCgojIyBJbnNwZWN0IEdURgoKYGBge3IgaW5zcGVjdC1HVEYsIGVuZ2luZT0nYmFzaCcsIGV2YWw9VFJVRX0Kc291cmNlIC5iYXNodmFycwoKaGVhZCAiJHtkYXRhX2Rpcn0iLyIke2d0Zn0iCgpgYGA=