Purpose
This script performs functional enrichment analysis (GO,
KEGG, Reactome, Hallmark) on the top 100 marker genes
per cluster (Supplementary Table S6) to independently validate
the cluster names you assigned manually. If the enriched pathways align
with your proposed name (e.g., “NK-like” cluster enriched for “natural
killer cell mediated cytotoxicity”), the annotation is supported.
load libraries
Load Top100 Marker Gene
Table
markers <- read_excel("Supplementary_Table_S6.xlsx")
# Expected columns: pval, avglog2FC, pct.1, pct.2, pvaladj, cluster, gene
markers <- markers %>%
rename_with(tolower) %>%
mutate(cluster = as.character(cluster))
clusters_list <- sort(unique(markers$cluster))
cat("Clusters found:", paste(clusters_list, collapse = ", "), "\n")
clusters_list <- as.character(sort(as.numeric(unique(markers$cluster))))
cat("Clusters found:", paste(clusters_list, collapse = ", "), "\n")
Map Gene Symbols to
Entrez IDs
gene_map <- bitr(unique(markers$gene),
fromType = "SYMBOL",
toType = "ENTREZID",
OrgDb = org.Hs.eg.db)
markers_mapped <- markers %>%
left_join(gene_map, by = c("gene" = "SYMBOL")) %>%
filter(!is.na(ENTREZID))
unmapped_genes <- setdiff(unique(markers$gene), gene_map$SYMBOL)
print(unmapped_genes)
Prepare Background Gene
Universe
universe_entrez <- unique(markers_mapped$ENTREZID)
Define Enrichment
Function
run_cluster_enrichment <- function(cluster_id, marker_df) {
sub <- marker_df %>% filter(cluster == cluster_id)
entrez_ids <- unique(sub$ENTREZID)
message("Cluster ", cluster_id, ": ", length(entrez_ids), " mapped genes")
results <- list()
results$GO_BP <- tryCatch(
enrichGO(gene = entrez_ids, universe = universe_entrez,
OrgDb = org.Hs.eg.db, keyType = "ENTREZID", ont = "BP",
pAdjustMethod = "BH", pvalueCutoff = 0.05, qvalueCutoff = 0.2,
readable = TRUE),
error = function(e) NULL)
results$KEGG <- tryCatch(
enrichKEGG(gene = entrez_ids, universe = universe_entrez,
organism = "hsa", pAdjustMethod = "BH",
pvalueCutoff = 0.05, qvalueCutoff = 0.2),
error = function(e) NULL)
results$Reactome <- tryCatch(
enrichPathway(gene = entrez_ids, universe = universe_entrez,
organism = "human", pAdjustMethod = "BH",
pvalueCutoff = 0.05, qvalueCutoff = 0.2, readable = TRUE),
error = function(e) NULL)
hallmark_sets <- msigdbr(species = "Homo sapiens", category = "H") %>%
dplyr::select(gs_name, entrez_gene)
results$Hallmark <- tryCatch(
enricher(gene = entrez_ids, universe = universe_entrez,
TERM2GENE = hallmark_sets, pAdjustMethod = "BH",
pvalueCutoff = 0.05, qvalueCutoff = 0.2),
error = function(e) NULL)
return(results)
}
Run Enrichment for
Every Cluster
all_results <- list()
for (cl in clusters_list) {
all_results[[cl]] <- tryCatch(
run_cluster_enrichment(cl, markers_mapped),
error = function(e) {
message("Cluster ", cl, " failed: ", e$message)
NULL
}
)
}
Export Enrichment
Tables to Excel
wb <- createWorkbook()
for (cl in names(all_results)) {
res <- all_results[[cl]]
if (is.null(res)) next
for (ont in names(res)) {
obj <- res[[ont]]
if (is.null(obj) || nrow(as.data.frame(obj)) == 0) next
sheet_name <- substr(paste0("C", cl, "_", ont), 1, 31)
addWorksheet(wb, sheet_name)
writeData(wb, sheet_name, as.data.frame(obj))
}
}
saveWorkbook(wb, "Cluster_Enrichment_Results_Top100.xlsx", overwrite = TRUE)
Visualize Top Pathways
per Cluster
for (cl in names(all_results)) {
res <- all_results[[cl]]
if (is.null(res)) next
cat("\n## Cluster", cl, "\n")
for (ont in c("GO_BP", "KEGG", "Reactome", "Hallmark")) {
obj <- res[[ont]]
if (is.null(obj) || nrow(as.data.frame(obj)) == 0) next
cat("\n###", ont, "- Cluster", cl, "\n")
p <- dotplot(obj, showCategory = 10) +
ggtitle(paste("Cluster", cl, "-", ont)) +
theme(axis.text.y = element_text(size = 9))
print(p)
ggsave(filename = paste0("cluster", cl, "_", ont, "_dotplot.png"),
plot = p, width = 8, height = 6, dpi = 300)
}
}
## Cluster 0
### KEGG - Cluster 0
## Cluster 1
## Cluster 2
## Cluster 3
## Cluster 4
## Cluster 5
### Reactome - Cluster 5
## Cluster 6
## Cluster 7
### GO_BP - Cluster 7
### KEGG - Cluster 7
### Reactome - Cluster 7
### Hallmark - Cluster 7
## Cluster 8
## Cluster 9
## Cluster 10
## Cluster 11
### GO_BP - Cluster 11
### Reactome - Cluster 11
### Hallmark - Cluster 11
## Cluster 12
### GO_BP - Cluster 12
### Hallmark - Cluster 12
## Cluster 13
### GO_BP - Cluster 13
### KEGG - Cluster 13
### Reactome - Cluster 13
### Hallmark - Cluster 13















Cross-Check:
Annotation vs Enrichment
Manual cross-check: proposed name vs. expected enriched
terms
| Cluster |
Proposed_Name |
Expected_Enriched_Terms |
| 0 |
MHC-II high aberrant state |
antigen processing and presentation, MHC class II,
interferon response (STAT1) |
| 1 |
NK-like cytotoxic |
natural killer cell mediated cytotoxicity, NK receptor
signalling, KIR family activity |
| 2 |
Th2-like |
Th2 cytokine production, IL-13 signalling, chemokine
(CCL17) response |
| 3 |
Naive/CD4 reference |
T cell differentiation, naive T cell, TCR
signalling |
| 4 |
Migratory/Adhesion state |
cell adhesion, extracellular matrix interaction, cell
migration (ENPP2/autotaxin signalling) |
| 5 |
Stem-like |
stem cell population maintenance, chromatin remodeling
(HMGA2), T-cell lineage commitment (RUNX1) |
| 6 |
Th2-like (Activated) |
T cell activation, costimulation (4-1BB/TNFRSF9), Th2
cytokine production |
| 7 |
Cycling (G2/M) |
mitotic nuclear division, kinetochore organization,
G2/M cell cycle checkpoint |
| 8 |
Metabolically reprogrammed |
lipid biosynthesis, metabolic reprogramming, lymphocyte
activation (CD38) |
| 9 |
GZMA-cytotoxic |
T cell mediated cytotoxicity, granzyme-mediated
apoptotic signalling |
| 10 |
Central memory/CD4 reference |
T cell quiescence, central memory differentiation |
| 11 |
Pro-inflammatory |
inflammatory response, S100 alarmin signalling,
TNF-family receptor signalling |
| 12 |
GZMB-high inflammatory |
granzyme-mediated cytotoxicity, inflammatory chemokine
signalling, GM-CSF response |
| 13 |
IFN stimulated |
type I interferon signalling, response to virus,
ISGylation |
Notes
- Enrichment used each cluster’s top 100 marker genes (Supplementary
Table S6).
- Background universe restricted to the union of all top100 genes
across clusters.
- Clusters 3 and 10 are healthy CD4 T-cell reference populations, not
malignant states.
tryCatch() wraps every enrichment call so one failed
database (e.g., no significant KEGG terms for a cluster) won’t stop the
whole loop. ```
LS0tCnRpdGxlOiAiQ2x1c3RlciBFbnJpY2htZW50IEFuYWx5c2lzIC0gVmFsaWRhdGlvbiBvZiBDbHVzdGVyIEFubm90YXRpb25zIgphdXRob3I6ICJOYXNpciBNYWhtb29kIEFiYmFzaSIKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJUIgJWQsICVZJylgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogdHJ1ZQogICAgdGhlbWU6IGpvdXJuYWwKLS0tCgoKIyBQdXJwb3NlCgpUaGlzIHNjcmlwdCBwZXJmb3JtcyAqKmZ1bmN0aW9uYWwgZW5yaWNobWVudCBhbmFseXNpcyAoR08sIEtFR0csIFJlYWN0b21lLCBIYWxsbWFyaykqKgpvbiB0aGUgKip0b3AgMTAwIG1hcmtlciBnZW5lcyBwZXIgY2x1c3RlcioqIChTdXBwbGVtZW50YXJ5IFRhYmxlIFM2KSB0byBpbmRlcGVuZGVudGx5CnZhbGlkYXRlIHRoZSBjbHVzdGVyIG5hbWVzIHlvdSBhc3NpZ25lZCBtYW51YWxseS4gSWYgdGhlIGVucmljaGVkIHBhdGh3YXlzIGFsaWduIHdpdGgKeW91ciBwcm9wb3NlZCBuYW1lIChlLmcuLCAiTkstbGlrZSIgY2x1c3RlciBlbnJpY2hlZCBmb3IgIm5hdHVyYWwga2lsbGVyIGNlbGwgbWVkaWF0ZWQKY3l0b3RveGljaXR5IiksIHRoZSBhbm5vdGF0aW9uIGlzIHN1cHBvcnRlZC4KCgojIGxvYWQgbGlicmFyaWVzCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQojIExvYWQgYmVsb3cgbGlicmFyaWVzCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGNsdXN0ZXJQcm9maWxlcikKbGlicmFyeShvcmcuSHMuZWcuZGIpCmxpYnJhcnkobXNpZ2RicikKbGlicmFyeShSZWFjdG9tZVBBKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZW5yaWNocGxvdCkKbGlicmFyeShvcGVueGxzeCkKbGlicmFyeSh0aWJibGUpCmBgYAoKCiMgTG9hZCBUb3AxMDAgTWFya2VyIEdlbmUgVGFibGUKYGBge3J9CgptYXJrZXJzIDwtIHJlYWRfZXhjZWwoIlN1cHBsZW1lbnRhcnlfVGFibGVfUzYueGxzeCIpCgojIEV4cGVjdGVkIGNvbHVtbnM6IHB2YWwsIGF2Z2xvZzJGQywgcGN0LjEsIHBjdC4yLCBwdmFsYWRqLCBjbHVzdGVyLCBnZW5lCm1hcmtlcnMgPC0gbWFya2VycyAlPiUKICByZW5hbWVfd2l0aCh0b2xvd2VyKSAlPiUKICBtdXRhdGUoY2x1c3RlciA9IGFzLmNoYXJhY3RlcihjbHVzdGVyKSkKCmNsdXN0ZXJzX2xpc3QgPC0gc29ydCh1bmlxdWUobWFya2VycyRjbHVzdGVyKSkKY2F0KCJDbHVzdGVycyBmb3VuZDoiLCBwYXN0ZShjbHVzdGVyc19saXN0LCBjb2xsYXBzZSA9ICIsICIpLCAiXG4iKQoKY2x1c3RlcnNfbGlzdCA8LSBhcy5jaGFyYWN0ZXIoc29ydChhcy5udW1lcmljKHVuaXF1ZShtYXJrZXJzJGNsdXN0ZXIpKSkpCmNhdCgiQ2x1c3RlcnMgZm91bmQ6IiwgcGFzdGUoY2x1c3RlcnNfbGlzdCwgY29sbGFwc2UgPSAiLCAiKSwgIlxuIikKCmBgYAoKIyBNYXAgR2VuZSBTeW1ib2xzIHRvIEVudHJleiBJRHMKYGBge3IgZ2VuZS1pZC1tYXBwaW5nfQpnZW5lX21hcCA8LSBiaXRyKHVuaXF1ZShtYXJrZXJzJGdlbmUpLAogICAgICAgICAgICAgICAgICBmcm9tVHlwZSA9ICJTWU1CT0wiLAogICAgICAgICAgICAgICAgICB0b1R5cGUgPSAiRU5UUkVaSUQiLAogICAgICAgICAgICAgICAgICBPcmdEYiA9IG9yZy5Icy5lZy5kYikKCm1hcmtlcnNfbWFwcGVkIDwtIG1hcmtlcnMgJT4lCiAgbGVmdF9qb2luKGdlbmVfbWFwLCBieSA9IGMoImdlbmUiID0gIlNZTUJPTCIpKSAlPiUKICBmaWx0ZXIoIWlzLm5hKEVOVFJFWklEKSkKCnVubWFwcGVkX2dlbmVzIDwtIHNldGRpZmYodW5pcXVlKG1hcmtlcnMkZ2VuZSksIGdlbmVfbWFwJFNZTUJPTCkKcHJpbnQodW5tYXBwZWRfZ2VuZXMpCgpgYGAKCiMgUHJlcGFyZSBCYWNrZ3JvdW5kIEdlbmUgVW5pdmVyc2UKYGBge3J9CnVuaXZlcnNlX2VudHJleiA8LSB1bmlxdWUobWFya2Vyc19tYXBwZWQkRU5UUkVaSUQpCmBgYAoKIyBEZWZpbmUgRW5yaWNobWVudCBGdW5jdGlvbgpgYGB7cn0KcnVuX2NsdXN0ZXJfZW5yaWNobWVudCA8LSBmdW5jdGlvbihjbHVzdGVyX2lkLCBtYXJrZXJfZGYpIHsKCiAgc3ViIDwtIG1hcmtlcl9kZiAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gY2x1c3Rlcl9pZCkKICBlbnRyZXpfaWRzIDwtIHVuaXF1ZShzdWIkRU5UUkVaSUQpCgogIG1lc3NhZ2UoIkNsdXN0ZXIgIiwgY2x1c3Rlcl9pZCwgIjogIiwgbGVuZ3RoKGVudHJlel9pZHMpLCAiIG1hcHBlZCBnZW5lcyIpCgogIHJlc3VsdHMgPC0gbGlzdCgpCgogIHJlc3VsdHMkR09fQlAgPC0gdHJ5Q2F0Y2goCiAgICBlbnJpY2hHTyhnZW5lID0gZW50cmV6X2lkcywgdW5pdmVyc2UgPSB1bml2ZXJzZV9lbnRyZXosCiAgICAgICAgICAgICBPcmdEYiA9IG9yZy5Icy5lZy5kYiwga2V5VHlwZSA9ICJFTlRSRVpJRCIsIG9udCA9ICJCUCIsCiAgICAgICAgICAgICBwQWRqdXN0TWV0aG9kID0gIkJIIiwgcHZhbHVlQ3V0b2ZmID0gMC4wNSwgcXZhbHVlQ3V0b2ZmID0gMC4yLAogICAgICAgICAgICAgcmVhZGFibGUgPSBUUlVFKSwKICAgIGVycm9yID0gZnVuY3Rpb24oZSkgTlVMTCkKCiAgcmVzdWx0cyRLRUdHIDwtIHRyeUNhdGNoKAogICAgZW5yaWNoS0VHRyhnZW5lID0gZW50cmV6X2lkcywgdW5pdmVyc2UgPSB1bml2ZXJzZV9lbnRyZXosCiAgICAgICAgICAgICAgIG9yZ2FuaXNtID0gImhzYSIsIHBBZGp1c3RNZXRob2QgPSAiQkgiLAogICAgICAgICAgICAgICBwdmFsdWVDdXRvZmYgPSAwLjA1LCBxdmFsdWVDdXRvZmYgPSAwLjIpLAogICAgZXJyb3IgPSBmdW5jdGlvbihlKSBOVUxMKQoKICByZXN1bHRzJFJlYWN0b21lIDwtIHRyeUNhdGNoKAogICAgZW5yaWNoUGF0aHdheShnZW5lID0gZW50cmV6X2lkcywgdW5pdmVyc2UgPSB1bml2ZXJzZV9lbnRyZXosCiAgICAgICAgICAgICAgICAgICBvcmdhbmlzbSA9ICJodW1hbiIsIHBBZGp1c3RNZXRob2QgPSAiQkgiLAogICAgICAgICAgICAgICAgICAgcHZhbHVlQ3V0b2ZmID0gMC4wNSwgcXZhbHVlQ3V0b2ZmID0gMC4yLCByZWFkYWJsZSA9IFRSVUUpLAogICAgZXJyb3IgPSBmdW5jdGlvbihlKSBOVUxMKQoKICBoYWxsbWFya19zZXRzIDwtIG1zaWdkYnIoc3BlY2llcyA9ICJIb21vIHNhcGllbnMiLCBjYXRlZ29yeSA9ICJIIikgJT4lCiAgICBkcGx5cjo6c2VsZWN0KGdzX25hbWUsIGVudHJlel9nZW5lKQoKICByZXN1bHRzJEhhbGxtYXJrIDwtIHRyeUNhdGNoKAogICAgZW5yaWNoZXIoZ2VuZSA9IGVudHJlel9pZHMsIHVuaXZlcnNlID0gdW5pdmVyc2VfZW50cmV6LAogICAgICAgICAgICAgVEVSTTJHRU5FID0gaGFsbG1hcmtfc2V0cywgcEFkanVzdE1ldGhvZCA9ICJCSCIsCiAgICAgICAgICAgICBwdmFsdWVDdXRvZmYgPSAwLjA1LCBxdmFsdWVDdXRvZmYgPSAwLjIpLAogICAgZXJyb3IgPSBmdW5jdGlvbihlKSBOVUxMKQoKICByZXR1cm4ocmVzdWx0cykKfQpgYGAKCgojIFJ1biBFbnJpY2htZW50IGZvciBFdmVyeSBDbHVzdGVyCmBgYHtyfQphbGxfcmVzdWx0cyA8LSBsaXN0KCkKCmZvciAoY2wgaW4gY2x1c3RlcnNfbGlzdCkgewogIGFsbF9yZXN1bHRzW1tjbF1dIDwtIHRyeUNhdGNoKAogICAgcnVuX2NsdXN0ZXJfZW5yaWNobWVudChjbCwgbWFya2Vyc19tYXBwZWQpLAogICAgZXJyb3IgPSBmdW5jdGlvbihlKSB7CiAgICAgIG1lc3NhZ2UoIkNsdXN0ZXIgIiwgY2wsICIgZmFpbGVkOiAiLCBlJG1lc3NhZ2UpCiAgICAgIE5VTEwKICAgIH0KICApCn0KYGBgCgoKIyBFeHBvcnQgRW5yaWNobWVudCBUYWJsZXMgdG8gRXhjZWwKYGBge3J9CndiIDwtIGNyZWF0ZVdvcmtib29rKCkKCmZvciAoY2wgaW4gbmFtZXMoYWxsX3Jlc3VsdHMpKSB7CiAgcmVzIDwtIGFsbF9yZXN1bHRzW1tjbF1dCiAgaWYgKGlzLm51bGwocmVzKSkgbmV4dAoKICBmb3IgKG9udCBpbiBuYW1lcyhyZXMpKSB7CiAgICBvYmogPC0gcmVzW1tvbnRdXQogICAgaWYgKGlzLm51bGwob2JqKSB8fCBucm93KGFzLmRhdGEuZnJhbWUob2JqKSkgPT0gMCkgbmV4dAoKICAgIHNoZWV0X25hbWUgPC0gc3Vic3RyKHBhc3RlMCgiQyIsIGNsLCAiXyIsIG9udCksIDEsIDMxKQogICAgYWRkV29ya3NoZWV0KHdiLCBzaGVldF9uYW1lKQogICAgd3JpdGVEYXRhKHdiLCBzaGVldF9uYW1lLCBhcy5kYXRhLmZyYW1lKG9iaikpCiAgfQp9CgpzYXZlV29ya2Jvb2sod2IsICJDbHVzdGVyX0VucmljaG1lbnRfUmVzdWx0c19Ub3AxMDAueGxzeCIsIG92ZXJ3cml0ZSA9IFRSVUUpCmBgYAoKCgojIFZpc3VhbGl6ZSBUb3AgUGF0aHdheXMgcGVyIENsdXN0ZXIKYGBge3J9CmZvciAoY2wgaW4gbmFtZXMoYWxsX3Jlc3VsdHMpKSB7CiAgcmVzIDwtIGFsbF9yZXN1bHRzW1tjbF1dCiAgaWYgKGlzLm51bGwocmVzKSkgbmV4dAoKICBjYXQoIlxuIyMgQ2x1c3RlciIsIGNsLCAiXG4iKQoKICBmb3IgKG9udCBpbiBjKCJHT19CUCIsICJLRUdHIiwgIlJlYWN0b21lIiwgIkhhbGxtYXJrIikpIHsKICAgIG9iaiA8LSByZXNbW29udF1dCiAgICBpZiAoaXMubnVsbChvYmopIHx8IG5yb3coYXMuZGF0YS5mcmFtZShvYmopKSA9PSAwKSBuZXh0CgogICAgY2F0KCJcbiMjIyIsIG9udCwgIi0gQ2x1c3RlciIsIGNsLCAiXG4iKQogICAgcCA8LSBkb3RwbG90KG9iaiwgc2hvd0NhdGVnb3J5ID0gMTApICsKICAgICAgZ2d0aXRsZShwYXN0ZSgiQ2x1c3RlciIsIGNsLCAiLSIsIG9udCkpICsKICAgICAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDkpKQogICAgcHJpbnQocCkKICAgIGdnc2F2ZShmaWxlbmFtZSA9IHBhc3RlMCgiY2x1c3RlciIsIGNsLCAiXyIsIG9udCwgIl9kb3RwbG90LnBuZyIpLAogICAgICAgICAgIHBsb3QgPSBwLCB3aWR0aCA9IDgsIGhlaWdodCA9IDYsIGRwaSA9IDMwMCkKICB9Cn0KYGBgCgojIENyb3NzLUNoZWNrOiBBbm5vdGF0aW9uIHZzIEVucmljaG1lbnQKYGBge3J9CmFubm90YXRpb25fY2hlY2sgPC0gdGliYmxlOjp0aWJibGUoCiAgQ2x1c3RlciA9IGFzLmNoYXJhY3RlcigwOjEzKSwKICBQcm9wb3NlZF9OYW1lID0gYygKICAgICJNSEMtSUkgaGlnaCBhYmVycmFudCBzdGF0ZSIsICJOSy1saWtlIGN5dG90b3hpYyIsICJUaDItbGlrZSIsCiAgICAiTmFpdmUvQ0Q0IHJlZmVyZW5jZSIsICJNaWdyYXRvcnkvQWRoZXNpb24gc3RhdGUiLCAiU3RlbS1saWtlIiwKICAgICJUaDItbGlrZSAoQWN0aXZhdGVkKSIsICJDeWNsaW5nIChHMi9NKSIsICJNZXRhYm9saWNhbGx5IHJlcHJvZ3JhbW1lZCIsCiAgICAiR1pNQS1jeXRvdG94aWMiLCAiQ2VudHJhbCBtZW1vcnkvQ0Q0IHJlZmVyZW5jZSIsICJQcm8taW5mbGFtbWF0b3J5IiwKICAgICJHWk1CLWhpZ2ggaW5mbGFtbWF0b3J5IiwgIklGTiBzdGltdWxhdGVkIgogICksCiAgRXhwZWN0ZWRfRW5yaWNoZWRfVGVybXMgPSBjKAogICAgImFudGlnZW4gcHJvY2Vzc2luZyBhbmQgcHJlc2VudGF0aW9uLCBNSEMgY2xhc3MgSUksIGludGVyZmVyb24gcmVzcG9uc2UgKFNUQVQxKSIsCiAgICAibmF0dXJhbCBraWxsZXIgY2VsbCBtZWRpYXRlZCBjeXRvdG94aWNpdHksIE5LIHJlY2VwdG9yIHNpZ25hbGxpbmcsIEtJUiBmYW1pbHkgYWN0aXZpdHkiLAogICAgIlRoMiBjeXRva2luZSBwcm9kdWN0aW9uLCBJTC0xMyBzaWduYWxsaW5nLCBjaGVtb2tpbmUgKENDTDE3KSByZXNwb25zZSIsCiAgICAiVCBjZWxsIGRpZmZlcmVudGlhdGlvbiwgbmFpdmUgVCBjZWxsLCBUQ1Igc2lnbmFsbGluZyIsCiAgICAiY2VsbCBhZGhlc2lvbiwgZXh0cmFjZWxsdWxhciBtYXRyaXggaW50ZXJhY3Rpb24sIGNlbGwgbWlncmF0aW9uIChFTlBQMi9hdXRvdGF4aW4gc2lnbmFsbGluZykiLAogICAgInN0ZW0gY2VsbCBwb3B1bGF0aW9uIG1haW50ZW5hbmNlLCBjaHJvbWF0aW4gcmVtb2RlbGluZyAoSE1HQTIpLCBULWNlbGwgbGluZWFnZSBjb21taXRtZW50IChSVU5YMSkiLAogICAgIlQgY2VsbCBhY3RpdmF0aW9uLCBjb3N0aW11bGF0aW9uICg0LTFCQi9UTkZSU0Y5KSwgVGgyIGN5dG9raW5lIHByb2R1Y3Rpb24iLAogICAgIm1pdG90aWMgbnVjbGVhciBkaXZpc2lvbiwga2luZXRvY2hvcmUgb3JnYW5pemF0aW9uLCBHMi9NIGNlbGwgY3ljbGUgY2hlY2twb2ludCIsCiAgICAibGlwaWQgYmlvc3ludGhlc2lzLCBtZXRhYm9saWMgcmVwcm9ncmFtbWluZywgbHltcGhvY3l0ZSBhY3RpdmF0aW9uIChDRDM4KSIsCiAgICAiVCBjZWxsIG1lZGlhdGVkIGN5dG90b3hpY2l0eSwgZ3Jhbnp5bWUtbWVkaWF0ZWQgYXBvcHRvdGljIHNpZ25hbGxpbmciLAogICAgIlQgY2VsbCBxdWllc2NlbmNlLCBjZW50cmFsIG1lbW9yeSBkaWZmZXJlbnRpYXRpb24iLAogICAgImluZmxhbW1hdG9yeSByZXNwb25zZSwgUzEwMCBhbGFybWluIHNpZ25hbGxpbmcsIFRORi1mYW1pbHkgcmVjZXB0b3Igc2lnbmFsbGluZyIsCiAgICAiZ3Jhbnp5bWUtbWVkaWF0ZWQgY3l0b3RveGljaXR5LCBpbmZsYW1tYXRvcnkgY2hlbW9raW5lIHNpZ25hbGxpbmcsIEdNLUNTRiByZXNwb25zZSIsCiAgICAidHlwZSBJIGludGVyZmVyb24gc2lnbmFsbGluZywgcmVzcG9uc2UgdG8gdmlydXMsIElTR3lsYXRpb24iCiAgKQopCgprbml0cjo6a2FibGUoYW5ub3RhdGlvbl9jaGVjaywgY2FwdGlvbiA9ICJNYW51YWwgY3Jvc3MtY2hlY2s6IHByb3Bvc2VkIG5hbWUgdnMuIGV4cGVjdGVkIGVucmljaGVkIHRlcm1zIikKCmBgYAoKIyBOb3RlcwoKLSBFbnJpY2htZW50IHVzZWQgZWFjaCBjbHVzdGVyJ3MgdG9wIDEwMCBtYXJrZXIgZ2VuZXMgKFN1cHBsZW1lbnRhcnkgVGFibGUgUzYpLgotIEJhY2tncm91bmQgdW5pdmVyc2UgcmVzdHJpY3RlZCB0byB0aGUgdW5pb24gb2YgYWxsIHRvcDEwMCBnZW5lcyBhY3Jvc3MgY2x1c3RlcnMuCi0gQ2x1c3RlcnMgMyBhbmQgMTAgYXJlIGhlYWx0aHkgQ0Q0IFQtY2VsbCByZWZlcmVuY2UgcG9wdWxhdGlvbnMsIG5vdCBtYWxpZ25hbnQgc3RhdGVzLgotIGB0cnlDYXRjaCgpYCB3cmFwcyBldmVyeSBlbnJpY2htZW50IGNhbGwgc28gb25lIGZhaWxlZCBkYXRhYmFzZSAoZS5nLiwgbm8gc2lnbmlmaWNhbnQKICBLRUdHIHRlcm1zIGZvciBhIGNsdXN0ZXIpIHdvbid0IHN0b3AgdGhlIHdob2xlIGxvb3AuCmBgYAoKCg==