Note that the directories used to store data are likely different on your computer, and such references will need to be changed before using any such code.
library(knitr)
library(kableExtra)
html_df <- function(text, cols=NULL, col1=FALSE, full=F) {
if(!length(cols)) {
cols=colnames(text)
}
if(!col1) {
kable(text,"html", col.names = cols, align = c("l",rep('c',length(cols)-1))) %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width=full)
} else {
kable(text,"html", col.names = cols, align = c("l",rep('c',length(cols)-1))) %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width=full) %>%
column_spec(1,bold=T)
}
}
library(tidyverse)
readRDS("../../Data/corp_summary.rds")
corp_FOG <- readRDS("../../Data/corp_readability_FOG.rds")
corp_FOG %>%
head() %>%
html_df()
document |
FOG |
0000002178-14-000010.txt |
21.03917 |
0000003499-14-000005.txt |
20.36549 |
0000003570-14-000031.txt |
22.24386 |
0000004187-14-000020.txt |
18.75720 |
0000004457-14-000036.txt |
19.22683 |
0000004904-14-000019.txt |
20.51594 |
summary(corp_FOG$FOG)
Min. 1st Qu. Median Mean 3rd Qu. Max.
14.33 20.32 21.01 21.05 21.75 35.37
ggplot(corp_FOG, aes(x=FOG)) + geom_density()
df_SIC <- read.csv('../../Data/Filings2014.csv') %>%
select(accession, regsic) %>%
mutate(accession=paste0(accession, ".txt")) %>%
rename(document=accession) %>%
mutate(industry = case_when(
regsic >=0100 & regsic <= 0999 ~ "Agriculture",
regsic >=1000 & regsic <= 1499 ~ "Mining",
regsic >=1500 & regsic <= 1799 ~ "Construction",
regsic >=2000 & regsic <= 3999 ~ "Manufacturing",
regsic >=4000 & regsic <= 4999 ~ "Utilities",
regsic >=5000 & regsic <= 5199 ~ "Wholesale Trade",
regsic >=5200 & regsic <= 5999 ~ "Retail Trade",
regsic >=6000 & regsic <= 6799 ~ "Finance",
regsic >=7000 & regsic <= 8999 ~ "Services",
regsic >=9100 & regsic <= 9999 ~ "Public Admin" )) %>%
group_by(document) %>%
slice(1) %>%
ungroup()
corp_FOG <- corp_FOG %>% left_join(df_SIC)
Joining, by = "document"
corp_FOG %>%
head() %>%
html_df()
document |
FOG |
regsic |
industry |
0000002178-14-000010.txt |
21.03917 |
5172 |
Wholesale Trade |
0000003499-14-000005.txt |
20.36549 |
6798 |
Finance |
0000003570-14-000031.txt |
22.24386 |
4924 |
Utilities |
0000004187-14-000020.txt |
18.75720 |
4950 |
Utilities |
0000004457-14-000036.txt |
19.22683 |
7510 |
Services |
0000004904-14-000019.txt |
20.51594 |
4911 |
Utilities |
ggplot(corp_FOG[!is.na(corp_FOG$industry),], aes(x=factor(industry), y=FOG)) +
geom_violin(draw_quantiles = c(0.25, 0.5, 0.75)) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
ggplot(corp_FOG[!is.na(corp_FOG$industry),], aes(x=FOG)) +
geom_density() + facet_wrap(~industry)
library(lattice)
densityplot(~FOG | industry,
data=corp_FOG,
plot.points=F,
main="Fog index distibution by industry (SIC)",
xlab="Fog index",
layout=c(3,3))
library(DT)
readRDS('../../Data/corp_kwic.rds') %>%
mutate(text=paste(pre,keyword,post)) %>%
left_join(select(df_SIC, document, industry), by = c("docname" = "document")) %>%
select(docname, industry, text) %>%
datatable(options = list(pageLength = 5), rownames=F)
gw_count <- readRDS('../../Data/corp_kwic.rds') %>%
left_join(select(df_SIC, document, industry), by = c("docname" = "document")) %>%
group_by(industry) %>%
mutate(docs_gw = n()) %>%
slice(1) %>%
ungroup() %>%
select(industry, docs_gw)
corp_FOG %>% group_by(industry) %>%
mutate(docs = n()) %>%
slice(1) %>%
ungroup() %>%
left_join(gw_count) %>%
mutate(docs_gw = ifelse(is.na(docs_gw),0,docs_gw)) %>%
mutate(`Global warming` = docs_gw / docs,
`Does not mention` = (docs - docs_gw) / docs) %>%
gather(mention, percent, `Global warming`, `Does not mention`) %>%
ggplot() +
geom_col(aes(x = industry, y = percent, fill=mention)) +
ylab("Percent of annual reports") +
xlab("Industry (SIC code)") +
ggtitle("Industries discussing global warming in 2014") +
scale_y_continuous(labels = scales::percent) +
scale_fill_manual(values=c("#CCCCCCAA", "#88CC88")) +
theme(axis.text.x = element_text(angle=45, hjust=1))
readRDS('../../Data/corp_dfm_feat.rds')
$`Wholesale Trade`
compani oper million financi product
30371 20340 18085 17552 17300
$Finance
compani loan financi decemb million
438185 392164 299978 286791 274376
$Utilities
oper million compani financi includ
112038 107322 101971 79010 76604
$Services
compani oper million financi servic
222276 145506 138397 131881 120817
$Manufacturing
compani product million oper financi
434805 368900 275829 240181 231687
$Mining
compani oper gas oil decemb
97798 92076 74150 65532 60475
$Construction
compani million oper financi decemb
15479 14885 12431 10899 10149
$`Retail Trade`
compani oper million financi includ
62780 43637 41428 35824 32478
$Agriculture
compani oper financi year product
4200 3016 2949 2756 2750
$<NA>
numeric(0)
readRDS('../../Data/corp_tfidf_feat.rds')
$`Wholesale Trade`
graybar grainger oil million bottl
0.3140485 0.2899255 0.2187512 0.2184815 0.2122642
$Finance
ab mortgag depositor loan reit
9.863862 7.414096 6.192815 5.109854 5.046502
$Utilities
gas fcc pipelin energi aircraft
2.005220 1.484092 1.227766 1.164767 1.020255
$Services
game client casino million softwar
2.394468 1.760647 1.635549 1.496073 1.404740
$Manufacturing
clinic fda trial drug patient
7.057913 5.487707 3.949705 3.935010 3.799611
$Mining
gas oil drill well explor
6.550322 6.308205 4.935983 2.412994 2.035304
$Construction
homebuild home iveda layn alp
0.5143533 0.3827212 0.3557692 0.2360279 0.2303252
$`Retail Trade`
restaur store merchandis franchise franchis
2.6829714 1.5131383 1.3382872 0.8695339 0.5706876
$Agriculture
yew uspb mushroom prc nbp
0.2894138 0.2140732 0.2066838 0.2031097 0.1862960
$<NA>
numeric(0)
readRDS('../../Data/corp_tfidf_bank.rds')
ab mortgag depositor loan reit trust
9.863862 7.414096 6.192815 5.109854 5.046502 4.394811
reinsur truste estat tenant instruct partnership
3.809024 3.607591 3.188824 3.100092 2.970419 2.697215
real million pool fdic residenti bancorp
2.506670 2.482285 2.287610 2.238533 2.149133 2.074819
obligor rmbs
2.055811 2.055453
out <- readRDS('../../Data/corp_out_stm.rds')
#out <- readRDS('../../Data/corp_out_lda.rds')
#out <- readRDS('../../Data/corp_out_topicmodels.rds')
out$documents[[1]][,386:390]
[,1] [,2] [,3] [,4] [,5]
[1,] 14590 14593 14598 14614 14625
[2,] 1 1 38 3 1
out$vocab[c(out$documents[[1]][,386:390][1,])]
[1] "earlier" "earliest" "earn" "earthen" "eas"
library(stm)
topics <- readRDS('../../Data/corp_stm_topics.rds')
labelTopics(topics)
Topic 1 Top Words:
Highest Prob: properti, oper, million, decemb, compani, interest, leas
FREX: ffo, efih, efh, tenant, hotel, casino, guc
Lift: aliansc, baluma, change-of-ownership, crj700s, directly-reimburs, escena, hhmk
Score: reit, hotel, game, ffo, tenant, casino, efih
Topic 2 Top Words:
Highest Prob: compani, stock, share, common, financi, director, offic
FREX: prc, asher, shaanxi, wfoe, eit, hubei, yew
Lift: aagc, abramowitz, accello, akash, alix, alkam, almati
Score: prc, compani, penni, stock, share, rmb, director
Topic 3 Top Words:
Highest Prob: product, develop, compani, clinic, market, includ, approv
FREX: dose, preclin, nda, vaccin, oncolog, anda, fdas
Lift: 1064nm, 12-001hr, 25-gaug, 2ml, 3shape, 503b, 600mg
Score: clinic, fda, preclin, dose, patent, nda, product
Topic 4 Top Words:
Highest Prob: invest, fund, manag, market, asset, trade, interest
FREX: uscf, nfa, unl, uga, mlai, bno, dno
Lift: a-1t, aion, apx-endex, bessey, bolduc, broyhil, buran
Score: uscf, fhlbank, rmbs, uga, invest, mlai, ung
Topic 5 Top Words:
Highest Prob: servic, report, file, program, provid, network, requir
FREX: echostar, fcc, fccs, telesat, ilec, starz, retransmiss
Lift: 1100-n, 2-usb, 2011-c1, 2012-ccre4, 2013-c9, aastra, accreditor
Score: entergi, fcc, echostar, wireless, broadcast, video, cabl
Topic 6 Top Words:
Highest Prob: loan, bank, compani, financi, decemb, million, interest
FREX: nonaccru, oreo, tdrs, bancorp, fdic, charge-off, alll
Lift: 100bp, 4-famili, acnb, acquired-impair, amerihom, ameriserv, annb
Score: fhlb, loan, bank, mortgag, risk-weight, tdrs, nonaccru
Topic 7 Top Words:
Highest Prob: compani, million, oper, financi, revenu, result, includ
FREX: vmware, imax, franchise, merchandis, affinion, exhibitor, softwar
Lift: 4.75x, 9corpor, accessdm, acvc, adtech, adxpos, aecsoft
Score: million, product, restaur, custom, game, video, merchandis
Topic 8 Top Words:
Highest Prob: compani, insur, million, loss, financi, invest, rate
FREX: policyhold, reinsur, lae, dac, annuiti, ambac, cede
Lift: agcpl, ahccc, amcareco, argoglob, asil, connecticut-domicil, feinsod
Score: reinsur, policyhold, lae, onebeacon, insur, million, dac
Topic 9 Top Words:
Highest Prob: million, compani, oper, financi, cost, product, decemb
FREX: wafer, alcoa, pepco, dpl, nstar, usec, kcsm
Lift: 1.5mw, 11-hour, 1ynanomet, 3dfx, 3ms, 3pd, 40g
Score: million, product, ameren, cleco, manufactur, wafer, postretir
Topic 10 Top Words:
Highest Prob: gas, oper, oil, natur, million, cost, decemb
FREX: ngl, ngls, oneok, mgp, permian, qep, wes
Lift: 12asset, 1businesscommerci, 94-mile, aivh, amargo, amopp, angell
Score: gas, drill, oil, ngl, crude, unithold, ngls
out$meta$industry <- factor(out$meta$industry)
doc_topics = data.frame(document=names(out$documents),
industry=out$meta$industry,
topic=1,
weight=topics$theta[,1])
for (i in 2:10) {
temp = data.frame(document=names(out$documents),
industry=out$meta$industry,
topic=i,
weight=topics$theta[,i])
doc_topics = rbind(doc_topics, temp)
}
# Proportional topics (%)
doc_topics <- doc_topics %>%
group_by(document) %>%
mutate(topic_prop = weight / sum(weight)) %>%
ungroup()
# Manually label topics
topic_labels = data.frame(topic = 1:10,
topic_name = c('Real Estate', 'Management', 'Product',
'Investment', 'Services', 'Financing',
'Service2', 'Insurance', 'Industrial',
'Utility'))
doc_topics <- doc_topics %>% left_join(topic_labels)
doc_topics %>% filter(document=='0001104659-14-015152.txt')
doc_topics %>%
filter(document=='0001104659-14-015152.txt' |
document=='0000019617-14-000289.txt') %>%
mutate(Company=ifelse(document=='0001104659-14-015152.txt', 'Citi','JPM')) %>%
ggplot(aes(x=factor(topic_name), y=topic_prop, fill=factor(topic_name))) +
geom_col() + facet_wrap(~Company) +
theme(axis.text.x=element_blank(),axis.ticks.x = element_blank())
doc_topics %>%
group_by(industry, topic) %>%
mutate(topic_prop = mean(topic_prop)) %>%
slice(1) %>%
ungroup() %>%
ggplot(aes(x=factor(topic_name), y=topic_prop, fill=factor(topic_name))) +
geom_col() + facet_wrap(~industry) +
theme(axis.text.x=element_blank(),axis.ticks.x = element_blank())
library(tidyr)
wide_topics <- spread(doc_topics[,c(1,2,5,6)], topic_name, topic_prop)
mat <- wide_topics[,3:12]
mat[,1:6] %>% head() %>% html_df()
Financing |
Industrial |
Insurance |
Investment |
Management |
Product |
0.0105862 |
0.1578543 |
0.1088631 |
0.0004632 |
0.1161191 |
0.0002101 |
0.0467173 |
0.0059438 |
0.0235389 |
0.0005284 |
0.0801189 |
0.0001432 |
0.0069105 |
0.0351987 |
0.0003661 |
0.0201215 |
0.0023672 |
0.0000186 |
0.0870371 |
0.8271759 |
0.0003259 |
0.0003334 |
0.0206444 |
0.0000485 |
0.0036086 |
0.2680866 |
0.2677154 |
0.0008808 |
0.0026448 |
0.0000949 |
0.0000976 |
0.5299432 |
0.0001593 |
0.0007533 |
0.0009532 |
0.0000318 |
set.seed(6845868)
clusters <- kmeans(mat, 9)
clusters$cluster %>% head()
[1] 7 3 2 9 4 7
cbind(as.data.frame(clusters$center), data.frame(kmean=1:9)) %>%
gather("Topics","weights",-kmean) %>%
ggplot(aes(x=factor(Topics), y=weights, fill=factor(Topics))) +
geom_col() +
facet_wrap(~kmean) +
theme(axis.text.x=element_blank(),axis.ticks.x = element_blank())
library(cluster) # Uses PCA (principle component analysis)
clusplot(mat, clusters$cluster, color=TRUE, shade=TRUE,
labels=4)
library(Rtsne)
dups <- which(duplicated(mat))
wide_nodup <- wide_topics[-dups,]
wide_nodup$kmean <- clusters$cluster[-dups]
tsne_data <- readRDS('../../Data/corp_tsne.rds')
wide_nodup <- wide_nodup %>%
mutate(tsne1 = tsne_data$Y[, 1], tsne2 = tsne_data$Y[, 2])
ggplot(wide_nodup, aes(x = tsne1, y = tsne2, colour = industry)) +
geom_point(alpha = 0.3) + theme_bw()
ggplot(wide_nodup, aes(x = tsne1, y = tsne2, colour = factor(kmean))) +
geom_point(alpha = 0.3) + theme_bw()
ggplot(wide_nodup, aes(x=kmean)) + geom_bar() + facet_wrap(~factor(industry))
ggplot(wide_nodup, aes(x=tsne1, y=tsne2, color=factor(kmean))) + geom_point() +
facet_wrap(~factor(industry))
ggplot(wide_nodup, aes(x=tsne1, y=tsne2, color=factor(industry))) + geom_point() +
facet_wrap(~factor(kmean))
#wide_topics$dist <- sqrt(rowSums(mat - fitted(clusters))^2)
wide_topics$dist <- sqrt(rowSums(abs(mat - fitted(clusters))))
wide_topics[,c(1,2,3,5,13)] %>% arrange(desc(dist)) %>% slice(1:5) %>% html_df()
document |
industry |
Financing |
Insurance |
dist |
0001171500-14-000007.txt |
Finance |
0.0003177 |
0.9972499 |
1.341244 |
0001193125-14-077320.txt |
Finance |
0.0001725 |
0.9794704 |
1.337283 |
0001095073-14-000008.txt |
Finance |
0.0002339 |
0.9916079 |
1.337031 |
0000356130-14-000052.txt |
Finance |
0.0002991 |
0.9845263 |
1.334780 |
0000021175-14-000021.txt |
Finance |
0.0036298 |
0.9875105 |
1.333963 |
wide_topics[,c(2,5,8,9,10,11,13)] %>% filter(industry!="Finance") %>% arrange(desc(dist)) %>% mutate(id=1:n())%>% select(id,everything()) %>% slice(1:2,7,6,8) %>% html_df()
id |
industry |
Insurance |
Product |
Real Estate |
Service2 |
Services |
dist |
1 |
Services |
0.5161854 |
0.2641739 |
0.1112599 |
0.0136804 |
0.0764400 |
1.252719 |
2 |
Services |
0.4154754 |
0.2778976 |
0.1109746 |
0.1116213 |
0.0814478 |
1.185233 |
7 |
Services |
0.5878499 |
0.1535348 |
0.0006138 |
0.1822722 |
0.0231219 |
1.123097 |
6 |
Services |
0.3184271 |
0.2718329 |
0.2489164 |
0.0520256 |
0.1037725 |
1.128411 |
8 |
Retail Trade |
0.3603968 |
0.1876330 |
0.0854220 |
0.0934442 |
0.0894848 |
1.119306 |
wide_topics[,c(2,6,9,10,11,12,13)] %>% filter(industry!="Finance") %>% arrange(desc(dist)) %>% mutate(id=1:n())%>% select(id,everything()) %>% slice(5) %>% html_df()
id |
industry |
Investment |
Real Estate |
Service2 |
Services |
Utility |
dist |
5 |
Utilities |
0.1768971 |
0.1143861 |
0.2481198 |
0.4017117 |
0.053171 |
1.144542 |
train <- wide_topics[!is.na(wide_topics$industry),]
label <- wide_topics[is.na(wide_topics$industry),]
library(caret)
tout <- readRDS('../../Data/corp_knn.rds')
tout
k-Nearest Neighbors
5804 samples
10 predictor
9 classes: 'Agriculture', 'Construction', 'Finance', 'Manufacturing', 'Mining', 'Retail Trade', 'Services', 'Utilities', 'Wholesale Trade'
No pre-processing
Resampling: Cross-Validated (10 fold)
Summary of sample sizes: 5226, 5222, 5223, 5224, 5223, 5226, ...
Resampling results across tuning parameters:
k Accuracy Kappa
1 0.6922669 0.6037548
2 0.6883222 0.5984635
3 0.7219205 0.6397779
4 0.7305403 0.6495724
5 0.7374387 0.6581581
6 0.7384702 0.6592123
7 0.7460449 0.6686815
8 0.7505306 0.6741651
9 0.7515604 0.6753179
10 0.7512102 0.6749574
11 0.7489795 0.6718804
12 0.7491537 0.6719035
13 0.7525919 0.6764543
14 0.7508766 0.6741010
15 0.7529349 0.6766597
16 0.7506983 0.6737148
17 0.7500110 0.6727821
18 0.7488041 0.6711643
19 0.7494908 0.6718556
20 0.7496676 0.6719961
Accuracy was used to select the optimal model using the largest value.
The final value used for the model was k = 15.
ggplot(tout$results, aes(x=k, y=Accuracy)) +
geom_line() +
geom_ribbon(aes(ymin=Accuracy - AccuracySD*1.96,
ymax=Accuracy + AccuracySD*1.96), alpha=0.2) +
geom_vline(xintercept=15, color="blue") +
xlab("k, optimal = 15")
label$industry_pred <- predict(tout,
label)
label[,c("document",
"industry_pred")] %>%
head %>% html_df
document |
industry_pred |
0000817473-14-000010.txt |
Finance |
0000820027-14-000025.txt |
Finance |
0000837465-14-000002.txt |
Manufacturing |
0000837919-14-000002.txt |
Finance |
0000891092-14-000570.txt |
Finance |
0000891092-14-002078.txt |
Finance |
ts_wt <- wide_nodup %>% left_join(label[,c("document","industry_pred")])
ts_wt <- ts_wt %>%
mutate(tsne1 = tsne_data$Y[, 1], tsne2 = tsne_data$Y[, 2])
# Force consistent factor values
inds <- unique(ts_wt$industry)
ts_wt$industry <- factor(ts_wt$industry, inds)
ts_wt$industry_pred <- factor(ts_wt$industry_pred, inds)
# Replicate default ggplot colors
ggplotColours <- function(n = 6, h = c(0, 360) + 15){
if ((diff(h) %% 360) < 1) h[2] <- h[2] - 360/n
hcl(h = (seq(h[1], h[2], length = n)), c = 100, l = 65)
}
ggplot() +
scale_shape_identity() + # Allow for more plot point options
geom_point(data=ts_wt[!is.na(ts_wt$industry),],
aes(x=tsne1, y=tsne2, color=industry, shape=1), size=1) +
geom_point(data=ts_wt[!is.na(ts_wt$industry_pred),], aes(x=tsne1, y=tsne2,
fill=industry_pred, shape=23, stroke=0.5), size=2) +
guides(fill = "none") +
scale_color_manual(values=ggplotColours(n = 9), labels=inds, drop=FALSE) +
scale_fill_manual(values=ggplotColours(n = 9), labels=inds, drop=FALSE)
clusters <- kmeans(mat, 20)
clusters$cluster %>% head()
[1] 10 3 17 1 2 10
wide_nodup$kmean2 <- clusters$cluster[-dups]
ggplot(wide_nodup, aes(x = tsne1, y = tsne2, colour = factor(kmean2))) +
geom_point(alpha = 0.3) + theme_bw()
ggplot(wide_nodup, aes(x=kmean2)) + geom_bar() + facet_wrap(~factor(industry))
ggplot(wide_nodup, aes(x=tsne1, y=tsne2, color=factor(kmean2))) + geom_point() +
facet_wrap(~factor(industry))
LS0tDQp0aXRsZTogIkNvZGUgZm9yIFNlc3Npb24gOCINCmF1dGhvcjogIkRyLiBSaWNoYXJkIE0uIENyb3dsZXkiDQpkYXRlOiAiIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rDQotLS0NCg0KTm90ZSB0aGF0IHRoZSBkaXJlY3RvcmllcyB1c2VkIHRvIHN0b3JlIGRhdGEgYXJlIGxpa2VseSBkaWZmZXJlbnQgb24geW91ciBjb21wdXRlciwgYW5kIHN1Y2ggcmVmZXJlbmNlcyB3aWxsIG5lZWQgdG8gYmUgY2hhbmdlZCBiZWZvcmUgdXNpbmcgYW55IHN1Y2ggY29kZS4NCg0KYGBge3IgaGVscGVycywgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpodG1sX2RmIDwtIGZ1bmN0aW9uKHRleHQsIGNvbHM9TlVMTCwgY29sMT1GQUxTRSwgZnVsbD1GKSB7DQogIGlmKCFsZW5ndGgoY29scykpIHsNCiAgICBjb2xzPWNvbG5hbWVzKHRleHQpDQogIH0NCiAgaWYoIWNvbDEpIHsNCiAgICBrYWJsZSh0ZXh0LCJodG1sIiwgY29sLm5hbWVzID0gY29scywgYWxpZ24gPSBjKCJsIixyZXAoJ2MnLGxlbmd0aChjb2xzKS0xKSkpICU+JQ0KICAgICAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCJob3ZlciIpLCBmdWxsX3dpZHRoPWZ1bGwpDQogIH0gZWxzZSB7DQogICAga2FibGUodGV4dCwiaHRtbCIsIGNvbC5uYW1lcyA9IGNvbHMsIGFsaWduID0gYygibCIscmVwKCdjJyxsZW5ndGgoY29scyktMSkpKSAlPiUNCiAgICAgIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwiaG92ZXIiKSwgZnVsbF93aWR0aD1mdWxsKSAlPiUNCiAgICAgIGNvbHVtbl9zcGVjKDEsYm9sZD1UKQ0KICB9DQp9DQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQpgYGB7cn0NCnJlYWRSRFMoIi4uLy4uL0RhdGEvY29ycF9zdW1tYXJ5LnJkcyIpDQpgYGANCg0KYGBge3J9DQpjb3JwX0ZPRyA8LSByZWFkUkRTKCIuLi8uLi9EYXRhL2NvcnBfcmVhZGFiaWxpdHlfRk9HLnJkcyIpDQpjb3JwX0ZPRyAlPiUNCiAgaGVhZCgpICU+JQ0KICBodG1sX2RmKCkNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD00fQ0Kc3VtbWFyeShjb3JwX0ZPRyRGT0cpDQpnZ3Bsb3QoY29ycF9GT0csIGFlcyh4PUZPRykpICsgZ2VvbV9kZW5zaXR5KCkNCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GfQ0KZGZfU0lDIDwtIHJlYWQuY3N2KCcuLi8uLi9EYXRhL0ZpbGluZ3MyMDE0LmNzdicpICU+JQ0KICBzZWxlY3QoYWNjZXNzaW9uLCByZWdzaWMpICU+JQ0KICBtdXRhdGUoYWNjZXNzaW9uPXBhc3RlMChhY2Nlc3Npb24sICIudHh0IikpICU+JQ0KICByZW5hbWUoZG9jdW1lbnQ9YWNjZXNzaW9uKSAlPiUNCiAgbXV0YXRlKGluZHVzdHJ5ID0gY2FzZV93aGVuKA0KICAgIHJlZ3NpYyA+PTAxMDAgJiByZWdzaWMgPD0gMDk5OSB+ICJBZ3JpY3VsdHVyZSIsDQogICAgcmVnc2ljID49MTAwMCAmIHJlZ3NpYyA8PSAxNDk5IH4gIk1pbmluZyIsDQogICAgcmVnc2ljID49MTUwMCAmIHJlZ3NpYyA8PSAxNzk5IH4gIkNvbnN0cnVjdGlvbiIsDQogICAgcmVnc2ljID49MjAwMCAmIHJlZ3NpYyA8PSAzOTk5IH4gIk1hbnVmYWN0dXJpbmciLA0KICAgIHJlZ3NpYyA+PTQwMDAgJiByZWdzaWMgPD0gNDk5OSB+ICJVdGlsaXRpZXMiLA0KICAgIHJlZ3NpYyA+PTUwMDAgJiByZWdzaWMgPD0gNTE5OSB+ICJXaG9sZXNhbGUgVHJhZGUiLA0KICAgIHJlZ3NpYyA+PTUyMDAgJiByZWdzaWMgPD0gNTk5OSB+ICJSZXRhaWwgVHJhZGUiLA0KICAgIHJlZ3NpYyA+PTYwMDAgJiByZWdzaWMgPD0gNjc5OSB+ICJGaW5hbmNlIiwNCiAgICByZWdzaWMgPj03MDAwICYgcmVnc2ljIDw9IDg5OTkgfiAiU2VydmljZXMiLA0KICAgIHJlZ3NpYyA+PTkxMDAgJiByZWdzaWMgPD0gOTk5OSB+ICJQdWJsaWMgQWRtaW4iICkpICU+JQ0KICBncm91cF9ieShkb2N1bWVudCkgJT4lDQogIHNsaWNlKDEpICU+JQ0KICB1bmdyb3VwKCkNCmNvcnBfRk9HIDwtIGNvcnBfRk9HICU+JSBsZWZ0X2pvaW4oZGZfU0lDKQ0KYGBgDQoNCmBgYHtyfQ0KY29ycF9GT0cgJT4lDQogIGhlYWQoKSAlPiUNCiAgaHRtbF9kZigpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NSwgd2FybmluZz1GLCBtZXNzYWdlPUZ9DQpnZ3Bsb3QoY29ycF9GT0dbIWlzLm5hKGNvcnBfRk9HJGluZHVzdHJ5KSxdLCBhZXMoeD1mYWN0b3IoaW5kdXN0cnkpLCB5PUZPRykpICsNCiAgZ2VvbV92aW9saW4oZHJhd19xdWFudGlsZXMgPSBjKDAuMjUsIDAuNSwgMC43NSkpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD00fQ0KZ2dwbG90KGNvcnBfRk9HWyFpcy5uYShjb3JwX0ZPRyRpbmR1c3RyeSksXSwgYWVzKHg9Rk9HKSkgKw0KICBnZW9tX2RlbnNpdHkoKSArIGZhY2V0X3dyYXAofmluZHVzdHJ5KQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTR9DQpsaWJyYXJ5KGxhdHRpY2UpDQpkZW5zaXR5cGxvdCh+Rk9HIHwgaW5kdXN0cnksDQogICAgICAgICAgICBkYXRhPWNvcnBfRk9HLA0KICAgICAgICAgICAgcGxvdC5wb2ludHM9RiwNCiAgICAgICAgICAgIG1haW49IkZvZyBpbmRleCBkaXN0aWJ1dGlvbiBieSBpbmR1c3RyeSAoU0lDKSIsDQogICAgICAgICAgICB4bGFiPSJGb2cgaW5kZXgiLA0KICAgICAgICAgICAgbGF5b3V0PWMoMywzKSkNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9DQpsaWJyYXJ5KERUKQ0KcmVhZFJEUygnLi4vLi4vRGF0YS9jb3JwX2t3aWMucmRzJykgJT4lDQogIG11dGF0ZSh0ZXh0PXBhc3RlKHByZSxrZXl3b3JkLHBvc3QpKSAlPiUNCiAgbGVmdF9qb2luKHNlbGVjdChkZl9TSUMsIGRvY3VtZW50LCBpbmR1c3RyeSksIGJ5ID0gYygiZG9jbmFtZSIgPSAiZG9jdW1lbnQiKSkgJT4lDQogIHNlbGVjdChkb2NuYW1lLCBpbmR1c3RyeSwgdGV4dCkgJT4lDQogIGRhdGF0YWJsZShvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gNSksIHJvd25hbWVzPUYpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0KZ3dfY291bnQgPC0gcmVhZFJEUygnLi4vLi4vRGF0YS9jb3JwX2t3aWMucmRzJykgJT4lDQogIGxlZnRfam9pbihzZWxlY3QoZGZfU0lDLCBkb2N1bWVudCwgaW5kdXN0cnkpLCBieSA9IGMoImRvY25hbWUiID0gImRvY3VtZW50IikpICU+JQ0KICBncm91cF9ieShpbmR1c3RyeSkgJT4lDQogIG11dGF0ZShkb2NzX2d3ID0gbigpKSAlPiUNCiAgc2xpY2UoMSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgc2VsZWN0KGluZHVzdHJ5LCBkb2NzX2d3KQ0KDQpjb3JwX0ZPRyAlPiUgZ3JvdXBfYnkoaW5kdXN0cnkpICU+JQ0KICBtdXRhdGUoZG9jcyA9IG4oKSkgJT4lDQogIHNsaWNlKDEpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIGxlZnRfam9pbihnd19jb3VudCkgJT4lDQogIG11dGF0ZShkb2NzX2d3ID0gaWZlbHNlKGlzLm5hKGRvY3NfZ3cpLDAsZG9jc19ndykpICU+JQ0KICBtdXRhdGUoYEdsb2JhbCB3YXJtaW5nYCA9IGRvY3NfZ3cgLyBkb2NzLA0KICAgICAgICAgYERvZXMgbm90IG1lbnRpb25gID0gKGRvY3MgLSBkb2NzX2d3KSAvIGRvY3MpICU+JQ0KICBnYXRoZXIobWVudGlvbiwgcGVyY2VudCwgYEdsb2JhbCB3YXJtaW5nYCwgYERvZXMgbm90IG1lbnRpb25gKSAlPiUNCiAgZ2dwbG90KCkgKyANCiAgZ2VvbV9jb2woYWVzKHggPSBpbmR1c3RyeSwgeSA9IHBlcmNlbnQsIGZpbGw9bWVudGlvbikpICsgDQogIHlsYWIoIlBlcmNlbnQgb2YgYW5udWFsIHJlcG9ydHMiKSArDQogIHhsYWIoIkluZHVzdHJ5IChTSUMgY29kZSkiKSArDQogIGdndGl0bGUoIkluZHVzdHJpZXMgZGlzY3Vzc2luZyBnbG9iYWwgd2FybWluZyBpbiAyMDE0IikgKyANCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKyANCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiNDQ0NDQ0NBQSIsICIjODhDQzg4IikpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIGhqdXN0PTEpKQ0KYGBgDQoNCmBgYHtyfQ0KcmVhZFJEUygnLi4vLi4vRGF0YS9jb3JwX2RmbV9mZWF0LnJkcycpDQpgYGANCg0KYGBge3J9DQpyZWFkUkRTKCcuLi8uLi9EYXRhL2NvcnBfdGZpZGZfZmVhdC5yZHMnKQ0KYGBgDQoNCmBgYHtyfQ0KcmVhZFJEUygnLi4vLi4vRGF0YS9jb3JwX3RmaWRmX2JhbmsucmRzJykNCmBgYA0KDQpgYGB7cn0NCm91dCA8LSByZWFkUkRTKCcuLi8uLi9EYXRhL2NvcnBfb3V0X3N0bS5yZHMnKQ0KI291dCA8LSByZWFkUkRTKCcuLi8uLi9EYXRhL2NvcnBfb3V0X2xkYS5yZHMnKQ0KI291dCA8LSByZWFkUkRTKCcuLi8uLi9EYXRhL2NvcnBfb3V0X3RvcGljbW9kZWxzLnJkcycpDQpgYGANCg0KYGBge3J9DQpvdXQkZG9jdW1lbnRzW1sxXV1bLDM4NjozOTBdDQpvdXQkdm9jYWJbYyhvdXQkZG9jdW1lbnRzW1sxXV1bLDM4NjozOTBdWzEsXSldDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KHN0bSkNCnRvcGljcyA8LSByZWFkUkRTKCcuLi8uLi9EYXRhL2NvcnBfc3RtX3RvcGljcy5yZHMnKQ0KYGBgDQoNCmBgYHtyfQ0KbGFiZWxUb3BpY3ModG9waWNzKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZ9DQpvdXQkbWV0YSRpbmR1c3RyeSA8LSBmYWN0b3Iob3V0JG1ldGEkaW5kdXN0cnkpDQoNCmRvY190b3BpY3MgPSBkYXRhLmZyYW1lKGRvY3VtZW50PW5hbWVzKG91dCRkb2N1bWVudHMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgaW5kdXN0cnk9b3V0JG1ldGEkaW5kdXN0cnksDQogICAgICAgICAgICAgICAgICAgICAgICB0b3BpYz0xLA0KICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0PXRvcGljcyR0aGV0YVssMV0pDQpmb3IgKGkgaW4gMjoxMCkgew0KICB0ZW1wID0gZGF0YS5mcmFtZShkb2N1bWVudD1uYW1lcyhvdXQkZG9jdW1lbnRzKSwNCiAgICAgICAgICAgICAgICAgICAgaW5kdXN0cnk9b3V0JG1ldGEkaW5kdXN0cnksDQogICAgICAgICAgICAgICAgICAgIHRvcGljPWksDQogICAgICAgICAgICAgICAgICAgIHdlaWdodD10b3BpY3MkdGhldGFbLGldKQ0KICBkb2NfdG9waWNzID0gcmJpbmQoZG9jX3RvcGljcywgdGVtcCkNCn0NCg0KIyBQcm9wb3J0aW9uYWwgdG9waWNzICglKQ0KZG9jX3RvcGljcyA8LSBkb2NfdG9waWNzICU+JQ0KICBncm91cF9ieShkb2N1bWVudCkgJT4lDQogIG11dGF0ZSh0b3BpY19wcm9wID0gd2VpZ2h0IC8gc3VtKHdlaWdodCkpICU+JQ0KICB1bmdyb3VwKCkNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GfQ0KIyBNYW51YWxseSBsYWJlbCB0b3BpY3MNCnRvcGljX2xhYmVscyA9IGRhdGEuZnJhbWUodG9waWMgPSAxOjEwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB0b3BpY19uYW1lID0gYygnUmVhbCBFc3RhdGUnLCAnTWFuYWdlbWVudCcsICdQcm9kdWN0JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0ludmVzdG1lbnQnLCAnU2VydmljZXMnLCAnRmluYW5jaW5nJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1NlcnZpY2UyJywgJ0luc3VyYW5jZScsICdJbmR1c3RyaWFsJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1V0aWxpdHknKSkNCg0KZG9jX3RvcGljcyA8LSBkb2NfdG9waWNzICU+JSBsZWZ0X2pvaW4odG9waWNfbGFiZWxzKQ0KYGBgDQoNCmBgYHtyfQ0KZG9jX3RvcGljcyAlPiUgZmlsdGVyKGRvY3VtZW50PT0nMDAwMTEwNDY1OS0xNC0wMTUxNTIudHh0JykNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD00fQ0KZG9jX3RvcGljcyAlPiUNCiAgZmlsdGVyKGRvY3VtZW50PT0nMDAwMTEwNDY1OS0xNC0wMTUxNTIudHh0JyB8DQogICAgICAgICBkb2N1bWVudD09JzAwMDAwMTk2MTctMTQtMDAwMjg5LnR4dCcpICU+JQ0KICBtdXRhdGUoQ29tcGFueT1pZmVsc2UoZG9jdW1lbnQ9PScwMDAxMTA0NjU5LTE0LTAxNTE1Mi50eHQnLCAnQ2l0aScsJ0pQTScpKSAlPiUNCiAgZ2dwbG90KGFlcyh4PWZhY3Rvcih0b3BpY19uYW1lKSwgeT10b3BpY19wcm9wLCBmaWxsPWZhY3Rvcih0b3BpY19uYW1lKSkpICsgDQogIGdlb21fY29sKCkgKyBmYWNldF93cmFwKH5Db21wYW55KSArIA0KICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTQsIHdhcm5pbmc9Rn0NCmRvY190b3BpY3MgJT4lDQogIGdyb3VwX2J5KGluZHVzdHJ5LCB0b3BpYykgJT4lDQogIG11dGF0ZSh0b3BpY19wcm9wID0gbWVhbih0b3BpY19wcm9wKSkgJT4lDQogIHNsaWNlKDEpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIGdncGxvdChhZXMoeD1mYWN0b3IodG9waWNfbmFtZSksIHk9dG9waWNfcHJvcCwgZmlsbD1mYWN0b3IodG9waWNfbmFtZSkpKSArIA0KICBnZW9tX2NvbCgpICsgZmFjZXRfd3JhcCh+aW5kdXN0cnkpICsNCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GfQ0KbGlicmFyeSh0aWR5cikNCndpZGVfdG9waWNzIDwtIHNwcmVhZChkb2NfdG9waWNzWyxjKDEsMiw1LDYpXSwgdG9waWNfbmFtZSwgdG9waWNfcHJvcCkNCm1hdCA8LSB3aWRlX3RvcGljc1ssMzoxMl0NCg0KbWF0WywxOjZdICU+JSBoZWFkKCkgJT4lIGh0bWxfZGYoKQ0KYGBgDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoNjg0NTg2OCkNCmNsdXN0ZXJzIDwtIGttZWFucyhtYXQsIDkpDQpjbHVzdGVycyRjbHVzdGVyICU+JSBoZWFkKCkNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD00fQ0KY2JpbmQoYXMuZGF0YS5mcmFtZShjbHVzdGVycyRjZW50ZXIpLCBkYXRhLmZyYW1lKGttZWFuPTE6OSkpICU+JQ0KICBnYXRoZXIoIlRvcGljcyIsIndlaWdodHMiLC1rbWVhbikgJT4lDQogIGdncGxvdChhZXMoeD1mYWN0b3IoVG9waWNzKSwgeT13ZWlnaHRzLCBmaWxsPWZhY3RvcihUb3BpY3MpKSkgKw0KICBnZW9tX2NvbCgpICsgDQogIGZhY2V0X3dyYXAofmttZWFuKSArIA0KICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTR9DQpsaWJyYXJ5KGNsdXN0ZXIpICMgVXNlcyBQQ0EgKHByaW5jaXBsZSBjb21wb25lbnQgYW5hbHlzaXMpDQpjbHVzcGxvdChtYXQsIGNsdXN0ZXJzJGNsdXN0ZXIsIGNvbG9yPVRSVUUsIHNoYWRlPVRSVUUsIA0KICAgICAgICAgbGFiZWxzPTQpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0KbGlicmFyeShSdHNuZSkNCmR1cHMgPC0gd2hpY2goZHVwbGljYXRlZChtYXQpKQ0Kd2lkZV9ub2R1cCA8LSB3aWRlX3RvcGljc1stZHVwcyxdDQp3aWRlX25vZHVwJGttZWFuIDwtIGNsdXN0ZXJzJGNsdXN0ZXJbLWR1cHNdDQp0c25lX2RhdGEgPC0gcmVhZFJEUygnLi4vLi4vRGF0YS9jb3JwX3RzbmUucmRzJykNCg0Kd2lkZV9ub2R1cCA8LSB3aWRlX25vZHVwICU+JQ0KICBtdXRhdGUodHNuZTEgPSB0c25lX2RhdGEkWVssIDFdLCB0c25lMiA9IHRzbmVfZGF0YSRZWywgMl0pDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NX0NCmdncGxvdCh3aWRlX25vZHVwLCBhZXMoeCA9IHRzbmUxLCB5ID0gdHNuZTIsIGNvbG91ciA9IGluZHVzdHJ5KSkgKyANCiAgICBnZW9tX3BvaW50KGFscGhhID0gMC4zKSArIHRoZW1lX2J3KCkNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD01fQ0KZ2dwbG90KHdpZGVfbm9kdXAsIGFlcyh4ID0gdHNuZTEsIHkgPSB0c25lMiwgY29sb3VyID0gZmFjdG9yKGttZWFuKSkpICsgDQogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMykgKyB0aGVtZV9idygpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NX0NCmdncGxvdCh3aWRlX25vZHVwLCBhZXMoeD1rbWVhbikpICsgZ2VvbV9iYXIoKSArIGZhY2V0X3dyYXAofmZhY3RvcihpbmR1c3RyeSkpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NX0NCmdncGxvdCh3aWRlX25vZHVwLCBhZXMoeD10c25lMSwgeT10c25lMiwgY29sb3I9ZmFjdG9yKGttZWFuKSkpICsgZ2VvbV9wb2ludCgpICsNCiAgZmFjZXRfd3JhcCh+ZmFjdG9yKGluZHVzdHJ5KSkNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD01fQ0KZ2dwbG90KHdpZGVfbm9kdXAsIGFlcyh4PXRzbmUxLCB5PXRzbmUyLCBjb2xvcj1mYWN0b3IoaW5kdXN0cnkpKSkgKyBnZW9tX3BvaW50KCkgKw0KICBmYWNldF93cmFwKH5mYWN0b3Ioa21lYW4pKQ0KYGBgDQoNCmBgYHtyfQ0KI3dpZGVfdG9waWNzJGRpc3QgPC0gc3FydChyb3dTdW1zKG1hdCAtIGZpdHRlZChjbHVzdGVycykpXjIpDQp3aWRlX3RvcGljcyRkaXN0IDwtIHNxcnQocm93U3VtcyhhYnMobWF0IC0gZml0dGVkKGNsdXN0ZXJzKSkpKQ0Kd2lkZV90b3BpY3NbLGMoMSwyLDMsNSwxMyldICU+JSBhcnJhbmdlKGRlc2MoZGlzdCkpICU+JSBzbGljZSgxOjUpICU+JSBodG1sX2RmKCkNCmBgYA0KDQpgYGB7cn0NCndpZGVfdG9waWNzWyxjKDIsNSw4LDksMTAsMTEsMTMpXSAlPiUgZmlsdGVyKGluZHVzdHJ5IT0iRmluYW5jZSIpICU+JSBhcnJhbmdlKGRlc2MoZGlzdCkpICU+JSBtdXRhdGUoaWQ9MTpuKCkpJT4lIHNlbGVjdChpZCxldmVyeXRoaW5nKCkpICU+JSBzbGljZSgxOjIsNyw2LDgpICU+JSBodG1sX2RmKCkNCmBgYA0KDQpgYGB7cn0NCndpZGVfdG9waWNzWyxjKDIsNiw5LDEwLDExLDEyLDEzKV0gJT4lIGZpbHRlcihpbmR1c3RyeSE9IkZpbmFuY2UiKSAlPiUgYXJyYW5nZShkZXNjKGRpc3QpKSAlPiUgbXV0YXRlKGlkPTE6bigpKSU+JSBzZWxlY3QoaWQsZXZlcnl0aGluZygpKSAlPiUgc2xpY2UoNSkgJT4lIGh0bWxfZGYoKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZ9DQp0cmFpbiA8LSB3aWRlX3RvcGljc1shaXMubmEod2lkZV90b3BpY3MkaW5kdXN0cnkpLF0NCmxhYmVsIDwtIHdpZGVfdG9waWNzW2lzLm5hKHdpZGVfdG9waWNzJGluZHVzdHJ5KSxdDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Rn0NCmxpYnJhcnkoY2FyZXQpDQp0b3V0IDwtIHJlYWRSRFMoJy4uLy4uL0RhdGEvY29ycF9rbm4ucmRzJykNCmBgYA0KDQpgYGB7cn0NCnRvdXQNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD00fQ0KZ2dwbG90KHRvdXQkcmVzdWx0cywgYWVzKHg9aywgeT1BY2N1cmFjeSkpICsNCiAgZ2VvbV9saW5lKCkgKyANCiAgZ2VvbV9yaWJib24oYWVzKHltaW49QWNjdXJhY3kgLSBBY2N1cmFjeVNEKjEuOTYsDQogICAgICAgICAgICAgICAgICB5bWF4PUFjY3VyYWN5ICsgQWNjdXJhY3lTRCoxLjk2KSwgYWxwaGE9MC4yKSArIA0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9MTUsIGNvbG9yPSJibHVlIikgKyANCiAgeGxhYigiaywgb3B0aW1hbCA9IDE1IikNCmBgYA0KDQpgYGB7cn0NCmxhYmVsJGluZHVzdHJ5X3ByZWQgPC0gcHJlZGljdCh0b3V0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwpDQpsYWJlbFssYygiZG9jdW1lbnQiLA0KICAgICAgICAgImluZHVzdHJ5X3ByZWQiKV0gJT4lDQogIGhlYWQgJT4lIGh0bWxfZGYNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9DQp0c193dCA8LSB3aWRlX25vZHVwICU+JSBsZWZ0X2pvaW4obGFiZWxbLGMoImRvY3VtZW50IiwiaW5kdXN0cnlfcHJlZCIpXSkNCg0KdHNfd3QgPC0gdHNfd3QgJT4lDQogIG11dGF0ZSh0c25lMSA9IHRzbmVfZGF0YSRZWywgMV0sIHRzbmUyID0gdHNuZV9kYXRhJFlbLCAyXSkNCg0KIyBGb3JjZSBjb25zaXN0ZW50IGZhY3RvciB2YWx1ZXMNCmluZHMgPC0gdW5pcXVlKHRzX3d0JGluZHVzdHJ5KQ0KdHNfd3QkaW5kdXN0cnkgPC0gZmFjdG9yKHRzX3d0JGluZHVzdHJ5LCBpbmRzKQ0KdHNfd3QkaW5kdXN0cnlfcHJlZCA8LSBmYWN0b3IodHNfd3QkaW5kdXN0cnlfcHJlZCwgaW5kcykNCg0KIyBSZXBsaWNhdGUgZGVmYXVsdCBnZ3Bsb3QgY29sb3JzDQpnZ3Bsb3RDb2xvdXJzIDwtIGZ1bmN0aW9uKG4gPSA2LCBoID0gYygwLCAzNjApICsgMTUpew0KICBpZiAoKGRpZmYoaCkgJSUgMzYwKSA8IDEpIGhbMl0gPC0gaFsyXSAtIDM2MC9uDQogIGhjbChoID0gKHNlcShoWzFdLCBoWzJdLCBsZW5ndGggPSBuKSksIGMgPSAxMDAsIGwgPSA2NSkNCn0NCg0KZ2dwbG90KCkgKw0KICBzY2FsZV9zaGFwZV9pZGVudGl0eSgpICsgIyBBbGxvdyBmb3IgbW9yZSBwbG90IHBvaW50IG9wdGlvbnMNCiAgZ2VvbV9wb2ludChkYXRhPXRzX3d0WyFpcy5uYSh0c193dCRpbmR1c3RyeSksXSwNCiAgICAgICAgICAgICBhZXMoeD10c25lMSwgeT10c25lMiwgY29sb3I9aW5kdXN0cnksIHNoYXBlPTEpLCBzaXplPTEpICsgDQogIGdlb21fcG9pbnQoZGF0YT10c193dFshaXMubmEodHNfd3QkaW5kdXN0cnlfcHJlZCksXSwgYWVzKHg9dHNuZTEsIHk9dHNuZTIsDQogICAgICAgICAgICAgZmlsbD1pbmR1c3RyeV9wcmVkLCBzaGFwZT0yMywgc3Ryb2tlPTAuNSksIHNpemU9MikgKw0KICBndWlkZXMoZmlsbCA9ICJub25lIikgKyANCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1nZ3Bsb3RDb2xvdXJzKG4gPSA5KSwgbGFiZWxzPWluZHMsIGRyb3A9RkFMU0UpICsgDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1nZ3Bsb3RDb2xvdXJzKG4gPSA5KSwgbGFiZWxzPWluZHMsIGRyb3A9RkFMU0UpDQpgYGANCg0KYGBge3J9DQpjbHVzdGVycyA8LSBrbWVhbnMobWF0LCAyMCkNCmNsdXN0ZXJzJGNsdXN0ZXIgJT4lIGhlYWQoKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTR9DQp3aWRlX25vZHVwJGttZWFuMiA8LSBjbHVzdGVycyRjbHVzdGVyWy1kdXBzXQ0KZ2dwbG90KHdpZGVfbm9kdXAsIGFlcyh4ID0gdHNuZTEsIHkgPSB0c25lMiwgY29sb3VyID0gZmFjdG9yKGttZWFuMikpKSArIA0KICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjMpICsgdGhlbWVfYncoKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTV9DQpnZ3Bsb3Qod2lkZV9ub2R1cCwgYWVzKHg9a21lYW4yKSkgKyBnZW9tX2JhcigpICsgZmFjZXRfd3JhcCh+ZmFjdG9yKGluZHVzdHJ5KSkNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD01fQ0KZ2dwbG90KHdpZGVfbm9kdXAsIGFlcyh4PXRzbmUxLCB5PXRzbmUyLCBjb2xvcj1mYWN0b3Ioa21lYW4yKSkpICsgZ2VvbV9wb2ludCgpICsNCiAgZmFjZXRfd3JhcCh+ZmFjdG9yKGluZHVzdHJ5KSkNCmBgYA0KDQo=