Feedback should be send to goran.milovanovic_ext@wikimedia.de.

The campaign is run from 2018/10/19 to 2018/10/28.

CURRENT UPDATE: Complete dataset as of 2018/10/29.

0. Data Acquisiton

The data acquisition and aggregation R code.

0.1 Daily Update

NOTE: the Data Acquisition code chunk is not fully reproducible from this Report. The data are collected by running the script 2018_ABC_Production.R on stat1005.eqiad.wmnet, collecting the data as .tsv and .csv files, copying manually, and processing locally.

### --- Data Acquisition for the Autumn Banner Campaign 2018
### --- run from stat1005
### --- /home/goransm/RScripts/NewEditors/2018_AutumnBannerCampaign/_data

### --- to data directory
dataDir <- '/home/goransm/RScripts/NewEditors/2018_AutumnBannerCampaign/_data'
setwd(dataDir)

### --- determine cetDay
library(lubridate)
cetDay <- Sys.time()
cetDay
attr(cetDay, "tzone") <- "Europe/Berlin"
# - one day behind for crontab
# - (i.e. waiting for wmf.webrequest to complete is data acquisition)
cetDay <- ymd(
  strsplit(as.character(cetDay), 
           split = " ", 
           fixed = T)[[1]][1]
  ) - 1

### --- Collect Banner Impression Data

# - function: wmde_collect_banner_impressions
wmde_collect_banner_impressions <- function(uri_host, 
                                            uri_path, 
                                            uri_query, 
                                            cetDay,
                                            queryFile,
                                            fileName,
                                            dataDir) {
  
  # - NOTE:
  # - expected format for cetDay is: YYYY-MM-DD
  
  # - to dataDir
  setwd(dataDir)
  
  # - libraries
  library(stringr)
  
  # - WHERE condition: create datetime_condition
  cet_condition <- seq(
    from = as.POSIXct(paste0(cetDay," 0:00"), tz = "Europe/Berlin"),
    to = as.POSIXct(paste0(cetDay," 23:00"), tz = "Europe/Berlin"),
    by = "hour"
  ) 
  attr(cet_condition, "tzone") <- "UTC"
  cet_condition <- as.character(cet_condition)
  cet_condition <- unlist(str_extract_all(cet_condition, "^([[:digit:]]|\\s|-)*"))
  cet_years <- sapply(
    strsplit(cet_condition, split = " ", fixed = T), function(x) {
      strsplit(x, split = "-")[[1]][1]
    })
  cet_months <- sapply(
    strsplit(cet_condition, split = " ", fixed = T), function(x) {
      strsplit(x, split = "-")[[1]][2]
    })
  cet_months <- gsub("^0", "", cet_months)
  cet_days <- sapply(
    strsplit(cet_condition, split = " ", fixed = T), function(x) {
      strsplit(x, split = "-")[[1]][3]
    })
  cet_days <- gsub("^0", "", cet_days)
  cet_hours <- sapply(strsplit(cet_condition, split = " ", fixed = T), 
                      function(x) {
                        x[2]
                      })
  cet_hours <- gsub("^0", "", cet_hours)
  datetimeCondition <- paste0(
    "year = ", cet_years, " AND ",
    "month = ", cet_months, " AND ",
    "day = ", cet_days, " AND ", 
    "hour = ", cet_hours
  )
  datetimeCondition <- paste("(", 
                             datetimeCondition, 
                             ")",
                             collapse = " OR ", 
                             sep = "")
  
  # - WHERE condition: create uri_path_condition
  if (length(uri_path) > 1) {
    uri_path_condition <- paste0("(",
                                 paste(
                                   paste0("uri_path = '", uri_path, "'"),
                                   collapse = " OR ", sep = " "),
                                 ")"
                                 )
  } else {
    uri_path_condition = paste0("uri_path = '", uri_path, "'")
  }
  
  # - WHERE condition: create uri_host_condition
  if (length(uri_host) > 1) {
    uri_host_condition <- paste0("(",
                                 paste(
                                   paste0("uri_host = '", uri_host, "'"),
                                   collapse = " OR ", sep = " "),
                                 ")"
    )
  } else {
    uri_host_condition = paste0("uri_host = '", uri_host, "'")
  }
  
  # - WHERE condition: create uri_query_condition
  if (length(uri_query) > 1) {
    uri_query_condition <- paste0("(",
                                 paste(
                                   paste0("uri_query LIKE '%", uri_query, "%'"),
                                   collapse = " OR ", sep = " "),
                                 ")"
    )
  } else {
    uri_query_condition = paste0("uri_query LIKE '%", uri_query, "%'")
  }
  
  # - compose HiveQL query
  hiveQuery <- paste0(
    "USE wmf;
    SELECT uri_query FROM webrequest
    WHERE (",
    uri_host_condition, " AND ",
    uri_path_condition, " AND ",
    uri_query_condition, " AND ",
    "(", datetimeCondition, ")",
    ");"
  )
  
  # - write hql
  write(hiveQuery, queryFile)
  # - execute hql script:
  hiveArgs <- '/usr/local/bin/beeline -f'
  hiveInput <- paste0(queryFile, ' > ', fileName)
  # - command:
  hiveCommand <- paste(hiveArgs, hiveInput)
  return(
    system(command = hiveCommand, wait = TRUE))
}

# - set params to wmde_collect_banner_impressions
# - for the Autumn Banner Campaign 2018
uri_host <- c('de.wikipedia.org', 'de.m.wikipedia.org')
uri_path  <- '/beacon/impression'
uri_query <- c('WMDE_neweditors_autumn_2018_lpn', 
               'B18WMDE_neweditors_autumn_2018_lp'
               )
queryFile <- 'abc2018_BannerImpressions.hql'
fileName <- paste0("bannerImpressions_", cetDay, ".tsv")
dataDir <- '/home/goransm/RScripts/NewEditors/2018_AutumnBannerCampaign/_data'

# - collect Banner Impression data
wmde_collect_banner_impressions(uri_host,
                                uri_path,
                                uri_query,
                                cetDay,
                                queryFile,
                                fileName,
                                dataDir)

### --- Wrangle Banner Impression Data

# - function: wmde_process_banner_impressions
wmde_process_banner_impressions <- function(fileName,
                                            dataDir, 
                                            cetDay, 
                                            campaignName) {
  
  # - to dataDir
  setwd(dataDir)
  
  # - libraries
  library(stringr)
  library(dplyr)
  
  # - load
  bannerData <- read.delim(fileName, 
                           stringsAsFactors = F)
  colnames(bannerData) <- 'uri_query'
  
  # - clean
  wStart <- which(bannerData$uri_query == "uri_query")
  bannerData <- bannerData[(wStart + 1):(dim(bannerData)[1] - 2), ]
  
  # - split
  bannerData <- strsplit(bannerData, split = "&", fixed = T)
  # - extract relevant fields
  # - banner:
  banner <- sapply(bannerData, function(x) {
    x[which(grepl("^banner=", x))]
  })
  banner <- gsub("^banner=", "", banner)
  # - recordImpressionSampleRate:
  recordImpressionSampleRate <- sapply(bannerData, function(x) {
    x[which(grepl("^recordImpressionSampleRate=", x))]
  })
  recordImpressionSampleRate <- as.numeric(
    gsub("^recordImpressionSampleRate=", "", recordImpressionSampleRate)
    )
  # - result:
  result <- sapply(bannerData, function(x) {
    x[which(grepl("^result=", x))]
  })
  result <- gsub("^result=", "", result)
  
  # - compose table:
  bannerObservations <- data.frame(banner = banner, 
                                   recordImpressionSampleRate = recordImpressionSampleRate, 
                                   result = result, 
                                   stringsAsFactors = F)
  
  # - filter for result=show
  bannerObservations <- dplyr::filter(bannerObservations,
                                      result == "show")
  
  # - correction for recordImpressionSampleRate
  bannerObservations$recordImpressionSampleRate <- 
    1/bannerObservations$recordImpressionSampleRate
  
  # - aggregate:
  bannerObservations <- bannerObservations %>% 
    dplyr::select(banner, recordImpressionSampleRate) %>% 
    dplyr::group_by(banner) %>% 
    dplyr::summarise(impressions = sum(recordImpressionSampleRate))
  
  # - add cetDay, campaignName
  bannerObservations$date <- cetDay
  bannerObservations$campaign <- campaignName
  
  # - store:
  write.csv(bannerObservations, 
            paste0("bannerImpressionsAggregated_",
                   strsplit(
                     strsplit(fileName, split = "_", fixed = T)[[1]][2],
                     split = ".", 
                     fixed = T)[[1]][1],
                   ".csv"
                   )
            )
  
}

# - wrangle Banner Impression data
campaignName <- "2018_AuBC"
wmde_process_banner_impressions(fileName = fileName, 
                                dataDir = dataDir, 
                                cetDay = cetDay,
                                campaignName = campaignName)

### --- Collect Pageviews

# - function: wmde_collect_pageviews
wmde_collect_pageviews <- function(uri_host,
                                   uri_path,
                                   cetDay,
                                   queryFile,
                                   fileName,
                                   dataDir) {
  
  # - NOTE:
  # - expected format for cetDay is: YYYY-MM-DD
  
  # - to dataDir
  setwd(dataDir)
  
  # - libraries
  library(stringr)
  
  # - WHERE condition: create datetime_condition
  cet_condition <- seq(
    from = as.POSIXct(paste0(cetDay," 0:00"), tz = "Europe/Berlin"),
    to = as.POSIXct(paste0(cetDay," 23:00"), tz = "Europe/Berlin"),
    by = "hour"
  ) 
  attr(cet_condition, "tzone") <- "UTC"
  cet_condition <- as.character(cet_condition)
  cet_condition <- unlist(str_extract_all(cet_condition, "^([[:digit:]]|\\s|-)*"))
  cet_years <- sapply(
    strsplit(cet_condition, split = " ", fixed = T), function(x) {
      strsplit(x, split = "-")[[1]][1]
    })
  cet_months <- sapply(
    strsplit(cet_condition, split = " ", fixed = T), function(x) {
      strsplit(x, split = "-")[[1]][2]
    })
  cet_months <- gsub("^0", "", cet_months)
  cet_days <- sapply(
    strsplit(cet_condition, split = " ", fixed = T), function(x) {
      strsplit(x, split = "-")[[1]][3]
    })
  cet_days <- gsub("^0", "", cet_days)
  cet_hours <- sapply(strsplit(cet_condition, split = " ", fixed = T), 
                      function(x) {
                        x[2]
                      })
  cet_hours <- gsub("^0", "", cet_hours)
  datetimeCondition <- paste0(
    "year = ", cet_years, " AND ",
    "month = ", cet_months, " AND ",
    "day = ", cet_days, " AND ", 
    "hour = ", cet_hours
  )
  datetimeCondition <- paste("(", 
                             datetimeCondition, 
                             ")",
                             collapse = " OR ", 
                             sep = "")
  
  # - WHERE condition: create uri_path_condition
  if (length(uri_path) > 1) {
    uri_path_condition <- paste0("(",
                                 paste(
                                   paste0("uri_path = '", uri_path, "'"),
                                   collapse = " OR ", sep = " "),
                                 ")"
    )
  } else {
    uri_path_condition = paste0("uri_path = '", uri_path, "'")
  }
  
  # - WHERE condition: create uri_host_condition
  if (length(uri_host) > 1) {
    uri_host_condition <- paste0("(",
                                 paste(
                                   paste0("uri_host = '", uri_host, "'"),
                                   collapse = " OR ", sep = " "),
                                 ")"
    )
  } else {
    uri_host_condition = paste0("uri_host = '", uri_host, "'")
  }
  
  # - compose HiveQL query
  hiveQuery <- paste0(
    "USE wmf;
    SELECT uri_path, uri_query, referer FROM webrequest
    WHERE (",
    uri_host_condition, " AND ",
    uri_path_condition, " AND ",
    "(", datetimeCondition, ")",
    ");"
    )
  
  # - write hql
  write(hiveQuery, queryFile)
  # - execute hql script:
  hiveArgs <- '/usr/local/bin/beeline -f'
  hiveInput <- paste0(queryFile, ' > ', fileName)
  # - command:
  hiveCommand <- paste(hiveArgs, hiveInput)
  return(
    system(command = hiveCommand, wait = TRUE))
}

# - set params to wmde_collect_pageviews
# - for the Autumn Banner Campaign 2018
uri_host <- c('de.wikipedia.org', 'de.m.wikipedia.org')
uri_path  <- c(
  '/wiki/Wikipedia:Wikipedia_vor_Ort_2018',
  '/wiki/Wikipedia:Wikimedia_Deutschland/LerneWikipedia', 
  '/wiki/Wikipedia:Wikipedia_vor_Ort_2018/Berlin', 
  '/wiki/Wikipedia:Wikipedia_vor_Ort_2018/Hannover', 
  '/wiki/Wikipedia:Wikipedia_vor_Ort_2018/Köln', 
  '/wiki/Wikipedia:Wikipedia_vor_Ort_2018/Stuttgart', 
  '/wiki/Wikipedia:Wikipedia_vor_Ort_2018/Ulm', 
  '/wiki/Wikipedia:Aktionstag_Wikipedia_2018/München', 
  '/wiki/Wikipedia:Wikipedia_vor_Ort_2018/Augsburg', 
  '/wiki/Wikipedia:Wikipedia_vor_Ort_2018/Wien', 
  '/wiki/Wikipedia:Wikipedia_vor_Ort_2018/Linz', 
  '/wiki/Wikipedia:Wikipedia_vor_Ort_2018/Zürich')
queryFile <- 'abc2018_Pageviews.hql'
fileName <- paste0("pageviews_", cetDay, ".tsv")
dataDir <- '/home/goransm/RScripts/NewEditors/2018_AutumnBannerCampaign/_data'

# - collect Pageviews data
wmde_collect_pageviews(uri_host,
                       uri_path,
                       cetDay,
                       queryFile,
                       fileName,
                       dataDir)

### --- Wrangle Pageviews
# - function: wmde_process_pageviews
wmde_process_pageviews <- function(fileName,
                                   dataDir, 
                                   uri_query_filter, 
                                   cetDay = cetDay,
                                   campaignName = campaignName) {
  
  # - to dataDir
  setwd(dataDir)
  
  # - libraries
  library(stringr)
  library(dplyr)
  library(tidyr)
  library(data.table)

  # - load
  pageviewsData <- readLines(fileName)
  wStart <- which(grepl("^uri_path", pageviewsData))
  pageviewsData <- pageviewsData[(wStart + 2):(length(pageviewsData) - 2)]
  pageviewsData <- data.frame(dat = pageviewsData, 
                              stringsAsFactors = F)
  pageviewsData <- separate(pageviewsData,
                            dat,
                            into = c('uri_path', 'uri_query', 'referer'),
                            sep = "\t")
  # - apply uri_query_filter
  # - NOTE: Autumn 2018, looking in both: uri_query, referer
  w_uri_query <- which(grepl(uri_query_filter, pageviewsData$uri_query))
  w_uri_query_referer <- which(grepl(uri_query_filter, pageviewsData$referer))
  w_uri_query <- unique(c(w_uri_query, w_uri_query_referer))
  pageviewsData <- pageviewsData[w_uri_query, ]
  w_uri_query_referer <- which(grepl(uri_query_filter, pageviewsData$referer))
  w_uri_query_referer_delete <- setdiff(1:dim(pageviewsData)[1], w_uri_query_referer)
  pageviewsData$referer[w_uri_query_referer_delete] <- ''
  # - when there is no uri_query, use the query from the referer field if present there
  pageviewsData$referer <- str_extract(pageviewsData$referer, "\\?campaign=.*$")
  pageviewsData$referer[is.na(pageviewsData$referer)] <- ""
  pageviewsData$referer <- gsub("?campaign=", "", pageviewsData$referer, fixed = T)
  pageviewsData$uri_query <- gsub("?campaign=", "", pageviewsData$uri_query, fixed = T)
  pageviewsData$uri_query[pageviewsData$uri_query == ""] <- 
    pageviewsData$referer[pageviewsData$uri_query == ""]
  pageviewsData <- dplyr::filter(pageviewsData, 
                          uri_query != "")
  pageviewsData$referer <- NULL
  # - clean up a bit:
  pageviewsData$uri_query <- gsub("/.*$", "", pageviewsData$uri_query)
  
  # - aggregate:
  pageviewsData <- pageviewsData %>% 
    dplyr::group_by(uri_query, uri_path) %>% 
    dplyr::summarise(pageviews = n())
  colnames(pageviewsData) <- c('Tag', 'Page', 'Pageviews')
  
  # - add cetDay, campaignName
  pageviewsData$date <- cetDay
  pageviewsData$campaign <- campaignName

  # - store:
  write.csv(pageviewsData, 
            paste0("pageviewsAggregated_",
                   strsplit(
                     strsplit(fileName, split = "_", fixed = T)[[1]][2],
                     split = ".", 
                     fixed = T)[[1]][1],
                   ".csv"
            )
  )
  
}

# - set params to wmde_process_pageviews
# - for the Autumn Banner Campaign 2018
uri_query_filter <- 'WMDE_neweditors_autumn_2018'

# - wrangle pageviews
wmde_process_pageviews(fileName = fileName,
                       dataDir = dataDir,
                       uri_query_filter = uri_query_filter, 
                       cetDay = cetDay,
                       campaignName = campaignName) 


### --- Collect User Registrations

# - function: wmde_collect_registrations
wmde_collect_registrations <- function(logSchema, 
                                       web_host,
                                       event_campaign, 
                                       cetDay,
                                       dataDir, 
                                       fileName, 
                                       campaignName) {
  
  # - WHERE condition: create start_timestamp, stop_timestamp
  cet_condition <- seq(
    from = as.POSIXct(paste0(cetDay," 0:00"), tz = "Europe/Berlin"),
    to = as.POSIXct(paste0(cetDay," 24:00"), tz = "Europe/Berlin"),
    by = "hour"
  ) 
  attr(cet_condition, "tzone") <- "UTC"
  cet_condition <- as.character(cet_condition)
  start_timestamp <- paste(
    unlist(str_extract_all(cet_condition[1],
                    "[[:digit:]]")),
    collapse = "")
  stop_timestamp <- paste(
    unlist(str_extract_all(tail(cet_condition, 1),
                           "[[:digit:]]")),
    collapse = "")
  
  # - WHERE condition: create event_campaign_condition
  if (length(event_campaign) > 1) {
    event_campaign_condition <- paste0("(",
                                       paste(
                                         paste0("event_campaign LIKE '%", event_campaign, "%'"),
                                         collapse = " OR ", sep = " "),
                                       ")"
    )
  } else {
    event_campaign_condition = paste0("event_campaign LIKE '%", event_campaign, "%'")
  }
  

  # - compose SQL query:
  sqlParams <- 'mysql --defaults-file=/etc/mysql/conf.d/analytics-research-client.cnf -h analytics-slave.eqiad.wmnet -A -e'
  query <- paste0(
    "\"SELECT * FROM ", 
    paste0("log.", logSchema), 
    " WHERE ((webHost = '", 
    web_host, 
    "') AND (timestamp > ", 
    start_timestamp, 
    ") AND (timestamp <= ", 
    stop_timestamp, 
    ") AND (", 
    event_campaign_condition,
    "));\"")
  sqlOutput <- paste0("> ", paste0(dataDir, "/", fileName))
    
  # - run command
  qCommand <- paste(sqlParams, query, sqlOutput, sep = " ")
  system(command = qCommand, wait = TRUE)
  
  # - to dataDir
  setwd(dataDir)
  
  # - libraries
  library(stringr)
  library(dplyr)
  library(tidyr)
  library(data.table)
  
  # - load
  userReg <- fread(fileName, sep = "\t")
  
  # - filter bots
  wBot <- which(grepl("\"is_bot\": true", userReg$userAgent))
  if (length(wBot) > 0) {
    userReg <- userReg[-wBot, ]
  }
  
  # - select fields
  userReg <- userReg %>% 
    dplyr::select(event_userId, 
                  event_userName, 
                  event_isSelfMade, 
                  event_campaign, 
                  timestamp)
  
  # - add cetDay, campaignName
  userReg$date <- cetDay
  userReg$campaign <- campaignName
  
  # - store:
  write.csv(userReg, 
            paste0(
              strsplit(fileName, split = ".", fixed = T)[[1]][1],
            ".csv")
  )
  
  # - remove temp .tsv file
  file.remove(fileName)
  
}

# - set params for: wmde_collect_registrations
logSchema <- 'ServerSideAccountCreation_17719237' 
web_host <- 'de.wikipedia.org'
event_campaign <- 'WMDE_neweditors_autumn_2018'
fileName <- paste0("userRegistrations_", cetDay, ".tsv")

# - collect user registrations
wmde_collect_registrations(logSchema = logSchema,
                           web_host = web_host,
                           event_campaign = event_campaign,
                           cetDay = cetDay,
                           dataDir = dataDir,
                           fileName = fileName, 
                           campaignName = campaignName)

### --- Wrangle User Registrations
# - function: wmde_process_registrations
wmde_process_registrations <- function(fileName,
                                       dataDir, 
                                       cetDay, 
                                       campaignName) {
  
  # - to dataDir
  setwd(dataDir)
  
  # - libraries
  library(dplyr)
  library(data.table)

  # - load
  userReg <- fread(fileName)
  
  # - agregate
  userReg <- userReg %>% 
    dplyr::select(event_campaign) %>% 
    dplyr::group_by(event_campaign) %>% 
    dplyr::summarise(Registrations = n())

  # - add cetDay, campaignName
  userReg$date <- cetDay
  userReg$campaign <- campaignName
  
  # - store:
  write.csv(userReg, 
            paste0('userRegistrationsAggreagted_', cetDay, ".csv")
  )
  
}

# - set params for: wmde_process_registrations
fileName <- paste0("userRegistrations_", cetDay, ".csv")

# - wrangle user registrations:
wmde_process_registrations(fileName,
                           dataDir,
                           cetDay,
                           campaignName)

### --- Collect Newsletter registrations - for 2018_AuBC excl.
# - ServerSideAccountCreation_17719237 schema
qCommand <- "mysql --defaults-file=/etc/mysql/conf.d/analytics-research-client.cnf -h analytics-slave.eqiad.wmnet -A -e \"select * from log.ServerSideAccountCreation_17719237 where ((webHost = 'de.wikipedia.org') and (timestamp >= 20181011000000) and (event_campaign like '%WMDE_neweditors_autumn_2018_lpn%'));\" > /home/goransm/RScripts/NewEditors/2018_AutumnBannerCampaign/_data/Newsletter_userRegistrations.tsv"
system(command = qCommand, wait = TRUE)
### --- Wrangle Newsletter registrations - for 2018_AuBC excl.
# - function: wmde_process_registrations_general
wmde_process_registrations_general <- function(fileName,
                                       dataDir, 
                                       cetDay, 
                                       campaignName, 
                                       outFileName) {
  
  # - to dataDir
  setwd(dataDir)
  
  # - libraries
  library(dplyr)
  library(data.table)
  
  # - load
  userReg <- fread(fileName)
  
  # - agregate
  userReg <- userReg %>% 
    dplyr::select(event_campaign) %>% 
    dplyr::group_by(event_campaign) %>% 
    dplyr::summarise(Registrations = n())
  
  # - add cetDay, campaignName
  userReg$date <- cetDay
  userReg$campaign <- campaignName
  
  # - store:
  write.csv(userReg, 
            outFileName
  )
  
}

# - set params for: wmde_process_registrations
# - wrangle user registrations:
fileName = 'Newsletter_userRegistrations.tsv'
outFileName <- 'all_Newsletter_userRegistrations.csv'
wmde_process_registrations_general(fileName = fileName,
                                   dataDir = dataDir,
                                   cetDay = cetDay,
                                   campaignName = campaignName, 
                                   outFileName = outFileName)

0.2 Data Aggregation

NOTE: Not run from this report; the data were already pre-processed and aggregated by the following R script before being submitted to analytical procedures.

### --- Report Generation for the Autumn Banner Campaign 2018
### --- run locally

### --- to data directory
dataDir <- 
  '/home/goransm/Work/___DataKolektiv/Projects/WikimediaDEU/_WMDE_Projects/_misc/NewEditors_Team/2018_AutumnBannerCampaign/_data/'
analyticsDir <- 
  '/home/goransm/Work/___DataKolektiv/Projects/WikimediaDEU/_WMDE_Projects/_misc/NewEditors_Team/2018_AutumnBannerCampaign/_analytics/'
setwd(analyticsDir)

### --- Report Banner Impression Data

# - function: wmde_report_banner_impressions
wmde_report_banner_impressions <- function(dataDir) {
  
  # - Setup
  library(data.table)
  library(dplyr)

  # - list files:
  lF <- list.files(dataDir)
  
  # - filter aggregated banner impression data
  lF <- lF[grepl("bannerImpressionsAggregated_", lF, fixed = T)]
  
  # - load files and merge
  bannerData <- vector(mode = "list", length = length(lF))
  for (i in 1:length(lF)) {
    if (grepl("csv$|tsv$", lF[i])) {
      bannerData[[i]] <- fread(paste0(dataDir, lF[i]))
    } else {
      bannerData[[i]] <- NULL
    }
  }
  bannerData <- rbindlist(bannerData)
  bannerData$V1 <- NULL
  
  # - aggregates
  perBannerTotals <- bannerData %>% 
    select(banner, impressions) %>% 
    group_by(banner) %>% 
    summarise(totalImpressions = sum(impressions))
  perDayTotals <- bannerData %>% 
    select(date, impressions) %>% 
    group_by(date) %>% 
    summarise(totalImpressions = sum(impressions))
  
  # - output
  return(
    list(bannerImpressionsReport = bannerData, 
         perBannerTotals = perBannerTotals, 
         perDayTotals = perDayTotals)
  )
    
}

# - Report banner impressions
bannerImpressionsData <- wmde_report_banner_impressions(dataDir)
bannerImpressionsFile <- bannerImpressionsData$bannerImpressionsReport
write.csv(bannerImpressionsFile, "bannerImpressionsFile.csv")
bannerTotals <- bannerImpressionsData$perBannerTotals
write.csv(bannerTotals, "bannerTotals.csv")
bannerDayTotals <- bannerImpressionsData$perDayTotals
write.csv(bannerDayTotals, "bannerDayTotals.csv")


### --- Report Pageviews Data

# - function: wmde_report_pageviews
wmde_report_pageviews <- function(dataDir) {
  
  # - Setup
  library(data.table)
  library(dplyr)
  
  # - list files:
  lF <- list.files(dataDir)
  
  # - filter aggregated banner impression data
  lF <- lF[grepl("pageviewsAggregated_", lF, fixed = T)]
  
  # - load files and merge
  pageviewsData <- vector(mode = "list", length = length(lF))
  for (i in 1:length(lF)) {
    if (grepl("csv$|tsv$", lF[i])) {
      pageviewsData[[i]] <- fread(paste0(dataDir, lF[i]))
    } else {
      pageviewsData[[i]] <- NULL
    }
  }
  pageviewsData <- rbindlist(pageviewsData)
  pageviewsData$V1 <- NULL
  
  # - aggregates
  perDayTotals <- pageviewsData %>% 
    select(date, Pageviews) %>% 
    group_by(date) %>% 
    summarise(totalPageviews = sum(Pageviews))
  perTagTotals <- pageviewsData %>% 
    select(Tag, Pageviews) %>% 
    group_by(Tag) %>% 
    summarise(totalPageviews = sum(Pageviews))
  perPageTotals <- pageviewsData %>% 
    select(Page, Pageviews) %>% 
    group_by(Page) %>% 
    summarise(totalPageviews = sum(Pageviews))
  perPageDayTotals <- pageviewsData %>% 
    select(Page, date, Pageviews) %>%
    group_by(Page, date) %>% 
    summarise(totalPageviews = sum(Pageviews))
  perTagDayTotals <- pageviewsData %>% 
    select(Tag, date, Pageviews) %>%
    group_by(Tag, date) %>% 
    summarise(totalPageviews = sum(Pageviews))
  perTagPageTotals <- pageviewsData %>% 
    select(Tag, Page, Pageviews) %>%
    group_by(Tag, Page) %>% 
    summarise(totalPageviews = sum(Pageviews))
    
  # - output
  return(
    list(pageviewsDataReport = pageviewsData, 
         perDayTotals = perDayTotals, 
         perTagTotals = perTagTotals, 
         perPageTotals = perPageTotals, 
         perPageDayTotals = perPageDayTotals, 
         perTagDayTotals = perTagDayTotals,
         perTagPageTotals = perTagPageTotals)
  )
  
}

# - Report pageviews:
pageviewsData <- wmde_report_pageviews(dataDir)
pageviewsReportFile <- pageviewsData$pageviewsDataReport
write.csv(pageviewsReportFile, "pageviewsReportFile.csv")
pageviews_perDayTotals <- pageviewsData$perDayTotals
write.csv(pageviews_perDayTotals, "pageviews_perDayTotals.csv")
pageviews_perTagTotals <- pageviewsData$perTagTotals
write.csv(pageviews_perTagTotals, "pageviews_perTagTotals.csv")
pageviews_perPageTotals <- pageviewsData$perPageTotals
write.csv(pageviews_perPageTotals, "pageviews_perPageTotals.csv")
pageviews_perPageDayTotals <- pageviewsData$perPageDayTotals
write.csv(pageviews_perPageDayTotals, "pageviews_perPageDayTotals.csv")
pageviews_perTagDayTotals <- pageviewsData$perTagDayTotals
write.csv(pageviews_perTagDayTotals, "pageviews_perTagDayTotals.csv")
pageviews_perTagPageTotals <- pageviewsData$perTagPageTotals
write.csv(pageviews_perTagPageTotals, "perTagPageTotals.csv")

### --- Report User Registrations

# - function: wmde_report_registrations
wmde_report_registrations <- function(dataDir) {
  
  # - Setup
  library(data.table)
  library(dplyr)
  
  # - list files:
  lF <- list.files(dataDir)
  
  # - filter aggregated user registration data
  lF <- lF[grepl("userRegistrationsAggreagted_", lF, fixed = T)]
  
  # - load files and merge
  registrationData <- vector(mode = "list", length = length(lF))
  for (i in 1:length(lF)) {
    if (grepl("csv$|tsv$", lF[i])) {
      registrationData[[i]] <- fread(paste0(dataDir, lF[i]))
    } else {
      registrationData[[i]] <- NULL
    }
  }
  registrationData <- rbindlist(registrationData)
  registrationData$V1 <- NULL
  
  # - aggregates
  perDayTotals <- registrationData %>% 
    select(date, Registrations) %>% 
    group_by(date) %>% 
    summarise(totalRegistrations = sum(Registrations))
  perTagTotals <- registrationData %>% 
    select(event_campaign, Registrations) %>% 
    group_by(event_campaign) %>% 
    summarise(totalRegistrations = sum(Registrations))
  perTagDayTotals <- registrationData %>% 
    select(event_campaign, date, Registrations) %>% 
    group_by(event_campaign, date) %>% 
    summarise(totalRegistrations = sum(Registrations))
  
  # - output
  return(
    list(registrationsDataReport = registrationData, 
         perDayTotals = perDayTotals, 
         perTagTotals = perTagTotals, 
         perTagDayTotals = perTagDayTotals)
  )
  
}

# - Report upon user registrations
userRegData <- wmde_report_registrations(dataDir)
userRegistrationsReportFile <- userRegData$registrationsDataReport
write.csv(userRegistrationsReportFile, "userRegistrationsReportFile.csv")
userRegistrations_perDayTotals <- userRegData$perDayTotals
write.csv(userRegistrations_perDayTotals, "userRegistrations_perDayTotals.csv")
userRegistrations_perTagTotals <- userRegData$perTagTotals
write.csv(userRegistrations_perTagTotals, "userRegistrations_perTagTotals.csv")
userRegistrations_perTagDayTotals <- userRegData$perTagDayTotals
write.csv(userRegistrations_perTagDayTotals, "userRegistrations_perTagDayTotals.csv")

1. Campaign Banners and Pages

This section presents all data and statistics on the campaign banners and pages.

1.2 Pageviews

1.2.1 Pageviews Overview

Chart 1.2.1. Pageviews Overview. Log scaling of the pageviews is necessary; the numbers reported in the data point labels are exact.

dataSet <- read.csv(
  '_analytics/pageviews_perPageDayTotals.csv',
                    header = T,
                    row.names = 1,
                    check.names = F,
                    stringsAsFactors = F)
dataSet$Page <- gsub("/wiki/Wikipedia:Wikipedia_|/wiki/Wikipedia:Wikimedia_", "", dataSet$Page)
# - Visualize w. {ggplot2}
ggplot(dataSet, aes(x = date,
                    y = log10(totalPageviews),
                    group = Page,
                    color = Page,
                    fill = Page,
                    label = totalPageviews,
                    )) + 
  geom_path(size = .25) + 
  geom_point(size = 1.5) +
  geom_point(size = 1, color = "white") +
  scale_y_continuous(labels = comma) +
  ggtitle('Autumn Banner Campaign 2018: Pageviews') +
  theme_minimal() + 
  geom_text_repel(size = 3.5, show.legend = FALSE) + 
  scale_y_continuous(labels = comma) +
  theme(axis.text.x = element_text(angle = 90, size = 8)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) + 
  theme(legend.position = "top")
Scale for 'y' is already present. Adding another scale for 'y', which will replace the existing scale.

1.2.1 Pageviews Overview: Table

Table 1.2.1. Pageviews Overview

### --- Full Dataset (Table Report)
datatable(dataSet %>% arrange(desc(totalPageviews)))

1.2.2 Pageviews Overview: totals per Page

Chart 1.2.2. Pageviews Overview: totals per Page

dataSet <- read.csv(
  '_analytics/pageviews_perPageTotals.csv',
                    header = T,
                    row.names = 1,
                    check.names = F,
                    stringsAsFactors = F)
dataSet$Page <- gsub("/wiki/Wikipedia:Wikipedia_|/wiki/Wikipedia:Wikimedia_", "", dataSet$Page)
dataSet$Page <- factor(dataSet$Page, 
                       levels = dataSet$Page[order(-dataSet$totalPageviews)])
ggplot(dataSet, aes(x = Page, 
                    y = log(totalPageviews), 
                    color = Page,
                    fill = Page,
                    label = totalPageviews)) + 
  geom_bar(width = .5, stat = "identity") + 
  scale_y_continuous(labels = comma) +
  ggtitle('Autumn Banner Campaign 2018: Banner Total Pageviews') +
  theme_minimal() + 
  geom_label_repel(size = 3.5, color = "white", show.legend = FALSE) + 
  scale_y_continuous(labels = comma) +
  theme(axis.text.x = element_text(angle = 90, size = 8)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) + 
  theme(legend.position = "right") + 
  theme(axis.text.x = element_blank())
Scale for 'y' is already present. Adding another scale for 'y', which will replace the existing scale.

1.2.3 Pageviews Overview: totals per day

Chart 1.2.3. Pageviews Overview: totals per day

dataSet <- read.csv(
  '_analytics/pageviews_perDayTotals.csv',
                    header = T,
                    row.names = 1,
                    check.names = F,
                    stringsAsFactors = F)
ggplot(dataSet, aes(x = date, 
                    y = totalPageviews, 
                    label = totalPageviews)) +
  geom_path(size = .25, group = 1, color = "darkblue") +
  geom_point(size = 1.5, color = "darkblue") + 
  geom_point(size = 1, color = "white") + 
  scale_y_continuous(labels = comma) +
  ggtitle('Autumn Banner Campaign 2018: Pageviews') +
  theme_minimal() + 
  geom_label_repel(size = 3.5, show.legend = FALSE) + 
  scale_y_continuous(labels = comma) +
  theme(axis.text.x = element_text(angle = 90, size = 8)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) + 
  theme(legend.position = "right") + 
  theme(axis.text.x = element_text(angle = 90))
Scale for 'y' is already present. Adding another scale for 'y', which will replace the existing scale.

1.2.3 Pageviews Overview: totals per Tag/Page

Chart 1.2.3. Pageviews Overview: totals per Tag/Page

dataSet <- read.csv(
  '_analytics/perTagPageTotals.csv',
                    header = T,
                    row.names = 1,
                    check.names = F,
                    stringsAsFactors = F)
campaignTags <- c('WMDE_neweditors_autumn_2018_lp1', 
                  'WMDE_neweditors_autumn_2018_lp1m',
                  'WMDE_neweditors_autumn_2018_lpn')
dataSet <- filter(dataSet, 
                  grepl(campaignTags[1], dataSet$Tag)|grepl(campaignTags[2], dataSet$Tag)|grepl(campaignTags[3], dataSet$Tag))
dataSet$Tag[grepl(campaignTags[2], dataSet$Tag, fixed = T)] <- "lp1m"
dataSet$Tag[grepl(campaignTags[1], dataSet$Tag, fixed = T)] <- "lp1"
dataSet$Tag[grepl(campaignTags[3], dataSet$Tag, fixed = T)] <- "lp1n"
dataSet$Page <- gsub("/wiki/Wikipedia:Wikipedia_|/wiki/Wikipedia:Wikimedia_", "", dataSet$Page)
dataSet <- dataSet %>% 
  select(Tag, Page, totalPageviews) %>% 
  group_by(Tag, Page) %>% 
  summarise(totalPageviews = sum(totalPageviews))
dataSet %>% 
  ggplot(aes(x = Tag,
             y = Page,
             color = Tag,
             label = totalPageviews)) + 
  ggtitle('Autumn Banner Campaign 2018: Pageviews per Tag') +
  geom_point(aes(size = totalPageviews), shape = 19) +
  geom_text_repel(size = 3, nudge_x = .3, show.legend = F) + 
  xlab("Tags") + ylab("Pages") + 
  theme_bw() + 
  theme(panel.background = element_rect(fill = "white"))

1.2.4 Pageviews per Tag, daily totals

Chart 1.2.4. Pageviews per Tag, daily totals. Log scaling of the pageviews is necessary; the numbers reported in the data point labels are exact.

dataSet <- read.csv(
  '_analytics/pageviews_perTagDayTotals.csv',
                    header = T,
                    row.names = 1,
                    check.names = F,
                    stringsAsFactors = F)
campaignTags <- c('WMDE_neweditors_autumn_2018_lp1', 
                  'WMDE_neweditors_autumn_2018_lp1m',
                  'WMDE_neweditors_autumn_2018_lpn')
dataSet <- filter(dataSet, 
                  grepl(campaignTags[1], dataSet$Tag)|grepl(campaignTags[2], dataSet$Tag)|grepl(campaignTags[3], dataSet$Tag))
dataSet$Tag[grepl(campaignTags[2], dataSet$Tag, fixed = T)] <- "lp1m"
dataSet$Tag[grepl(campaignTags[1], dataSet$Tag, fixed = T)] <- "lp1"
dataSet$Tag[grepl(campaignTags[3], dataSet$Tag, fixed = T)] <- "lp1n"# - Visualize w. {ggplot2}
dataSet <- dataSet %>% 
  group_by(Tag, date) %>% 
  summarise(totalPageviews = sum(totalPageviews))
ggplot(dataSet, aes(x = date,
                    y = log10(totalPageviews),
                    group = Tag,
                    color = Tag,
                    fill = Tag,
                    label = totalPageviews,
                    )) + 
  geom_path(size = .25) + 
  geom_point(size = 1.5) +
  geom_point(size = 1, color = "white") +
  scale_y_continuous(labels = comma) +
  ggtitle('Autumn Banner Campaign 2018: Pageviews per Tag, daily totals') +
  theme_minimal() + 
  geom_text_repel(size = 3.5, show.legend = FALSE) + 
  scale_y_continuous(labels = comma) +
  theme(axis.text.x = element_text(angle = 90, size = 8)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) + 
  theme(legend.position = "top")
Scale for 'y' is already present. Adding another scale for 'y', which will replace the existing scale.

2. User Registrations

All data on user registrations are presented in this section.

2.1 Registrations per tag and day

Chart 2.1 Registrations per tag and day. Please note: points with no data labels signify 0 user registrations.

# - Standard registrations
dataSet <- read.csv(
  '_analytics/userRegistrationsReportFile.csv',
                    header = T,
                    row.names = 1,
                    check.names = F,
                    stringsAsFactors = F)
dataSet <- dataSet %>% 
  filter(!(event_campaign %in% 'WMDE_neweditors_autumn_2018_lpn'))
dataSet$campaign <- NULL
# - Newsletter registrations
dataSet2 <- read.delim(
  '_analytics/Newsletter_userRegistrations.tsv',
  sep = "\t",
  header = T,
  row.names = 1,
  check.names = F,
  stringsAsFactors = F)
dataSet2 <- dataSet2 %>% 
  select(event_campaign, timestamp)
dataSet2$timestamp <- as.character(dataSet2$timestamp)
dataSet2$timestamp <- sapply(dataSet2$timestamp, function(x) {
  year <- substr(x, 1, 4)
  month <- substr(x, 5, 6)
  day <- substr(x, 7, 8)
  paste(year, month, day, sep = "-")
})
dataSet2 <- dataSet2 %>% 
  group_by(event_campaign, timestamp) %>% 
  summarise(Registrations = n())
colnames(dataSet2)[2] <- "date"
dataSet2 <- dataSet2[, c(1, 3, 2)]
dataSet <- rbindlist(list(dataSet, dataSet2))
dataSet$event_campaign <- gsub("WMDE_neweditors_autumn_2018_", "", dataSet$event_campaign)
ggplot(dataSet, aes(x = date,
                    y = Registrations,
                    group = event_campaign,
                    color = event_campaign,
                    fill = event_campaign,
                    label = Registrations,
                    )) + 
  geom_path(size = .25) + 
  geom_point(size = 1.5) +
  geom_point(size = 1, color = "white") +
  scale_y_continuous(labels = comma) +
  ggtitle('Autumn Banner Campaign 2018: Registrations per Tag') +
  theme_minimal() + 
  geom_text_repel(size = 3.5, show.legend = F) + 
  scale_y_continuous(labels = comma) +
  theme(axis.text.x = element_text(angle = 90, size = 8)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) + 
  theme(legend.position = "top")
Scale for 'y' is already present. Adding another scale for 'y', which will replace the existing scale.

### --- Full Dataset (Table Report)
colnames(dataSet)[1] <- 'Tag'
datatable(dataSet %>% arrange(Tag, date, desc(Registrations)))

2.2 Total registrations per tag

Chart 2.2 Total registrations per tag.

dataSet <- dataSet %>% 
  group_by(Tag) %>% 
  summarise(totalRegistrations = sum(Registrations))
ggplot(dataSet, aes(x = Tag, 
                    y = totalRegistrations, 
                    color = Tag,
                    fill = Tag,
                    label = totalRegistrations)) + 
  geom_bar(width = .5, stat = "identity") + 
  scale_y_continuous(labels = comma) +
  ggtitle('Autumn Banner Campaign 2018: Total Registrations per Tag') +
  theme_minimal() + 
  geom_label_repel(size = 3.5, color = "white", show.legend = FALSE) + 
  scale_y_continuous(labels = comma) + 
  ylab("Registrations") +
  theme(axis.text.x = element_text(angle = 90, size = 8)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) + 
  theme(legend.position = "right")
Scale for 'y' is already present. Adding another scale for 'y', which will replace the existing scale.

3. User Edits

All data on user edits are presented in this section.

3.1 User edits: distribution

dataSet <- read.csv(
  '_analytics/allUserEdits_2018_AuBC.csv',
                    header = T,
                    row.names = 1,
                    check.names = F,
                    stringsAsFactors = F)
# - Edit | 1 | 2-4 | 5-9 | 10-49 | >50
editBoundaries <- list(
  c(0, 1), 
  c(2, 4),
  c(5, 9),
  c(10, 49)
)
dataSet$editClass <- sapply(dataSet$edits, function(x) {
  wEC <- sapply(editBoundaries, function(y) {
    x >= y[1] & x <= y[2]
  })
  if (sum(wEC) == 0) {
    return(">= 50")
  } else {
    return(paste0("(",
                  editBoundaries[[which(wEC)]][1],
                  " - ",
                  editBoundaries[[which(wEC)]][2], 
                  ")"
                  )
    )
  }
})
editClass <- as.data.frame(table(dataSet$editClass), 
                           stringsAsFactors = F)
colnames(editClass) <- c('Edit Class', 'Num.Users')
editClass$order <- as.numeric(sapply(editClass$`Edit Class`, function(x) {
  lower <- str_extract(x, '[[:digit:]]+')
}))
editClass <- arrange(editClass, order)
editClass$order <- NULL
datatable(editClass)
LS0tCnRpdGxlOiAnU3VtbWVyIEJhbm5lciBDYW1wYWlnbiAyMDE4OiBSZXBvcnQnCmF1dGhvcjogIkdvcmFuIFMuIE1pbG92YW5vdmljLCBEYXRhIFNjaWVudGlzdCwgV01ERSIKZGF0ZTogIk9jdG9iZXIgMzAsIDIwMTgiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICB0aGVtZTogc2ltcGxleAogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICB0b2NfZGVwdGg6IDUKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNQotLS0KCioqRmVlZGJhY2sqKiBzaG91bGQgYmUgc2VuZCB0byBgZ29yYW4ubWlsb3Zhbm92aWNfZXh0QHdpa2ltZWRpYS5kZWAuIAoKVGhlIGNhbXBhaWduIGlzIHJ1biBmcm9tIDIwMTgvMTAvMTkgdG8gMjAxOC8xMC8yOC4KCioqQ1VSUkVOVCBVUERBVEU6KiogQ29tcGxldGUgZGF0YXNldCBhcyBvZiAyMDE4LzEwLzI5LgoKYGBge3IsIGVjaG8gPSBGLCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRiwgcmVzdWx0cyA9ICdoaWRlJ30KIyAhZGlhZ25vc3RpY3Mgb2ZmCiMjIyAtLS0gU2V0dXAKa25pdHI6Om9wdHNfY2h1bmskc2V0KGZpZy53aWR0aCA9IDE1LCBmaWcuaGVpZ2h0ID0gOCkgCnJtKGxpc3QgPSBscygpKQpsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdncmVwZWwpCmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKbGlicmFyeShrYWJsZUV4dHJhKQpsaWJyYXJ5KHJtYXJrZG93bikKbGlicmFyeShrbml0cikKbGlicmFyeShEVCkKbGlicmFyeShyZXNoYXBlMikKIyMjIC0tLSBBbmFseXRpY3MgZGF0YSBkaXJlY3RvcnkKYW5hbHl0aWNzRGlyIDwtIAogICcvX2FuYWx5dGljcy8nCmBgYAoKIyMgMC4gRGF0YSBBY3F1aXNpdG9uCgpUaGUgZGF0YSBhY3F1aXNpdGlvbiBhbmQgYWdncmVnYXRpb24gYFJgIGNvZGUuCgojIyMgMC4xIERhaWx5IFVwZGF0ZQoKKipOT1RFOioqIHRoZSBEYXRhIEFjcXVpc2l0aW9uIGNvZGUgY2h1bmsgaXMgbm90IGZ1bGx5IHJlcHJvZHVjaWJsZSBmcm9tIHRoaXMgUmVwb3J0LiBUaGUgZGF0YSBhcmUgY29sbGVjdGVkIGJ5IHJ1bm5pbmcgdGhlIHNjcmlwdCBgMjAxOF9BQkNfUHJvZHVjdGlvbi5SYCBvbiBzdGF0MTAwNS5lcWlhZC53bW5ldCwgY29sbGVjdGluZyB0aGUgZGF0YSBhcyBgLnRzdmAgYW5kIGAuY3N2YCBmaWxlcywgY29weWluZyBtYW51YWxseSwgYW5kIHByb2Nlc3NpbmcgbG9jYWxseS4gCgpgYGB7ciwgZWNobyA9IFQsIGV2YWwgPSBGfQoKIyMjIC0tLSBEYXRhIEFjcXVpc2l0aW9uIGZvciB0aGUgQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE4CiMjIyAtLS0gcnVuIGZyb20gc3RhdDEwMDUKIyMjIC0tLSAvaG9tZS9nb3JhbnNtL1JTY3JpcHRzL05ld0VkaXRvcnMvMjAxOF9BdXR1bW5CYW5uZXJDYW1wYWlnbi9fZGF0YQoKIyMjIC0tLSB0byBkYXRhIGRpcmVjdG9yeQpkYXRhRGlyIDwtICcvaG9tZS9nb3JhbnNtL1JTY3JpcHRzL05ld0VkaXRvcnMvMjAxOF9BdXR1bW5CYW5uZXJDYW1wYWlnbi9fZGF0YScKc2V0d2QoZGF0YURpcikKCiMjIyAtLS0gZGV0ZXJtaW5lIGNldERheQpsaWJyYXJ5KGx1YnJpZGF0ZSkKY2V0RGF5IDwtIFN5cy50aW1lKCkKY2V0RGF5CmF0dHIoY2V0RGF5LCAidHpvbmUiKSA8LSAiRXVyb3BlL0JlcmxpbiIKIyAtIG9uZSBkYXkgYmVoaW5kIGZvciBjcm9udGFiCiMgLSAoaS5lLiB3YWl0aW5nIGZvciB3bWYud2VicmVxdWVzdCB0byBjb21wbGV0ZSBpcyBkYXRhIGFjcXVpc2l0aW9uKQpjZXREYXkgPC0geW1kKAogIHN0cnNwbGl0KGFzLmNoYXJhY3RlcihjZXREYXkpLCAKICAgICAgICAgICBzcGxpdCA9ICIgIiwgCiAgICAgICAgICAgZml4ZWQgPSBUKVtbMV1dWzFdCiAgKSAtIDEKCiMjIyAtLS0gQ29sbGVjdCBCYW5uZXIgSW1wcmVzc2lvbiBEYXRhCgojIC0gZnVuY3Rpb246IHdtZGVfY29sbGVjdF9iYW5uZXJfaW1wcmVzc2lvbnMKd21kZV9jb2xsZWN0X2Jhbm5lcl9pbXByZXNzaW9ucyA8LSBmdW5jdGlvbih1cmlfaG9zdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXJpX3BhdGgsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVyaV9xdWVyeSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2V0RGF5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1ZXJ5RmlsZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlTmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyKSB7CiAgCiAgIyAtIE5PVEU6CiAgIyAtIGV4cGVjdGVkIGZvcm1hdCBmb3IgY2V0RGF5IGlzOiBZWVlZLU1NLURECiAgCiAgIyAtIHRvIGRhdGFEaXIKICBzZXR3ZChkYXRhRGlyKQogIAogICMgLSBsaWJyYXJpZXMKICBsaWJyYXJ5KHN0cmluZ3IpCiAgCiAgIyAtIFdIRVJFIGNvbmRpdGlvbjogY3JlYXRlIGRhdGV0aW1lX2NvbmRpdGlvbgogIGNldF9jb25kaXRpb24gPC0gc2VxKAogICAgZnJvbSA9IGFzLlBPU0lYY3QocGFzdGUwKGNldERheSwiIDA6MDAiKSwgdHogPSAiRXVyb3BlL0JlcmxpbiIpLAogICAgdG8gPSBhcy5QT1NJWGN0KHBhc3RlMChjZXREYXksIiAyMzowMCIpLCB0eiA9ICJFdXJvcGUvQmVybGluIiksCiAgICBieSA9ICJob3VyIgogICkgCiAgYXR0cihjZXRfY29uZGl0aW9uLCAidHpvbmUiKSA8LSAiVVRDIgogIGNldF9jb25kaXRpb24gPC0gYXMuY2hhcmFjdGVyKGNldF9jb25kaXRpb24pCiAgY2V0X2NvbmRpdGlvbiA8LSB1bmxpc3Qoc3RyX2V4dHJhY3RfYWxsKGNldF9jb25kaXRpb24sICJeKFtbOmRpZ2l0Ol1dfFxcc3wtKSoiKSkKICBjZXRfeWVhcnMgPC0gc2FwcGx5KAogICAgc3Ryc3BsaXQoY2V0X2NvbmRpdGlvbiwgc3BsaXQgPSAiICIsIGZpeGVkID0gVCksIGZ1bmN0aW9uKHgpIHsKICAgICAgc3Ryc3BsaXQoeCwgc3BsaXQgPSAiLSIpW1sxXV1bMV0KICAgIH0pCiAgY2V0X21vbnRocyA8LSBzYXBwbHkoCiAgICBzdHJzcGxpdChjZXRfY29uZGl0aW9uLCBzcGxpdCA9ICIgIiwgZml4ZWQgPSBUKSwgZnVuY3Rpb24oeCkgewogICAgICBzdHJzcGxpdCh4LCBzcGxpdCA9ICItIilbWzFdXVsyXQogICAgfSkKICBjZXRfbW9udGhzIDwtIGdzdWIoIl4wIiwgIiIsIGNldF9tb250aHMpCiAgY2V0X2RheXMgPC0gc2FwcGx5KAogICAgc3Ryc3BsaXQoY2V0X2NvbmRpdGlvbiwgc3BsaXQgPSAiICIsIGZpeGVkID0gVCksIGZ1bmN0aW9uKHgpIHsKICAgICAgc3Ryc3BsaXQoeCwgc3BsaXQgPSAiLSIpW1sxXV1bM10KICAgIH0pCiAgY2V0X2RheXMgPC0gZ3N1YigiXjAiLCAiIiwgY2V0X2RheXMpCiAgY2V0X2hvdXJzIDwtIHNhcHBseShzdHJzcGxpdChjZXRfY29uZGl0aW9uLCBzcGxpdCA9ICIgIiwgZml4ZWQgPSBUKSwgCiAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHhbMl0KICAgICAgICAgICAgICAgICAgICAgIH0pCiAgY2V0X2hvdXJzIDwtIGdzdWIoIl4wIiwgIiIsIGNldF9ob3VycykKICBkYXRldGltZUNvbmRpdGlvbiA8LSBwYXN0ZTAoCiAgICAieWVhciA9ICIsIGNldF95ZWFycywgIiBBTkQgIiwKICAgICJtb250aCA9ICIsIGNldF9tb250aHMsICIgQU5EICIsCiAgICAiZGF5ID0gIiwgY2V0X2RheXMsICIgQU5EICIsIAogICAgImhvdXIgPSAiLCBjZXRfaG91cnMKICApCiAgZGF0ZXRpbWVDb25kaXRpb24gPC0gcGFzdGUoIigiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRldGltZUNvbmRpdGlvbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIiBPUiAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiIikKICAKICAjIC0gV0hFUkUgY29uZGl0aW9uOiBjcmVhdGUgdXJpX3BhdGhfY29uZGl0aW9uCiAgaWYgKGxlbmd0aCh1cmlfcGF0aCkgPiAxKSB7CiAgICB1cmlfcGF0aF9jb25kaXRpb24gPC0gcGFzdGUwKCIoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJ1cmlfcGF0aCA9ICciLCB1cmlfcGF0aCwgIiciKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9ICIgT1IgIiwgc2VwID0gIiAiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICB9IGVsc2UgewogICAgdXJpX3BhdGhfY29uZGl0aW9uID0gcGFzdGUwKCJ1cmlfcGF0aCA9ICciLCB1cmlfcGF0aCwgIiciKQogIH0KICAKICAjIC0gV0hFUkUgY29uZGl0aW9uOiBjcmVhdGUgdXJpX2hvc3RfY29uZGl0aW9uCiAgaWYgKGxlbmd0aCh1cmlfaG9zdCkgPiAxKSB7CiAgICB1cmlfaG9zdF9jb25kaXRpb24gPC0gcGFzdGUwKCIoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJ1cmlfaG9zdCA9ICciLCB1cmlfaG9zdCwgIiciKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9ICIgT1IgIiwgc2VwID0gIiAiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiCiAgICApCiAgfSBlbHNlIHsKICAgIHVyaV9ob3N0X2NvbmRpdGlvbiA9IHBhc3RlMCgidXJpX2hvc3QgPSAnIiwgdXJpX2hvc3QsICInIikKICB9CiAgCiAgIyAtIFdIRVJFIGNvbmRpdGlvbjogY3JlYXRlIHVyaV9xdWVyeV9jb25kaXRpb24KICBpZiAobGVuZ3RoKHVyaV9xdWVyeSkgPiAxKSB7CiAgICB1cmlfcXVlcnlfY29uZGl0aW9uIDwtIHBhc3RlMCgiKCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMCgidXJpX3F1ZXJ5IExJS0UgJyUiLCB1cmlfcXVlcnksICIlJyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIiBPUiAiLCBzZXAgPSAiICIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiKSIKICAgICkKICB9IGVsc2UgewogICAgdXJpX3F1ZXJ5X2NvbmRpdGlvbiA9IHBhc3RlMCgidXJpX3F1ZXJ5IExJS0UgJyUiLCB1cmlfcXVlcnksICIlJyIpCiAgfQogIAogICMgLSBjb21wb3NlIEhpdmVRTCBxdWVyeQogIGhpdmVRdWVyeSA8LSBwYXN0ZTAoCiAgICAiVVNFIHdtZjsKICAgIFNFTEVDVCB1cmlfcXVlcnkgRlJPTSB3ZWJyZXF1ZXN0CiAgICBXSEVSRSAoIiwKICAgIHVyaV9ob3N0X2NvbmRpdGlvbiwgIiBBTkQgIiwKICAgIHVyaV9wYXRoX2NvbmRpdGlvbiwgIiBBTkQgIiwKICAgIHVyaV9xdWVyeV9jb25kaXRpb24sICIgQU5EICIsCiAgICAiKCIsIGRhdGV0aW1lQ29uZGl0aW9uLCAiKSIsCiAgICAiKTsiCiAgKQogIAogICMgLSB3cml0ZSBocWwKICB3cml0ZShoaXZlUXVlcnksIHF1ZXJ5RmlsZSkKICAjIC0gZXhlY3V0ZSBocWwgc2NyaXB0OgogIGhpdmVBcmdzIDwtICcvdXNyL2xvY2FsL2Jpbi9iZWVsaW5lIC1mJwogIGhpdmVJbnB1dCA8LSBwYXN0ZTAocXVlcnlGaWxlLCAnID4gJywgZmlsZU5hbWUpCiAgIyAtIGNvbW1hbmQ6CiAgaGl2ZUNvbW1hbmQgPC0gcGFzdGUoaGl2ZUFyZ3MsIGhpdmVJbnB1dCkKICByZXR1cm4oCiAgICBzeXN0ZW0oY29tbWFuZCA9IGhpdmVDb21tYW5kLCB3YWl0ID0gVFJVRSkpCn0KCiMgLSBzZXQgcGFyYW1zIHRvIHdtZGVfY29sbGVjdF9iYW5uZXJfaW1wcmVzc2lvbnMKIyAtIGZvciB0aGUgQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE4CnVyaV9ob3N0IDwtIGMoJ2RlLndpa2lwZWRpYS5vcmcnLCAnZGUubS53aWtpcGVkaWEub3JnJykKdXJpX3BhdGggIDwtICcvYmVhY29uL2ltcHJlc3Npb24nCnVyaV9xdWVyeSA8LSBjKCdXTURFX25ld2VkaXRvcnNfYXV0dW1uXzIwMThfbHBuJywgCiAgICAgICAgICAgICAgICdCMThXTURFX25ld2VkaXRvcnNfYXV0dW1uXzIwMThfbHAnCiAgICAgICAgICAgICAgICkKcXVlcnlGaWxlIDwtICdhYmMyMDE4X0Jhbm5lckltcHJlc3Npb25zLmhxbCcKZmlsZU5hbWUgPC0gcGFzdGUwKCJiYW5uZXJJbXByZXNzaW9uc18iLCBjZXREYXksICIudHN2IikKZGF0YURpciA8LSAnL2hvbWUvZ29yYW5zbS9SU2NyaXB0cy9OZXdFZGl0b3JzLzIwMThfQXV0dW1uQmFubmVyQ2FtcGFpZ24vX2RhdGEnCgojIC0gY29sbGVjdCBCYW5uZXIgSW1wcmVzc2lvbiBkYXRhCndtZGVfY29sbGVjdF9iYW5uZXJfaW1wcmVzc2lvbnModXJpX2hvc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXJpX3BhdGgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXJpX3F1ZXJ5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNldERheSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWVyeUZpbGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsZU5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YURpcikKCiMjIyAtLS0gV3JhbmdsZSBCYW5uZXIgSW1wcmVzc2lvbiBEYXRhCgojIC0gZnVuY3Rpb246IHdtZGVfcHJvY2Vzc19iYW5uZXJfaW1wcmVzc2lvbnMKd21kZV9wcm9jZXNzX2Jhbm5lcl9pbXByZXNzaW9ucyA8LSBmdW5jdGlvbihmaWxlTmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZXREYXksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbXBhaWduTmFtZSkgewogIAogICMgLSB0byBkYXRhRGlyCiAgc2V0d2QoZGF0YURpcikKICAKICAjIC0gbGlicmFyaWVzCiAgbGlicmFyeShzdHJpbmdyKQogIGxpYnJhcnkoZHBseXIpCiAgCiAgIyAtIGxvYWQKICBiYW5uZXJEYXRhIDwtIHJlYWQuZGVsaW0oZmlsZU5hbWUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKICBjb2xuYW1lcyhiYW5uZXJEYXRhKSA8LSAndXJpX3F1ZXJ5JwogIAogICMgLSBjbGVhbgogIHdTdGFydCA8LSB3aGljaChiYW5uZXJEYXRhJHVyaV9xdWVyeSA9PSAidXJpX3F1ZXJ5IikKICBiYW5uZXJEYXRhIDwtIGJhbm5lckRhdGFbKHdTdGFydCArIDEpOihkaW0oYmFubmVyRGF0YSlbMV0gLSAyKSwgXQogIAogICMgLSBzcGxpdAogIGJhbm5lckRhdGEgPC0gc3Ryc3BsaXQoYmFubmVyRGF0YSwgc3BsaXQgPSAiJiIsIGZpeGVkID0gVCkKICAjIC0gZXh0cmFjdCByZWxldmFudCBmaWVsZHMKICAjIC0gYmFubmVyOgogIGJhbm5lciA8LSBzYXBwbHkoYmFubmVyRGF0YSwgZnVuY3Rpb24oeCkgewogICAgeFt3aGljaChncmVwbCgiXmJhbm5lcj0iLCB4KSldCiAgfSkKICBiYW5uZXIgPC0gZ3N1YigiXmJhbm5lcj0iLCAiIiwgYmFubmVyKQogICMgLSByZWNvcmRJbXByZXNzaW9uU2FtcGxlUmF0ZToKICByZWNvcmRJbXByZXNzaW9uU2FtcGxlUmF0ZSA8LSBzYXBwbHkoYmFubmVyRGF0YSwgZnVuY3Rpb24oeCkgewogICAgeFt3aGljaChncmVwbCgiXnJlY29yZEltcHJlc3Npb25TYW1wbGVSYXRlPSIsIHgpKV0KICB9KQogIHJlY29yZEltcHJlc3Npb25TYW1wbGVSYXRlIDwtIGFzLm51bWVyaWMoCiAgICBnc3ViKCJecmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGU9IiwgIiIsIHJlY29yZEltcHJlc3Npb25TYW1wbGVSYXRlKQogICAgKQogICMgLSByZXN1bHQ6CiAgcmVzdWx0IDwtIHNhcHBseShiYW5uZXJEYXRhLCBmdW5jdGlvbih4KSB7CiAgICB4W3doaWNoKGdyZXBsKCJecmVzdWx0PSIsIHgpKV0KICB9KQogIHJlc3VsdCA8LSBnc3ViKCJecmVzdWx0PSIsICIiLCByZXN1bHQpCiAgCiAgIyAtIGNvbXBvc2UgdGFibGU6CiAgYmFubmVyT2JzZXJ2YXRpb25zIDwtIGRhdGEuZnJhbWUoYmFubmVyID0gYmFubmVyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWNvcmRJbXByZXNzaW9uU2FtcGxlUmF0ZSA9IHJlY29yZEltcHJlc3Npb25TYW1wbGVSYXRlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXN1bHQgPSByZXN1bHQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQogIAogICMgLSBmaWx0ZXIgZm9yIHJlc3VsdD1zaG93CiAgYmFubmVyT2JzZXJ2YXRpb25zIDwtIGRwbHlyOjpmaWx0ZXIoYmFubmVyT2JzZXJ2YXRpb25zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdCA9PSAic2hvdyIpCiAgCiAgIyAtIGNvcnJlY3Rpb24gZm9yIHJlY29yZEltcHJlc3Npb25TYW1wbGVSYXRlCiAgYmFubmVyT2JzZXJ2YXRpb25zJHJlY29yZEltcHJlc3Npb25TYW1wbGVSYXRlIDwtIAogICAgMS9iYW5uZXJPYnNlcnZhdGlvbnMkcmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGUKICAKICAjIC0gYWdncmVnYXRlOgogIGJhbm5lck9ic2VydmF0aW9ucyA8LSBiYW5uZXJPYnNlcnZhdGlvbnMgJT4lIAogICAgZHBseXI6OnNlbGVjdChiYW5uZXIsIHJlY29yZEltcHJlc3Npb25TYW1wbGVSYXRlKSAlPiUgCiAgICBkcGx5cjo6Z3JvdXBfYnkoYmFubmVyKSAlPiUgCiAgICBkcGx5cjo6c3VtbWFyaXNlKGltcHJlc3Npb25zID0gc3VtKHJlY29yZEltcHJlc3Npb25TYW1wbGVSYXRlKSkKICAKICAjIC0gYWRkIGNldERheSwgY2FtcGFpZ25OYW1lCiAgYmFubmVyT2JzZXJ2YXRpb25zJGRhdGUgPC0gY2V0RGF5CiAgYmFubmVyT2JzZXJ2YXRpb25zJGNhbXBhaWduIDwtIGNhbXBhaWduTmFtZQogIAogICMgLSBzdG9yZToKICB3cml0ZS5jc3YoYmFubmVyT2JzZXJ2YXRpb25zLCAKICAgICAgICAgICAgcGFzdGUwKCJiYW5uZXJJbXByZXNzaW9uc0FnZ3JlZ2F0ZWRfIiwKICAgICAgICAgICAgICAgICAgIHN0cnNwbGl0KAogICAgICAgICAgICAgICAgICAgICBzdHJzcGxpdChmaWxlTmFtZSwgc3BsaXQgPSAiXyIsIGZpeGVkID0gVClbWzFdXVsyXSwKICAgICAgICAgICAgICAgICAgICAgc3BsaXQgPSAiLiIsIAogICAgICAgICAgICAgICAgICAgICBmaXhlZCA9IFQpW1sxXV1bMV0sCiAgICAgICAgICAgICAgICAgICAiLmNzdiIKICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgKQogIAp9CgojIC0gd3JhbmdsZSBCYW5uZXIgSW1wcmVzc2lvbiBkYXRhCmNhbXBhaWduTmFtZSA8LSAiMjAxOF9BdUJDIgp3bWRlX3Byb2Nlc3NfYmFubmVyX2ltcHJlc3Npb25zKGZpbGVOYW1lID0gZmlsZU5hbWUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFEaXIgPSBkYXRhRGlyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZXREYXkgPSBjZXREYXksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FtcGFpZ25OYW1lID0gY2FtcGFpZ25OYW1lKQoKIyMjIC0tLSBDb2xsZWN0IFBhZ2V2aWV3cwoKIyAtIGZ1bmN0aW9uOiB3bWRlX2NvbGxlY3RfcGFnZXZpZXdzCndtZGVfY29sbGVjdF9wYWdldmlld3MgPC0gZnVuY3Rpb24odXJpX2hvc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXJpX3BhdGgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2V0RGF5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1ZXJ5RmlsZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlTmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyKSB7CiAgCiAgIyAtIE5PVEU6CiAgIyAtIGV4cGVjdGVkIGZvcm1hdCBmb3IgY2V0RGF5IGlzOiBZWVlZLU1NLURECiAgCiAgIyAtIHRvIGRhdGFEaXIKICBzZXR3ZChkYXRhRGlyKQogIAogICMgLSBsaWJyYXJpZXMKICBsaWJyYXJ5KHN0cmluZ3IpCiAgCiAgIyAtIFdIRVJFIGNvbmRpdGlvbjogY3JlYXRlIGRhdGV0aW1lX2NvbmRpdGlvbgogIGNldF9jb25kaXRpb24gPC0gc2VxKAogICAgZnJvbSA9IGFzLlBPU0lYY3QocGFzdGUwKGNldERheSwiIDA6MDAiKSwgdHogPSAiRXVyb3BlL0JlcmxpbiIpLAogICAgdG8gPSBhcy5QT1NJWGN0KHBhc3RlMChjZXREYXksIiAyMzowMCIpLCB0eiA9ICJFdXJvcGUvQmVybGluIiksCiAgICBieSA9ICJob3VyIgogICkgCiAgYXR0cihjZXRfY29uZGl0aW9uLCAidHpvbmUiKSA8LSAiVVRDIgogIGNldF9jb25kaXRpb24gPC0gYXMuY2hhcmFjdGVyKGNldF9jb25kaXRpb24pCiAgY2V0X2NvbmRpdGlvbiA8LSB1bmxpc3Qoc3RyX2V4dHJhY3RfYWxsKGNldF9jb25kaXRpb24sICJeKFtbOmRpZ2l0Ol1dfFxcc3wtKSoiKSkKICBjZXRfeWVhcnMgPC0gc2FwcGx5KAogICAgc3Ryc3BsaXQoY2V0X2NvbmRpdGlvbiwgc3BsaXQgPSAiICIsIGZpeGVkID0gVCksIGZ1bmN0aW9uKHgpIHsKICAgICAgc3Ryc3BsaXQoeCwgc3BsaXQgPSAiLSIpW1sxXV1bMV0KICAgIH0pCiAgY2V0X21vbnRocyA8LSBzYXBwbHkoCiAgICBzdHJzcGxpdChjZXRfY29uZGl0aW9uLCBzcGxpdCA9ICIgIiwgZml4ZWQgPSBUKSwgZnVuY3Rpb24oeCkgewogICAgICBzdHJzcGxpdCh4LCBzcGxpdCA9ICItIilbWzFdXVsyXQogICAgfSkKICBjZXRfbW9udGhzIDwtIGdzdWIoIl4wIiwgIiIsIGNldF9tb250aHMpCiAgY2V0X2RheXMgPC0gc2FwcGx5KAogICAgc3Ryc3BsaXQoY2V0X2NvbmRpdGlvbiwgc3BsaXQgPSAiICIsIGZpeGVkID0gVCksIGZ1bmN0aW9uKHgpIHsKICAgICAgc3Ryc3BsaXQoeCwgc3BsaXQgPSAiLSIpW1sxXV1bM10KICAgIH0pCiAgY2V0X2RheXMgPC0gZ3N1YigiXjAiLCAiIiwgY2V0X2RheXMpCiAgY2V0X2hvdXJzIDwtIHNhcHBseShzdHJzcGxpdChjZXRfY29uZGl0aW9uLCBzcGxpdCA9ICIgIiwgZml4ZWQgPSBUKSwgCiAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHhbMl0KICAgICAgICAgICAgICAgICAgICAgIH0pCiAgY2V0X2hvdXJzIDwtIGdzdWIoIl4wIiwgIiIsIGNldF9ob3VycykKICBkYXRldGltZUNvbmRpdGlvbiA8LSBwYXN0ZTAoCiAgICAieWVhciA9ICIsIGNldF95ZWFycywgIiBBTkQgIiwKICAgICJtb250aCA9ICIsIGNldF9tb250aHMsICIgQU5EICIsCiAgICAiZGF5ID0gIiwgY2V0X2RheXMsICIgQU5EICIsIAogICAgImhvdXIgPSAiLCBjZXRfaG91cnMKICApCiAgZGF0ZXRpbWVDb25kaXRpb24gPC0gcGFzdGUoIigiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRldGltZUNvbmRpdGlvbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIiBPUiAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiIikKICAKICAjIC0gV0hFUkUgY29uZGl0aW9uOiBjcmVhdGUgdXJpX3BhdGhfY29uZGl0aW9uCiAgaWYgKGxlbmd0aCh1cmlfcGF0aCkgPiAxKSB7CiAgICB1cmlfcGF0aF9jb25kaXRpb24gPC0gcGFzdGUwKCIoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJ1cmlfcGF0aCA9ICciLCB1cmlfcGF0aCwgIiciKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9ICIgT1IgIiwgc2VwID0gIiAiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiCiAgICApCiAgfSBlbHNlIHsKICAgIHVyaV9wYXRoX2NvbmRpdGlvbiA9IHBhc3RlMCgidXJpX3BhdGggPSAnIiwgdXJpX3BhdGgsICInIikKICB9CiAgCiAgIyAtIFdIRVJFIGNvbmRpdGlvbjogY3JlYXRlIHVyaV9ob3N0X2NvbmRpdGlvbgogIGlmIChsZW5ndGgodXJpX2hvc3QpID4gMSkgewogICAgdXJpX2hvc3RfY29uZGl0aW9uIDwtIHBhc3RlMCgiKCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMCgidXJpX2hvc3QgPSAnIiwgdXJpX2hvc3QsICInIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sbGFwc2UgPSAiIE9SICIsIHNlcCA9ICIgIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIpIgogICAgKQogIH0gZWxzZSB7CiAgICB1cmlfaG9zdF9jb25kaXRpb24gPSBwYXN0ZTAoInVyaV9ob3N0ID0gJyIsIHVyaV9ob3N0LCAiJyIpCiAgfQogIAogICMgLSBjb21wb3NlIEhpdmVRTCBxdWVyeQogIGhpdmVRdWVyeSA8LSBwYXN0ZTAoCiAgICAiVVNFIHdtZjsKICAgIFNFTEVDVCB1cmlfcGF0aCwgdXJpX3F1ZXJ5LCByZWZlcmVyIEZST00gd2VicmVxdWVzdAogICAgV0hFUkUgKCIsCiAgICB1cmlfaG9zdF9jb25kaXRpb24sICIgQU5EICIsCiAgICB1cmlfcGF0aF9jb25kaXRpb24sICIgQU5EICIsCiAgICAiKCIsIGRhdGV0aW1lQ29uZGl0aW9uLCAiKSIsCiAgICAiKTsiCiAgICApCiAgCiAgIyAtIHdyaXRlIGhxbAogIHdyaXRlKGhpdmVRdWVyeSwgcXVlcnlGaWxlKQogICMgLSBleGVjdXRlIGhxbCBzY3JpcHQ6CiAgaGl2ZUFyZ3MgPC0gJy91c3IvbG9jYWwvYmluL2JlZWxpbmUgLWYnCiAgaGl2ZUlucHV0IDwtIHBhc3RlMChxdWVyeUZpbGUsICcgPiAnLCBmaWxlTmFtZSkKICAjIC0gY29tbWFuZDoKICBoaXZlQ29tbWFuZCA8LSBwYXN0ZShoaXZlQXJncywgaGl2ZUlucHV0KQogIHJldHVybigKICAgIHN5c3RlbShjb21tYW5kID0gaGl2ZUNvbW1hbmQsIHdhaXQgPSBUUlVFKSkKfQoKIyAtIHNldCBwYXJhbXMgdG8gd21kZV9jb2xsZWN0X3BhZ2V2aWV3cwojIC0gZm9yIHRoZSBBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTgKdXJpX2hvc3QgPC0gYygnZGUud2lraXBlZGlhLm9yZycsICdkZS5tLndpa2lwZWRpYS5vcmcnKQp1cmlfcGF0aCAgPC0gYygKICAnL3dpa2kvV2lraXBlZGlhOldpa2lwZWRpYV92b3JfT3J0XzIwMTgnLAogICcvd2lraS9XaWtpcGVkaWE6V2lraW1lZGlhX0RldXRzY2hsYW5kL0xlcm5lV2lraXBlZGlhJywgCiAgJy93aWtpL1dpa2lwZWRpYTpXaWtpcGVkaWFfdm9yX09ydF8yMDE4L0JlcmxpbicsIAogICcvd2lraS9XaWtpcGVkaWE6V2lraXBlZGlhX3Zvcl9PcnRfMjAxOC9IYW5ub3ZlcicsIAogICcvd2lraS9XaWtpcGVkaWE6V2lraXBlZGlhX3Zvcl9PcnRfMjAxOC9Lw7ZsbicsIAogICcvd2lraS9XaWtpcGVkaWE6V2lraXBlZGlhX3Zvcl9PcnRfMjAxOC9TdHV0dGdhcnQnLCAKICAnL3dpa2kvV2lraXBlZGlhOldpa2lwZWRpYV92b3JfT3J0XzIwMTgvVWxtJywgCiAgJy93aWtpL1dpa2lwZWRpYTpBa3Rpb25zdGFnX1dpa2lwZWRpYV8yMDE4L03DvG5jaGVuJywgCiAgJy93aWtpL1dpa2lwZWRpYTpXaWtpcGVkaWFfdm9yX09ydF8yMDE4L0F1Z3NidXJnJywgCiAgJy93aWtpL1dpa2lwZWRpYTpXaWtpcGVkaWFfdm9yX09ydF8yMDE4L1dpZW4nLCAKICAnL3dpa2kvV2lraXBlZGlhOldpa2lwZWRpYV92b3JfT3J0XzIwMTgvTGlueicsIAogICcvd2lraS9XaWtpcGVkaWE6V2lraXBlZGlhX3Zvcl9PcnRfMjAxOC9aw7xyaWNoJykKcXVlcnlGaWxlIDwtICdhYmMyMDE4X1BhZ2V2aWV3cy5ocWwnCmZpbGVOYW1lIDwtIHBhc3RlMCgicGFnZXZpZXdzXyIsIGNldERheSwgIi50c3YiKQpkYXRhRGlyIDwtICcvaG9tZS9nb3JhbnNtL1JTY3JpcHRzL05ld0VkaXRvcnMvMjAxOF9BdXR1bW5CYW5uZXJDYW1wYWlnbi9fZGF0YScKCiMgLSBjb2xsZWN0IFBhZ2V2aWV3cyBkYXRhCndtZGVfY29sbGVjdF9wYWdldmlld3ModXJpX2hvc3QsCiAgICAgICAgICAgICAgICAgICAgICAgdXJpX3BhdGgsCiAgICAgICAgICAgICAgICAgICAgICAgY2V0RGF5LAogICAgICAgICAgICAgICAgICAgICAgIHF1ZXJ5RmlsZSwKICAgICAgICAgICAgICAgICAgICAgICBmaWxlTmFtZSwKICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyKQoKIyMjIC0tLSBXcmFuZ2xlIFBhZ2V2aWV3cwojIC0gZnVuY3Rpb246IHdtZGVfcHJvY2Vzc19wYWdldmlld3MKd21kZV9wcm9jZXNzX3BhZ2V2aWV3cyA8LSBmdW5jdGlvbihmaWxlTmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1cmlfcXVlcnlfZmlsdGVyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZXREYXkgPSBjZXREYXksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FtcGFpZ25OYW1lID0gY2FtcGFpZ25OYW1lKSB7CiAgCiAgIyAtIHRvIGRhdGFEaXIKICBzZXR3ZChkYXRhRGlyKQogIAogICMgLSBsaWJyYXJpZXMKICBsaWJyYXJ5KHN0cmluZ3IpCiAgbGlicmFyeShkcGx5cikKICBsaWJyYXJ5KHRpZHlyKQogIGxpYnJhcnkoZGF0YS50YWJsZSkKCiAgIyAtIGxvYWQKICBwYWdldmlld3NEYXRhIDwtIHJlYWRMaW5lcyhmaWxlTmFtZSkKICB3U3RhcnQgPC0gd2hpY2goZ3JlcGwoIl51cmlfcGF0aCIsIHBhZ2V2aWV3c0RhdGEpKQogIHBhZ2V2aWV3c0RhdGEgPC0gcGFnZXZpZXdzRGF0YVsod1N0YXJ0ICsgMik6KGxlbmd0aChwYWdldmlld3NEYXRhKSAtIDIpXQogIHBhZ2V2aWV3c0RhdGEgPC0gZGF0YS5mcmFtZShkYXQgPSBwYWdldmlld3NEYXRhLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCiAgcGFnZXZpZXdzRGF0YSA8LSBzZXBhcmF0ZShwYWdldmlld3NEYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgaW50byA9IGMoJ3VyaV9wYXRoJywgJ3VyaV9xdWVyeScsICdyZWZlcmVyJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiXHQiKQogICMgLSBhcHBseSB1cmlfcXVlcnlfZmlsdGVyCiAgIyAtIE5PVEU6IEF1dHVtbiAyMDE4LCBsb29raW5nIGluIGJvdGg6IHVyaV9xdWVyeSwgcmVmZXJlcgogIHdfdXJpX3F1ZXJ5IDwtIHdoaWNoKGdyZXBsKHVyaV9xdWVyeV9maWx0ZXIsIHBhZ2V2aWV3c0RhdGEkdXJpX3F1ZXJ5KSkKICB3X3VyaV9xdWVyeV9yZWZlcmVyIDwtIHdoaWNoKGdyZXBsKHVyaV9xdWVyeV9maWx0ZXIsIHBhZ2V2aWV3c0RhdGEkcmVmZXJlcikpCiAgd191cmlfcXVlcnkgPC0gdW5pcXVlKGMod191cmlfcXVlcnksIHdfdXJpX3F1ZXJ5X3JlZmVyZXIpKQogIHBhZ2V2aWV3c0RhdGEgPC0gcGFnZXZpZXdzRGF0YVt3X3VyaV9xdWVyeSwgXQogIHdfdXJpX3F1ZXJ5X3JlZmVyZXIgPC0gd2hpY2goZ3JlcGwodXJpX3F1ZXJ5X2ZpbHRlciwgcGFnZXZpZXdzRGF0YSRyZWZlcmVyKSkKICB3X3VyaV9xdWVyeV9yZWZlcmVyX2RlbGV0ZSA8LSBzZXRkaWZmKDE6ZGltKHBhZ2V2aWV3c0RhdGEpWzFdLCB3X3VyaV9xdWVyeV9yZWZlcmVyKQogIHBhZ2V2aWV3c0RhdGEkcmVmZXJlclt3X3VyaV9xdWVyeV9yZWZlcmVyX2RlbGV0ZV0gPC0gJycKICAjIC0gd2hlbiB0aGVyZSBpcyBubyB1cmlfcXVlcnksIHVzZSB0aGUgcXVlcnkgZnJvbSB0aGUgcmVmZXJlciBmaWVsZCBpZiBwcmVzZW50IHRoZXJlCiAgcGFnZXZpZXdzRGF0YSRyZWZlcmVyIDwtIHN0cl9leHRyYWN0KHBhZ2V2aWV3c0RhdGEkcmVmZXJlciwgIlxcP2NhbXBhaWduPS4qJCIpCiAgcGFnZXZpZXdzRGF0YSRyZWZlcmVyW2lzLm5hKHBhZ2V2aWV3c0RhdGEkcmVmZXJlcildIDwtICIiCiAgcGFnZXZpZXdzRGF0YSRyZWZlcmVyIDwtIGdzdWIoIj9jYW1wYWlnbj0iLCAiIiwgcGFnZXZpZXdzRGF0YSRyZWZlcmVyLCBmaXhlZCA9IFQpCiAgcGFnZXZpZXdzRGF0YSR1cmlfcXVlcnkgPC0gZ3N1YigiP2NhbXBhaWduPSIsICIiLCBwYWdldmlld3NEYXRhJHVyaV9xdWVyeSwgZml4ZWQgPSBUKQogIHBhZ2V2aWV3c0RhdGEkdXJpX3F1ZXJ5W3BhZ2V2aWV3c0RhdGEkdXJpX3F1ZXJ5ID09ICIiXSA8LSAKICAgIHBhZ2V2aWV3c0RhdGEkcmVmZXJlcltwYWdldmlld3NEYXRhJHVyaV9xdWVyeSA9PSAiIl0KICBwYWdldmlld3NEYXRhIDwtIGRwbHlyOjpmaWx0ZXIocGFnZXZpZXdzRGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgdXJpX3F1ZXJ5ICE9ICIiKQogIHBhZ2V2aWV3c0RhdGEkcmVmZXJlciA8LSBOVUxMCiAgIyAtIGNsZWFuIHVwIGEgYml0OgogIHBhZ2V2aWV3c0RhdGEkdXJpX3F1ZXJ5IDwtIGdzdWIoIi8uKiQiLCAiIiwgcGFnZXZpZXdzRGF0YSR1cmlfcXVlcnkpCiAgCiAgIyAtIGFnZ3JlZ2F0ZToKICBwYWdldmlld3NEYXRhIDwtIHBhZ2V2aWV3c0RhdGEgJT4lIAogICAgZHBseXI6Omdyb3VwX2J5KHVyaV9xdWVyeSwgdXJpX3BhdGgpICU+JSAKICAgIGRwbHlyOjpzdW1tYXJpc2UocGFnZXZpZXdzID0gbigpKQogIGNvbG5hbWVzKHBhZ2V2aWV3c0RhdGEpIDwtIGMoJ1RhZycsICdQYWdlJywgJ1BhZ2V2aWV3cycpCiAgCiAgIyAtIGFkZCBjZXREYXksIGNhbXBhaWduTmFtZQogIHBhZ2V2aWV3c0RhdGEkZGF0ZSA8LSBjZXREYXkKICBwYWdldmlld3NEYXRhJGNhbXBhaWduIDwtIGNhbXBhaWduTmFtZQoKICAjIC0gc3RvcmU6CiAgd3JpdGUuY3N2KHBhZ2V2aWV3c0RhdGEsIAogICAgICAgICAgICBwYXN0ZTAoInBhZ2V2aWV3c0FnZ3JlZ2F0ZWRfIiwKICAgICAgICAgICAgICAgICAgIHN0cnNwbGl0KAogICAgICAgICAgICAgICAgICAgICBzdHJzcGxpdChmaWxlTmFtZSwgc3BsaXQgPSAiXyIsIGZpeGVkID0gVClbWzFdXVsyXSwKICAgICAgICAgICAgICAgICAgICAgc3BsaXQgPSAiLiIsIAogICAgICAgICAgICAgICAgICAgICBmaXhlZCA9IFQpW1sxXV1bMV0sCiAgICAgICAgICAgICAgICAgICAiLmNzdiIKICAgICAgICAgICAgKQogICkKICAKfQoKIyAtIHNldCBwYXJhbXMgdG8gd21kZV9wcm9jZXNzX3BhZ2V2aWV3cwojIC0gZm9yIHRoZSBBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTgKdXJpX3F1ZXJ5X2ZpbHRlciA8LSAnV01ERV9uZXdlZGl0b3JzX2F1dHVtbl8yMDE4JwoKIyAtIHdyYW5nbGUgcGFnZXZpZXdzCndtZGVfcHJvY2Vzc19wYWdldmlld3MoZmlsZU5hbWUgPSBmaWxlTmFtZSwKICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyID0gZGF0YURpciwKICAgICAgICAgICAgICAgICAgICAgICB1cmlfcXVlcnlfZmlsdGVyID0gdXJpX3F1ZXJ5X2ZpbHRlciwgCiAgICAgICAgICAgICAgICAgICAgICAgY2V0RGF5ID0gY2V0RGF5LAogICAgICAgICAgICAgICAgICAgICAgIGNhbXBhaWduTmFtZSA9IGNhbXBhaWduTmFtZSkgCgoKIyMjIC0tLSBDb2xsZWN0IFVzZXIgUmVnaXN0cmF0aW9ucwoKIyAtIGZ1bmN0aW9uOiB3bWRlX2NvbGxlY3RfcmVnaXN0cmF0aW9ucwp3bWRlX2NvbGxlY3RfcmVnaXN0cmF0aW9ucyA8LSBmdW5jdGlvbihsb2dTY2hlbWEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWJfaG9zdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnRfY2FtcGFpZ24sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZXREYXksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFEaXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlTmFtZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbXBhaWduTmFtZSkgewogIAogICMgLSBXSEVSRSBjb25kaXRpb246IGNyZWF0ZSBzdGFydF90aW1lc3RhbXAsIHN0b3BfdGltZXN0YW1wCiAgY2V0X2NvbmRpdGlvbiA8LSBzZXEoCiAgICBmcm9tID0gYXMuUE9TSVhjdChwYXN0ZTAoY2V0RGF5LCIgMDowMCIpLCB0eiA9ICJFdXJvcGUvQmVybGluIiksCiAgICB0byA9IGFzLlBPU0lYY3QocGFzdGUwKGNldERheSwiIDI0OjAwIiksIHR6ID0gIkV1cm9wZS9CZXJsaW4iKSwKICAgIGJ5ID0gImhvdXIiCiAgKSAKICBhdHRyKGNldF9jb25kaXRpb24sICJ0em9uZSIpIDwtICJVVEMiCiAgY2V0X2NvbmRpdGlvbiA8LSBhcy5jaGFyYWN0ZXIoY2V0X2NvbmRpdGlvbikKICBzdGFydF90aW1lc3RhbXAgPC0gcGFzdGUoCiAgICB1bmxpc3Qoc3RyX2V4dHJhY3RfYWxsKGNldF9jb25kaXRpb25bMV0sCiAgICAgICAgICAgICAgICAgICAgIltbOmRpZ2l0Ol1dIikpLAogICAgY29sbGFwc2UgPSAiIikKICBzdG9wX3RpbWVzdGFtcCA8LSBwYXN0ZSgKICAgIHVubGlzdChzdHJfZXh0cmFjdF9hbGwodGFpbChjZXRfY29uZGl0aW9uLCAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIltbOmRpZ2l0Ol1dIikpLAogICAgY29sbGFwc2UgPSAiIikKICAKICAjIC0gV0hFUkUgY29uZGl0aW9uOiBjcmVhdGUgZXZlbnRfY2FtcGFpZ25fY29uZGl0aW9uCiAgaWYgKGxlbmd0aChldmVudF9jYW1wYWlnbikgPiAxKSB7CiAgICBldmVudF9jYW1wYWlnbl9jb25kaXRpb24gPC0gcGFzdGUwKCIoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJldmVudF9jYW1wYWlnbiBMSUtFICclIiwgZXZlbnRfY2FtcGFpZ24sICIlJyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIiBPUiAiLCBzZXAgPSAiICIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiKSIKICAgICkKICB9IGVsc2UgewogICAgZXZlbnRfY2FtcGFpZ25fY29uZGl0aW9uID0gcGFzdGUwKCJldmVudF9jYW1wYWlnbiBMSUtFICclIiwgZXZlbnRfY2FtcGFpZ24sICIlJyIpCiAgfQogIAoKICAjIC0gY29tcG9zZSBTUUwgcXVlcnk6CiAgc3FsUGFyYW1zIDwtICdteXNxbCAtLWRlZmF1bHRzLWZpbGU9L2V0Yy9teXNxbC9jb25mLmQvYW5hbHl0aWNzLXJlc2VhcmNoLWNsaWVudC5jbmYgLWggYW5hbHl0aWNzLXNsYXZlLmVxaWFkLndtbmV0IC1BIC1lJwogIHF1ZXJ5IDwtIHBhc3RlMCgKICAgICJcIlNFTEVDVCAqIEZST00gIiwgCiAgICBwYXN0ZTAoImxvZy4iLCBsb2dTY2hlbWEpLCAKICAgICIgV0hFUkUgKCh3ZWJIb3N0ID0gJyIsIAogICAgd2ViX2hvc3QsIAogICAgIicpIEFORCAodGltZXN0YW1wID4gIiwgCiAgICBzdGFydF90aW1lc3RhbXAsIAogICAgIikgQU5EICh0aW1lc3RhbXAgPD0gIiwgCiAgICBzdG9wX3RpbWVzdGFtcCwgCiAgICAiKSBBTkQgKCIsIAogICAgZXZlbnRfY2FtcGFpZ25fY29uZGl0aW9uLAogICAgIikpO1wiIikKICBzcWxPdXRwdXQgPC0gcGFzdGUwKCI+ICIsIHBhc3RlMChkYXRhRGlyLCAiLyIsIGZpbGVOYW1lKSkKICAgIAogICMgLSBydW4gY29tbWFuZAogIHFDb21tYW5kIDwtIHBhc3RlKHNxbFBhcmFtcywgcXVlcnksIHNxbE91dHB1dCwgc2VwID0gIiAiKQogIHN5c3RlbShjb21tYW5kID0gcUNvbW1hbmQsIHdhaXQgPSBUUlVFKQogIAogICMgLSB0byBkYXRhRGlyCiAgc2V0d2QoZGF0YURpcikKICAKICAjIC0gbGlicmFyaWVzCiAgbGlicmFyeShzdHJpbmdyKQogIGxpYnJhcnkoZHBseXIpCiAgbGlicmFyeSh0aWR5cikKICBsaWJyYXJ5KGRhdGEudGFibGUpCiAgCiAgIyAtIGxvYWQKICB1c2VyUmVnIDwtIGZyZWFkKGZpbGVOYW1lLCBzZXAgPSAiXHQiKQogIAogICMgLSBmaWx0ZXIgYm90cwogIHdCb3QgPC0gd2hpY2goZ3JlcGwoIlwiaXNfYm90XCI6IHRydWUiLCB1c2VyUmVnJHVzZXJBZ2VudCkpCiAgaWYgKGxlbmd0aCh3Qm90KSA+IDApIHsKICAgIHVzZXJSZWcgPC0gdXNlclJlZ1std0JvdCwgXQogIH0KICAKICAjIC0gc2VsZWN0IGZpZWxkcwogIHVzZXJSZWcgPC0gdXNlclJlZyAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KGV2ZW50X3VzZXJJZCwgCiAgICAgICAgICAgICAgICAgIGV2ZW50X3VzZXJOYW1lLCAKICAgICAgICAgICAgICAgICAgZXZlbnRfaXNTZWxmTWFkZSwgCiAgICAgICAgICAgICAgICAgIGV2ZW50X2NhbXBhaWduLCAKICAgICAgICAgICAgICAgICAgdGltZXN0YW1wKQogIAogICMgLSBhZGQgY2V0RGF5LCBjYW1wYWlnbk5hbWUKICB1c2VyUmVnJGRhdGUgPC0gY2V0RGF5CiAgdXNlclJlZyRjYW1wYWlnbiA8LSBjYW1wYWlnbk5hbWUKICAKICAjIC0gc3RvcmU6CiAgd3JpdGUuY3N2KHVzZXJSZWcsIAogICAgICAgICAgICBwYXN0ZTAoCiAgICAgICAgICAgICAgc3Ryc3BsaXQoZmlsZU5hbWUsIHNwbGl0ID0gIi4iLCBmaXhlZCA9IFQpW1sxXV1bMV0sCiAgICAgICAgICAgICIuY3N2IikKICApCiAgCiAgIyAtIHJlbW92ZSB0ZW1wIC50c3YgZmlsZQogIGZpbGUucmVtb3ZlKGZpbGVOYW1lKQogIAp9CgojIC0gc2V0IHBhcmFtcyBmb3I6IHdtZGVfY29sbGVjdF9yZWdpc3RyYXRpb25zCmxvZ1NjaGVtYSA8LSAnU2VydmVyU2lkZUFjY291bnRDcmVhdGlvbl8xNzcxOTIzNycgCndlYl9ob3N0IDwtICdkZS53aWtpcGVkaWEub3JnJwpldmVudF9jYW1wYWlnbiA8LSAnV01ERV9uZXdlZGl0b3JzX2F1dHVtbl8yMDE4JwpmaWxlTmFtZSA8LSBwYXN0ZTAoInVzZXJSZWdpc3RyYXRpb25zXyIsIGNldERheSwgIi50c3YiKQoKIyAtIGNvbGxlY3QgdXNlciByZWdpc3RyYXRpb25zCndtZGVfY29sbGVjdF9yZWdpc3RyYXRpb25zKGxvZ1NjaGVtYSA9IGxvZ1NjaGVtYSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgd2ViX2hvc3QgPSB3ZWJfaG9zdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnRfY2FtcGFpZ24gPSBldmVudF9jYW1wYWlnbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgY2V0RGF5ID0gY2V0RGF5LAogICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyID0gZGF0YURpciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsZU5hbWUgPSBmaWxlTmFtZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbXBhaWduTmFtZSA9IGNhbXBhaWduTmFtZSkKCiMjIyAtLS0gV3JhbmdsZSBVc2VyIFJlZ2lzdHJhdGlvbnMKIyAtIGZ1bmN0aW9uOiB3bWRlX3Byb2Nlc3NfcmVnaXN0cmF0aW9ucwp3bWRlX3Byb2Nlc3NfcmVnaXN0cmF0aW9ucyA8LSBmdW5jdGlvbihmaWxlTmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YURpciwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNldERheSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbXBhaWduTmFtZSkgewogIAogICMgLSB0byBkYXRhRGlyCiAgc2V0d2QoZGF0YURpcikKICAKICAjIC0gbGlicmFyaWVzCiAgbGlicmFyeShkcGx5cikKICBsaWJyYXJ5KGRhdGEudGFibGUpCgogICMgLSBsb2FkCiAgdXNlclJlZyA8LSBmcmVhZChmaWxlTmFtZSkKICAKICAjIC0gYWdyZWdhdGUKICB1c2VyUmVnIDwtIHVzZXJSZWcgJT4lIAogICAgZHBseXI6OnNlbGVjdChldmVudF9jYW1wYWlnbikgJT4lIAogICAgZHBseXI6Omdyb3VwX2J5KGV2ZW50X2NhbXBhaWduKSAlPiUgCiAgICBkcGx5cjo6c3VtbWFyaXNlKFJlZ2lzdHJhdGlvbnMgPSBuKCkpCgogICMgLSBhZGQgY2V0RGF5LCBjYW1wYWlnbk5hbWUKICB1c2VyUmVnJGRhdGUgPC0gY2V0RGF5CiAgdXNlclJlZyRjYW1wYWlnbiA8LSBjYW1wYWlnbk5hbWUKICAKICAjIC0gc3RvcmU6CiAgd3JpdGUuY3N2KHVzZXJSZWcsIAogICAgICAgICAgICBwYXN0ZTAoJ3VzZXJSZWdpc3RyYXRpb25zQWdncmVhZ3RlZF8nLCBjZXREYXksICIuY3N2IikKICApCiAgCn0KCiMgLSBzZXQgcGFyYW1zIGZvcjogd21kZV9wcm9jZXNzX3JlZ2lzdHJhdGlvbnMKZmlsZU5hbWUgPC0gcGFzdGUwKCJ1c2VyUmVnaXN0cmF0aW9uc18iLCBjZXREYXksICIuY3N2IikKCiMgLSB3cmFuZ2xlIHVzZXIgcmVnaXN0cmF0aW9uczoKd21kZV9wcm9jZXNzX3JlZ2lzdHJhdGlvbnMoZmlsZU5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFEaXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNldERheSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FtcGFpZ25OYW1lKQoKIyMjIC0tLSBDb2xsZWN0IE5ld3NsZXR0ZXIgcmVnaXN0cmF0aW9ucyAtIGZvciAyMDE4X0F1QkMgZXhjbC4KIyAtIFNlcnZlclNpZGVBY2NvdW50Q3JlYXRpb25fMTc3MTkyMzcgc2NoZW1hCnFDb21tYW5kIDwtICJteXNxbCAtLWRlZmF1bHRzLWZpbGU9L2V0Yy9teXNxbC9jb25mLmQvYW5hbHl0aWNzLXJlc2VhcmNoLWNsaWVudC5jbmYgLWggYW5hbHl0aWNzLXNsYXZlLmVxaWFkLndtbmV0IC1BIC1lIFwic2VsZWN0ICogZnJvbSBsb2cuU2VydmVyU2lkZUFjY291bnRDcmVhdGlvbl8xNzcxOTIzNyB3aGVyZSAoKHdlYkhvc3QgPSAnZGUud2lraXBlZGlhLm9yZycpIGFuZCAodGltZXN0YW1wID49IDIwMTgxMDExMDAwMDAwKSBhbmQgKGV2ZW50X2NhbXBhaWduIGxpa2UgJyVXTURFX25ld2VkaXRvcnNfYXV0dW1uXzIwMThfbHBuJScpKTtcIiA+IC9ob21lL2dvcmFuc20vUlNjcmlwdHMvTmV3RWRpdG9ycy8yMDE4X0F1dHVtbkJhbm5lckNhbXBhaWduL19kYXRhL05ld3NsZXR0ZXJfdXNlclJlZ2lzdHJhdGlvbnMudHN2IgpzeXN0ZW0oY29tbWFuZCA9IHFDb21tYW5kLCB3YWl0ID0gVFJVRSkKIyMjIC0tLSBXcmFuZ2xlIE5ld3NsZXR0ZXIgcmVnaXN0cmF0aW9ucyAtIGZvciAyMDE4X0F1QkMgZXhjbC4KIyAtIGZ1bmN0aW9uOiB3bWRlX3Byb2Nlc3NfcmVnaXN0cmF0aW9uc19nZW5lcmFsCndtZGVfcHJvY2Vzc19yZWdpc3RyYXRpb25zX2dlbmVyYWwgPC0gZnVuY3Rpb24oZmlsZU5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFEaXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZXREYXksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW1wYWlnbk5hbWUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRGaWxlTmFtZSkgewogIAogICMgLSB0byBkYXRhRGlyCiAgc2V0d2QoZGF0YURpcikKICAKICAjIC0gbGlicmFyaWVzCiAgbGlicmFyeShkcGx5cikKICBsaWJyYXJ5KGRhdGEudGFibGUpCiAgCiAgIyAtIGxvYWQKICB1c2VyUmVnIDwtIGZyZWFkKGZpbGVOYW1lKQogIAogICMgLSBhZ3JlZ2F0ZQogIHVzZXJSZWcgPC0gdXNlclJlZyAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KGV2ZW50X2NhbXBhaWduKSAlPiUgCiAgICBkcGx5cjo6Z3JvdXBfYnkoZXZlbnRfY2FtcGFpZ24pICU+JSAKICAgIGRwbHlyOjpzdW1tYXJpc2UoUmVnaXN0cmF0aW9ucyA9IG4oKSkKICAKICAjIC0gYWRkIGNldERheSwgY2FtcGFpZ25OYW1lCiAgdXNlclJlZyRkYXRlIDwtIGNldERheQogIHVzZXJSZWckY2FtcGFpZ24gPC0gY2FtcGFpZ25OYW1lCiAgCiAgIyAtIHN0b3JlOgogIHdyaXRlLmNzdih1c2VyUmVnLCAKICAgICAgICAgICAgb3V0RmlsZU5hbWUKICApCiAgCn0KCiMgLSBzZXQgcGFyYW1zIGZvcjogd21kZV9wcm9jZXNzX3JlZ2lzdHJhdGlvbnMKIyAtIHdyYW5nbGUgdXNlciByZWdpc3RyYXRpb25zOgpmaWxlTmFtZSA9ICdOZXdzbGV0dGVyX3VzZXJSZWdpc3RyYXRpb25zLnRzdicKb3V0RmlsZU5hbWUgPC0gJ2FsbF9OZXdzbGV0dGVyX3VzZXJSZWdpc3RyYXRpb25zLmNzdicKd21kZV9wcm9jZXNzX3JlZ2lzdHJhdGlvbnNfZ2VuZXJhbChmaWxlTmFtZSA9IGZpbGVOYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFEaXIgPSBkYXRhRGlyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNldERheSA9IGNldERheSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW1wYWlnbk5hbWUgPSBjYW1wYWlnbk5hbWUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dEZpbGVOYW1lID0gb3V0RmlsZU5hbWUpCmBgYAoKIyMjIDAuMiBEYXRhIEFnZ3JlZ2F0aW9uCgoqKk5PVEU6KiogTm90IHJ1biBmcm9tIHRoaXMgcmVwb3J0OyB0aGUgZGF0YSB3ZXJlIGFscmVhZHkgcHJlLXByb2Nlc3NlZCBhbmQgYWdncmVnYXRlZCBieSB0aGUgZm9sbG93aW5nIGBSYCBzY3JpcHQgYmVmb3JlIGJlaW5nIHN1Ym1pdHRlZCB0byBhbmFseXRpY2FsIHByb2NlZHVyZXMuCgpgYGB7ciwgZWNobyA9IFQsIGV2YWwgPSBGfQoKIyMjIC0tLSBSZXBvcnQgR2VuZXJhdGlvbiBmb3IgdGhlIEF1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxOAojIyMgLS0tIHJ1biBsb2NhbGx5CgojIyMgLS0tIHRvIGRhdGEgZGlyZWN0b3J5CmRhdGFEaXIgPC0gCiAgJy9ob21lL2dvcmFuc20vV29yay9fX19EYXRhS29sZWt0aXYvUHJvamVjdHMvV2lraW1lZGlhREVVL19XTURFX1Byb2plY3RzL19taXNjL05ld0VkaXRvcnNfVGVhbS8yMDE4X0F1dHVtbkJhbm5lckNhbXBhaWduL19kYXRhLycKYW5hbHl0aWNzRGlyIDwtIAogICcvaG9tZS9nb3JhbnNtL1dvcmsvX19fRGF0YUtvbGVrdGl2L1Byb2plY3RzL1dpa2ltZWRpYURFVS9fV01ERV9Qcm9qZWN0cy9fbWlzYy9OZXdFZGl0b3JzX1RlYW0vMjAxOF9BdXR1bW5CYW5uZXJDYW1wYWlnbi9fYW5hbHl0aWNzLycKc2V0d2QoYW5hbHl0aWNzRGlyKQoKIyMjIC0tLSBSZXBvcnQgQmFubmVyIEltcHJlc3Npb24gRGF0YQoKIyAtIGZ1bmN0aW9uOiB3bWRlX3JlcG9ydF9iYW5uZXJfaW1wcmVzc2lvbnMKd21kZV9yZXBvcnRfYmFubmVyX2ltcHJlc3Npb25zIDwtIGZ1bmN0aW9uKGRhdGFEaXIpIHsKICAKICAjIC0gU2V0dXAKICBsaWJyYXJ5KGRhdGEudGFibGUpCiAgbGlicmFyeShkcGx5cikKCiAgIyAtIGxpc3QgZmlsZXM6CiAgbEYgPC0gbGlzdC5maWxlcyhkYXRhRGlyKQogIAogICMgLSBmaWx0ZXIgYWdncmVnYXRlZCBiYW5uZXIgaW1wcmVzc2lvbiBkYXRhCiAgbEYgPC0gbEZbZ3JlcGwoImJhbm5lckltcHJlc3Npb25zQWdncmVnYXRlZF8iLCBsRiwgZml4ZWQgPSBUKV0KICAKICAjIC0gbG9hZCBmaWxlcyBhbmQgbWVyZ2UKICBiYW5uZXJEYXRhIDwtIHZlY3Rvcihtb2RlID0gImxpc3QiLCBsZW5ndGggPSBsZW5ndGgobEYpKQogIGZvciAoaSBpbiAxOmxlbmd0aChsRikpIHsKICAgIGlmIChncmVwbCgiY3N2JHx0c3YkIiwgbEZbaV0pKSB7CiAgICAgIGJhbm5lckRhdGFbW2ldXSA8LSBmcmVhZChwYXN0ZTAoZGF0YURpciwgbEZbaV0pKQogICAgfSBlbHNlIHsKICAgICAgYmFubmVyRGF0YVtbaV1dIDwtIE5VTEwKICAgIH0KICB9CiAgYmFubmVyRGF0YSA8LSByYmluZGxpc3QoYmFubmVyRGF0YSkKICBiYW5uZXJEYXRhJFYxIDwtIE5VTEwKICAKICAjIC0gYWdncmVnYXRlcwogIHBlckJhbm5lclRvdGFscyA8LSBiYW5uZXJEYXRhICU+JSAKICAgIHNlbGVjdChiYW5uZXIsIGltcHJlc3Npb25zKSAlPiUgCiAgICBncm91cF9ieShiYW5uZXIpICU+JSAKICAgIHN1bW1hcmlzZSh0b3RhbEltcHJlc3Npb25zID0gc3VtKGltcHJlc3Npb25zKSkKICBwZXJEYXlUb3RhbHMgPC0gYmFubmVyRGF0YSAlPiUgCiAgICBzZWxlY3QoZGF0ZSwgaW1wcmVzc2lvbnMpICU+JSAKICAgIGdyb3VwX2J5KGRhdGUpICU+JSAKICAgIHN1bW1hcmlzZSh0b3RhbEltcHJlc3Npb25zID0gc3VtKGltcHJlc3Npb25zKSkKICAKICAjIC0gb3V0cHV0CiAgcmV0dXJuKAogICAgbGlzdChiYW5uZXJJbXByZXNzaW9uc1JlcG9ydCA9IGJhbm5lckRhdGEsIAogICAgICAgICBwZXJCYW5uZXJUb3RhbHMgPSBwZXJCYW5uZXJUb3RhbHMsIAogICAgICAgICBwZXJEYXlUb3RhbHMgPSBwZXJEYXlUb3RhbHMpCiAgKQogICAgCn0KCiMgLSBSZXBvcnQgYmFubmVyIGltcHJlc3Npb25zCmJhbm5lckltcHJlc3Npb25zRGF0YSA8LSB3bWRlX3JlcG9ydF9iYW5uZXJfaW1wcmVzc2lvbnMoZGF0YURpcikKYmFubmVySW1wcmVzc2lvbnNGaWxlIDwtIGJhbm5lckltcHJlc3Npb25zRGF0YSRiYW5uZXJJbXByZXNzaW9uc1JlcG9ydAp3cml0ZS5jc3YoYmFubmVySW1wcmVzc2lvbnNGaWxlLCAiYmFubmVySW1wcmVzc2lvbnNGaWxlLmNzdiIpCmJhbm5lclRvdGFscyA8LSBiYW5uZXJJbXByZXNzaW9uc0RhdGEkcGVyQmFubmVyVG90YWxzCndyaXRlLmNzdihiYW5uZXJUb3RhbHMsICJiYW5uZXJUb3RhbHMuY3N2IikKYmFubmVyRGF5VG90YWxzIDwtIGJhbm5lckltcHJlc3Npb25zRGF0YSRwZXJEYXlUb3RhbHMKd3JpdGUuY3N2KGJhbm5lckRheVRvdGFscywgImJhbm5lckRheVRvdGFscy5jc3YiKQoKCiMjIyAtLS0gUmVwb3J0IFBhZ2V2aWV3cyBEYXRhCgojIC0gZnVuY3Rpb246IHdtZGVfcmVwb3J0X3BhZ2V2aWV3cwp3bWRlX3JlcG9ydF9wYWdldmlld3MgPC0gZnVuY3Rpb24oZGF0YURpcikgewogIAogICMgLSBTZXR1cAogIGxpYnJhcnkoZGF0YS50YWJsZSkKICBsaWJyYXJ5KGRwbHlyKQogIAogICMgLSBsaXN0IGZpbGVzOgogIGxGIDwtIGxpc3QuZmlsZXMoZGF0YURpcikKICAKICAjIC0gZmlsdGVyIGFnZ3JlZ2F0ZWQgYmFubmVyIGltcHJlc3Npb24gZGF0YQogIGxGIDwtIGxGW2dyZXBsKCJwYWdldmlld3NBZ2dyZWdhdGVkXyIsIGxGLCBmaXhlZCA9IFQpXQogIAogICMgLSBsb2FkIGZpbGVzIGFuZCBtZXJnZQogIHBhZ2V2aWV3c0RhdGEgPC0gdmVjdG9yKG1vZGUgPSAibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChsRikpCiAgZm9yIChpIGluIDE6bGVuZ3RoKGxGKSkgewogICAgaWYgKGdyZXBsKCJjc3YkfHRzdiQiLCBsRltpXSkpIHsKICAgICAgcGFnZXZpZXdzRGF0YVtbaV1dIDwtIGZyZWFkKHBhc3RlMChkYXRhRGlyLCBsRltpXSkpCiAgICB9IGVsc2UgewogICAgICBwYWdldmlld3NEYXRhW1tpXV0gPC0gTlVMTAogICAgfQogIH0KICBwYWdldmlld3NEYXRhIDwtIHJiaW5kbGlzdChwYWdldmlld3NEYXRhKQogIHBhZ2V2aWV3c0RhdGEkVjEgPC0gTlVMTAogIAogICMgLSBhZ2dyZWdhdGVzCiAgcGVyRGF5VG90YWxzIDwtIHBhZ2V2aWV3c0RhdGEgJT4lIAogICAgc2VsZWN0KGRhdGUsIFBhZ2V2aWV3cykgJT4lIAogICAgZ3JvdXBfYnkoZGF0ZSkgJT4lIAogICAgc3VtbWFyaXNlKHRvdGFsUGFnZXZpZXdzID0gc3VtKFBhZ2V2aWV3cykpCiAgcGVyVGFnVG90YWxzIDwtIHBhZ2V2aWV3c0RhdGEgJT4lIAogICAgc2VsZWN0KFRhZywgUGFnZXZpZXdzKSAlPiUgCiAgICBncm91cF9ieShUYWcpICU+JSAKICAgIHN1bW1hcmlzZSh0b3RhbFBhZ2V2aWV3cyA9IHN1bShQYWdldmlld3MpKQogIHBlclBhZ2VUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSAlPiUgCiAgICBzZWxlY3QoUGFnZSwgUGFnZXZpZXdzKSAlPiUgCiAgICBncm91cF9ieShQYWdlKSAlPiUgCiAgICBzdW1tYXJpc2UodG90YWxQYWdldmlld3MgPSBzdW0oUGFnZXZpZXdzKSkKICBwZXJQYWdlRGF5VG90YWxzIDwtIHBhZ2V2aWV3c0RhdGEgJT4lIAogICAgc2VsZWN0KFBhZ2UsIGRhdGUsIFBhZ2V2aWV3cykgJT4lCiAgICBncm91cF9ieShQYWdlLCBkYXRlKSAlPiUgCiAgICBzdW1tYXJpc2UodG90YWxQYWdldmlld3MgPSBzdW0oUGFnZXZpZXdzKSkKICBwZXJUYWdEYXlUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSAlPiUgCiAgICBzZWxlY3QoVGFnLCBkYXRlLCBQYWdldmlld3MpICU+JQogICAgZ3JvdXBfYnkoVGFnLCBkYXRlKSAlPiUgCiAgICBzdW1tYXJpc2UodG90YWxQYWdldmlld3MgPSBzdW0oUGFnZXZpZXdzKSkKICBwZXJUYWdQYWdlVG90YWxzIDwtIHBhZ2V2aWV3c0RhdGEgJT4lIAogICAgc2VsZWN0KFRhZywgUGFnZSwgUGFnZXZpZXdzKSAlPiUKICAgIGdyb3VwX2J5KFRhZywgUGFnZSkgJT4lIAogICAgc3VtbWFyaXNlKHRvdGFsUGFnZXZpZXdzID0gc3VtKFBhZ2V2aWV3cykpCiAgICAKICAjIC0gb3V0cHV0CiAgcmV0dXJuKAogICAgbGlzdChwYWdldmlld3NEYXRhUmVwb3J0ID0gcGFnZXZpZXdzRGF0YSwgCiAgICAgICAgIHBlckRheVRvdGFscyA9IHBlckRheVRvdGFscywgCiAgICAgICAgIHBlclRhZ1RvdGFscyA9IHBlclRhZ1RvdGFscywgCiAgICAgICAgIHBlclBhZ2VUb3RhbHMgPSBwZXJQYWdlVG90YWxzLCAKICAgICAgICAgcGVyUGFnZURheVRvdGFscyA9IHBlclBhZ2VEYXlUb3RhbHMsIAogICAgICAgICBwZXJUYWdEYXlUb3RhbHMgPSBwZXJUYWdEYXlUb3RhbHMsCiAgICAgICAgIHBlclRhZ1BhZ2VUb3RhbHMgPSBwZXJUYWdQYWdlVG90YWxzKQogICkKICAKfQoKIyAtIFJlcG9ydCBwYWdldmlld3M6CnBhZ2V2aWV3c0RhdGEgPC0gd21kZV9yZXBvcnRfcGFnZXZpZXdzKGRhdGFEaXIpCnBhZ2V2aWV3c1JlcG9ydEZpbGUgPC0gcGFnZXZpZXdzRGF0YSRwYWdldmlld3NEYXRhUmVwb3J0CndyaXRlLmNzdihwYWdldmlld3NSZXBvcnRGaWxlLCAicGFnZXZpZXdzUmVwb3J0RmlsZS5jc3YiKQpwYWdldmlld3NfcGVyRGF5VG90YWxzIDwtIHBhZ2V2aWV3c0RhdGEkcGVyRGF5VG90YWxzCndyaXRlLmNzdihwYWdldmlld3NfcGVyRGF5VG90YWxzLCAicGFnZXZpZXdzX3BlckRheVRvdGFscy5jc3YiKQpwYWdldmlld3NfcGVyVGFnVG90YWxzIDwtIHBhZ2V2aWV3c0RhdGEkcGVyVGFnVG90YWxzCndyaXRlLmNzdihwYWdldmlld3NfcGVyVGFnVG90YWxzLCAicGFnZXZpZXdzX3BlclRhZ1RvdGFscy5jc3YiKQpwYWdldmlld3NfcGVyUGFnZVRvdGFscyA8LSBwYWdldmlld3NEYXRhJHBlclBhZ2VUb3RhbHMKd3JpdGUuY3N2KHBhZ2V2aWV3c19wZXJQYWdlVG90YWxzLCAicGFnZXZpZXdzX3BlclBhZ2VUb3RhbHMuY3N2IikKcGFnZXZpZXdzX3BlclBhZ2VEYXlUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSRwZXJQYWdlRGF5VG90YWxzCndyaXRlLmNzdihwYWdldmlld3NfcGVyUGFnZURheVRvdGFscywgInBhZ2V2aWV3c19wZXJQYWdlRGF5VG90YWxzLmNzdiIpCnBhZ2V2aWV3c19wZXJUYWdEYXlUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSRwZXJUYWdEYXlUb3RhbHMKd3JpdGUuY3N2KHBhZ2V2aWV3c19wZXJUYWdEYXlUb3RhbHMsICJwYWdldmlld3NfcGVyVGFnRGF5VG90YWxzLmNzdiIpCnBhZ2V2aWV3c19wZXJUYWdQYWdlVG90YWxzIDwtIHBhZ2V2aWV3c0RhdGEkcGVyVGFnUGFnZVRvdGFscwp3cml0ZS5jc3YocGFnZXZpZXdzX3BlclRhZ1BhZ2VUb3RhbHMsICJwZXJUYWdQYWdlVG90YWxzLmNzdiIpCgojIyMgLS0tIFJlcG9ydCBVc2VyIFJlZ2lzdHJhdGlvbnMKCiMgLSBmdW5jdGlvbjogd21kZV9yZXBvcnRfcmVnaXN0cmF0aW9ucwp3bWRlX3JlcG9ydF9yZWdpc3RyYXRpb25zIDwtIGZ1bmN0aW9uKGRhdGFEaXIpIHsKICAKICAjIC0gU2V0dXAKICBsaWJyYXJ5KGRhdGEudGFibGUpCiAgbGlicmFyeShkcGx5cikKICAKICAjIC0gbGlzdCBmaWxlczoKICBsRiA8LSBsaXN0LmZpbGVzKGRhdGFEaXIpCiAgCiAgIyAtIGZpbHRlciBhZ2dyZWdhdGVkIHVzZXIgcmVnaXN0cmF0aW9uIGRhdGEKICBsRiA8LSBsRltncmVwbCgidXNlclJlZ2lzdHJhdGlvbnNBZ2dyZWFndGVkXyIsIGxGLCBmaXhlZCA9IFQpXQogIAogICMgLSBsb2FkIGZpbGVzIGFuZCBtZXJnZQogIHJlZ2lzdHJhdGlvbkRhdGEgPC0gdmVjdG9yKG1vZGUgPSAibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChsRikpCiAgZm9yIChpIGluIDE6bGVuZ3RoKGxGKSkgewogICAgaWYgKGdyZXBsKCJjc3YkfHRzdiQiLCBsRltpXSkpIHsKICAgICAgcmVnaXN0cmF0aW9uRGF0YVtbaV1dIDwtIGZyZWFkKHBhc3RlMChkYXRhRGlyLCBsRltpXSkpCiAgICB9IGVsc2UgewogICAgICByZWdpc3RyYXRpb25EYXRhW1tpXV0gPC0gTlVMTAogICAgfQogIH0KICByZWdpc3RyYXRpb25EYXRhIDwtIHJiaW5kbGlzdChyZWdpc3RyYXRpb25EYXRhKQogIHJlZ2lzdHJhdGlvbkRhdGEkVjEgPC0gTlVMTAogIAogICMgLSBhZ2dyZWdhdGVzCiAgcGVyRGF5VG90YWxzIDwtIHJlZ2lzdHJhdGlvbkRhdGEgJT4lIAogICAgc2VsZWN0KGRhdGUsIFJlZ2lzdHJhdGlvbnMpICU+JSAKICAgIGdyb3VwX2J5KGRhdGUpICU+JSAKICAgIHN1bW1hcmlzZSh0b3RhbFJlZ2lzdHJhdGlvbnMgPSBzdW0oUmVnaXN0cmF0aW9ucykpCiAgcGVyVGFnVG90YWxzIDwtIHJlZ2lzdHJhdGlvbkRhdGEgJT4lIAogICAgc2VsZWN0KGV2ZW50X2NhbXBhaWduLCBSZWdpc3RyYXRpb25zKSAlPiUgCiAgICBncm91cF9ieShldmVudF9jYW1wYWlnbikgJT4lIAogICAgc3VtbWFyaXNlKHRvdGFsUmVnaXN0cmF0aW9ucyA9IHN1bShSZWdpc3RyYXRpb25zKSkKICBwZXJUYWdEYXlUb3RhbHMgPC0gcmVnaXN0cmF0aW9uRGF0YSAlPiUgCiAgICBzZWxlY3QoZXZlbnRfY2FtcGFpZ24sIGRhdGUsIFJlZ2lzdHJhdGlvbnMpICU+JSAKICAgIGdyb3VwX2J5KGV2ZW50X2NhbXBhaWduLCBkYXRlKSAlPiUgCiAgICBzdW1tYXJpc2UodG90YWxSZWdpc3RyYXRpb25zID0gc3VtKFJlZ2lzdHJhdGlvbnMpKQogIAogICMgLSBvdXRwdXQKICByZXR1cm4oCiAgICBsaXN0KHJlZ2lzdHJhdGlvbnNEYXRhUmVwb3J0ID0gcmVnaXN0cmF0aW9uRGF0YSwgCiAgICAgICAgIHBlckRheVRvdGFscyA9IHBlckRheVRvdGFscywgCiAgICAgICAgIHBlclRhZ1RvdGFscyA9IHBlclRhZ1RvdGFscywgCiAgICAgICAgIHBlclRhZ0RheVRvdGFscyA9IHBlclRhZ0RheVRvdGFscykKICApCiAgCn0KCiMgLSBSZXBvcnQgdXBvbiB1c2VyIHJlZ2lzdHJhdGlvbnMKdXNlclJlZ0RhdGEgPC0gd21kZV9yZXBvcnRfcmVnaXN0cmF0aW9ucyhkYXRhRGlyKQp1c2VyUmVnaXN0cmF0aW9uc1JlcG9ydEZpbGUgPC0gdXNlclJlZ0RhdGEkcmVnaXN0cmF0aW9uc0RhdGFSZXBvcnQKd3JpdGUuY3N2KHVzZXJSZWdpc3RyYXRpb25zUmVwb3J0RmlsZSwgInVzZXJSZWdpc3RyYXRpb25zUmVwb3J0RmlsZS5jc3YiKQp1c2VyUmVnaXN0cmF0aW9uc19wZXJEYXlUb3RhbHMgPC0gdXNlclJlZ0RhdGEkcGVyRGF5VG90YWxzCndyaXRlLmNzdih1c2VyUmVnaXN0cmF0aW9uc19wZXJEYXlUb3RhbHMsICJ1c2VyUmVnaXN0cmF0aW9uc19wZXJEYXlUb3RhbHMuY3N2IikKdXNlclJlZ2lzdHJhdGlvbnNfcGVyVGFnVG90YWxzIDwtIHVzZXJSZWdEYXRhJHBlclRhZ1RvdGFscwp3cml0ZS5jc3YodXNlclJlZ2lzdHJhdGlvbnNfcGVyVGFnVG90YWxzLCAidXNlclJlZ2lzdHJhdGlvbnNfcGVyVGFnVG90YWxzLmNzdiIpCnVzZXJSZWdpc3RyYXRpb25zX3BlclRhZ0RheVRvdGFscyA8LSB1c2VyUmVnRGF0YSRwZXJUYWdEYXlUb3RhbHMKd3JpdGUuY3N2KHVzZXJSZWdpc3RyYXRpb25zX3BlclRhZ0RheVRvdGFscywgInVzZXJSZWdpc3RyYXRpb25zX3BlclRhZ0RheVRvdGFscy5jc3YiKQpgYGAKCiMjIDEuIENhbXBhaWduIEJhbm5lcnMgYW5kIFBhZ2VzCgpUaGlzIHNlY3Rpb24gcHJlc2VudHMgYWxsIGRhdGEgYW5kIHN0YXRpc3RpY3Mgb24gdGhlIGNhbXBhaWduIGJhbm5lcnMgYW5kIHBhZ2VzLgoKIyMjIDEuMSBCYW5uZXIgSW1wcmVzc2lvbnMKCiMjIyMgMS4xLjEgQmFubmVyIEltcHJlc3Npb25zIE92ZXJ2aWV3CgoqKkNoYXJ0IDEuMS4xIERhaWx5IEJhbm5lciBJbXByZXNzaW9ucyAqKgoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0V9CmRhdGFTZXQgPC0gcmVhZC5jc3YoCiAgJ19hbmFseXRpY3MvYmFubmVySW1wcmVzc2lvbnNGaWxlLmNzdicsCiAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLAogICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKIyAtIFZpc3VhbGl6ZSB3LiB7Z2dwbG90Mn0KZ2dwbG90KGRhdGFTZXQsIGFlcyh4ID0gZGF0ZSwKICAgICAgICAgICAgICAgICAgICB5ID0gaW1wcmVzc2lvbnMsCiAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBiYW5uZXIsCiAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBiYW5uZXIsCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGJhbm5lciwKICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IGltcHJlc3Npb25zLAogICAgICAgICAgICAgICAgICAgICkpICsgCiAgZ2VvbV9wYXRoKHNpemUgPSAuNSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAxLjUpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTg6IEJhbm5lciBJbXByZXNzaW9ucycpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICBnZW9tX3RleHRfcmVwZWwoc2l6ZSA9IDMuNSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBzaXplID0gOCkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpCmBgYAoKIyMjIyAxLjEuMSBCYW5uZXIgSW1wcmVzc2lvbnMgT3ZlcnZpZXc6IFRhYmxlCgoqKlRhYmxlIDEuMS4xLiBEYWlseSBCYW5uZXIgSW1wcmVzc2lvbnMgKioKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyMjIC0tLSBGdWxsIERhdGFzZXQgKFRhYmxlIFJlcG9ydCkKZGF0YXRhYmxlKGRhdGFTZXQpCmBgYAoKIyMjIyAxLjEuMiBUb3RhbCBCYW5uZXIgSW1wcmVzc2lvbnMKCioqQ2hhcnQgMS4xLjIuIFRvdGFsIEJhbm5lciBJbXByZXNzaW9ucyAqKgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpkYXRhU2V0IDwtIHJlYWQuY3N2KAogICdfYW5hbHl0aWNzL2Jhbm5lclRvdGFscy5jc3YnLAogICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSwKICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmdncGxvdChkYXRhU2V0LCBhZXMoeCA9IGJhbm5lciwgCiAgICAgICAgICAgICAgICAgICAgeSA9IHRvdGFsSW1wcmVzc2lvbnMsIAogICAgICAgICAgICAgICAgICAgIGNvbG9yID0gYmFubmVyLCAKICAgICAgICAgICAgICAgICAgICBmaWxsID0gYmFubmVyLCAKICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IHRvdGFsSW1wcmVzc2lvbnMpKSArIAogIGdlb21fYmFyKHdpZHRoID0gLjUsIHN0YXQgPSAiaWRlbnRpdHkiKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxODogQmFubmVyIEltcHJlc3Npb25zJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIGdlb21fbGFiZWxfcmVwZWwoc2l6ZSA9IDMuNSwgY29sb3IgPSAid2hpdGUiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4KSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCiMjIyMgMS4xLjMgQmFubmVyIEltcHJlc3Npb25zIHBlciBEYXkKCioqQ2hhcnQgMS4xLjMuIEJhbm5lciBJbXByZXNzaW9ucyBwZXIgRGF5ICoqCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CmRhdGFTZXQgPC0gcmVhZC5jc3YoCiAgJ19hbmFseXRpY3MvYmFubmVyRGF5VG90YWxzLmNzdicsCiAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLAogICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKZ2dwbG90KGRhdGFTZXQsIGFlcyh4ID0gZGF0ZSwgCiAgICAgICAgICAgICAgICAgICAgeSA9IHRvdGFsSW1wcmVzc2lvbnMsIAogICAgICAgICAgICAgICAgICAgIGxhYmVsID0gdG90YWxJbXByZXNzaW9ucykpICsKICBnZW9tX3BhdGgoc2l6ZSA9IC4yNSwgZ3JvdXAgPSAxLCBjb2xvciA9ICJkYXJrYmx1ZSIpICsKICBnZW9tX3BvaW50KHNpemUgPSAxLjUsIGNvbG9yID0gImRhcmtibHVlIikgKyAKICBnZW9tX3BvaW50KHNpemUgPSAxLCBjb2xvciA9ICJ3aGl0ZSIpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgZ2d0aXRsZSgnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE4OiBCYW5uZXIgSW1wcmVzc2lvbnMnKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgZ2VvbV9sYWJlbF9yZXBlbChzaXplID0gMy41LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4KSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQpgYGAKCgojIyMgMS4yIFBhZ2V2aWV3cwoKIyMjIDEuMi4xIFBhZ2V2aWV3cyBPdmVydmlldwoKKipDaGFydCAxLjIuMS4gUGFnZXZpZXdzIE92ZXJ2aWV3LioqIExvZyBzY2FsaW5nIG9mIHRoZSBwYWdldmlld3MgaXMgbmVjZXNzYXJ5OyB0aGUgbnVtYmVycyByZXBvcnRlZCBpbiB0aGUgZGF0YSBwb2ludCBsYWJlbHMgYXJlIGV4YWN0LgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpkYXRhU2V0IDwtIHJlYWQuY3N2KAogICdfYW5hbHl0aWNzL3BhZ2V2aWV3c19wZXJQYWdlRGF5VG90YWxzLmNzdicsCiAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLAogICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKZGF0YVNldCRQYWdlIDwtIGdzdWIoIi93aWtpL1dpa2lwZWRpYTpXaWtpcGVkaWFffC93aWtpL1dpa2lwZWRpYTpXaWtpbWVkaWFfIiwgIiIsIGRhdGFTZXQkUGFnZSkKIyAtIFZpc3VhbGl6ZSB3LiB7Z2dwbG90Mn0KZ2dwbG90KGRhdGFTZXQsIGFlcyh4ID0gZGF0ZSwKICAgICAgICAgICAgICAgICAgICB5ID0gbG9nMTAodG90YWxQYWdldmlld3MpLAogICAgICAgICAgICAgICAgICAgIGdyb3VwID0gUGFnZSwKICAgICAgICAgICAgICAgICAgICBjb2xvciA9IFBhZ2UsCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFBhZ2UsCiAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSB0b3RhbFBhZ2V2aWV3cywKICAgICAgICAgICAgICAgICAgICApKSArIAogIGdlb21fcGF0aChzaXplID0gLjI1KSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDEuNSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDEsIGNvbG9yID0gIndoaXRlIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxODogUGFnZXZpZXdzJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIGdlb21fdGV4dF9yZXBlbChzaXplID0gMy41LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4KSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikKYGBgCgojIyMgMS4yLjEgUGFnZXZpZXdzIE92ZXJ2aWV3OiBUYWJsZQoKKipUYWJsZSAxLjIuMS4gUGFnZXZpZXdzIE92ZXJ2aWV3KioKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyMjIC0tLSBGdWxsIERhdGFzZXQgKFRhYmxlIFJlcG9ydCkKZGF0YXRhYmxlKGRhdGFTZXQgJT4lIGFycmFuZ2UoZGVzYyh0b3RhbFBhZ2V2aWV3cykpKQpgYGAKCiMjIyAxLjIuMiBQYWdldmlld3MgT3ZlcnZpZXc6IHRvdGFscyBwZXIgUGFnZQoKKipDaGFydCAxLjIuMi4gUGFnZXZpZXdzIE92ZXJ2aWV3OiB0b3RhbHMgcGVyIFBhZ2UqKgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpkYXRhU2V0IDwtIHJlYWQuY3N2KAogICdfYW5hbHl0aWNzL3BhZ2V2aWV3c19wZXJQYWdlVG90YWxzLmNzdicsCiAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLAogICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKZGF0YVNldCRQYWdlIDwtIGdzdWIoIi93aWtpL1dpa2lwZWRpYTpXaWtpcGVkaWFffC93aWtpL1dpa2lwZWRpYTpXaWtpbWVkaWFfIiwgIiIsIGRhdGFTZXQkUGFnZSkKZGF0YVNldCRQYWdlIDwtIGZhY3RvcihkYXRhU2V0JFBhZ2UsIAogICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGRhdGFTZXQkUGFnZVtvcmRlcigtZGF0YVNldCR0b3RhbFBhZ2V2aWV3cyldKQpnZ3Bsb3QoZGF0YVNldCwgYWVzKHggPSBQYWdlLCAKICAgICAgICAgICAgICAgICAgICB5ID0gbG9nKHRvdGFsUGFnZXZpZXdzKSwgCiAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBQYWdlLAogICAgICAgICAgICAgICAgICAgIGZpbGwgPSBQYWdlLAogICAgICAgICAgICAgICAgICAgIGxhYmVsID0gdG90YWxQYWdldmlld3MpKSArIAogIGdlb21fYmFyKHdpZHRoID0gLjUsIHN0YXQgPSAiaWRlbnRpdHkiKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxODogQmFubmVyIFRvdGFsIFBhZ2V2aWV3cycpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICBnZW9tX2xhYmVsX3JlcGVsKHNpemUgPSAzLjUsIGNvbG9yID0gIndoaXRlIiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBzaXplID0gOCkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgoKCiMjIyAxLjIuMyBQYWdldmlld3MgT3ZlcnZpZXc6IHRvdGFscyBwZXIgZGF5CgoqKkNoYXJ0IDEuMi4zLiBQYWdldmlld3MgT3ZlcnZpZXc6IHRvdGFscyBwZXIgZGF5KioKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KZGF0YVNldCA8LSByZWFkLmNzdigKICAnX2FuYWx5dGljcy9wYWdldmlld3NfcGVyRGF5VG90YWxzLmNzdicsCiAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLAogICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKZ2dwbG90KGRhdGFTZXQsIGFlcyh4ID0gZGF0ZSwgCiAgICAgICAgICAgICAgICAgICAgeSA9IHRvdGFsUGFnZXZpZXdzLCAKICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IHRvdGFsUGFnZXZpZXdzKSkgKwogIGdlb21fcGF0aChzaXplID0gLjI1LCBncm91cCA9IDEsIGNvbG9yID0gImRhcmtibHVlIikgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDEuNSwgY29sb3IgPSAiZGFya2JsdWUiKSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDEsIGNvbG9yID0gIndoaXRlIikgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTg6IFBhZ2V2aWV3cycpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICBnZW9tX2xhYmVsX3JlcGVsKHNpemUgPSAzLjUsIHNob3cubGVnZW5kID0gRkFMU0UpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpCmBgYAoKIyMjIDEuMi4zIFBhZ2V2aWV3cyBPdmVydmlldzogdG90YWxzIHBlciBUYWcvUGFnZQoKKipDaGFydCAxLjIuMy4gUGFnZXZpZXdzIE92ZXJ2aWV3OiB0b3RhbHMgcGVyIFRhZy9QYWdlKioKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KZGF0YVNldCA8LSByZWFkLmNzdigKICAnX2FuYWx5dGljcy9wZXJUYWdQYWdlVG90YWxzLmNzdicsCiAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLAogICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKY2FtcGFpZ25UYWdzIDwtIGMoJ1dNREVfbmV3ZWRpdG9yc19hdXR1bW5fMjAxOF9scDEnLCAKICAgICAgICAgICAgICAgICAgJ1dNREVfbmV3ZWRpdG9yc19hdXR1bW5fMjAxOF9scDFtJywKICAgICAgICAgICAgICAgICAgJ1dNREVfbmV3ZWRpdG9yc19hdXR1bW5fMjAxOF9scG4nKQpkYXRhU2V0IDwtIGZpbHRlcihkYXRhU2V0LCAKICAgICAgICAgICAgICAgICAgZ3JlcGwoY2FtcGFpZ25UYWdzWzFdLCBkYXRhU2V0JFRhZyl8Z3JlcGwoY2FtcGFpZ25UYWdzWzJdLCBkYXRhU2V0JFRhZyl8Z3JlcGwoY2FtcGFpZ25UYWdzWzNdLCBkYXRhU2V0JFRhZykpCmRhdGFTZXQkVGFnW2dyZXBsKGNhbXBhaWduVGFnc1syXSwgZGF0YVNldCRUYWcsIGZpeGVkID0gVCldIDwtICJscDFtIgpkYXRhU2V0JFRhZ1tncmVwbChjYW1wYWlnblRhZ3NbMV0sIGRhdGFTZXQkVGFnLCBmaXhlZCA9IFQpXSA8LSAibHAxIgpkYXRhU2V0JFRhZ1tncmVwbChjYW1wYWlnblRhZ3NbM10sIGRhdGFTZXQkVGFnLCBmaXhlZCA9IFQpXSA8LSAibHAxbiIKZGF0YVNldCRQYWdlIDwtIGdzdWIoIi93aWtpL1dpa2lwZWRpYTpXaWtpcGVkaWFffC93aWtpL1dpa2lwZWRpYTpXaWtpbWVkaWFfIiwgIiIsIGRhdGFTZXQkUGFnZSkKZGF0YVNldCA8LSBkYXRhU2V0ICU+JSAKICBzZWxlY3QoVGFnLCBQYWdlLCB0b3RhbFBhZ2V2aWV3cykgJT4lIAogIGdyb3VwX2J5KFRhZywgUGFnZSkgJT4lIAogIHN1bW1hcmlzZSh0b3RhbFBhZ2V2aWV3cyA9IHN1bSh0b3RhbFBhZ2V2aWV3cykpCmRhdGFTZXQgJT4lIAogIGdncGxvdChhZXMoeCA9IFRhZywKICAgICAgICAgICAgIHkgPSBQYWdlLAogICAgICAgICAgICAgY29sb3IgPSBUYWcsCiAgICAgICAgICAgICBsYWJlbCA9IHRvdGFsUGFnZXZpZXdzKSkgKyAKICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTg6IFBhZ2V2aWV3cyBwZXIgVGFnJykgKwogIGdlb21fcG9pbnQoYWVzKHNpemUgPSB0b3RhbFBhZ2V2aWV3cyksIHNoYXBlID0gMTkpICsKICBnZW9tX3RleHRfcmVwZWwoc2l6ZSA9IDMsIG51ZGdlX3ggPSAuMywgc2hvdy5sZWdlbmQgPSBGKSArIAogIHhsYWIoIlRhZ3MiKSArIHlsYWIoIlBhZ2VzIikgKyAKICB0aGVtZV9idygpICsgCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIikpCmBgYAoKIyMjIDEuMi40IFBhZ2V2aWV3cyBwZXIgVGFnLCBkYWlseSB0b3RhbHMKCioqQ2hhcnQgMS4yLjQuIFBhZ2V2aWV3cyBwZXIgVGFnLCBkYWlseSB0b3RhbHMuKiogTG9nIHNjYWxpbmcgb2YgdGhlIHBhZ2V2aWV3cyBpcyBuZWNlc3Nhcnk7IHRoZSBudW1iZXJzIHJlcG9ydGVkIGluIHRoZSBkYXRhIHBvaW50IGxhYmVscyBhcmUgZXhhY3QuCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CmRhdGFTZXQgPC0gcmVhZC5jc3YoCiAgJ19hbmFseXRpY3MvcGFnZXZpZXdzX3BlclRhZ0RheVRvdGFscy5jc3YnLAogICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSwKICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmNhbXBhaWduVGFncyA8LSBjKCdXTURFX25ld2VkaXRvcnNfYXV0dW1uXzIwMThfbHAxJywgCiAgICAgICAgICAgICAgICAgICdXTURFX25ld2VkaXRvcnNfYXV0dW1uXzIwMThfbHAxbScsCiAgICAgICAgICAgICAgICAgICdXTURFX25ld2VkaXRvcnNfYXV0dW1uXzIwMThfbHBuJykKZGF0YVNldCA8LSBmaWx0ZXIoZGF0YVNldCwgCiAgICAgICAgICAgICAgICAgIGdyZXBsKGNhbXBhaWduVGFnc1sxXSwgZGF0YVNldCRUYWcpfGdyZXBsKGNhbXBhaWduVGFnc1syXSwgZGF0YVNldCRUYWcpfGdyZXBsKGNhbXBhaWduVGFnc1szXSwgZGF0YVNldCRUYWcpKQpkYXRhU2V0JFRhZ1tncmVwbChjYW1wYWlnblRhZ3NbMl0sIGRhdGFTZXQkVGFnLCBmaXhlZCA9IFQpXSA8LSAibHAxbSIKZGF0YVNldCRUYWdbZ3JlcGwoY2FtcGFpZ25UYWdzWzFdLCBkYXRhU2V0JFRhZywgZml4ZWQgPSBUKV0gPC0gImxwMSIKZGF0YVNldCRUYWdbZ3JlcGwoY2FtcGFpZ25UYWdzWzNdLCBkYXRhU2V0JFRhZywgZml4ZWQgPSBUKV0gPC0gImxwMW4iIyAtIFZpc3VhbGl6ZSB3LiB7Z2dwbG90Mn0KZGF0YVNldCA8LSBkYXRhU2V0ICU+JSAKICBncm91cF9ieShUYWcsIGRhdGUpICU+JSAKICBzdW1tYXJpc2UodG90YWxQYWdldmlld3MgPSBzdW0odG90YWxQYWdldmlld3MpKQpnZ3Bsb3QoZGF0YVNldCwgYWVzKHggPSBkYXRlLAogICAgICAgICAgICAgICAgICAgIHkgPSBsb2cxMCh0b3RhbFBhZ2V2aWV3cyksCiAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBUYWcsCiAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBUYWcsCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFRhZywKICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IHRvdGFsUGFnZXZpZXdzLAogICAgICAgICAgICAgICAgICAgICkpICsgCiAgZ2VvbV9wYXRoKHNpemUgPSAuMjUpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMS41KSArCiAgZ2VvbV9wb2ludChzaXplID0gMSwgY29sb3IgPSAid2hpdGUiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgZ2d0aXRsZSgnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE4OiBQYWdldmlld3MgcGVyIFRhZywgZGFpbHkgdG90YWxzJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIGdlb21fdGV4dF9yZXBlbChzaXplID0gMy41LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4KSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikKYGBgCgojIyAyLiBVc2VyIFJlZ2lzdHJhdGlvbnMKCkFsbCBkYXRhIG9uIHVzZXIgcmVnaXN0cmF0aW9ucyBhcmUgcHJlc2VudGVkIGluIHRoaXMgc2VjdGlvbi4KCiMjIyAyLjEgUmVnaXN0cmF0aW9ucyBwZXIgdGFnIGFuZCBkYXkKCioqQ2hhcnQgMi4xIFJlZ2lzdHJhdGlvbnMgcGVyIHRhZyBhbmQgZGF5LioqIFBsZWFzZSAqKm5vdGU6KiogcG9pbnRzIHdpdGggbm8gZGF0YSBsYWJlbHMgc2lnbmlmeSAwIHVzZXIgcmVnaXN0cmF0aW9ucy4KCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyAtIFN0YW5kYXJkIHJlZ2lzdHJhdGlvbnMKZGF0YVNldCA8LSByZWFkLmNzdigKICAnX2FuYWx5dGljcy91c2VyUmVnaXN0cmF0aW9uc1JlcG9ydEZpbGUuY3N2JywKICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULAogICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsCiAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpkYXRhU2V0IDwtIGRhdGFTZXQgJT4lIAogIGZpbHRlcighKGV2ZW50X2NhbXBhaWduICVpbiUgJ1dNREVfbmV3ZWRpdG9yc19hdXR1bW5fMjAxOF9scG4nKSkKZGF0YVNldCRjYW1wYWlnbiA8LSBOVUxMCiMgLSBOZXdzbGV0dGVyIHJlZ2lzdHJhdGlvbnMKZGF0YVNldDIgPC0gcmVhZC5kZWxpbSgKICAnX2FuYWx5dGljcy9OZXdzbGV0dGVyX3VzZXJSZWdpc3RyYXRpb25zLnRzdicsCiAgc2VwID0gIlx0IiwKICBoZWFkZXIgPSBULAogIHJvdy5uYW1lcyA9IDEsCiAgY2hlY2submFtZXMgPSBGLAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpkYXRhU2V0MiA8LSBkYXRhU2V0MiAlPiUgCiAgc2VsZWN0KGV2ZW50X2NhbXBhaWduLCB0aW1lc3RhbXApCmRhdGFTZXQyJHRpbWVzdGFtcCA8LSBhcy5jaGFyYWN0ZXIoZGF0YVNldDIkdGltZXN0YW1wKQpkYXRhU2V0MiR0aW1lc3RhbXAgPC0gc2FwcGx5KGRhdGFTZXQyJHRpbWVzdGFtcCwgZnVuY3Rpb24oeCkgewogIHllYXIgPC0gc3Vic3RyKHgsIDEsIDQpCiAgbW9udGggPC0gc3Vic3RyKHgsIDUsIDYpCiAgZGF5IDwtIHN1YnN0cih4LCA3LCA4KQogIHBhc3RlKHllYXIsIG1vbnRoLCBkYXksIHNlcCA9ICItIikKfSkKZGF0YVNldDIgPC0gZGF0YVNldDIgJT4lIAogIGdyb3VwX2J5KGV2ZW50X2NhbXBhaWduLCB0aW1lc3RhbXApICU+JSAKICBzdW1tYXJpc2UoUmVnaXN0cmF0aW9ucyA9IG4oKSkKY29sbmFtZXMoZGF0YVNldDIpWzJdIDwtICJkYXRlIgpkYXRhU2V0MiA8LSBkYXRhU2V0MlssIGMoMSwgMywgMildCmRhdGFTZXQgPC0gcmJpbmRsaXN0KGxpc3QoZGF0YVNldCwgZGF0YVNldDIpKQpkYXRhU2V0JGV2ZW50X2NhbXBhaWduIDwtIGdzdWIoIldNREVfbmV3ZWRpdG9yc19hdXR1bW5fMjAxOF8iLCAiIiwgZGF0YVNldCRldmVudF9jYW1wYWlnbikKZ2dwbG90KGRhdGFTZXQsIGFlcyh4ID0gZGF0ZSwKICAgICAgICAgICAgICAgICAgICB5ID0gUmVnaXN0cmF0aW9ucywKICAgICAgICAgICAgICAgICAgICBncm91cCA9IGV2ZW50X2NhbXBhaWduLAogICAgICAgICAgICAgICAgICAgIGNvbG9yID0gZXZlbnRfY2FtcGFpZ24sCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGV2ZW50X2NhbXBhaWduLAogICAgICAgICAgICAgICAgICAgIGxhYmVsID0gUmVnaXN0cmF0aW9ucywKICAgICAgICAgICAgICAgICAgICApKSArIAogIGdlb21fcGF0aChzaXplID0gLjI1KSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDEuNSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDEsIGNvbG9yID0gIndoaXRlIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxODogUmVnaXN0cmF0aW9ucyBwZXIgVGFnJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIGdlb21fdGV4dF9yZXBlbChzaXplID0gMy41LCBzaG93LmxlZ2VuZCA9IEYpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQpgYGAKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyMjIC0tLSBGdWxsIERhdGFzZXQgKFRhYmxlIFJlcG9ydCkKY29sbmFtZXMoZGF0YVNldClbMV0gPC0gJ1RhZycKZGF0YXRhYmxlKGRhdGFTZXQgJT4lIGFycmFuZ2UoVGFnLCBkYXRlLCBkZXNjKFJlZ2lzdHJhdGlvbnMpKSkKYGBgCgojIyMgMi4yIFRvdGFsIHJlZ2lzdHJhdGlvbnMgcGVyIHRhZwoKKipDaGFydCAyLjIgVG90YWwgcmVnaXN0cmF0aW9ucyBwZXIgdGFnLioqCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CmRhdGFTZXQgPC0gZGF0YVNldCAlPiUgCiAgZ3JvdXBfYnkoVGFnKSAlPiUgCiAgc3VtbWFyaXNlKHRvdGFsUmVnaXN0cmF0aW9ucyA9IHN1bShSZWdpc3RyYXRpb25zKSkKZ2dwbG90KGRhdGFTZXQsIGFlcyh4ID0gVGFnLCAKICAgICAgICAgICAgICAgICAgICB5ID0gdG90YWxSZWdpc3RyYXRpb25zLCAKICAgICAgICAgICAgICAgICAgICBjb2xvciA9IFRhZywKICAgICAgICAgICAgICAgICAgICBmaWxsID0gVGFnLAogICAgICAgICAgICAgICAgICAgIGxhYmVsID0gdG90YWxSZWdpc3RyYXRpb25zKSkgKyAKICBnZW9tX2Jhcih3aWR0aCA9IC41LCBzdGF0ID0gImlkZW50aXR5IikgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTg6IFRvdGFsIFJlZ2lzdHJhdGlvbnMgcGVyIFRhZycpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICBnZW9tX2xhYmVsX3JlcGVsKHNpemUgPSAzLjUsIGNvbG9yID0gIndoaXRlIiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsgCiAgeWxhYigiUmVnaXN0cmF0aW9ucyIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBzaXplID0gOCkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikKYGBgCgojIyAzLiBVc2VyIEVkaXRzCgpBbGwgZGF0YSBvbiB1c2VyIGVkaXRzIGFyZSBwcmVzZW50ZWQgaW4gdGhpcyBzZWN0aW9uLgoKIyMjIDMuMSBVc2VyIGVkaXRzOiBkaXN0cmlidXRpb24KCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KZGF0YVNldCA8LSByZWFkLmNzdigKICAnX2FuYWx5dGljcy9hbGxVc2VyRWRpdHNfMjAxOF9BdUJDLmNzdicsCiAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLAogICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKIyAtIEVkaXQgfCAxIHwgMi00IHwgNS05IHwgMTAtNDkgfCA+NTAKZWRpdEJvdW5kYXJpZXMgPC0gbGlzdCgKICBjKDAsIDEpLCAKICBjKDIsIDQpLAogIGMoNSwgOSksCiAgYygxMCwgNDkpCikKZGF0YVNldCRlZGl0Q2xhc3MgPC0gc2FwcGx5KGRhdGFTZXQkZWRpdHMsIGZ1bmN0aW9uKHgpIHsKICB3RUMgPC0gc2FwcGx5KGVkaXRCb3VuZGFyaWVzLCBmdW5jdGlvbih5KSB7CiAgICB4ID49IHlbMV0gJiB4IDw9IHlbMl0KICB9KQogIGlmIChzdW0od0VDKSA9PSAwKSB7CiAgICByZXR1cm4oIj49IDUwIikKICB9IGVsc2UgewogICAgcmV0dXJuKHBhc3RlMCgiKCIsCiAgICAgICAgICAgICAgICAgIGVkaXRCb3VuZGFyaWVzW1t3aGljaCh3RUMpXV1bMV0sCiAgICAgICAgICAgICAgICAgICIgLSAiLAogICAgICAgICAgICAgICAgICBlZGl0Qm91bmRhcmllc1tbd2hpY2god0VDKV1dWzJdLCAKICAgICAgICAgICAgICAgICAgIikiCiAgICAgICAgICAgICAgICAgICkKICAgICkKICB9Cn0pCmVkaXRDbGFzcyA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGRhdGFTZXQkZWRpdENsYXNzKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpjb2xuYW1lcyhlZGl0Q2xhc3MpIDwtIGMoJ0VkaXQgQ2xhc3MnLCAnTnVtLlVzZXJzJykKZWRpdENsYXNzJG9yZGVyIDwtIGFzLm51bWVyaWMoc2FwcGx5KGVkaXRDbGFzcyRgRWRpdCBDbGFzc2AsIGZ1bmN0aW9uKHgpIHsKICBsb3dlciA8LSBzdHJfZXh0cmFjdCh4LCAnW1s6ZGlnaXQ6XV0rJykKfSkpCmVkaXRDbGFzcyA8LSBhcnJhbmdlKGVkaXRDbGFzcywgb3JkZXIpCmVkaXRDbGFzcyRvcmRlciA8LSBOVUxMCmRhdGF0YWJsZShlZGl0Q2xhc3MpCmBgYAoKCgoKCg==