Feedback should be send to goran.milovanovic_ext@wikimedia.de
.
1. Data Acquistion
The Data Acquisition code chunk is not reproducible from this report. It is run as an newEds_mediawiki_history.R
script in production (currently stat1005
). The userIds
are anonymized and the dataset is copied mannually from production and processed locally to produce this Report.
### --- Script: newEds_mediawiki_history.R
### --- the following runs on stat1005.eqiad.wmnet
### --- Rscript /home/goransm/_miscWMDE/newEditors_MediaWikiHistory/newEds_mediawiki_history.R
### --- The script collects (a) userIds, and (b) dates (yyyymmdd) on which a particular user
### --- has reached >= 10 edits on a given project.
### --- The datasets are used for the New Editors Report on `dewiki`
### --- Goran S. Milovanovic, Data Analyst, WMDE
### --- October 16, 2017.
rm(list = ls())
library(dplyr)
library(tidyr)
library(stringr)
library(data.table)
### --- Define snapshot for wmf.mediawiki_history
snapshot <- as.character(Sys.time())
snapshotY <- strsplit(snapshot, split = "-")[[1]][1]
snapshotM <- strsplit(snapshot, split = "-")[[1]][2]
snapshot <- paste(snapshotY, snapshotM, sep = "-")
### --- NOTE OCTOBER SNAPSHOT NOT READY (11/11/2017)
snapshot <- '2017-09'
### --- END define snapshot
### --- projects list
projects <- c('dewiki', 'enwiki', 'frwiki')
### --- dir struct:
baseDir <- '/srv/home/goransm/_miscWMDE/newEditors_MediaWikiHistory'
outDir <- paste(baseDir, '/_results/', sep = "")
scriptDir <- paste(baseDir, '/_script/', sep = "")
setwd(scriptDir)
### --- run HiveQL scripts
for (i in 1:length(projects)) {
hiveQL <- paste("SELECT event_user_id, SUBSTR(from_utc_timestamp(event_timestamp, 'CET'), 1, 10)
FROM (
SELECT *,
row_number() OVER (partition by event_user_id ORDER by event_timestamp) rownum
FROM wmf.mediawiki_history WHERE wiki_db = '", projects[i],
"' AND event_entity = 'revision'
AND event_type = 'create'
AND event_user_is_created_by_self = true
AND event_user_is_bot_by_name = false
AND page_namespace = 0
AND page_is_redirect_latest = false
AND !(event_user_id = 0)
AND snapshot = '", snapshot,
"') tab1
WHERE rownum = 10;",
sep = "")
# - write hql
write(hiveQL, 'newEds10.hql')
### --- output filename
filename <- paste('newUsers10_', projects[i],".tsv", sep = "")
filename <- paste(outDir, filename, sep = "")
### --- execute hql script:
hiveArgs <- 'beeline -f'
hiveInput <- paste(paste(scriptDir, 'newEds10.hql', sep = ""),
" > ",
filename,
sep = "")
# - command:
hiveCommand <- paste(hiveArgs, hiveInput)
system(command = hiveCommand, wait = TRUE)
}
### --- END run HiveQL scripts
### --- anonymize, wrangle, and save
setwd(outDir)
Sys.setlocale("LC_TIME", "C")
lF <- list.files()
lF <- lF[grepl("tsv", lF, fixed = T)]
for (i in 1:length(lF)) {
project <- strsplit(
strsplit(lF[i], split = "_", fixed = T)[[1]][2],
split = ".", fixed = T)[[1]][1]
dataSet <- readLines(lF[i])
dataSet <- dataSet[16:(length(dataSet) - 2)]
User <- sapply(dataSet, function(x) {
strsplit(x, split = "\t", fixed = T)[[1]][1]
})
Date <- sapply(dataSet, function(x) {
strsplit(x, split = "\t", fixed = T)[[1]][2]
})
dsFrame <- data.frame(User = User,
Date = Date,
stringsAsFactors = F,
row.names = seq(1, length(User), by = 1))
rm(Date); rm(User); rm(dataSet); gc()
dsFrame$Date <- as.Date(dsFrame$Date)
dsFrame <- dsFrame %>%
arrange(Date)
# - anonymize user Ids
dsFrame$User <- paste("u_", seq(1, length(dsFrame$User), by = 1))
# - produce dataset w. daily resoluton
filename <- paste("NewUsersDaily_", project, ".csv", sep = "")
dailyRes <- dsFrame %>%
group_by(Date) %>%
summarise(Count = n())
dailyRes$Month <- sapply(months(dailyRes$Date), function(x) {
which(month.name %in% x)
})
dailyRes$Year <- year(dailyRes$Date)
dailyRes$Week <- week(dailyRes$Date)
dailyRes$DayWeek <- weekdays(dailyRes$Date)
write.csv(dailyRes, filename)
}
2. Weekly Trends (Big Picture)
### --- Define projects under consideration:
projects <- c('dewiki', 'enwiki', 'frwiki')
lF <- list.files('./_results/')
lF <- lF[grepl(".csv", lF, fixed = T)]
dwSets <- list()
### --- Recent weekly trends:
for (i in 1:length(projects)) {
w <- which(grepl(projects[i], lF, fixed = T))
dataSet <- read.csv(paste('./_results/', lF[w], sep = ""),
row.names = 1,
check.names = F,
header = T,
stringsAsFactors = F)
dWeekly <- dataSet
dWeekly$Month <- sapply(dWeekly$Month, function(x) {
if (!(nchar(x) == 2)) {
return(paste("0", x, sep = ""))
} else {
return(x)
}
})
dWeekly$Week <- sapply(dWeekly$Week, function(x) {
if (!(nchar(x) == 2)) {
return(paste("0", x, sep = ""))
} else {
return(x)
}
})
dWeekly$YW <- paste(dWeekly$Year, dWeekly$Week, sep = "-")
dWeekly <- dWeekly %>%
group_by(YW) %>%
summarise(Count = sum(Count)) %>%
arrange(YW)
dwSets[[i]] <- dWeekly
rm(dWeekly); rm(dataSet); gc()
}
### --- produce plotSet
dweeks <- unlist(lapply(dwSets, function(x) {
x$YW
}))
dMat <- as.data.frame(matrix('', nrow = length(dweeks), ncol = length(projects) + 1),
stringsAsFactors = F)
colnames(dMat)[1] <- 'Week'
colnames(dMat)[2:dim(dMat)[2]] <- projects
dMat[, 1] <- dweeks
for (i in 1:length(dwSets)) {
w <- which(dMat$Week %in% dwSets[[i]]$YW)
dMat[, i+1][w] <- dwSets[[i]]$Count
}
dMat <- dMat %>%
gather(key = Project,
value = Count,
projects)
dMat$Count <- as.numeric(dMat$Count)
# - x-axis labels
dMat$XLabs <- sapply(dMat$Week, function(x) {
if (grepl("-01$", x)) {
return(strsplit(x, split = "-", fixed = T)[[1]][1])
} else {
return("")
}
})
# - Visualize w. {ggplot2}
ggplot(dMat, aes(x = Week,
y = Count,
group = Project,
color = Project,
fill = Project)) +
geom_line(size = .25) +
scale_y_continuous(labels = comma) +
scale_x_discrete(labels = dMat$XLabs) +
xlab("Year (weekly data resolution)") +
ylab("New editors") +
ggtitle('New Editors (>= 10 edits) Income:\nWeekly comparison (starting at: Week 16. of 2016.)') +
theme_minimal() +
theme(axis.text.x = element_text(angle = 90)) +
theme(plot.title = element_text(size = 10)) +
theme(legend.title = element_blank()) +
theme(panel.grid.major.x = element_blank()) +
theme(panel.grid.minor.x = element_blank())
Log-scale:
# - Visualize w. {ggplot2}
ggplot(dMat, aes(x = Week,
y = log(Count),
group = Project,
color = Project,
fill = Project)) +
geom_line(size = .25) +
scale_y_continuous(labels = comma) +
scale_x_discrete(labels = dMat$XLabs) +
xlab("Year (weekly data resolution)") +
ylab("log(New editors)") +
ggtitle('New Editors (>= 10 edits) Income:\nWeekly comparison (starting at: Week 16. of 2016.)') +
theme_minimal() +
theme(axis.text.x = element_text(angle = 90)) +
theme(plot.title = element_text(size = 10)) +
theme(legend.title = element_blank()) +
theme(panel.grid.major.x = element_blank()) +
theme(panel.grid.minor.x = element_blank())
3. Monthly Trends: The Previous Six Months
### --- Define projects under consideration/get files
lF <- list.files('./_results/')
lF <- lF[grepl(".csv", lF, fixed = T)]
dwSets <- list()
### --- Monthly trends:
for (i in 1:length(lF)) {
project <- strsplit(
strsplit(lF[i], split = "_", fixed = T)[[1]][2],
split = ".", fixed = T)[[1]][1]
dS <- read.csv(paste('./_results/', lF[i], sep = ""),
header = T,
check.names = F,
stringsAsFactors = F,
row.names = 1)
dS$YearMonth <- paste(dS$Year,
ifelse(nchar(dS$Month) == 2,
dS$Month,
paste("0", dS$Month, sep = "")
),
sep = "-")
dS <- dplyr::select(dS, YearMonth, Count)
dS$Project <- project
dwSets[[i]] <- dS
rm(dS)
}
dwSets <- rbindlist(dwSets)
dwSets <- dwSets %>%
group_by(Project, YearMonth) %>%
summarise(Editors = sum(Count)) %>%
arrange(Project, YearMonth)
### --- determine: last 6 months
actualDate <- as.character(Sys.time())
actualDateY <- as.numeric(strsplit(actualDate, split = "-")[[1]][1])
actualDateM <- as.numeric(strsplit(actualDate, split = "-")[[1]][2])
actualDateM <- actualDateM - 1
actualDateM1 <- actualDateM - 5
monthsSeq <- actualDateM1:actualDateM
yearsSeq <- rep(actualDateY, length(monthsSeq))
yearsSeq[which(monthsSeq <= 0)] <- actualDateY - 1
monthsSeq[which(monthsSeq<= 0)] <- 12 - abs(monthsSeq[which(monthsSeq<= 0)])
targetYM <- paste(yearsSeq,
ifelse(nchar(monthsSeq) == 2,
monthsSeq,
paste("0", monthsSeq, sep = "")
),
sep = "-")
### --- filter: last 6 months
dwSets <- dwSets %>%
filter(YearMonth %in% targetYM)
### --- visualize w. {ggplot2}
colnames(dwSets)[2] <- 'Month'
dwSets$Month <- factor(dwSets$Month, levels = sort(unique(dwSets$Month)))
ggplot(dwSets,
aes(x = Month, y = Editors, label = Editors)) +
geom_line(size = .25, color = "#4c8cff", group = 1) +
geom_point(size = 1.5, color = "#4c8cff") +
geom_point(size = 1, color = "white") +
geom_text_repel(data = dwSets,
aes(x = Month, y = Editors, label = Editors),
size = 3) +
facet_wrap(~ Project, ncol = 3, scales = "free_y") +
xlab('Month') + ylab('New Editors (>= 10 edits)') +
scale_y_continuous(labels = comma) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 90, size = 10, hjust = 1)) +
theme(axis.title.x = element_text(size = 12)) +
theme(axis.title.y = element_text(size = 12)) +
theme(plot.title = element_text(size = 15))
3. Monthly Trends: The Previous Two Years
### --- Define projects under consideration/get files
lF <- list.files('./_results/')
lF <- lF[grepl(".csv", lF, fixed = T)]
dwSets <- list()
### --- Monthly trends:
for (i in 1:length(lF)) {
project <- strsplit(
strsplit(lF[i], split = "_", fixed = T)[[1]][2],
split = ".", fixed = T)[[1]][1]
dS <- read.csv(paste('./_results/', lF[i], sep = ""),
header = T,
check.names = F,
stringsAsFactors = F,
row.names = 1)
dS$YearMonth <- paste(dS$Year,
ifelse(nchar(dS$Month) == 2,
dS$Month,
paste("0", dS$Month, sep = "")
),
sep = "-")
dS <- dplyr::select(dS, YearMonth, Count)
dS$Project <- project
dwSets[[i]] <- dS
rm(dS)
}
dwSets <- rbindlist(dwSets)
dwSets <- dwSets %>%
group_by(Project, YearMonth) %>%
summarise(Editors = sum(Count)) %>%
arrange(Project, YearMonth)
### --- determine: last 2 years
actualDate <- as.character(Sys.time())
actualDateY <- as.numeric(strsplit(actualDate, split = "-")[[1]][1])
actualDateM <- as.numeric(strsplit(actualDate, split = "-")[[1]][2])
actualDateM <- actualDateM - 1
actualDateM1 <- actualDateM - 12
monthsSeq <- actualDateM1:actualDateM
yearsSeq <- rep(actualDateY, length(monthsSeq))
yearsSeq[which(monthsSeq <= 0)] <- actualDateY - 1
monthsSeq[which(monthsSeq <= 0)] <- 12 - abs(monthsSeq[which(monthsSeq<= 0)])
targetYM <- paste(yearsSeq,
ifelse(nchar(monthsSeq) == 2,
monthsSeq,
paste("0", monthsSeq, sep = "")
),
sep = "-")
targetYM1 <- lapply(targetYM, function(x) {
x <- strsplit(x, split = "-")[[1]]
x[1] <- as.numeric(x[1]) - 1
x <- paste(x, collapse = "-")
return(x)
})
targetYM <- unique(c(targetYM, unlist(targetYM1)))
### --- filter: last 6 months
dwSets <- dwSets %>%
filter(YearMonth %in% targetYM)
### --- visualize w. {ggplot2}
colnames(dwSets)[2] <- 'Month'
dwSets$Month <- factor(dwSets$Month, levels = sort(unique(dwSets$Month)))
ggplot(dwSets,
aes(x = Month, y = Editors, label = Editors)) +
geom_line(size = .25, color = "#4c8cff", group = 1) +
geom_point(size = 1.5, color = "#4c8cff") +
geom_point(size = 1, color = "white") +
geom_text_repel(data = dwSets,
aes(x = Month, y = Editors, label = Editors),
size = 3) +
facet_wrap(~ Project, ncol = 1, scales = "free_y") +
xlab('Month') + ylab('New Editors (>= 10 edits)') +
scale_y_continuous(labels = comma) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 90, size = 8, hjust = 1)) +
theme(axis.title.x = element_text(size = 12)) +
theme(axis.title.y = element_text(size = 12)) +
theme(plot.title = element_text(size = 15))
4. dewiki Forecast
The optimal ARIMA forecast for dewiki
, starting from the first year with a complete monthly dataset (2007):
library(tseries)
library(forecast)
### --- Data
dataSet <- read.csv(paste(getwd(), '/_results/NewUsersDaily_dewiki.csv', sep = ''),
header = T,
check.names = F,
stringsAsFactors = F,
row.names = 1)
### --- Wrangle
dataSet$Month <- sapply(dataSet$Month, function(x) {
if(nchar(x) == 1) {
x <- paste("0", x, sep = "")
}
x
})
dataSet <- arrange(dataSet, Year, Month)
# - complete data since 2007:
completeYears <- 2007:2017
wComplete <- rowSums(sapply(completeYears, function(x) {
grepl(x, dataSet$Year)
}))
dataSet <- dataSet[as.logical(wComplete), ]
# - summarise per month and drop incomplete data for the last month:
dataSet <- dataSet %>%
select(Year, Month, Count) %>%
group_by(Year, Month) %>%
summarise(Edits = sum(Count))
### --- as time series object:
timeEds <- ts(dataSet$Edits,
start = c(2007, 1),
end = c(2017, as.numeric(dataSet$Month[dim(dataSet)[1]])),
frequency = 12)
### --- ARIMA model
timeEdsARIMA <- auto.arima(timeEds, D = 1, seasonal = T)
timeEdsARIMA
Series: timeEds
ARIMA(0,1,1)(2,1,0)[12]
Coefficients:
ma1 sar1 sar2
-0.5167 -0.9003 -0.4265
s.e. 0.0803 0.0886 0.0941
sigma^2 estimated as 3036: log likelihood=-633.74
AIC=1275.48 AICc=1275.84 BIC=1286.5
plot(forecast(timeEdsARIMA))
LS0tCnRpdGxlOiAiTmV3IEVkaXRvcnMgb24gZGUud2lraXBlZGlhLm9yZyIKYXV0aG9yOiAiR29yYW4gUy4gTWlsb3Zhbm92aWMsIERhdGEgU2NpZW50aXN0LCBXTURFIgpkYXRlOiAiTm92ZW1iZXIgMTEsIDIwMTciCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICB0aGVtZTogc2ltcGxleAogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMgogICAgdG9jX2Zsb2F0OiB5ZXMKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMgotLS0KCioqRmVlZGJhY2sqKiBzaG91bGQgYmUgc2VuZCB0byBgZ29yYW4ubWlsb3Zhbm92aWNfZXh0QHdpa2ltZWRpYS5kZWAuIAoKYGBge3IsIGVjaG8gPSBGLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEYsIHJlc3VsdHMgPSAnaGlkZSd9CiMgIWRpYWdub3N0aWNzIG9mZgojIyMgLS0tIFNldHVwCmtuaXRyOjpvcHRzX2NodW5rJHNldChmaWcud2lkdGggPSAxNSwgZmlnLmhlaWdodCA9IDgpIApsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGdncmVwZWwpCmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKbGlicmFyeShrYWJsZUV4dHJhKQpsaWJyYXJ5KHJtYXJrZG93bikKbGlicmFyeShrbml0cikKbGlicmFyeShEVCkKYGBgCgojIyAxLiBEYXRhIEFjcXVpc3Rpb24KClRoZSBEYXRhIEFjcXVpc2l0aW9uIGNvZGUgY2h1bmsgaXMgbm90IHJlcHJvZHVjaWJsZSBmcm9tIHRoaXMgcmVwb3J0LiBJdCBpcyBydW4gYXMgYW4gYG5ld0Vkc19tZWRpYXdpa2lfaGlzdG9yeS5SYCBzY3JpcHQgaW4gcHJvZHVjdGlvbiAoY3VycmVudGx5IGBzdGF0MTAwNWApLiBUaGUgYHVzZXJJZHNgIGFyZSBhbm9ueW1pemVkIGFuZCB0aGUgZGF0YXNldCBpcyBjb3BpZWQgbWFubnVhbGx5IGZyb20gcHJvZHVjdGlvbiBhbmQgcHJvY2Vzc2VkIGxvY2FsbHkgdG8gcHJvZHVjZSB0aGlzIFJlcG9ydC4KCmBgYHtyLCBlY2hvID0gVCwgZXZhbCA9IEZ9CiMjIyAtLS0gU2NyaXB0OiBuZXdFZHNfbWVkaWF3aWtpX2hpc3RvcnkuUgojIyMgLS0tIHRoZSBmb2xsb3dpbmcgcnVucyBvbiBzdGF0MTAwNS5lcWlhZC53bW5ldAojIyMgLS0tIFJzY3JpcHQgL2hvbWUvZ29yYW5zbS9fbWlzY1dNREUvbmV3RWRpdG9yc19NZWRpYVdpa2lIaXN0b3J5L25ld0Vkc19tZWRpYXdpa2lfaGlzdG9yeS5SCgojIyMgLS0tIFRoZSBzY3JpcHQgY29sbGVjdHMgKGEpIHVzZXJJZHMsIGFuZCAoYikgZGF0ZXMgKHl5eXltbWRkKSBvbiB3aGljaCBhIHBhcnRpY3VsYXIgdXNlcgojIyMgLS0tIGhhcyByZWFjaGVkID49IDEwIGVkaXRzIG9uIGEgZ2l2ZW4gcHJvamVjdC4KIyMjIC0tLSBUaGUgZGF0YXNldHMgYXJlIHVzZWQgZm9yIHRoZSBOZXcgRWRpdG9ycyBSZXBvcnQgb24gYGRld2lraWAKCiMjIyAtLS0gR29yYW4gUy4gTWlsb3Zhbm92aWMsIERhdGEgQW5hbHlzdCwgV01ERQojIyMgLS0tIE9jdG9iZXIgMTYsIDIwMTcuCgpybShsaXN0ID0gbHMoKSkKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KGRhdGEudGFibGUpCgojIyMgLS0tIERlZmluZSBzbmFwc2hvdCBmb3Igd21mLm1lZGlhd2lraV9oaXN0b3J5CnNuYXBzaG90IDwtIGFzLmNoYXJhY3RlcihTeXMudGltZSgpKQpzbmFwc2hvdFkgPC0gc3Ryc3BsaXQoc25hcHNob3QsIHNwbGl0ID0gIi0iKVtbMV1dWzFdCnNuYXBzaG90TSA8LSBzdHJzcGxpdChzbmFwc2hvdCwgc3BsaXQgPSAiLSIpW1sxXV1bMl0Kc25hcHNob3QgPC0gcGFzdGUoc25hcHNob3RZLCBzbmFwc2hvdE0sIHNlcCA9ICItIikKIyMjIC0tLSBOT1RFIE9DVE9CRVIgU05BUFNIT1QgTk9UIFJFQURZICgxMS8xMS8yMDE3KQpzbmFwc2hvdCA8LSAnMjAxNy0wOScgCiMjIyAtLS0gRU5EIGRlZmluZSBzbmFwc2hvdAoKIyMjIC0tLSBwcm9qZWN0cyBsaXN0CnByb2plY3RzIDwtIGMoJ2Rld2lraScsICdlbndpa2knLCAnZnJ3aWtpJykKCiMjIyAtLS0gZGlyIHN0cnVjdDoKYmFzZURpciA8LSAnL3Nydi9ob21lL2dvcmFuc20vX21pc2NXTURFL25ld0VkaXRvcnNfTWVkaWFXaWtpSGlzdG9yeScKb3V0RGlyIDwtIHBhc3RlKGJhc2VEaXIsICcvX3Jlc3VsdHMvJywgc2VwID0gIiIpCnNjcmlwdERpciA8LSBwYXN0ZShiYXNlRGlyLCAnL19zY3JpcHQvJywgc2VwID0gIiIpCnNldHdkKHNjcmlwdERpcikKCiMjIyAtLS0gcnVuIEhpdmVRTCBzY3JpcHRzCmZvciAoaSBpbiAxOmxlbmd0aChwcm9qZWN0cykpIHsKICAKICBoaXZlUUwgPC0gcGFzdGUoIlNFTEVDVCBldmVudF91c2VyX2lkLCBTVUJTVFIoZnJvbV91dGNfdGltZXN0YW1wKGV2ZW50X3RpbWVzdGFtcCwgJ0NFVCcpLCAxLCAxMCkgCiAgICAgICAgICAgICAgICAgICAgRlJPTSAoCiAgICAgICAgICAgICAgICAgICAgICBTRUxFQ1QgKiwKICAgICAgICAgICAgICAgICAgICAgIHJvd19udW1iZXIoKSBPVkVSIChwYXJ0aXRpb24gYnkgZXZlbnRfdXNlcl9pZCBPUkRFUiBieSBldmVudF90aW1lc3RhbXApIHJvd251bQogICAgICAgICAgICAgICAgICAgICAgRlJPTSB3bWYubWVkaWF3aWtpX2hpc3RvcnkgV0hFUkUgd2lraV9kYiA9ICciLCBwcm9qZWN0c1tpXSwKICAgICAgICAgICAgICAgICAgICAgICInIEFORCBldmVudF9lbnRpdHkgPSAncmV2aXNpb24nCiAgICAgICAgICAgICAgICAgICAgICBBTkQgZXZlbnRfdHlwZSA9ICdjcmVhdGUnCiAgICAgICAgICAgICAgICAgICAgICBBTkQgZXZlbnRfdXNlcl9pc19jcmVhdGVkX2J5X3NlbGYgPSB0cnVlCiAgICAgICAgICAgICAgICAgICAgICBBTkQgZXZlbnRfdXNlcl9pc19ib3RfYnlfbmFtZSA9IGZhbHNlCiAgICAgICAgICAgICAgICAgICAgICBBTkQgcGFnZV9uYW1lc3BhY2UgPSAwIAogICAgICAgICAgICAgICAgICAgICAgQU5EIHBhZ2VfaXNfcmVkaXJlY3RfbGF0ZXN0ID0gZmFsc2UKICAgICAgICAgICAgICAgICAgICAgIEFORCAhKGV2ZW50X3VzZXJfaWQgPSAwKSAKICAgICAgICAgICAgICAgICAgICAgIEFORCBzbmFwc2hvdCA9ICciLCBzbmFwc2hvdCwKICAgICAgICAgICAgICAgICAgIicpIHRhYjEgCiAgICAgICAgICAgICAgICAgICAgV0hFUkUgcm93bnVtID0gMTA7IiwKICAgICAgICAgICAgICAgICAgc2VwID0gIiIpCiAgCiAgIyAtIHdyaXRlIGhxbAogIHdyaXRlKGhpdmVRTCwgJ25ld0VkczEwLmhxbCcpCiAgCiAgIyMjIC0tLSBvdXRwdXQgZmlsZW5hbWUKICBmaWxlbmFtZSA8LSBwYXN0ZSgnbmV3VXNlcnMxMF8nLCBwcm9qZWN0c1tpXSwiLnRzdiIsIHNlcCA9ICIiKQogIGZpbGVuYW1lIDwtIHBhc3RlKG91dERpciwgZmlsZW5hbWUsIHNlcCA9ICIiKQogIAogICMjIyAtLS0gZXhlY3V0ZSBocWwgc2NyaXB0OgogIGhpdmVBcmdzIDwtICdiZWVsaW5lIC1mJwogIGhpdmVJbnB1dCA8LSBwYXN0ZShwYXN0ZShzY3JpcHREaXIsICduZXdFZHMxMC5ocWwnLCBzZXAgPSAiIiksCiAgICAgICAgICAgICAgICAgICAgICIgPiAiLAogICAgICAgICAgICAgICAgICAgICBmaWxlbmFtZSwKICAgICAgICAgICAgICAgICAgICAgc2VwID0gIiIpCiAgIyAtIGNvbW1hbmQ6CiAgaGl2ZUNvbW1hbmQgPC0gcGFzdGUoaGl2ZUFyZ3MsIGhpdmVJbnB1dCkKICBzeXN0ZW0oY29tbWFuZCA9IGhpdmVDb21tYW5kLCB3YWl0ID0gVFJVRSkKfQojIyMgLS0tIEVORCBydW4gSGl2ZVFMIHNjcmlwdHMKCiMjIyAtLS0gYW5vbnltaXplLCB3cmFuZ2xlLCBhbmQgc2F2ZQpzZXR3ZChvdXREaXIpClN5cy5zZXRsb2NhbGUoIkxDX1RJTUUiLCAiQyIpCmxGIDwtIGxpc3QuZmlsZXMoKQpsRiA8LSBsRltncmVwbCgidHN2IiwgbEYsIGZpeGVkID0gVCldCmZvciAoaSBpbiAxOmxlbmd0aChsRikpIHsKICBwcm9qZWN0IDwtIHN0cnNwbGl0KAogICAgc3Ryc3BsaXQobEZbaV0sIHNwbGl0ID0gIl8iLCBmaXhlZCA9IFQpW1sxXV1bMl0sCiAgICBzcGxpdCA9ICIuIiwgZml4ZWQgPSBUKVtbMV1dWzFdCiAgZGF0YVNldCA8LSByZWFkTGluZXMobEZbaV0pCiAgZGF0YVNldCA8LSBkYXRhU2V0WzE2OihsZW5ndGgoZGF0YVNldCkgLSAyKV0KICBVc2VyIDwtIHNhcHBseShkYXRhU2V0LCBmdW5jdGlvbih4KSB7CiAgICBzdHJzcGxpdCh4LCBzcGxpdCA9ICJcdCIsIGZpeGVkID0gVClbWzFdXVsxXQogIH0pCiAgRGF0ZSA8LSBzYXBwbHkoZGF0YVNldCwgZnVuY3Rpb24oeCkgewogICAgc3Ryc3BsaXQoeCwgc3BsaXQgPSAiXHQiLCBmaXhlZCA9IFQpW1sxXV1bMl0KICB9KQogIGRzRnJhbWUgPC0gZGF0YS5mcmFtZShVc2VyID0gVXNlciwgCiAgICAgICAgICAgICAgICAgICAgICAgIERhdGUgPSBEYXRlLCAKICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IHNlcSgxLCBsZW5ndGgoVXNlciksIGJ5ID0gMSkpCiAgcm0oRGF0ZSk7IHJtKFVzZXIpOyBybShkYXRhU2V0KTsgZ2MoKQogIGRzRnJhbWUkRGF0ZSA8LSBhcy5EYXRlKGRzRnJhbWUkRGF0ZSkKICBkc0ZyYW1lIDwtIGRzRnJhbWUgJT4lIAogICAgYXJyYW5nZShEYXRlKQogICMgLSBhbm9ueW1pemUgdXNlciBJZHMKICBkc0ZyYW1lJFVzZXIgPC0gcGFzdGUoInVfIiwgc2VxKDEsIGxlbmd0aChkc0ZyYW1lJFVzZXIpLCBieSA9IDEpKQogICMgLSBwcm9kdWNlIGRhdGFzZXQgdy4gZGFpbHkgcmVzb2x1dG9uCiAgZmlsZW5hbWUgPC0gcGFzdGUoIk5ld1VzZXJzRGFpbHlfIiwgcHJvamVjdCwgIi5jc3YiLCBzZXAgPSAiIikKICBkYWlseVJlcyA8LSBkc0ZyYW1lICU+JSAKICAgIGdyb3VwX2J5KERhdGUpICU+JSAKICAgIHN1bW1hcmlzZShDb3VudCA9IG4oKSkKICBkYWlseVJlcyRNb250aCA8LSBzYXBwbHkobW9udGhzKGRhaWx5UmVzJERhdGUpLCBmdW5jdGlvbih4KSB7CiAgICB3aGljaChtb250aC5uYW1lICVpbiUgeCkKICB9KQogIGRhaWx5UmVzJFllYXIgPC0geWVhcihkYWlseVJlcyREYXRlKQogIGRhaWx5UmVzJFdlZWsgPC0gd2VlayhkYWlseVJlcyREYXRlKQogIGRhaWx5UmVzJERheVdlZWsgPC0gd2Vla2RheXMoZGFpbHlSZXMkRGF0ZSkKICB3cml0ZS5jc3YoZGFpbHlSZXMsIGZpbGVuYW1lKQp9CmBgYAoKIyMgMi4gV2Vla2x5IFRyZW5kcyAoQmlnIFBpY3R1cmUpCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gRGVmaW5lIHByb2plY3RzIHVuZGVyIGNvbnNpZGVyYXRpb246CnByb2plY3RzIDwtIGMoJ2Rld2lraScsICdlbndpa2knLCAnZnJ3aWtpJykKbEYgPC0gbGlzdC5maWxlcygnLi9fcmVzdWx0cy8nKQpsRiA8LSBsRltncmVwbCgiLmNzdiIsIGxGLCBmaXhlZCA9IFQpXQpkd1NldHMgPC0gbGlzdCgpCiMjIyAtLS0gUmVjZW50IHdlZWtseSB0cmVuZHM6CmZvciAoaSBpbiAxOmxlbmd0aChwcm9qZWN0cykpIHsKICB3IDwtIHdoaWNoKGdyZXBsKHByb2plY3RzW2ldLCBsRiwgZml4ZWQgPSBUKSkKICBkYXRhU2V0IDwtIHJlYWQuY3N2KHBhc3RlKCcuL19yZXN1bHRzLycsIGxGW3ddLCBzZXAgPSAiIiksIAogICAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSwKICAgICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKICBkV2Vla2x5IDwtIGRhdGFTZXQKICBkV2Vla2x5JE1vbnRoIDwtIHNhcHBseShkV2Vla2x5JE1vbnRoLCBmdW5jdGlvbih4KSB7CiAgICBpZiAoIShuY2hhcih4KSA9PSAyKSkgewogICAgICByZXR1cm4ocGFzdGUoIjAiLCB4LCBzZXAgPSAiIikpCiAgICB9IGVsc2UgewogICAgICByZXR1cm4oeCkKICAgIH0KICB9KQogIGRXZWVrbHkkV2VlayA8LSBzYXBwbHkoZFdlZWtseSRXZWVrLCBmdW5jdGlvbih4KSB7CiAgICBpZiAoIShuY2hhcih4KSA9PSAyKSkgewogICAgICByZXR1cm4ocGFzdGUoIjAiLCB4LCBzZXAgPSAiIikpCiAgICB9IGVsc2UgewogICAgICByZXR1cm4oeCkKICAgIH0KICB9KQogIGRXZWVrbHkkWVcgPC0gcGFzdGUoZFdlZWtseSRZZWFyLCBkV2Vla2x5JFdlZWssIHNlcCA9ICItIikKICBkV2Vla2x5IDwtIGRXZWVrbHkgJT4lIAogICAgZ3JvdXBfYnkoWVcpICU+JSAKICAgIHN1bW1hcmlzZShDb3VudCA9IHN1bShDb3VudCkpICU+JSAKICAgIGFycmFuZ2UoWVcpCiAgZHdTZXRzW1tpXV0gPC0gZFdlZWtseQogIHJtKGRXZWVrbHkpOyBybShkYXRhU2V0KTsgZ2MoKQp9CiMjIyAtLS0gcHJvZHVjZSBwbG90U2V0CmR3ZWVrcyA8LSB1bmxpc3QobGFwcGx5KGR3U2V0cywgZnVuY3Rpb24oeCkgewogIHgkWVcKfSkpCmRNYXQgPC0gYXMuZGF0YS5mcmFtZShtYXRyaXgoJycsIG5yb3cgPSBsZW5ndGgoZHdlZWtzKSwgbmNvbCA9IGxlbmd0aChwcm9qZWN0cykgKyAxKSwgCiAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKY29sbmFtZXMoZE1hdClbMV0gPC0gJ1dlZWsnCmNvbG5hbWVzKGRNYXQpWzI6ZGltKGRNYXQpWzJdXSA8LSBwcm9qZWN0cwpkTWF0WywgMV0gPC0gZHdlZWtzCmZvciAoaSBpbiAxOmxlbmd0aChkd1NldHMpKSB7CiAgdyA8LSB3aGljaChkTWF0JFdlZWsgJWluJSBkd1NldHNbW2ldXSRZVykKICBkTWF0WywgaSsxXVt3XSA8LSBkd1NldHNbW2ldXSRDb3VudCAKfQpkTWF0IDwtIGRNYXQgJT4lIAogIGdhdGhlcihrZXkgPSBQcm9qZWN0LAogICAgICAgICB2YWx1ZSA9IENvdW50LAogICAgICAgICBwcm9qZWN0cykKZE1hdCRDb3VudCA8LSBhcy5udW1lcmljKGRNYXQkQ291bnQpCiMgLSB4LWF4aXMgbGFiZWxzCmRNYXQkWExhYnMgPC0gc2FwcGx5KGRNYXQkV2VlaywgZnVuY3Rpb24oeCkgewogIGlmIChncmVwbCgiLTAxJCIsIHgpKSB7CiAgICByZXR1cm4oc3Ryc3BsaXQoeCwgc3BsaXQgPSAiLSIsIGZpeGVkID0gVClbWzFdXVsxXSkgCiAgfSBlbHNlIHsKICAgIHJldHVybigiIikKICAgIH0KfSkKIyAtIFZpc3VhbGl6ZSB3LiB7Z2dwbG90Mn0KZ2dwbG90KGRNYXQsIGFlcyh4ID0gV2VlaywKICAgICAgICAgICAgICAgICB5ID0gQ291bnQsCiAgICAgICAgICAgICAgICAgZ3JvdXAgPSBQcm9qZWN0LAogICAgICAgICAgICAgICAgIGNvbG9yID0gUHJvamVjdCwKICAgICAgICAgICAgICAgICBmaWxsID0gUHJvamVjdCkpICsgCiAgZ2VvbV9saW5lKHNpemUgPSAuMjUpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArIAogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gZE1hdCRYTGFicykgKwogIHhsYWIoIlllYXIgKHdlZWtseSBkYXRhIHJlc29sdXRpb24pIikgKwogIHlsYWIoIk5ldyBlZGl0b3JzIikgKwogIGdndGl0bGUoJ05ldyBFZGl0b3JzICg+PSAxMCBlZGl0cykgSW5jb21lOlxuV2Vla2x5IGNvbXBhcmlzb24gKHN0YXJ0aW5nIGF0OiBXZWVrIDE2LiBvZiAyMDE2LiknKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsgCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCkxvZy1zY2FsZToKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyAtIFZpc3VhbGl6ZSB3LiB7Z2dwbG90Mn0KZ2dwbG90KGRNYXQsIGFlcyh4ID0gV2VlaywKICAgICAgICAgICAgICAgICB5ID0gbG9nKENvdW50KSwKICAgICAgICAgICAgICAgICBncm91cCA9IFByb2plY3QsCiAgICAgICAgICAgICAgICAgY29sb3IgPSBQcm9qZWN0LAogICAgICAgICAgICAgICAgIGZpbGwgPSBQcm9qZWN0KSkgKyAKICBnZW9tX2xpbmUoc2l6ZSA9IC4yNSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsgCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBkTWF0JFhMYWJzKSArCiAgeGxhYigiWWVhciAod2Vla2x5IGRhdGEgcmVzb2x1dGlvbikiKSArCiAgeWxhYigibG9nKE5ldyBlZGl0b3JzKSIpICsKICBnZ3RpdGxlKCdOZXcgRWRpdG9ycyAoPj0gMTAgZWRpdHMpIEluY29tZTpcbldlZWtseSBjb21wYXJpc29uIChzdGFydGluZyBhdDogV2VlayAxNi4gb2YgMjAxNi4pJykgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsgCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArIAogIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgojIyAzLiBNb250aGx5IFRyZW5kczogVGhlIFByZXZpb3VzIFNpeCBNb250aHMKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyMjIC0tLSBEZWZpbmUgcHJvamVjdHMgdW5kZXIgY29uc2lkZXJhdGlvbi9nZXQgZmlsZXMKbEYgPC0gbGlzdC5maWxlcygnLi9fcmVzdWx0cy8nKQpsRiA8LSBsRltncmVwbCgiLmNzdiIsIGxGLCBmaXhlZCA9IFQpXQpkd1NldHMgPC0gbGlzdCgpCiMjIyAtLS0gTW9udGhseSB0cmVuZHM6CmZvciAoaSBpbiAxOmxlbmd0aChsRikpIHsKICBwcm9qZWN0IDwtIHN0cnNwbGl0KAogICAgc3Ryc3BsaXQobEZbaV0sIHNwbGl0ID0gIl8iLCBmaXhlZCA9IFQpW1sxXV1bMl0sCiAgICBzcGxpdCA9ICIuIiwgZml4ZWQgPSBUKVtbMV1dWzFdCiAgZFMgPC0gcmVhZC5jc3YocGFzdGUoJy4vX3Jlc3VsdHMvJywgbEZbaV0sIHNlcCA9ICIiKSwKICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRiwKICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSkKICBkUyRZZWFyTW9udGggPC0gcGFzdGUoZFMkWWVhciwgCiAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShuY2hhcihkUyRNb250aCkgPT0gMiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkUyRNb250aCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZSgiMCIsIGRTJE1vbnRoLCBzZXAgPSAiIikKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICItIikKICBkUyA8LSBkcGx5cjo6c2VsZWN0KGRTLCBZZWFyTW9udGgsIENvdW50KQogIGRTJFByb2plY3QgPC0gcHJvamVjdAogIGR3U2V0c1tbaV1dIDwtIGRTCiAgcm0oZFMpCn0KZHdTZXRzIDwtIHJiaW5kbGlzdChkd1NldHMpCmR3U2V0cyA8LSBkd1NldHMgJT4lIAogIGdyb3VwX2J5KFByb2plY3QsIFllYXJNb250aCkgJT4lIAogIHN1bW1hcmlzZShFZGl0b3JzID0gc3VtKENvdW50KSkgJT4lIAogIGFycmFuZ2UoUHJvamVjdCwgWWVhck1vbnRoKQojIyMgLS0tIGRldGVybWluZTogbGFzdCA2IG1vbnRocwphY3R1YWxEYXRlIDwtIGFzLmNoYXJhY3RlcihTeXMudGltZSgpKQphY3R1YWxEYXRlWSA8LSBhcy5udW1lcmljKHN0cnNwbGl0KGFjdHVhbERhdGUsIHNwbGl0ID0gIi0iKVtbMV1dWzFdKQphY3R1YWxEYXRlTSA8LSBhcy5udW1lcmljKHN0cnNwbGl0KGFjdHVhbERhdGUsIHNwbGl0ID0gIi0iKVtbMV1dWzJdKQphY3R1YWxEYXRlTSA8LSBhY3R1YWxEYXRlTSAtIDEKYWN0dWFsRGF0ZU0xIDwtIGFjdHVhbERhdGVNIC0gNQptb250aHNTZXEgPC0gYWN0dWFsRGF0ZU0xOmFjdHVhbERhdGVNCnllYXJzU2VxIDwtIHJlcChhY3R1YWxEYXRlWSwgbGVuZ3RoKG1vbnRoc1NlcSkpCnllYXJzU2VxW3doaWNoKG1vbnRoc1NlcSA8PSAwKV0gPC0gYWN0dWFsRGF0ZVkgLSAxCm1vbnRoc1NlcVt3aGljaChtb250aHNTZXE8PSAwKV0gPC0gMTIgLSBhYnMobW9udGhzU2VxW3doaWNoKG1vbnRoc1NlcTw9IDApXSkKdGFyZ2V0WU0gPC0gcGFzdGUoeWVhcnNTZXEsIAogICAgICAgICAgICAgICAgICBpZmVsc2UobmNoYXIobW9udGhzU2VxKSA9PSAyLAogICAgICAgICAgICAgICAgICAgICAgICAgbW9udGhzU2VxLAogICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoIjAiLCBtb250aHNTZXEsIHNlcCA9ICIiKQogICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgc2VwID0gIi0iKQojIyMgLS0tIGZpbHRlcjogbGFzdCA2IG1vbnRocwpkd1NldHMgPC0gZHdTZXRzICU+JSAKICBmaWx0ZXIoWWVhck1vbnRoICVpbiUgdGFyZ2V0WU0pCiMjIyAtLS0gdmlzdWFsaXplIHcuIHtnZ3Bsb3QyfQpjb2xuYW1lcyhkd1NldHMpWzJdIDwtICdNb250aCcKZHdTZXRzJE1vbnRoIDwtIGZhY3Rvcihkd1NldHMkTW9udGgsIGxldmVscyA9IHNvcnQodW5pcXVlKGR3U2V0cyRNb250aCkpKQpnZ3Bsb3QoZHdTZXRzLAogICAgICAgYWVzKHggPSBNb250aCwgeSA9IEVkaXRvcnMsIGxhYmVsID0gRWRpdG9ycykpICsKICAgICAgICAgIGdlb21fbGluZShzaXplID0gLjI1LCBjb2xvciA9ICIjNGM4Y2ZmIiwgZ3JvdXAgPSAxKSArCiAgICAgICAgICBnZW9tX3BvaW50KHNpemUgPSAxLjUsIGNvbG9yID0gIiM0YzhjZmYiKSArIAogICAgICAgICAgZ2VvbV9wb2ludChzaXplID0gMSwgY29sb3IgPSAid2hpdGUiKSArIAogICAgICAgICAgZ2VvbV90ZXh0X3JlcGVsKGRhdGEgPSBkd1NldHMsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyh4ID0gTW9udGgsIHkgPSBFZGl0b3JzLCBsYWJlbCA9IEVkaXRvcnMpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMykgKwogICAgICAgICAgZmFjZXRfd3JhcCh+IFByb2plY3QsIG5jb2wgPSAzLCBzY2FsZXMgPSAiZnJlZV95IikgKwogICAgICAgICAgeGxhYignTW9udGgnKSArIHlsYWIoJ05ldyBFZGl0b3JzICg+PSAxMCBlZGl0cyknKSArCiAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsgCiAgICAgICAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDEwLCBoanVzdCA9IDEpKSArCiAgICAgICAgICB0aGVtZShheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSkgKwogICAgICAgICAgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMikpICsKICAgICAgICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE1KSkKYGBgCgojIyAzLiBNb250aGx5IFRyZW5kczogVGhlIFByZXZpb3VzIFR3byBZZWFycwoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQojIyMgLS0tIERlZmluZSBwcm9qZWN0cyB1bmRlciBjb25zaWRlcmF0aW9uL2dldCBmaWxlcwpsRiA8LSBsaXN0LmZpbGVzKCcuL19yZXN1bHRzLycpCmxGIDwtIGxGW2dyZXBsKCIuY3N2IiwgbEYsIGZpeGVkID0gVCldCmR3U2V0cyA8LSBsaXN0KCkKIyMjIC0tLSBNb250aGx5IHRyZW5kczoKZm9yIChpIGluIDE6bGVuZ3RoKGxGKSkgewogIHByb2plY3QgPC0gc3Ryc3BsaXQoCiAgICBzdHJzcGxpdChsRltpXSwgc3BsaXQgPSAiXyIsIGZpeGVkID0gVClbWzFdXVsyXSwKICAgIHNwbGl0ID0gIi4iLCBmaXhlZCA9IFQpW1sxXV1bMV0KICBkUyA8LSByZWFkLmNzdihwYXN0ZSgnLi9fcmVzdWx0cy8nLCBsRltpXSwgc2VwID0gIiIpLAogICAgICAgICAgICAgICBoZWFkZXIgPSBULAogICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGLAogICAgICAgICAgICAgICByb3cubmFtZXMgPSAxKQogIGRTJFllYXJNb250aCA8LSBwYXN0ZShkUyRZZWFyLCAKICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKG5jaGFyKGRTJE1vbnRoKSA9PSAyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRTJE1vbnRoLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKCIwIiwgZFMkTW9udGgsIHNlcCA9ICIiKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIi0iKQogIGRTIDwtIGRwbHlyOjpzZWxlY3QoZFMsIFllYXJNb250aCwgQ291bnQpCiAgZFMkUHJvamVjdCA8LSBwcm9qZWN0CiAgZHdTZXRzW1tpXV0gPC0gZFMKICBybShkUykKfQpkd1NldHMgPC0gcmJpbmRsaXN0KGR3U2V0cykKZHdTZXRzIDwtIGR3U2V0cyAlPiUgCiAgZ3JvdXBfYnkoUHJvamVjdCwgWWVhck1vbnRoKSAlPiUgCiAgc3VtbWFyaXNlKEVkaXRvcnMgPSBzdW0oQ291bnQpKSAlPiUgCiAgYXJyYW5nZShQcm9qZWN0LCBZZWFyTW9udGgpCiMjIyAtLS0gZGV0ZXJtaW5lOiBsYXN0IDIgeWVhcnMKYWN0dWFsRGF0ZSA8LSBhcy5jaGFyYWN0ZXIoU3lzLnRpbWUoKSkKYWN0dWFsRGF0ZVkgPC0gYXMubnVtZXJpYyhzdHJzcGxpdChhY3R1YWxEYXRlLCBzcGxpdCA9ICItIilbWzFdXVsxXSkKYWN0dWFsRGF0ZU0gPC0gYXMubnVtZXJpYyhzdHJzcGxpdChhY3R1YWxEYXRlLCBzcGxpdCA9ICItIilbWzFdXVsyXSkKYWN0dWFsRGF0ZU0gPC0gYWN0dWFsRGF0ZU0gLSAxCmFjdHVhbERhdGVNMSA8LSBhY3R1YWxEYXRlTSAtIDEyCm1vbnRoc1NlcSA8LSBhY3R1YWxEYXRlTTE6YWN0dWFsRGF0ZU0KeWVhcnNTZXEgPC0gcmVwKGFjdHVhbERhdGVZLCBsZW5ndGgobW9udGhzU2VxKSkKeWVhcnNTZXFbd2hpY2gobW9udGhzU2VxIDw9IDApXSA8LSBhY3R1YWxEYXRlWSAtIDEKbW9udGhzU2VxW3doaWNoKG1vbnRoc1NlcSA8PSAwKV0gPC0gMTIgLSBhYnMobW9udGhzU2VxW3doaWNoKG1vbnRoc1NlcTw9IDApXSkKdGFyZ2V0WU0gPC0gcGFzdGUoeWVhcnNTZXEsIAogICAgICAgICAgICAgICAgICBpZmVsc2UobmNoYXIobW9udGhzU2VxKSA9PSAyLAogICAgICAgICAgICAgICAgICAgICAgICAgbW9udGhzU2VxLAogICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoIjAiLCBtb250aHNTZXEsIHNlcCA9ICIiKQogICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgc2VwID0gIi0iKQp0YXJnZXRZTTEgPC0gbGFwcGx5KHRhcmdldFlNLCBmdW5jdGlvbih4KSB7CiAgeCA8LSBzdHJzcGxpdCh4LCBzcGxpdCA9ICItIilbWzFdXQogIHhbMV0gPC0gYXMubnVtZXJpYyh4WzFdKSAtIDEKICB4IDwtIHBhc3RlKHgsIGNvbGxhcHNlID0gIi0iKQogIHJldHVybih4KQp9KQp0YXJnZXRZTSA8LSB1bmlxdWUoYyh0YXJnZXRZTSwgdW5saXN0KHRhcmdldFlNMSkpKQojIyMgLS0tIGZpbHRlcjogbGFzdCA2IG1vbnRocwpkd1NldHMgPC0gZHdTZXRzICU+JSAKICBmaWx0ZXIoWWVhck1vbnRoICVpbiUgdGFyZ2V0WU0pCiMjIyAtLS0gdmlzdWFsaXplIHcuIHtnZ3Bsb3QyfQpjb2xuYW1lcyhkd1NldHMpWzJdIDwtICdNb250aCcKZHdTZXRzJE1vbnRoIDwtIGZhY3Rvcihkd1NldHMkTW9udGgsIGxldmVscyA9IHNvcnQodW5pcXVlKGR3U2V0cyRNb250aCkpKQpnZ3Bsb3QoZHdTZXRzLAogICAgICAgYWVzKHggPSBNb250aCwgeSA9IEVkaXRvcnMsIGxhYmVsID0gRWRpdG9ycykpICsKICAgICAgICAgIGdlb21fbGluZShzaXplID0gLjI1LCBjb2xvciA9ICIjNGM4Y2ZmIiwgZ3JvdXAgPSAxKSArCiAgICAgICAgICBnZW9tX3BvaW50KHNpemUgPSAxLjUsIGNvbG9yID0gIiM0YzhjZmYiKSArIAogICAgICAgICAgZ2VvbV9wb2ludChzaXplID0gMSwgY29sb3IgPSAid2hpdGUiKSArIAogICAgICAgICAgZ2VvbV90ZXh0X3JlcGVsKGRhdGEgPSBkd1NldHMsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyh4ID0gTW9udGgsIHkgPSBFZGl0b3JzLCBsYWJlbCA9IEVkaXRvcnMpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMykgKwogICAgICAgICAgZmFjZXRfd3JhcCh+IFByb2plY3QsIG5jb2wgPSAxLCBzY2FsZXMgPSAiZnJlZV95IikgKwogICAgICAgICAgeGxhYignTW9udGgnKSArIHlsYWIoJ05ldyBFZGl0b3JzICg+PSAxMCBlZGl0cyknKSArCiAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsgCiAgICAgICAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgsIGhqdXN0ID0gMSkpICsKICAgICAgICAgIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKSArCiAgICAgICAgICB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSkgKwogICAgICAgICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTUpKQpgYGAKCiMjIDQuIGRld2lraSBGb3JlY2FzdAoKVGhlIG9wdGltYWwgQVJJTUEgZm9yZWNhc3QgZm9yIGBkZXdpa2lgLCBzdGFydGluZyBmcm9tIHRoZSBmaXJzdCB5ZWFyIHdpdGggYSBjb21wbGV0ZSBtb250aGx5IGRhdGFzZXQgKDIwMDcpOgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpsaWJyYXJ5KHRzZXJpZXMpCmxpYnJhcnkoZm9yZWNhc3QpCiMjIyAtLS0gRGF0YQpkYXRhU2V0IDwtIHJlYWQuY3N2KHBhc3RlKGdldHdkKCksICcvX3Jlc3VsdHMvTmV3VXNlcnNEYWlseV9kZXdpa2kuY3N2Jywgc2VwID0gJycpLAogICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGLAogICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEpCiMjIyAtLS0gV3JhbmdsZQpkYXRhU2V0JE1vbnRoIDwtIHNhcHBseShkYXRhU2V0JE1vbnRoLCBmdW5jdGlvbih4KSB7CiAgaWYobmNoYXIoeCkgPT0gMSkgewogICAgeCA8LSBwYXN0ZSgiMCIsIHgsIHNlcCA9ICIiKQogIH0KICB4Cn0pCmRhdGFTZXQgPC0gYXJyYW5nZShkYXRhU2V0LCBZZWFyLCBNb250aCkKIyAtIGNvbXBsZXRlIGRhdGEgc2luY2UgMjAwNzoKY29tcGxldGVZZWFycyA8LSAyMDA3OjIwMTcKd0NvbXBsZXRlIDwtIHJvd1N1bXMoc2FwcGx5KGNvbXBsZXRlWWVhcnMsIGZ1bmN0aW9uKHgpIHsKICBncmVwbCh4LCBkYXRhU2V0JFllYXIpCiAgfSkpCmRhdGFTZXQgPC0gZGF0YVNldFthcy5sb2dpY2FsKHdDb21wbGV0ZSksIF0KIyAtIHN1bW1hcmlzZSBwZXIgbW9udGggYW5kIGRyb3AgaW5jb21wbGV0ZSBkYXRhIGZvciB0aGUgbGFzdCBtb250aDoKZGF0YVNldCA8LSBkYXRhU2V0ICU+JSAKICBzZWxlY3QoWWVhciwgTW9udGgsIENvdW50KSAlPiUgCiAgZ3JvdXBfYnkoWWVhciwgTW9udGgpICU+JSAKICBzdW1tYXJpc2UoRWRpdHMgPSBzdW0oQ291bnQpKQojIyMgLS0tIGFzIHRpbWUgc2VyaWVzIG9iamVjdDoKdGltZUVkcyA8LSB0cyhkYXRhU2V0JEVkaXRzLCAKICAgICAgICAgICAgICBzdGFydCA9IGMoMjAwNywgMSksIAogICAgICAgICAgICAgIGVuZCA9IGMoMjAxNywgYXMubnVtZXJpYyhkYXRhU2V0JE1vbnRoW2RpbShkYXRhU2V0KVsxXV0pKSwgCiAgICAgICAgICAgICAgZnJlcXVlbmN5ID0gMTIpCiMjIyAtLS0gQVJJTUEgbW9kZWwKdGltZUVkc0FSSU1BIDwtIGF1dG8uYXJpbWEodGltZUVkcywgRCA9IDEsIHNlYXNvbmFsID0gVCkKdGltZUVkc0FSSU1BCnBsb3QoZm9yZWNhc3QodGltZUVkc0FSSU1BKSkKYGBgCg==