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)
Warning messages:
1: In regexpr("<body[^>]*>", html, perl = TRUE) :
input string 1 is invalid UTF-8
2: In regexpr("</body>", html, perl = TRUE) :
input string 1 is invalid UTF-8
3: In regexpr("<body[^>]*>", html, perl = TRUE) :
input string 1 is invalid UTF-8
4: In regexpr("</body>", html, perl = TRUE) :
input string 1 is invalid UTF-8
5: In regexpr("<body[^>]*>", html, perl = TRUE) :
input string 1 is invalid UTF-8
6: In regexpr("</body>", html, perl = TRUE) :
input string 1 is invalid UTF-8
7: In regexpr("<body[^>]*>", html, perl = TRUE) :
input string 1 is invalid UTF-8
8: In regexpr("</body>", html, perl = TRUE) :
input string 1 is invalid UTF-8
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=