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

The campaign is run from 2019/01/02 to 2019/01/16.

CURRENT UPDATE: Complete dataset as of 2019/01/16.

0. Data Acquisiton

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

0.1 Daily Update

### --- Data Acquisition for the Thank You 2019 Campaign
### --- run from stat1007
### --- /home/goransm/Analytics/NewEditors/Campaigns/2019_ThankYou

### --- to data directory
dataDir <- '/home/goransm/Analytics/NewEditors/Campaigns/2019_ThankYou/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, ' > ', paste0(dataDir, fileName))
  # - command:
  hiveCommand <- paste(hiveArgs, hiveInput)
  return(
    system(command = hiveCommand, wait = TRUE))
}

# - set params to wmde_collect_banner_impressions
# - for the Thank You 2919
uri_host <- c('de.wikipedia.org', 'de.m.wikipedia.org')
uri_path  <- '/beacon/impression'
uri_query <- c('WMDE_2019_thx'
               )
queryFile <- 'thankyou2019_BannerImpressions.hql'
fileName <- paste0("bannerImpressions_", cetDay, ".tsv")

# - 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, 
                           sep = "\t")
  colnames(bannerData) <- 'uri_query'
  
  # - clean
  wInfo <- which(grepl("campaign=", bannerData$uri_query))
  bannerData <- bannerData[wInfo, ]
  
  # - 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, me
  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 <- "thankyou2019"
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 Thank You 2919
uri_host <- c('de.wikipedia.org', 'de.m.wikipedia.org')
uri_path  <- c(
  '/wiki/Wikipedia:Wikimedia_Deutschland/LerneWikipedia')
queryFile <- 'thankyou2019_Pageviews.hql'
fileName <- paste0("pageviews_", cetDay, ".tsv")

# - 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 Thank You 2919
uri_query_filter <- 'WMDE_2019_thx'

# - 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_2019_thx'
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
  )
  
}

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 Thank You 2919
### --- run locally

### --- to data directory
dataDir <- 
  '/home/goransm/Work/___DataKolektiv/Projects/WikimediaDEU/_WMDE_Projects/_misc/NewEditors_Team/2019_ThankYou/_data/'
analyticsDir <- 
  '/home/goransm/Work/___DataKolektiv/Projects/WikimediaDEU/_WMDE_Projects/_misc/NewEditors_Team/2019_ThankYou/_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.

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 = 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('Thank You 2919: 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")

1.2.1 Pageviews Overview: Table

Table 1.2.1. Pageviews Overview

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

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
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('Thank You 2919: 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")

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

3. User Edits

This analysis is scheduled for January 21st and 30th Phab

3.1 User edits: distribution

dataSet <- read.csv(
  '_analytics/allUserEdits_ThankYou2019.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)
LS0tCnRpdGxlOiAnVGhhbmsgWW91IENhbXBhaWduIDIwMTk6IFJlcG9ydCcKYXV0aG9yOiAiR29yYW4gUy4gTWlsb3Zhbm92aWMsIERhdGEgU2NpZW50aXN0LCBXTURFIgpkYXRlOiAiSmFudWFyeSAyMSwgMjAxOSIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIHRoZW1lOiBzaW1wbGV4CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIHRvY19kZXB0aDogNQogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiA1Ci0tLQoKKipGZWVkYmFjayoqIHNob3VsZCBiZSBzZW5kIHRvIGBnb3Jhbi5taWxvdmFub3ZpY19leHRAd2lraW1lZGlhLmRlYC4gCgpUaGUgY2FtcGFpZ24gaXMgcnVuIGZyb20gMjAxOS8wMS8wMiB0byAyMDE5LzAxLzE2LgoKKipDVVJSRU5UIFVQREFURToqKiBDb21wbGV0ZSBkYXRhc2V0IGFzIG9mIDIwMTkvMDEvMTYuCgpgYGB7ciwgZWNobyA9IEYsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGLCByZXN1bHRzID0gJ2hpZGUnfQojICFkaWFnbm9zdGljcyBvZmYKIyMjIC0tLSBTZXR1cAprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLndpZHRoID0gMTUsIGZpZy5oZWlnaHQgPSA4KSAKcm0obGlzdCA9IGxzKCkpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dyZXBlbCkKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkocm1hcmtkb3duKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KERUKQpsaWJyYXJ5KHJlc2hhcGUyKQpgYGAKCiMjIDAuIERhdGEgQWNxdWlzaXRvbgoKKipOT1RFOioqIHRoZSBEYXRhIEFjcXVpc2l0aW9uIGNvZGUgY2h1bmsgaXMgbm90IGZ1bGx5IHJlcHJvZHVjaWJsZSBmcm9tIHRoaXMgUmVwb3J0LiBUaGUgZGF0YSBhcmUgY29sbGVjdGVkIGJ5IHJ1bm5pbmcgYW4gUiBzY3JpcHQgb24gYHN0YXQxMDA3LmVxaWFkLndtbmV0YCwgY29sbGVjdGluZyB0aGUgZGF0YSBhcyBgLnRzdmAgYW5kIGAuY3N2YCBmaWxlcywgY29weWluZyBtYW51YWxseSwgYW5kIHByb2Nlc3NpbmcgbG9jYWxseS4gCgojIyMgMC4xIERhaWx5IFVwZGF0ZQoKYGBge3IsIGVjaG8gPSBULCBldmFsID0gRn0KCiMjIyAtLS0gRGF0YSBBY3F1aXNpdGlvbiBmb3IgdGhlIFRoYW5rIFlvdSAyMDE5IENhbXBhaWduCiMjIyAtLS0gcnVuIGZyb20gc3RhdDEwMDcKIyMjIC0tLSAvaG9tZS9nb3JhbnNtL0FuYWx5dGljcy9OZXdFZGl0b3JzL0NhbXBhaWducy8yMDE5X1RoYW5rWW91CgojIyMgLS0tIHRvIGRhdGEgZGlyZWN0b3J5CmRhdGFEaXIgPC0gJy9ob21lL2dvcmFuc20vQW5hbHl0aWNzL05ld0VkaXRvcnMvQ2FtcGFpZ25zLzIwMTlfVGhhbmtZb3UvZGF0YS8nCnNldHdkKGRhdGFEaXIpCgojIyMgLS0tIGRldGVybWluZSBjZXREYXkKbGlicmFyeShsdWJyaWRhdGUpCmNldERheSA8LSBTeXMudGltZSgpCmNldERheQphdHRyKGNldERheSwgInR6b25lIikgPC0gIkV1cm9wZS9CZXJsaW4iCiMgLSBvbmUgZGF5IGJlaGluZCBmb3IgY3JvbnRhYgojIC0gKGkuZS4gd2FpdGluZyBmb3Igd21mLndlYnJlcXVlc3QgdG8gY29tcGxldGUgaXMgZGF0YSBhY3F1aXNpdGlvbikKY2V0RGF5IDwtIHltZCgKICBzdHJzcGxpdChhcy5jaGFyYWN0ZXIoY2V0RGF5KSwgCiAgICAgICAgICAgc3BsaXQgPSAiICIsIAogICAgICAgICAgIGZpeGVkID0gVClbWzFdXVsxXQogICkgLSAxCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBDb2xsZWN0IEJhbm5lciBJbXByZXNzaW9uIERhdGEKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIC0gZnVuY3Rpb246IHdtZGVfY29sbGVjdF9iYW5uZXJfaW1wcmVzc2lvbnMKd21kZV9jb2xsZWN0X2Jhbm5lcl9pbXByZXNzaW9ucyA8LSBmdW5jdGlvbih1cmlfaG9zdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXJpX3BhdGgsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVyaV9xdWVyeSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2V0RGF5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1ZXJ5RmlsZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlTmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyKSB7CiAgCiAgIyAtIE5PVEU6CiAgIyAtIGV4cGVjdGVkIGZvcm1hdCBmb3IgY2V0RGF5IGlzOiBZWVlZLU1NLURECiAgCiAgIyAtIHRvIGRhdGFEaXIKICBzZXR3ZChkYXRhRGlyKQogIAogICMgLSBsaWJyYXJpZXMKICBsaWJyYXJ5KHN0cmluZ3IpCiAgCiAgIyAtIFdIRVJFIGNvbmRpdGlvbjogY3JlYXRlIGRhdGV0aW1lX2NvbmRpdGlvbgogIGNldF9jb25kaXRpb24gPC0gc2VxKAogICAgZnJvbSA9IGFzLlBPU0lYY3QocGFzdGUwKGNldERheSwiIDA6MDAiKSwgdHogPSAiRXVyb3BlL0JlcmxpbiIpLAogICAgdG8gPSBhcy5QT1NJWGN0KHBhc3RlMChjZXREYXksIiAyMzowMCIpLCB0eiA9ICJFdXJvcGUvQmVybGluIiksCiAgICBieSA9ICJob3VyIgogICkgCiAgYXR0cihjZXRfY29uZGl0aW9uLCAidHpvbmUiKSA8LSAiVVRDIgogIGNldF9jb25kaXRpb24gPC0gYXMuY2hhcmFjdGVyKGNldF9jb25kaXRpb24pCiAgY2V0X2NvbmRpdGlvbiA8LSB1bmxpc3Qoc3RyX2V4dHJhY3RfYWxsKGNldF9jb25kaXRpb24sICJeKFtbOmRpZ2l0Ol1dfFxcc3wtKSoiKSkKICBjZXRfeWVhcnMgPC0gc2FwcGx5KAogICAgc3Ryc3BsaXQoY2V0X2NvbmRpdGlvbiwgc3BsaXQgPSAiICIsIGZpeGVkID0gVCksIGZ1bmN0aW9uKHgpIHsKICAgICAgc3Ryc3BsaXQoeCwgc3BsaXQgPSAiLSIpW1sxXV1bMV0KICAgIH0pCiAgY2V0X21vbnRocyA8LSBzYXBwbHkoCiAgICBzdHJzcGxpdChjZXRfY29uZGl0aW9uLCBzcGxpdCA9ICIgIiwgZml4ZWQgPSBUKSwgZnVuY3Rpb24oeCkgewogICAgICBzdHJzcGxpdCh4LCBzcGxpdCA9ICItIilbWzFdXVsyXQogICAgfSkKICBjZXRfbW9udGhzIDwtIGdzdWIoIl4wIiwgIiIsIGNldF9tb250aHMpCiAgY2V0X2RheXMgPC0gc2FwcGx5KAogICAgc3Ryc3BsaXQoY2V0X2NvbmRpdGlvbiwgc3BsaXQgPSAiICIsIGZpeGVkID0gVCksIGZ1bmN0aW9uKHgpIHsKICAgICAgc3Ryc3BsaXQoeCwgc3BsaXQgPSAiLSIpW1sxXV1bM10KICAgIH0pCiAgY2V0X2RheXMgPC0gZ3N1YigiXjAiLCAiIiwgY2V0X2RheXMpCiAgY2V0X2hvdXJzIDwtIHNhcHBseShzdHJzcGxpdChjZXRfY29uZGl0aW9uLCBzcGxpdCA9ICIgIiwgZml4ZWQgPSBUKSwgCiAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHhbMl0KICAgICAgICAgICAgICAgICAgICAgIH0pCiAgY2V0X2hvdXJzIDwtIGdzdWIoIl4wIiwgIiIsIGNldF9ob3VycykKICBkYXRldGltZUNvbmRpdGlvbiA8LSBwYXN0ZTAoCiAgICAieWVhciA9ICIsIGNldF95ZWFycywgIiBBTkQgIiwKICAgICJtb250aCA9ICIsIGNldF9tb250aHMsICIgQU5EICIsCiAgICAiZGF5ID0gIiwgY2V0X2RheXMsICIgQU5EICIsIAogICAgImhvdXIgPSAiLCBjZXRfaG91cnMKICApCiAgZGF0ZXRpbWVDb25kaXRpb24gPC0gcGFzdGUoIigiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRldGltZUNvbmRpdGlvbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIiBPUiAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiIikKICAKICAjIC0gV0hFUkUgY29uZGl0aW9uOiBjcmVhdGUgdXJpX3BhdGhfY29uZGl0aW9uCiAgaWYgKGxlbmd0aCh1cmlfcGF0aCkgPiAxKSB7CiAgICB1cmlfcGF0aF9jb25kaXRpb24gPC0gcGFzdGUwKCIoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJ1cmlfcGF0aCA9ICciLCB1cmlfcGF0aCwgIiciKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9ICIgT1IgIiwgc2VwID0gIiAiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICB9IGVsc2UgewogICAgdXJpX3BhdGhfY29uZGl0aW9uID0gcGFzdGUwKCJ1cmlfcGF0aCA9ICciLCB1cmlfcGF0aCwgIiciKQogIH0KICAKICAjIC0gV0hFUkUgY29uZGl0aW9uOiBjcmVhdGUgdXJpX2hvc3RfY29uZGl0aW9uCiAgaWYgKGxlbmd0aCh1cmlfaG9zdCkgPiAxKSB7CiAgICB1cmlfaG9zdF9jb25kaXRpb24gPC0gcGFzdGUwKCIoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJ1cmlfaG9zdCA9ICciLCB1cmlfaG9zdCwgIiciKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9ICIgT1IgIiwgc2VwID0gIiAiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiCiAgICApCiAgfSBlbHNlIHsKICAgIHVyaV9ob3N0X2NvbmRpdGlvbiA9IHBhc3RlMCgidXJpX2hvc3QgPSAnIiwgdXJpX2hvc3QsICInIikKICB9CiAgCiAgIyAtIFdIRVJFIGNvbmRpdGlvbjogY3JlYXRlIHVyaV9xdWVyeV9jb25kaXRpb24KICBpZiAobGVuZ3RoKHVyaV9xdWVyeSkgPiAxKSB7CiAgICB1cmlfcXVlcnlfY29uZGl0aW9uIDwtIHBhc3RlMCgiKCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMCgidXJpX3F1ZXJ5IExJS0UgJyUiLCB1cmlfcXVlcnksICIlJyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIiBPUiAiLCBzZXAgPSAiICIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiKSIKICAgICkKICB9IGVsc2UgewogICAgdXJpX3F1ZXJ5X2NvbmRpdGlvbiA9IHBhc3RlMCgidXJpX3F1ZXJ5IExJS0UgJyUiLCB1cmlfcXVlcnksICIlJyIpCiAgfQogIAogICMgLSBjb21wb3NlIEhpdmVRTCBxdWVyeQogIGhpdmVRdWVyeSA8LSBwYXN0ZTAoCiAgICAiVVNFIHdtZjsKICAgIFNFTEVDVCB1cmlfcXVlcnkgRlJPTSB3ZWJyZXF1ZXN0CiAgICBXSEVSRSAoIiwKICAgIHVyaV9ob3N0X2NvbmRpdGlvbiwgIiBBTkQgIiwKICAgIHVyaV9wYXRoX2NvbmRpdGlvbiwgIiBBTkQgIiwKICAgIHVyaV9xdWVyeV9jb25kaXRpb24sICIgQU5EICIsCiAgICAiKCIsIGRhdGV0aW1lQ29uZGl0aW9uLCAiKSIsCiAgICAiKTsiCiAgKQogIAogICMgLSB3cml0ZSBocWwKICB3cml0ZShoaXZlUXVlcnksIHF1ZXJ5RmlsZSkKICAjIC0gZXhlY3V0ZSBocWwgc2NyaXB0OgogIGhpdmVBcmdzIDwtICcvdXNyL2xvY2FsL2Jpbi9iZWVsaW5lIC1mJwogIGhpdmVJbnB1dCA8LSBwYXN0ZTAocXVlcnlGaWxlLCAnID4gJywgcGFzdGUwKGRhdGFEaXIsIGZpbGVOYW1lKSkKICAjIC0gY29tbWFuZDoKICBoaXZlQ29tbWFuZCA8LSBwYXN0ZShoaXZlQXJncywgaGl2ZUlucHV0KQogIHJldHVybigKICAgIHN5c3RlbShjb21tYW5kID0gaGl2ZUNvbW1hbmQsIHdhaXQgPSBUUlVFKSkKfQoKIyAtIHNldCBwYXJhbXMgdG8gd21kZV9jb2xsZWN0X2Jhbm5lcl9pbXByZXNzaW9ucwojIC0gZm9yIHRoZSBUaGFuayBZb3UgMjkxOQp1cmlfaG9zdCA8LSBjKCdkZS53aWtpcGVkaWEub3JnJywgJ2RlLm0ud2lraXBlZGlhLm9yZycpCnVyaV9wYXRoICA8LSAnL2JlYWNvbi9pbXByZXNzaW9uJwp1cmlfcXVlcnkgPC0gYygnV01ERV8yMDE5X3RoeCcKICAgICAgICAgICAgICAgKQpxdWVyeUZpbGUgPC0gJ3RoYW5reW91MjAxOV9CYW5uZXJJbXByZXNzaW9ucy5ocWwnCmZpbGVOYW1lIDwtIHBhc3RlMCgiYmFubmVySW1wcmVzc2lvbnNfIiwgY2V0RGF5LCAiLnRzdiIpCgojIC0gY29sbGVjdCBCYW5uZXIgSW1wcmVzc2lvbiBkYXRhCndtZGVfY29sbGVjdF9iYW5uZXJfaW1wcmVzc2lvbnModXJpX2hvc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXJpX3BhdGgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXJpX3F1ZXJ5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNldERheSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWVyeUZpbGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsZU5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YURpcikKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFdyYW5nbGUgQmFubmVyIEltcHJlc3Npb24gRGF0YQojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMgLSBmdW5jdGlvbjogd21kZV9wcm9jZXNzX2Jhbm5lcl9pbXByZXNzaW9ucwp3bWRlX3Byb2Nlc3NfYmFubmVyX2ltcHJlc3Npb25zIDwtIGZ1bmN0aW9uKGZpbGVOYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFEaXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNldERheSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FtcGFpZ25OYW1lKSB7CiAgCiAgIyAtIHRvIGRhdGFEaXIKICBzZXR3ZChkYXRhRGlyKQogIAogICMgLSBsaWJyYXJpZXMKICBsaWJyYXJ5KHN0cmluZ3IpCiAgbGlicmFyeShkcGx5cikKICAKICAjIC0gbG9hZAogIGJhbm5lckRhdGEgPC0gcmVhZC5kZWxpbShmaWxlTmFtZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIlx0IikKICBjb2xuYW1lcyhiYW5uZXJEYXRhKSA8LSAndXJpX3F1ZXJ5JwogIAogICMgLSBjbGVhbgogIHdJbmZvIDwtIHdoaWNoKGdyZXBsKCJjYW1wYWlnbj0iLCBiYW5uZXJEYXRhJHVyaV9xdWVyeSkpCiAgYmFubmVyRGF0YSA8LSBiYW5uZXJEYXRhW3dJbmZvLCBdCiAgCiAgIyAtIHNwbGl0CiAgYmFubmVyRGF0YSA8LSBzdHJzcGxpdChiYW5uZXJEYXRhLCBzcGxpdCA9ICImIiwgZml4ZWQgPSBUKQogICMgLSBleHRyYWN0IHJlbGV2YW50IGZpZWxkcwogICMgLSBiYW5uZXI6CiAgYmFubmVyIDwtIHNhcHBseShiYW5uZXJEYXRhLCBmdW5jdGlvbih4KSB7CiAgICB4W3doaWNoKGdyZXBsKCJeYmFubmVyPSIsIHgpKV0KICB9KQogIGJhbm5lciA8LSBnc3ViKCJeYmFubmVyPSIsICIiLCBiYW5uZXIpCiAgIyAtIHJlY29yZEltcHJlc3Npb25TYW1wbGVSYXRlOgogIHJlY29yZEltcHJlc3Npb25TYW1wbGVSYXRlIDwtIHNhcHBseShiYW5uZXJEYXRhLCBmdW5jdGlvbih4KSB7CiAgICB4W3doaWNoKGdyZXBsKCJecmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGU9IiwgeCkpXQogIH0pCiAgcmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGUgPC0gYXMubnVtZXJpYygKICAgIGdzdWIoIl5yZWNvcmRJbXByZXNzaW9uU2FtcGxlUmF0ZT0iLCAiIiwgcmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGUpCiAgICApCiAgIyAtIHJlc3VsdDoKICByZXN1bHQgPC0gc2FwcGx5KGJhbm5lckRhdGEsIGZ1bmN0aW9uKHgpIHsKICAgIHhbd2hpY2goZ3JlcGwoIl5yZXN1bHQ9IiwgeCkpXQogIH0pCiAgcmVzdWx0IDwtIGdzdWIoIl5yZXN1bHQ9IiwgIiIsIHJlc3VsdCkKICAKICAjIC0gY29tcG9zZSB0YWJsZToKICBiYW5uZXJPYnNlcnZhdGlvbnMgPC0gZGF0YS5mcmFtZShiYW5uZXIgPSBiYW5uZXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlY29yZEltcHJlc3Npb25TYW1wbGVSYXRlID0gcmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdCA9IHJlc3VsdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCiAgCiAgIyAtIGZpbHRlciBmb3IgcmVzdWx0PXNob3cKICBiYW5uZXJPYnNlcnZhdGlvbnMgPC0gZHBseXI6OmZpbHRlcihiYW5uZXJPYnNlcnZhdGlvbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzdWx0ID09ICJzaG93IikKICAKICAjIC0gY29ycmVjdGlvbiBmb3IgcmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGUKICBiYW5uZXJPYnNlcnZhdGlvbnMkcmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGUgPC0gCiAgICAxL2Jhbm5lck9ic2VydmF0aW9ucyRyZWNvcmRJbXByZXNzaW9uU2FtcGxlUmF0ZQogIAogICMgLSBhZ2dyZWdhdGU6CiAgYmFubmVyT2JzZXJ2YXRpb25zIDwtIGJhbm5lck9ic2VydmF0aW9ucyAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KGJhbm5lciwgcmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGUpICU+JSAKICAgIGRwbHlyOjpncm91cF9ieShiYW5uZXIpICU+JSAKICAgIGRwbHlyOjpzdW1tYXJpc2UoaW1wcmVzc2lvbnMgPSBzdW0ocmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGUpKQogIAogICMgLSBhZGQgY2V0RGF5LCBtZQogIGJhbm5lck9ic2VydmF0aW9ucyRkYXRlIDwtIGNldERheQogIGJhbm5lck9ic2VydmF0aW9ucyRjYW1wYWlnbiA8LSBjYW1wYWlnbk5hbWUKICAKICAjIC0gc3RvcmU6CiAgd3JpdGUuY3N2KGJhbm5lck9ic2VydmF0aW9ucywgCiAgICAgICAgICAgIHBhc3RlMCgiYmFubmVySW1wcmVzc2lvbnNBZ2dyZWdhdGVkXyIsCiAgICAgICAgICAgICAgICAgICBzdHJzcGxpdCgKICAgICAgICAgICAgICAgICAgICAgc3Ryc3BsaXQoZmlsZU5hbWUsIHNwbGl0ID0gIl8iLCBmaXhlZCA9IFQpW1sxXV1bMl0sCiAgICAgICAgICAgICAgICAgICAgIHNwbGl0ID0gIi4iLCAKICAgICAgICAgICAgICAgICAgICAgZml4ZWQgPSBUKVtbMV1dWzFdLAogICAgICAgICAgICAgICAgICAgIi5jc3YiCiAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICkKICAKfQoKIyAtIHdyYW5nbGUgQmFubmVyIEltcHJlc3Npb24gZGF0YQpjYW1wYWlnbk5hbWUgPC0gInRoYW5reW91MjAxOSIKd21kZV9wcm9jZXNzX2Jhbm5lcl9pbXByZXNzaW9ucyhmaWxlTmFtZSA9IGZpbGVOYW1lLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyID0gZGF0YURpciwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2V0RGF5ID0gY2V0RGF5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbXBhaWduTmFtZSA9IGNhbXBhaWduTmFtZSkKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIENvbGxlY3QgUGFnZXZpZXdzCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyAtIGZ1bmN0aW9uOiB3bWRlX2NvbGxlY3RfcGFnZXZpZXdzCndtZGVfY29sbGVjdF9wYWdldmlld3MgPC0gZnVuY3Rpb24odXJpX2hvc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXJpX3BhdGgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2V0RGF5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1ZXJ5RmlsZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlTmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyKSB7CiAgCiAgIyAtIE5PVEU6CiAgIyAtIGV4cGVjdGVkIGZvcm1hdCBmb3IgY2V0RGF5IGlzOiBZWVlZLU1NLURECiAgCiAgIyAtIHRvIGRhdGFEaXIKICBzZXR3ZChkYXRhRGlyKQogIAogICMgLSBsaWJyYXJpZXMKICBsaWJyYXJ5KHN0cmluZ3IpCiAgCiAgIyAtIFdIRVJFIGNvbmRpdGlvbjogY3JlYXRlIGRhdGV0aW1lX2NvbmRpdGlvbgogIGNldF9jb25kaXRpb24gPC0gc2VxKAogICAgZnJvbSA9IGFzLlBPU0lYY3QocGFzdGUwKGNldERheSwiIDA6MDAiKSwgdHogPSAiRXVyb3BlL0JlcmxpbiIpLAogICAgdG8gPSBhcy5QT1NJWGN0KHBhc3RlMChjZXREYXksIiAyMzowMCIpLCB0eiA9ICJFdXJvcGUvQmVybGluIiksCiAgICBieSA9ICJob3VyIgogICkgCiAgYXR0cihjZXRfY29uZGl0aW9uLCAidHpvbmUiKSA8LSAiVVRDIgogIGNldF9jb25kaXRpb24gPC0gYXMuY2hhcmFjdGVyKGNldF9jb25kaXRpb24pCiAgY2V0X2NvbmRpdGlvbiA8LSB1bmxpc3Qoc3RyX2V4dHJhY3RfYWxsKGNldF9jb25kaXRpb24sICJeKFtbOmRpZ2l0Ol1dfFxcc3wtKSoiKSkKICBjZXRfeWVhcnMgPC0gc2FwcGx5KAogICAgc3Ryc3BsaXQoY2V0X2NvbmRpdGlvbiwgc3BsaXQgPSAiICIsIGZpeGVkID0gVCksIGZ1bmN0aW9uKHgpIHsKICAgICAgc3Ryc3BsaXQoeCwgc3BsaXQgPSAiLSIpW1sxXV1bMV0KICAgIH0pCiAgY2V0X21vbnRocyA8LSBzYXBwbHkoCiAgICBzdHJzcGxpdChjZXRfY29uZGl0aW9uLCBzcGxpdCA9ICIgIiwgZml4ZWQgPSBUKSwgZnVuY3Rpb24oeCkgewogICAgICBzdHJzcGxpdCh4LCBzcGxpdCA9ICItIilbWzFdXVsyXQogICAgfSkKICBjZXRfbW9udGhzIDwtIGdzdWIoIl4wIiwgIiIsIGNldF9tb250aHMpCiAgY2V0X2RheXMgPC0gc2FwcGx5KAogICAgc3Ryc3BsaXQoY2V0X2NvbmRpdGlvbiwgc3BsaXQgPSAiICIsIGZpeGVkID0gVCksIGZ1bmN0aW9uKHgpIHsKICAgICAgc3Ryc3BsaXQoeCwgc3BsaXQgPSAiLSIpW1sxXV1bM10KICAgIH0pCiAgY2V0X2RheXMgPC0gZ3N1YigiXjAiLCAiIiwgY2V0X2RheXMpCiAgY2V0X2hvdXJzIDwtIHNhcHBseShzdHJzcGxpdChjZXRfY29uZGl0aW9uLCBzcGxpdCA9ICIgIiwgZml4ZWQgPSBUKSwgCiAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHhbMl0KICAgICAgICAgICAgICAgICAgICAgIH0pCiAgY2V0X2hvdXJzIDwtIGdzdWIoIl4wIiwgIiIsIGNldF9ob3VycykKICBkYXRldGltZUNvbmRpdGlvbiA8LSBwYXN0ZTAoCiAgICAieWVhciA9ICIsIGNldF95ZWFycywgIiBBTkQgIiwKICAgICJtb250aCA9ICIsIGNldF9tb250aHMsICIgQU5EICIsCiAgICAiZGF5ID0gIiwgY2V0X2RheXMsICIgQU5EICIsIAogICAgImhvdXIgPSAiLCBjZXRfaG91cnMKICApCiAgZGF0ZXRpbWVDb25kaXRpb24gPC0gcGFzdGUoIigiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRldGltZUNvbmRpdGlvbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIiBPUiAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiIikKICAKICAjIC0gV0hFUkUgY29uZGl0aW9uOiBjcmVhdGUgdXJpX3BhdGhfY29uZGl0aW9uCiAgaWYgKGxlbmd0aCh1cmlfcGF0aCkgPiAxKSB7CiAgICB1cmlfcGF0aF9jb25kaXRpb24gPC0gcGFzdGUwKCIoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJ1cmlfcGF0aCA9ICciLCB1cmlfcGF0aCwgIiciKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9ICIgT1IgIiwgc2VwID0gIiAiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiCiAgICApCiAgfSBlbHNlIHsKICAgIHVyaV9wYXRoX2NvbmRpdGlvbiA9IHBhc3RlMCgidXJpX3BhdGggPSAnIiwgdXJpX3BhdGgsICInIikKICB9CiAgCiAgIyAtIFdIRVJFIGNvbmRpdGlvbjogY3JlYXRlIHVyaV9ob3N0X2NvbmRpdGlvbgogIGlmIChsZW5ndGgodXJpX2hvc3QpID4gMSkgewogICAgdXJpX2hvc3RfY29uZGl0aW9uIDwtIHBhc3RlMCgiKCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMCgidXJpX2hvc3QgPSAnIiwgdXJpX2hvc3QsICInIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sbGFwc2UgPSAiIE9SICIsIHNlcCA9ICIgIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIpIgogICAgKQogIH0gZWxzZSB7CiAgICB1cmlfaG9zdF9jb25kaXRpb24gPSBwYXN0ZTAoInVyaV9ob3N0ID0gJyIsIHVyaV9ob3N0LCAiJyIpCiAgfQogIAogICMgLSBjb21wb3NlIEhpdmVRTCBxdWVyeQogIGhpdmVRdWVyeSA8LSBwYXN0ZTAoCiAgICAiVVNFIHdtZjsKICAgIFNFTEVDVCB1cmlfcGF0aCwgdXJpX3F1ZXJ5LCByZWZlcmVyIEZST00gd2VicmVxdWVzdAogICAgV0hFUkUgKCIsCiAgICB1cmlfaG9zdF9jb25kaXRpb24sICIgQU5EICIsCiAgICB1cmlfcGF0aF9jb25kaXRpb24sICIgQU5EICIsCiAgICAiKCIsIGRhdGV0aW1lQ29uZGl0aW9uLCAiKSIsCiAgICAiKTsiCiAgICApCiAgCiAgIyAtIHdyaXRlIGhxbAogIHdyaXRlKGhpdmVRdWVyeSwgcXVlcnlGaWxlKQogICMgLSBleGVjdXRlIGhxbCBzY3JpcHQ6CiAgaGl2ZUFyZ3MgPC0gJy91c3IvbG9jYWwvYmluL2JlZWxpbmUgLWYnCiAgaGl2ZUlucHV0IDwtIHBhc3RlMChxdWVyeUZpbGUsICcgPiAnLCBmaWxlTmFtZSkKICAjIC0gY29tbWFuZDoKICBoaXZlQ29tbWFuZCA8LSBwYXN0ZShoaXZlQXJncywgaGl2ZUlucHV0KQogIHJldHVybigKICAgIHN5c3RlbShjb21tYW5kID0gaGl2ZUNvbW1hbmQsIHdhaXQgPSBUUlVFKSkKfQoKIyAtIHNldCBwYXJhbXMgdG8gd21kZV9jb2xsZWN0X3BhZ2V2aWV3cwojIC0gZm9yIHRoZSBUaGFuayBZb3UgMjkxOQp1cmlfaG9zdCA8LSBjKCdkZS53aWtpcGVkaWEub3JnJywgJ2RlLm0ud2lraXBlZGlhLm9yZycpCnVyaV9wYXRoICA8LSBjKAogICcvd2lraS9XaWtpcGVkaWE6V2lraW1lZGlhX0RldXRzY2hsYW5kL0xlcm5lV2lraXBlZGlhJykKcXVlcnlGaWxlIDwtICd0aGFua3lvdTIwMTlfUGFnZXZpZXdzLmhxbCcKZmlsZU5hbWUgPC0gcGFzdGUwKCJwYWdldmlld3NfIiwgY2V0RGF5LCAiLnRzdiIpCgojIC0gY29sbGVjdCBQYWdldmlld3MgZGF0YQp3bWRlX2NvbGxlY3RfcGFnZXZpZXdzKHVyaV9ob3N0LAogICAgICAgICAgICAgICAgICAgICAgIHVyaV9wYXRoLAogICAgICAgICAgICAgICAgICAgICAgIGNldERheSwKICAgICAgICAgICAgICAgICAgICAgICBxdWVyeUZpbGUsCiAgICAgICAgICAgICAgICAgICAgICAgZmlsZU5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YURpcikKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFdyYW5nbGUgUGFnZXZpZXdzCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyAtIGZ1bmN0aW9uOiB3bWRlX3Byb2Nlc3NfcGFnZXZpZXdzCndtZGVfcHJvY2Vzc19wYWdldmlld3MgPC0gZnVuY3Rpb24oZmlsZU5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YURpciwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXJpX3F1ZXJ5X2ZpbHRlciwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2V0RGF5ID0gY2V0RGF5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbXBhaWduTmFtZSA9IGNhbXBhaWduTmFtZSkgewogIAogICMgLSB0byBkYXRhRGlyCiAgc2V0d2QoZGF0YURpcikKICAKICAjIC0gbGlicmFyaWVzCiAgbGlicmFyeShzdHJpbmdyKQogIGxpYnJhcnkoZHBseXIpCiAgbGlicmFyeSh0aWR5cikKICBsaWJyYXJ5KGRhdGEudGFibGUpCgogICMgLSBsb2FkCiAgcGFnZXZpZXdzRGF0YSA8LSByZWFkTGluZXMoZmlsZU5hbWUpCiAgd1N0YXJ0IDwtIHdoaWNoKGdyZXBsKCJedXJpX3BhdGgiLCBwYWdldmlld3NEYXRhKSkKICBwYWdldmlld3NEYXRhIDwtIHBhZ2V2aWV3c0RhdGFbKHdTdGFydCArIDIpOihsZW5ndGgocGFnZXZpZXdzRGF0YSkgLSAyKV0KICBwYWdldmlld3NEYXRhIDwtIGRhdGEuZnJhbWUoZGF0ID0gcGFnZXZpZXdzRGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQogIHBhZ2V2aWV3c0RhdGEgPC0gc2VwYXJhdGUocGFnZXZpZXdzRGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGludG8gPSBjKCd1cmlfcGF0aCcsICd1cmlfcXVlcnknLCAncmVmZXJlcicpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIlx0IikKICAjIC0gYXBwbHkgdXJpX3F1ZXJ5X2ZpbHRlcgogICMgLSBOT1RFOiBBdXR1bW4gMjAxOCwgbG9va2luZyBpbiBib3RoOiB1cmlfcXVlcnksIHJlZmVyZXIKICB3X3VyaV9xdWVyeSA8LSB3aGljaChncmVwbCh1cmlfcXVlcnlfZmlsdGVyLCBwYWdldmlld3NEYXRhJHVyaV9xdWVyeSkpCiAgd191cmlfcXVlcnlfcmVmZXJlciA8LSB3aGljaChncmVwbCh1cmlfcXVlcnlfZmlsdGVyLCBwYWdldmlld3NEYXRhJHJlZmVyZXIpKQogIHdfdXJpX3F1ZXJ5IDwtIHVuaXF1ZShjKHdfdXJpX3F1ZXJ5LCB3X3VyaV9xdWVyeV9yZWZlcmVyKSkKICBwYWdldmlld3NEYXRhIDwtIHBhZ2V2aWV3c0RhdGFbd191cmlfcXVlcnksIF0KICB3X3VyaV9xdWVyeV9yZWZlcmVyIDwtIHdoaWNoKGdyZXBsKHVyaV9xdWVyeV9maWx0ZXIsIHBhZ2V2aWV3c0RhdGEkcmVmZXJlcikpCiAgd191cmlfcXVlcnlfcmVmZXJlcl9kZWxldGUgPC0gc2V0ZGlmZigxOmRpbShwYWdldmlld3NEYXRhKVsxXSwgd191cmlfcXVlcnlfcmVmZXJlcikKICBwYWdldmlld3NEYXRhJHJlZmVyZXJbd191cmlfcXVlcnlfcmVmZXJlcl9kZWxldGVdIDwtICcnCiAgIyAtIHdoZW4gdGhlcmUgaXMgbm8gdXJpX3F1ZXJ5LCB1c2UgdGhlIHF1ZXJ5IGZyb20gdGhlIHJlZmVyZXIgZmllbGQgaWYgcHJlc2VudCB0aGVyZQogIHBhZ2V2aWV3c0RhdGEkcmVmZXJlciA8LSBzdHJfZXh0cmFjdChwYWdldmlld3NEYXRhJHJlZmVyZXIsICJcXD9jYW1wYWlnbj0uKiQiKQogIHBhZ2V2aWV3c0RhdGEkcmVmZXJlcltpcy5uYShwYWdldmlld3NEYXRhJHJlZmVyZXIpXSA8LSAiIgogIHBhZ2V2aWV3c0RhdGEkcmVmZXJlciA8LSBnc3ViKCI/Y2FtcGFpZ249IiwgIiIsIHBhZ2V2aWV3c0RhdGEkcmVmZXJlciwgZml4ZWQgPSBUKQogIHBhZ2V2aWV3c0RhdGEkdXJpX3F1ZXJ5IDwtIGdzdWIoIj9jYW1wYWlnbj0iLCAiIiwgcGFnZXZpZXdzRGF0YSR1cmlfcXVlcnksIGZpeGVkID0gVCkKICBwYWdldmlld3NEYXRhJHVyaV9xdWVyeVtwYWdldmlld3NEYXRhJHVyaV9xdWVyeSA9PSAiIl0gPC0gCiAgICBwYWdldmlld3NEYXRhJHJlZmVyZXJbcGFnZXZpZXdzRGF0YSR1cmlfcXVlcnkgPT0gIiJdCiAgcGFnZXZpZXdzRGF0YSA8LSBkcGx5cjo6ZmlsdGVyKHBhZ2V2aWV3c0RhdGEsIAogICAgICAgICAgICAgICAgICAgICAgICAgIHVyaV9xdWVyeSAhPSAiIikKICBwYWdldmlld3NEYXRhJHJlZmVyZXIgPC0gTlVMTAogICMgLSBjbGVhbiB1cCBhIGJpdDoKICBwYWdldmlld3NEYXRhJHVyaV9xdWVyeSA8LSBnc3ViKCIvLiokIiwgIiIsIHBhZ2V2aWV3c0RhdGEkdXJpX3F1ZXJ5KQogIAogICMgLSBhZ2dyZWdhdGU6CiAgcGFnZXZpZXdzRGF0YSA8LSBwYWdldmlld3NEYXRhICU+JSAKICAgIGRwbHlyOjpncm91cF9ieSh1cmlfcXVlcnksIHVyaV9wYXRoKSAlPiUgCiAgICBkcGx5cjo6c3VtbWFyaXNlKHBhZ2V2aWV3cyA9IG4oKSkKICBjb2xuYW1lcyhwYWdldmlld3NEYXRhKSA8LSBjKCdUYWcnLCAnUGFnZScsICdQYWdldmlld3MnKQogIAogICMgLSBhZGQgY2V0RGF5LCBjYW1wYWlnbk5hbWUKICBwYWdldmlld3NEYXRhJGRhdGUgPC0gY2V0RGF5CiAgcGFnZXZpZXdzRGF0YSRjYW1wYWlnbiA8LSBjYW1wYWlnbk5hbWUKCiAgIyAtIHN0b3JlOgogIHdyaXRlLmNzdihwYWdldmlld3NEYXRhLCAKICAgICAgICAgICAgcGFzdGUwKCJwYWdldmlld3NBZ2dyZWdhdGVkXyIsCiAgICAgICAgICAgICAgICAgICBzdHJzcGxpdCgKICAgICAgICAgICAgICAgICAgICAgc3Ryc3BsaXQoZmlsZU5hbWUsIHNwbGl0ID0gIl8iLCBmaXhlZCA9IFQpW1sxXV1bMl0sCiAgICAgICAgICAgICAgICAgICAgIHNwbGl0ID0gIi4iLCAKICAgICAgICAgICAgICAgICAgICAgZml4ZWQgPSBUKVtbMV1dWzFdLAogICAgICAgICAgICAgICAgICAgIi5jc3YiCiAgICAgICAgICAgICkKICApCiAgCn0KCiMgLSBzZXQgcGFyYW1zIHRvIHdtZGVfcHJvY2Vzc19wYWdldmlld3MKIyAtIGZvciB0aGUgVGhhbmsgWW91IDI5MTkKdXJpX3F1ZXJ5X2ZpbHRlciA8LSAnV01ERV8yMDE5X3RoeCcKCiMgLSB3cmFuZ2xlIHBhZ2V2aWV3cwp3bWRlX3Byb2Nlc3NfcGFnZXZpZXdzKGZpbGVOYW1lID0gZmlsZU5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YURpciA9IGRhdGFEaXIsCiAgICAgICAgICAgICAgICAgICAgICAgdXJpX3F1ZXJ5X2ZpbHRlciA9IHVyaV9xdWVyeV9maWx0ZXIsIAogICAgICAgICAgICAgICAgICAgICAgIGNldERheSA9IGNldERheSwKICAgICAgICAgICAgICAgICAgICAgICBjYW1wYWlnbk5hbWUgPSBjYW1wYWlnbk5hbWUpIAoKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIENvbGxlY3QgVXNlciBSZWdpc3RyYXRpb25zCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyAtIGZ1bmN0aW9uOiB3bWRlX2NvbGxlY3RfcmVnaXN0cmF0aW9ucwp3bWRlX2NvbGxlY3RfcmVnaXN0cmF0aW9ucyA8LSBmdW5jdGlvbihsb2dTY2hlbWEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWJfaG9zdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnRfY2FtcGFpZ24sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZXREYXksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFEaXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlTmFtZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbXBhaWduTmFtZSkgewogIAogICMgLSBXSEVSRSBjb25kaXRpb246IGNyZWF0ZSBzdGFydF90aW1lc3RhbXAsIHN0b3BfdGltZXN0YW1wCiAgY2V0X2NvbmRpdGlvbiA8LSBzZXEoCiAgICBmcm9tID0gYXMuUE9TSVhjdChwYXN0ZTAoY2V0RGF5LCIgMDowMCIpLCB0eiA9ICJFdXJvcGUvQmVybGluIiksCiAgICB0byA9IGFzLlBPU0lYY3QocGFzdGUwKGNldERheSwiIDI0OjAwIiksIHR6ID0gIkV1cm9wZS9CZXJsaW4iKSwKICAgIGJ5ID0gImhvdXIiCiAgKSAKICBhdHRyKGNldF9jb25kaXRpb24sICJ0em9uZSIpIDwtICJVVEMiCiAgY2V0X2NvbmRpdGlvbiA8LSBhcy5jaGFyYWN0ZXIoY2V0X2NvbmRpdGlvbikKICBzdGFydF90aW1lc3RhbXAgPC0gcGFzdGUoCiAgICB1bmxpc3Qoc3RyX2V4dHJhY3RfYWxsKGNldF9jb25kaXRpb25bMV0sCiAgICAgICAgICAgICAgICAgICAgIltbOmRpZ2l0Ol1dIikpLAogICAgY29sbGFwc2UgPSAiIikKICBzdG9wX3RpbWVzdGFtcCA8LSBwYXN0ZSgKICAgIHVubGlzdChzdHJfZXh0cmFjdF9hbGwodGFpbChjZXRfY29uZGl0aW9uLCAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIltbOmRpZ2l0Ol1dIikpLAogICAgY29sbGFwc2UgPSAiIikKICAKICAjIC0gV0hFUkUgY29uZGl0aW9uOiBjcmVhdGUgZXZlbnRfY2FtcGFpZ25fY29uZGl0aW9uCiAgaWYgKGxlbmd0aChldmVudF9jYW1wYWlnbikgPiAxKSB7CiAgICBldmVudF9jYW1wYWlnbl9jb25kaXRpb24gPC0gcGFzdGUwKCIoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJldmVudF9jYW1wYWlnbiBMSUtFICclIiwgZXZlbnRfY2FtcGFpZ24sICIlJyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIiBPUiAiLCBzZXAgPSAiICIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiKSIKICAgICkKICB9IGVsc2UgewogICAgZXZlbnRfY2FtcGFpZ25fY29uZGl0aW9uID0gcGFzdGUwKCJldmVudF9jYW1wYWlnbiBMSUtFICclIiwgZXZlbnRfY2FtcGFpZ24sICIlJyIpCiAgfQogIAoKICAjIC0gY29tcG9zZSBTUUwgcXVlcnk6CiAgc3FsUGFyYW1zIDwtICdteXNxbCAtLWRlZmF1bHRzLWZpbGU9L2V0Yy9teXNxbC9jb25mLmQvYW5hbHl0aWNzLXJlc2VhcmNoLWNsaWVudC5jbmYgLWggYW5hbHl0aWNzLXNsYXZlLmVxaWFkLndtbmV0IC1BIC1lJwogIHF1ZXJ5IDwtIHBhc3RlMCgKICAgICJcIlNFTEVDVCAqIEZST00gIiwgCiAgICBwYXN0ZTAoImxvZy4iLCBsb2dTY2hlbWEpLCAKICAgICIgV0hFUkUgKCh3ZWJIb3N0ID0gJyIsIAogICAgd2ViX2hvc3QsIAogICAgIicpIEFORCAodGltZXN0YW1wID4gIiwgCiAgICBzdGFydF90aW1lc3RhbXAsIAogICAgIikgQU5EICh0aW1lc3RhbXAgPD0gIiwgCiAgICBzdG9wX3RpbWVzdGFtcCwgCiAgICAiKSBBTkQgKCIsIAogICAgZXZlbnRfY2FtcGFpZ25fY29uZGl0aW9uLAogICAgIikpO1wiIikKICBzcWxPdXRwdXQgPC0gcGFzdGUwKCI+ICIsIHBhc3RlMChkYXRhRGlyLCAiLyIsIGZpbGVOYW1lKSkKICAgIAogICMgLSBydW4gY29tbWFuZAogIHFDb21tYW5kIDwtIHBhc3RlKHNxbFBhcmFtcywgcXVlcnksIHNxbE91dHB1dCwgc2VwID0gIiAiKQogIHN5c3RlbShjb21tYW5kID0gcUNvbW1hbmQsIHdhaXQgPSBUUlVFKQogIAogICMgLSB0byBkYXRhRGlyCiAgc2V0d2QoZGF0YURpcikKICAKICAjIC0gbGlicmFyaWVzCiAgbGlicmFyeShzdHJpbmdyKQogIGxpYnJhcnkoZHBseXIpCiAgbGlicmFyeSh0aWR5cikKICBsaWJyYXJ5KGRhdGEudGFibGUpCiAgCiAgIyAtIGxvYWQKICB1c2VyUmVnIDwtIGZyZWFkKGZpbGVOYW1lLCBzZXAgPSAiXHQiKQogIAogICMgLSBmaWx0ZXIgYm90cwogIHdCb3QgPC0gd2hpY2goZ3JlcGwoIlwiaXNfYm90XCI6IHRydWUiLCB1c2VyUmVnJHVzZXJBZ2VudCkpCiAgaWYgKGxlbmd0aCh3Qm90KSA+IDApIHsKICAgIHVzZXJSZWcgPC0gdXNlclJlZ1std0JvdCwgXQogIH0KICAKICAjIC0gc2VsZWN0IGZpZWxkcwogIHVzZXJSZWcgPC0gdXNlclJlZyAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KGV2ZW50X3VzZXJJZCwgCiAgICAgICAgICAgICAgICAgIGV2ZW50X3VzZXJOYW1lLCAKICAgICAgICAgICAgICAgICAgZXZlbnRfaXNTZWxmTWFkZSwgCiAgICAgICAgICAgICAgICAgIGV2ZW50X2NhbXBhaWduLCAKICAgICAgICAgICAgICAgICAgdGltZXN0YW1wKQogIAogICMgLSBhZGQgY2V0RGF5LCBjYW1wYWlnbk5hbWUKICB1c2VyUmVnJGRhdGUgPC0gY2V0RGF5CiAgdXNlclJlZyRjYW1wYWlnbiA8LSBjYW1wYWlnbk5hbWUKICAKICAjIC0gc3RvcmU6CiAgd3JpdGUuY3N2KHVzZXJSZWcsIAogICAgICAgICAgICBwYXN0ZTAoCiAgICAgICAgICAgICAgc3Ryc3BsaXQoZmlsZU5hbWUsIHNwbGl0ID0gIi4iLCBmaXhlZCA9IFQpW1sxXV1bMV0sCiAgICAgICAgICAgICIuY3N2IikKICApCiAgCiAgIyAtIHJlbW92ZSB0ZW1wIC50c3YgZmlsZQogIGZpbGUucmVtb3ZlKGZpbGVOYW1lKQogIAp9CgojIC0gc2V0IHBhcmFtcyBmb3I6IHdtZGVfY29sbGVjdF9yZWdpc3RyYXRpb25zCmxvZ1NjaGVtYSA8LSAnU2VydmVyU2lkZUFjY291bnRDcmVhdGlvbl8xNzcxOTIzNycgCndlYl9ob3N0IDwtICdkZS53aWtpcGVkaWEub3JnJwpldmVudF9jYW1wYWlnbiA8LSAnV01ERV8yMDE5X3RoeCcKZmlsZU5hbWUgPC0gcGFzdGUwKCJ1c2VyUmVnaXN0cmF0aW9uc18iLCBjZXREYXksICIudHN2IikKCiMgLSBjb2xsZWN0IHVzZXIgcmVnaXN0cmF0aW9ucwp3bWRlX2NvbGxlY3RfcmVnaXN0cmF0aW9ucyhsb2dTY2hlbWEgPSBsb2dTY2hlbWEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlYl9ob3N0ID0gd2ViX2hvc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50X2NhbXBhaWduID0gZXZlbnRfY2FtcGFpZ24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNldERheSA9IGNldERheSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YURpciA9IGRhdGFEaXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVOYW1lID0gZmlsZU5hbWUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBjYW1wYWlnbk5hbWUgPSBjYW1wYWlnbk5hbWUpCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBXcmFuZ2xlIFVzZXIgUmVnaXN0cmF0aW9ucwojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMgLSBmdW5jdGlvbjogd21kZV9wcm9jZXNzX3JlZ2lzdHJhdGlvbnMKd21kZV9wcm9jZXNzX3JlZ2lzdHJhdGlvbnMgPC0gZnVuY3Rpb24oZmlsZU5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFEaXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZXREYXksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW1wYWlnbk5hbWUpIHsKICAKICAjIC0gdG8gZGF0YURpcgogIHNldHdkKGRhdGFEaXIpCiAgCiAgIyAtIGxpYnJhcmllcwogIGxpYnJhcnkoZHBseXIpCiAgbGlicmFyeShkYXRhLnRhYmxlKQoKICAjIC0gbG9hZAogIHVzZXJSZWcgPC0gZnJlYWQoZmlsZU5hbWUpCiAgCiAgIyAtIGFncmVnYXRlCiAgdXNlclJlZyA8LSB1c2VyUmVnICU+JSAKICAgIGRwbHlyOjpzZWxlY3QoZXZlbnRfY2FtcGFpZ24pICU+JSAKICAgIGRwbHlyOjpncm91cF9ieShldmVudF9jYW1wYWlnbikgJT4lIAogICAgZHBseXI6OnN1bW1hcmlzZShSZWdpc3RyYXRpb25zID0gbigpKQoKICAjIC0gYWRkIGNldERheSwgY2FtcGFpZ25OYW1lCiAgdXNlclJlZyRkYXRlIDwtIGNldERheQogIHVzZXJSZWckY2FtcGFpZ24gPC0gY2FtcGFpZ25OYW1lCiAgCiAgIyAtIHN0b3JlOgogIHdyaXRlLmNzdih1c2VyUmVnLCAKICAgICAgICAgICAgcGFzdGUwKCd1c2VyUmVnaXN0cmF0aW9uc0FnZ3JlYWd0ZWRfJywgY2V0RGF5LCAiLmNzdiIpCiAgKQogIAp9CgojIC0gc2V0IHBhcmFtcyBmb3I6IHdtZGVfcHJvY2Vzc19yZWdpc3RyYXRpb25zCmZpbGVOYW1lIDwtIHBhc3RlMCgidXNlclJlZ2lzdHJhdGlvbnNfIiwgY2V0RGF5LCAiLmNzdiIpCgojIC0gd3JhbmdsZSB1c2VyIHJlZ2lzdHJhdGlvbnM6CndtZGVfcHJvY2Vzc19yZWdpc3RyYXRpb25zKGZpbGVOYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyLAogICAgICAgICAgICAgICAgICAgICAgICAgICBjZXREYXksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbXBhaWduTmFtZSkKCiMjIyAtLS0gQ29sbGVjdCBOZXdzbGV0dGVyIHJlZ2lzdHJhdGlvbnMgLSBmb3IgMjAxOF9BdUJDIGV4Y2wuCiMgLSBTZXJ2ZXJTaWRlQWNjb3VudENyZWF0aW9uXzE3NzE5MjM3IHNjaGVtYQpxQ29tbWFuZCA8LSAibXlzcWwgLS1kZWZhdWx0cy1maWxlPS9ldGMvbXlzcWwvY29uZi5kL2FuYWx5dGljcy1yZXNlYXJjaC1jbGllbnQuY25mIC1oIGFuYWx5dGljcy1zbGF2ZS5lcWlhZC53bW5ldCAtQSAtZSBcInNlbGVjdCAqIGZyb20gbG9nLlNlcnZlclNpZGVBY2NvdW50Q3JlYXRpb25fMTc3MTkyMzcgd2hlcmUgKCh3ZWJIb3N0ID0gJ2RlLndpa2lwZWRpYS5vcmcnKSBhbmQgKHRpbWVzdGFtcCA+PSAyMDE4MTAxMTAwMDAwMCkgYW5kIChldmVudF9jYW1wYWlnbiBsaWtlICclV01ERV9uZXdlZGl0b3JzX2F1dHVtbl8yMDE4X2xwbiUnKSk7XCIgPiAvaG9tZS9nb3JhbnNtL1JTY3JpcHRzL05ld0VkaXRvcnMvMjAxOF9BdXR1bW5CYW5uZXJDYW1wYWlnbi9fZGF0YS9OZXdzbGV0dGVyX3VzZXJSZWdpc3RyYXRpb25zLnRzdiIKc3lzdGVtKGNvbW1hbmQgPSBxQ29tbWFuZCwgd2FpdCA9IFRSVUUpCiMjIyAtLS0gV3JhbmdsZSBOZXdzbGV0dGVyIHJlZ2lzdHJhdGlvbnMgLSBmb3IgMjAxOF9BdUJDIGV4Y2wuCiMgLSBmdW5jdGlvbjogd21kZV9wcm9jZXNzX3JlZ2lzdHJhdGlvbnNfZ2VuZXJhbAp3bWRlX3Byb2Nlc3NfcmVnaXN0cmF0aW9uc19nZW5lcmFsIDwtIGZ1bmN0aW9uKGZpbGVOYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2V0RGF5LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FtcGFpZ25OYW1lLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0RmlsZU5hbWUpIHsKICAKICAjIC0gdG8gZGF0YURpcgogIHNldHdkKGRhdGFEaXIpCiAgCiAgIyAtIGxpYnJhcmllcwogIGxpYnJhcnkoZHBseXIpCiAgbGlicmFyeShkYXRhLnRhYmxlKQogIAogICMgLSBsb2FkCiAgdXNlclJlZyA8LSBmcmVhZChmaWxlTmFtZSkKICAKICAjIC0gYWdyZWdhdGUKICB1c2VyUmVnIDwtIHVzZXJSZWcgJT4lIAogICAgZHBseXI6OnNlbGVjdChldmVudF9jYW1wYWlnbikgJT4lIAogICAgZHBseXI6Omdyb3VwX2J5KGV2ZW50X2NhbXBhaWduKSAlPiUgCiAgICBkcGx5cjo6c3VtbWFyaXNlKFJlZ2lzdHJhdGlvbnMgPSBuKCkpCiAgCiAgIyAtIGFkZCBjZXREYXksIGNhbXBhaWduTmFtZQogIHVzZXJSZWckZGF0ZSA8LSBjZXREYXkKICB1c2VyUmVnJGNhbXBhaWduIDwtIGNhbXBhaWduTmFtZQogIAogICMgLSBzdG9yZToKICB3cml0ZS5jc3YodXNlclJlZywgCiAgICAgICAgICAgIG91dEZpbGVOYW1lCiAgKQogIAp9CgpgYGAKCiMjIyAwLjIgRGF0YSBBZ2dyZWdhdGlvbgoKKipOT1RFOioqIE5vdCBydW4gZnJvbSB0aGlzIHJlcG9ydDsgdGhlIGRhdGEgd2VyZSBhbHJlYWR5IHByZS1wcm9jZXNzZWQgYW5kIGFnZ3JlZ2F0ZWQgYnkgdGhlIGZvbGxvd2luZyBgUmAgc2NyaXB0IGJlZm9yZSBiZWluZyBzdWJtaXR0ZWQgdG8gYW5hbHl0aWNhbCBwcm9jZWR1cmVzOgoKYGBge3IsIGVjaG8gPSBULCBldmFsID0gRn0KIyMjIC0tLSBSZXBvcnQgR2VuZXJhdGlvbiBmb3IgdGhlIFRoYW5rIFlvdSAyOTE5CiMjIyAtLS0gcnVuIGxvY2FsbHkKCiMjIyAtLS0gdG8gZGF0YSBkaXJlY3RvcnkKZGF0YURpciA8LSAKICAnL2hvbWUvZ29yYW5zbS9Xb3JrL19fX0RhdGFLb2xla3Rpdi9Qcm9qZWN0cy9XaWtpbWVkaWFERVUvX1dNREVfUHJvamVjdHMvX21pc2MvTmV3RWRpdG9yc19UZWFtLzIwMTlfVGhhbmtZb3UvX2RhdGEvJwphbmFseXRpY3NEaXIgPC0gCiAgJy9ob21lL2dvcmFuc20vV29yay9fX19EYXRhS29sZWt0aXYvUHJvamVjdHMvV2lraW1lZGlhREVVL19XTURFX1Byb2plY3RzL19taXNjL05ld0VkaXRvcnNfVGVhbS8yMDE5X1RoYW5rWW91L19hbmFseXRpY3MvJwpzZXR3ZChhbmFseXRpY3NEaXIpCgojIyMgLS0tIFJlcG9ydCBCYW5uZXIgSW1wcmVzc2lvbiBEYXRhCgojIC0gZnVuY3Rpb246IHdtZGVfcmVwb3J0X2Jhbm5lcl9pbXByZXNzaW9ucwp3bWRlX3JlcG9ydF9iYW5uZXJfaW1wcmVzc2lvbnMgPC0gZnVuY3Rpb24oZGF0YURpcikgewogIAogICMgLSBTZXR1cAogIGxpYnJhcnkoZGF0YS50YWJsZSkKICBsaWJyYXJ5KGRwbHlyKQoKICAjIC0gbGlzdCBmaWxlczoKICBsRiA8LSBsaXN0LmZpbGVzKGRhdGFEaXIpCiAgCiAgIyAtIGZpbHRlciBhZ2dyZWdhdGVkIGJhbm5lciBpbXByZXNzaW9uIGRhdGEKICBsRiA8LSBsRltncmVwbCgiYmFubmVySW1wcmVzc2lvbnNBZ2dyZWdhdGVkXyIsIGxGLCBmaXhlZCA9IFQpXQogIAogICMgLSBsb2FkIGZpbGVzIGFuZCBtZXJnZQogIGJhbm5lckRhdGEgPC0gdmVjdG9yKG1vZGUgPSAibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChsRikpCiAgZm9yIChpIGluIDE6bGVuZ3RoKGxGKSkgewogICAgaWYgKGdyZXBsKCJjc3YkfHRzdiQiLCBsRltpXSkpIHsKICAgICAgYmFubmVyRGF0YVtbaV1dIDwtIGZyZWFkKHBhc3RlMChkYXRhRGlyLCBsRltpXSkpCiAgICB9IGVsc2UgewogICAgICBiYW5uZXJEYXRhW1tpXV0gPC0gTlVMTAogICAgfQogIH0KICBiYW5uZXJEYXRhIDwtIHJiaW5kbGlzdChiYW5uZXJEYXRhKQogIGJhbm5lckRhdGEkVjEgPC0gTlVMTAogIAogICMgLSBhZ2dyZWdhdGVzCiAgcGVyQmFubmVyVG90YWxzIDwtIGJhbm5lckRhdGEgJT4lIAogICAgc2VsZWN0KGJhbm5lciwgaW1wcmVzc2lvbnMpICU+JSAKICAgIGdyb3VwX2J5KGJhbm5lcikgJT4lIAogICAgc3VtbWFyaXNlKHRvdGFsSW1wcmVzc2lvbnMgPSBzdW0oaW1wcmVzc2lvbnMpKQogIHBlckRheVRvdGFscyA8LSBiYW5uZXJEYXRhICU+JSAKICAgIHNlbGVjdChkYXRlLCBpbXByZXNzaW9ucykgJT4lIAogICAgZ3JvdXBfYnkoZGF0ZSkgJT4lIAogICAgc3VtbWFyaXNlKHRvdGFsSW1wcmVzc2lvbnMgPSBzdW0oaW1wcmVzc2lvbnMpKQogIAogICMgLSBvdXRwdXQKICByZXR1cm4oCiAgICBsaXN0KGJhbm5lckltcHJlc3Npb25zUmVwb3J0ID0gYmFubmVyRGF0YSwgCiAgICAgICAgIHBlckJhbm5lclRvdGFscyA9IHBlckJhbm5lclRvdGFscywgCiAgICAgICAgIHBlckRheVRvdGFscyA9IHBlckRheVRvdGFscykKICApCiAgICAKfQoKIyAtIFJlcG9ydCBiYW5uZXIgaW1wcmVzc2lvbnMKYmFubmVySW1wcmVzc2lvbnNEYXRhIDwtIHdtZGVfcmVwb3J0X2Jhbm5lcl9pbXByZXNzaW9ucyhkYXRhRGlyKQpiYW5uZXJJbXByZXNzaW9uc0ZpbGUgPC0gYmFubmVySW1wcmVzc2lvbnNEYXRhJGJhbm5lckltcHJlc3Npb25zUmVwb3J0CndyaXRlLmNzdihiYW5uZXJJbXByZXNzaW9uc0ZpbGUsICJiYW5uZXJJbXByZXNzaW9uc0ZpbGUuY3N2IikKYmFubmVyVG90YWxzIDwtIGJhbm5lckltcHJlc3Npb25zRGF0YSRwZXJCYW5uZXJUb3RhbHMKd3JpdGUuY3N2KGJhbm5lclRvdGFscywgImJhbm5lclRvdGFscy5jc3YiKQpiYW5uZXJEYXlUb3RhbHMgPC0gYmFubmVySW1wcmVzc2lvbnNEYXRhJHBlckRheVRvdGFscwp3cml0ZS5jc3YoYmFubmVyRGF5VG90YWxzLCAiYmFubmVyRGF5VG90YWxzLmNzdiIpCgoKIyMjIC0tLSBSZXBvcnQgUGFnZXZpZXdzIERhdGEKCiMgLSBmdW5jdGlvbjogd21kZV9yZXBvcnRfcGFnZXZpZXdzCndtZGVfcmVwb3J0X3BhZ2V2aWV3cyA8LSBmdW5jdGlvbihkYXRhRGlyKSB7CiAgCiAgIyAtIFNldHVwCiAgbGlicmFyeShkYXRhLnRhYmxlKQogIGxpYnJhcnkoZHBseXIpCiAgCiAgIyAtIGxpc3QgZmlsZXM6CiAgbEYgPC0gbGlzdC5maWxlcyhkYXRhRGlyKQogIAogICMgLSBmaWx0ZXIgYWdncmVnYXRlZCBiYW5uZXIgaW1wcmVzc2lvbiBkYXRhCiAgbEYgPC0gbEZbZ3JlcGwoInBhZ2V2aWV3c0FnZ3JlZ2F0ZWRfIiwgbEYsIGZpeGVkID0gVCldCiAgCiAgIyAtIGxvYWQgZmlsZXMgYW5kIG1lcmdlCiAgcGFnZXZpZXdzRGF0YSA8LSB2ZWN0b3IobW9kZSA9ICJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKGxGKSkKICBmb3IgKGkgaW4gMTpsZW5ndGgobEYpKSB7CiAgICBpZiAoZ3JlcGwoImNzdiR8dHN2JCIsIGxGW2ldKSkgewogICAgICBwYWdldmlld3NEYXRhW1tpXV0gPC0gZnJlYWQocGFzdGUwKGRhdGFEaXIsIGxGW2ldKSkKICAgIH0gZWxzZSB7CiAgICAgIHBhZ2V2aWV3c0RhdGFbW2ldXSA8LSBOVUxMCiAgICB9CiAgfQogIHBhZ2V2aWV3c0RhdGEgPC0gcmJpbmRsaXN0KHBhZ2V2aWV3c0RhdGEpCiAgcGFnZXZpZXdzRGF0YSRWMSA8LSBOVUxMCiAgCiAgIyAtIGFnZ3JlZ2F0ZXMKICBwZXJEYXlUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSAlPiUgCiAgICBzZWxlY3QoZGF0ZSwgUGFnZXZpZXdzKSAlPiUgCiAgICBncm91cF9ieShkYXRlKSAlPiUgCiAgICBzdW1tYXJpc2UodG90YWxQYWdldmlld3MgPSBzdW0oUGFnZXZpZXdzKSkKICBwZXJUYWdUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSAlPiUgCiAgICBzZWxlY3QoVGFnLCBQYWdldmlld3MpICU+JSAKICAgIGdyb3VwX2J5KFRhZykgJT4lIAogICAgc3VtbWFyaXNlKHRvdGFsUGFnZXZpZXdzID0gc3VtKFBhZ2V2aWV3cykpCiAgcGVyUGFnZVRvdGFscyA8LSBwYWdldmlld3NEYXRhICU+JSAKICAgIHNlbGVjdChQYWdlLCBQYWdldmlld3MpICU+JSAKICAgIGdyb3VwX2J5KFBhZ2UpICU+JSAKICAgIHN1bW1hcmlzZSh0b3RhbFBhZ2V2aWV3cyA9IHN1bShQYWdldmlld3MpKQogIHBlclBhZ2VEYXlUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSAlPiUgCiAgICBzZWxlY3QoUGFnZSwgZGF0ZSwgUGFnZXZpZXdzKSAlPiUKICAgIGdyb3VwX2J5KFBhZ2UsIGRhdGUpICU+JSAKICAgIHN1bW1hcmlzZSh0b3RhbFBhZ2V2aWV3cyA9IHN1bShQYWdldmlld3MpKQogIHBlclRhZ0RheVRvdGFscyA8LSBwYWdldmlld3NEYXRhICU+JSAKICAgIHNlbGVjdChUYWcsIGRhdGUsIFBhZ2V2aWV3cykgJT4lCiAgICBncm91cF9ieShUYWcsIGRhdGUpICU+JSAKICAgIHN1bW1hcmlzZSh0b3RhbFBhZ2V2aWV3cyA9IHN1bShQYWdldmlld3MpKQogIHBlclRhZ1BhZ2VUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSAlPiUgCiAgICBzZWxlY3QoVGFnLCBQYWdlLCBQYWdldmlld3MpICU+JQogICAgZ3JvdXBfYnkoVGFnLCBQYWdlKSAlPiUgCiAgICBzdW1tYXJpc2UodG90YWxQYWdldmlld3MgPSBzdW0oUGFnZXZpZXdzKSkKICAgIAogICMgLSBvdXRwdXQKICByZXR1cm4oCiAgICBsaXN0KHBhZ2V2aWV3c0RhdGFSZXBvcnQgPSBwYWdldmlld3NEYXRhLCAKICAgICAgICAgcGVyRGF5VG90YWxzID0gcGVyRGF5VG90YWxzLCAKICAgICAgICAgcGVyVGFnVG90YWxzID0gcGVyVGFnVG90YWxzLCAKICAgICAgICAgcGVyUGFnZVRvdGFscyA9IHBlclBhZ2VUb3RhbHMsIAogICAgICAgICBwZXJQYWdlRGF5VG90YWxzID0gcGVyUGFnZURheVRvdGFscywgCiAgICAgICAgIHBlclRhZ0RheVRvdGFscyA9IHBlclRhZ0RheVRvdGFscywKICAgICAgICAgcGVyVGFnUGFnZVRvdGFscyA9IHBlclRhZ1BhZ2VUb3RhbHMpCiAgKQogIAp9CgojIC0gUmVwb3J0IHBhZ2V2aWV3czoKcGFnZXZpZXdzRGF0YSA8LSB3bWRlX3JlcG9ydF9wYWdldmlld3MoZGF0YURpcikKcGFnZXZpZXdzUmVwb3J0RmlsZSA8LSBwYWdldmlld3NEYXRhJHBhZ2V2aWV3c0RhdGFSZXBvcnQKd3JpdGUuY3N2KHBhZ2V2aWV3c1JlcG9ydEZpbGUsICJwYWdldmlld3NSZXBvcnRGaWxlLmNzdiIpCnBhZ2V2aWV3c19wZXJEYXlUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSRwZXJEYXlUb3RhbHMKd3JpdGUuY3N2KHBhZ2V2aWV3c19wZXJEYXlUb3RhbHMsICJwYWdldmlld3NfcGVyRGF5VG90YWxzLmNzdiIpCnBhZ2V2aWV3c19wZXJUYWdUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSRwZXJUYWdUb3RhbHMKd3JpdGUuY3N2KHBhZ2V2aWV3c19wZXJUYWdUb3RhbHMsICJwYWdldmlld3NfcGVyVGFnVG90YWxzLmNzdiIpCnBhZ2V2aWV3c19wZXJQYWdlVG90YWxzIDwtIHBhZ2V2aWV3c0RhdGEkcGVyUGFnZVRvdGFscwp3cml0ZS5jc3YocGFnZXZpZXdzX3BlclBhZ2VUb3RhbHMsICJwYWdldmlld3NfcGVyUGFnZVRvdGFscy5jc3YiKQpwYWdldmlld3NfcGVyUGFnZURheVRvdGFscyA8LSBwYWdldmlld3NEYXRhJHBlclBhZ2VEYXlUb3RhbHMKd3JpdGUuY3N2KHBhZ2V2aWV3c19wZXJQYWdlRGF5VG90YWxzLCAicGFnZXZpZXdzX3BlclBhZ2VEYXlUb3RhbHMuY3N2IikKcGFnZXZpZXdzX3BlclRhZ0RheVRvdGFscyA8LSBwYWdldmlld3NEYXRhJHBlclRhZ0RheVRvdGFscwp3cml0ZS5jc3YocGFnZXZpZXdzX3BlclRhZ0RheVRvdGFscywgInBhZ2V2aWV3c19wZXJUYWdEYXlUb3RhbHMuY3N2IikKcGFnZXZpZXdzX3BlclRhZ1BhZ2VUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSRwZXJUYWdQYWdlVG90YWxzCndyaXRlLmNzdihwYWdldmlld3NfcGVyVGFnUGFnZVRvdGFscywgInBlclRhZ1BhZ2VUb3RhbHMuY3N2IikKCiMjIyAtLS0gUmVwb3J0IFVzZXIgUmVnaXN0cmF0aW9ucwoKIyAtIGZ1bmN0aW9uOiB3bWRlX3JlcG9ydF9yZWdpc3RyYXRpb25zCndtZGVfcmVwb3J0X3JlZ2lzdHJhdGlvbnMgPC0gZnVuY3Rpb24oZGF0YURpcikgewogIAogICMgLSBTZXR1cAogIGxpYnJhcnkoZGF0YS50YWJsZSkKICBsaWJyYXJ5KGRwbHlyKQogIAogICMgLSBsaXN0IGZpbGVzOgogIGxGIDwtIGxpc3QuZmlsZXMoZGF0YURpcikKICAKICAjIC0gZmlsdGVyIGFnZ3JlZ2F0ZWQgdXNlciByZWdpc3RyYXRpb24gZGF0YQogIGxGIDwtIGxGW2dyZXBsKCJ1c2VyUmVnaXN0cmF0aW9uc0FnZ3JlYWd0ZWRfIiwgbEYsIGZpeGVkID0gVCldCiAgCiAgIyAtIGxvYWQgZmlsZXMgYW5kIG1lcmdlCiAgcmVnaXN0cmF0aW9uRGF0YSA8LSB2ZWN0b3IobW9kZSA9ICJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKGxGKSkKICBmb3IgKGkgaW4gMTpsZW5ndGgobEYpKSB7CiAgICBpZiAoZ3JlcGwoImNzdiR8dHN2JCIsIGxGW2ldKSkgewogICAgICByZWdpc3RyYXRpb25EYXRhW1tpXV0gPC0gZnJlYWQocGFzdGUwKGRhdGFEaXIsIGxGW2ldKSkKICAgIH0gZWxzZSB7CiAgICAgIHJlZ2lzdHJhdGlvbkRhdGFbW2ldXSA8LSBOVUxMCiAgICB9CiAgfQogIHJlZ2lzdHJhdGlvbkRhdGEgPC0gcmJpbmRsaXN0KHJlZ2lzdHJhdGlvbkRhdGEpCiAgcmVnaXN0cmF0aW9uRGF0YSRWMSA8LSBOVUxMCiAgCiAgIyAtIGFnZ3JlZ2F0ZXMKICBwZXJEYXlUb3RhbHMgPC0gcmVnaXN0cmF0aW9uRGF0YSAlPiUgCiAgICBzZWxlY3QoZGF0ZSwgUmVnaXN0cmF0aW9ucykgJT4lIAogICAgZ3JvdXBfYnkoZGF0ZSkgJT4lIAogICAgc3VtbWFyaXNlKHRvdGFsUmVnaXN0cmF0aW9ucyA9IHN1bShSZWdpc3RyYXRpb25zKSkKICBwZXJUYWdUb3RhbHMgPC0gcmVnaXN0cmF0aW9uRGF0YSAlPiUgCiAgICBzZWxlY3QoZXZlbnRfY2FtcGFpZ24sIFJlZ2lzdHJhdGlvbnMpICU+JSAKICAgIGdyb3VwX2J5KGV2ZW50X2NhbXBhaWduKSAlPiUgCiAgICBzdW1tYXJpc2UodG90YWxSZWdpc3RyYXRpb25zID0gc3VtKFJlZ2lzdHJhdGlvbnMpKQogIHBlclRhZ0RheVRvdGFscyA8LSByZWdpc3RyYXRpb25EYXRhICU+JSAKICAgIHNlbGVjdChldmVudF9jYW1wYWlnbiwgZGF0ZSwgUmVnaXN0cmF0aW9ucykgJT4lIAogICAgZ3JvdXBfYnkoZXZlbnRfY2FtcGFpZ24sIGRhdGUpICU+JSAKICAgIHN1bW1hcmlzZSh0b3RhbFJlZ2lzdHJhdGlvbnMgPSBzdW0oUmVnaXN0cmF0aW9ucykpCiAgCiAgIyAtIG91dHB1dAogIHJldHVybigKICAgIGxpc3QocmVnaXN0cmF0aW9uc0RhdGFSZXBvcnQgPSByZWdpc3RyYXRpb25EYXRhLCAKICAgICAgICAgcGVyRGF5VG90YWxzID0gcGVyRGF5VG90YWxzLCAKICAgICAgICAgcGVyVGFnVG90YWxzID0gcGVyVGFnVG90YWxzLCAKICAgICAgICAgcGVyVGFnRGF5VG90YWxzID0gcGVyVGFnRGF5VG90YWxzKQogICkKICAKfQoKIyAtIFJlcG9ydCB1cG9uIHVzZXIgcmVnaXN0cmF0aW9ucwp1c2VyUmVnRGF0YSA8LSB3bWRlX3JlcG9ydF9yZWdpc3RyYXRpb25zKGRhdGFEaXIpCnVzZXJSZWdpc3RyYXRpb25zUmVwb3J0RmlsZSA8LSB1c2VyUmVnRGF0YSRyZWdpc3RyYXRpb25zRGF0YVJlcG9ydAp3cml0ZS5jc3YodXNlclJlZ2lzdHJhdGlvbnNSZXBvcnRGaWxlLCAidXNlclJlZ2lzdHJhdGlvbnNSZXBvcnRGaWxlLmNzdiIpCnVzZXJSZWdpc3RyYXRpb25zX3BlckRheVRvdGFscyA8LSB1c2VyUmVnRGF0YSRwZXJEYXlUb3RhbHMKd3JpdGUuY3N2KHVzZXJSZWdpc3RyYXRpb25zX3BlckRheVRvdGFscywgInVzZXJSZWdpc3RyYXRpb25zX3BlckRheVRvdGFscy5jc3YiKQp1c2VyUmVnaXN0cmF0aW9uc19wZXJUYWdUb3RhbHMgPC0gdXNlclJlZ0RhdGEkcGVyVGFnVG90YWxzCndyaXRlLmNzdih1c2VyUmVnaXN0cmF0aW9uc19wZXJUYWdUb3RhbHMsICJ1c2VyUmVnaXN0cmF0aW9uc19wZXJUYWdUb3RhbHMuY3N2IikKdXNlclJlZ2lzdHJhdGlvbnNfcGVyVGFnRGF5VG90YWxzIDwtIHVzZXJSZWdEYXRhJHBlclRhZ0RheVRvdGFscwp3cml0ZS5jc3YodXNlclJlZ2lzdHJhdGlvbnNfcGVyVGFnRGF5VG90YWxzLCAidXNlclJlZ2lzdHJhdGlvbnNfcGVyVGFnRGF5VG90YWxzLmNzdiIpCmBgYAoKIyMgMS4gQ2FtcGFpZ24gQmFubmVycyBhbmQgUGFnZXMKClRoaXMgc2VjdGlvbiBwcmVzZW50cyBhbGwgZGF0YSBhbmQgc3RhdGlzdGljcyBvbiB0aGUgY2FtcGFpZ24gYmFubmVycyBhbmQgcGFnZXMuCgojIyMgMS4xIEJhbm5lciBJbXByZXNzaW9ucwojIyMjIDEuMS4xIEJhbm5lciBJbXByZXNzaW9ucyBPdmVydmlldwoKKipDaGFydCAxLjEuMSoqIERhaWx5IEJhbm5lciBJbXByZXNzaW9ucwoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0V9CmRhdGFTZXQgPC0gcmVhZC5jc3YoCiAgJ19hbmFseXRpY3MvYmFubmVySW1wcmVzc2lvbnNGaWxlLmNzdicsCiAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLAogICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKIyAtIFZpc3VhbGl6ZSB3LiB7Z2dwbG90Mn0KZ2dwbG90KGRhdGFTZXQsIGFlcyh4ID0gZGF0ZSwKICAgICAgICAgICAgICAgICAgICB5ID0gaW1wcmVzc2lvbnMsCiAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBpbXByZXNzaW9ucywKICAgICAgICAgICAgICAgICAgICBncm91cCA9IGJhbm5lciwKICAgICAgICAgICAgICAgICAgICBjb2xvciA9IGJhbm5lciwKICAgICAgICAgICAgICAgICAgICBmaWxsID0gYmFubmVyCiAgICAgICAgICAgICAgICAgICAgKSkgKyAKICBnZW9tX3BhdGgoc2l6ZSA9IC41KSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDEuNSkgKwogIGZhY2V0X3dyYXAofmJhbm5lciwgbmNvbCA9IDIpICsgCiAgZ2d0aXRsZSgnVGhhbmsgWW91IDI5MTk6IEJhbm5lciBJbXByZXNzaW9ucycpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBzaXplID0gOCkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpCmBgYAoKIyMjIyAxLjEuMSBCYW5uZXIgSW1wcmVzc2lvbnMgT3ZlcnZpZXc6IFRhYmxlCgoqKlRhYmxlIDEuMS4xLioqIERhaWx5IEJhbm5lciBJbXByZXNzaW9ucwoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0V9CiMjIyAtLS0gRnVsbCBEYXRhc2V0IChUYWJsZSBSZXBvcnQpCmRhdGF0YWJsZShkYXRhU2V0KQpgYGAKCiMjIyMgMS4xLjIgVG90YWwgQmFubmVyIEltcHJlc3Npb25zCioqQ2hhcnQgMS4xLjIuKiogVG90YWwgQmFubmVyIEltcHJlc3Npb25zCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGQUxTRX0KZGF0YVNldCA8LSByZWFkLmNzdigKICAnX2FuYWx5dGljcy9iYW5uZXJUb3RhbHMuY3N2JywKICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULAogICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsCiAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpnZ3Bsb3QoZGF0YVNldCwgYWVzKHggPSBiYW5uZXIsIAogICAgICAgICAgICAgICAgICAgIHkgPSB0b3RhbEltcHJlc3Npb25zLCAKICAgICAgICAgICAgICAgICAgICBjb2xvciA9IGJhbm5lciwgCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGJhbm5lciwgCiAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSB0b3RhbEltcHJlc3Npb25zKSkgKyAKICBnZW9tX2Jhcih3aWR0aCA9IC41LCBzdGF0ID0gImlkZW50aXR5IikgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBnZ3RpdGxlKCdUaGFuayBZb3UgMjkxOTogQmFubmVyIEltcHJlc3Npb25zJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIGdlb21fbGFiZWxfcmVwZWwoc2l6ZSA9IDMuNSwgY29sb3IgPSAid2hpdGUiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4KSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCiMjIyMgMS4xLjMgQmFubmVyIEltcHJlc3Npb25zIHBlciBEYXkKCioqQ2hhcnQgMS4xLjMuKiogQmFubmVyIEltcHJlc3Npb25zIHBlciBEYXkKCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZBTFNFfQpkYXRhU2V0IDwtIHJlYWQuY3N2KAogICdfYW5hbHl0aWNzL2Jhbm5lckRheVRvdGFscy5jc3YnLAogICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSwKICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmdncGxvdChkYXRhU2V0LCBhZXMoeCA9IGRhdGUsIAogICAgICAgICAgICAgICAgICAgIHkgPSB0b3RhbEltcHJlc3Npb25zLCAKICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IHRvdGFsSW1wcmVzc2lvbnMpKSArCiAgZ2VvbV9wYXRoKHNpemUgPSAuMjUsIGdyb3VwID0gMSwgY29sb3IgPSAiZGFya2JsdWUiKSArCiAgZ2VvbV9wb2ludChzaXplID0gMS41LCBjb2xvciA9ICJkYXJrYmx1ZSIpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMSwgY29sb3IgPSAid2hpdGUiKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGdndGl0bGUoJ1RoYW5rIFlvdSAyOTE5OiBCYW5uZXIgSW1wcmVzc2lvbnMnKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgZ2VvbV9sYWJlbF9yZXBlbChzaXplID0gMy41LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4KSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQpgYGAKCiMjIyAxLjIgUGFnZXZpZXdzCgojIyMjIDEuMi4xIFBhZ2V2aWV3cyBPdmVydmlldwoKKipDaGFydCAxLjIuMS4qKiBQYWdldmlld3MgT3ZlcnZpZXcuIAoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0V9CmRhdGFTZXQgPC0gcmVhZC5jc3YoCiAgJ19hbmFseXRpY3MvcGFnZXZpZXdzX3BlclBhZ2VEYXlUb3RhbHMuY3N2JywKICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULAogICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsCiAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpkYXRhU2V0JFBhZ2UgPC0gZ3N1YigiL3dpa2kvV2lraXBlZGlhOldpa2lwZWRpYV98L3dpa2kvV2lraXBlZGlhOldpa2ltZWRpYV8iLCAiIiwgZGF0YVNldCRQYWdlKQojIC0gVmlzdWFsaXplIHcuIHtnZ3Bsb3QyfQpnZ3Bsb3QoZGF0YVNldCwgYWVzKHggPSBkYXRlLAogICAgICAgICAgICAgICAgICAgIHkgPSB0b3RhbFBhZ2V2aWV3cywKICAgICAgICAgICAgICAgICAgICBncm91cCA9IFBhZ2UsCiAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBQYWdlLAogICAgICAgICAgICAgICAgICAgIGZpbGwgPSBQYWdlLAogICAgICAgICAgICAgICAgICAgIGxhYmVsID0gdG90YWxQYWdldmlld3MsCiAgICAgICAgICAgICAgICAgICAgKSkgKyAKICBnZW9tX3BhdGgoc2l6ZSA9IC4yNSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAxLjUpICsKICBnZW9tX3BvaW50KHNpemUgPSAxLCBjb2xvciA9ICJ3aGl0ZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBnZ3RpdGxlKCdUaGFuayBZb3UgMjkxOTogUGFnZXZpZXdzJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIGdlb21fdGV4dF9yZXBlbChzaXplID0gMy41LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4KSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikKYGBgCgoKIyMjIyAxLjIuMSBQYWdldmlld3MgT3ZlcnZpZXc6IFRhYmxlCgoqKlRhYmxlIDEuMi4xLioqIFBhZ2V2aWV3cyBPdmVydmlldwoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0V9CiMjIyAtLS0gRnVsbCBEYXRhc2V0IChUYWJsZSBSZXBvcnQpCmRhdGF0YWJsZShkYXRhU2V0ICU+JSBhcnJhbmdlKGRlc2ModG90YWxQYWdldmlld3MpKSkKYGBgCgoKIyMgMi4gVXNlciBSZWdpc3RyYXRpb25zCgpBbGwgZGF0YSBvbiB1c2VyIHJlZ2lzdHJhdGlvbnMgYXJlIHByZXNlbnRlZCBpbiB0aGlzIHNlY3Rpb24uCgojIyMgMi4xIFJlZ2lzdHJhdGlvbnMgcGVyIHRhZyBhbmQgZGF5CgoqKkNoYXJ0IDIuMS4qKiBSZWdpc3RyYXRpb25zIHBlciB0YWcgYW5kIGRheS4gUGxlYXNlIG5vdGU6IHBvaW50cyB3aXRoIG5vIGRhdGEgbGFiZWxzIHNpZ25pZnkgMCB1c2VyIHJlZ2lzdHJhdGlvbnMuCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGQUxTRX0KIyAtIFN0YW5kYXJkIHJlZ2lzdHJhdGlvbnMKZGF0YVNldCA8LSByZWFkLmNzdigKICAnX2FuYWx5dGljcy91c2VyUmVnaXN0cmF0aW9uc1JlcG9ydEZpbGUuY3N2JywKICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULAogICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsCiAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpkYXRhU2V0IDwtIGRhdGFTZXQgJT4lIAogIGZpbHRlcighKGV2ZW50X2NhbXBhaWduICVpbiUgJ1dNREVfbmV3ZWRpdG9yc19hdXR1bW5fMjAxOF9scG4nKSkKZGF0YVNldCRjYW1wYWlnbiA8LSBOVUxMCmdncGxvdChkYXRhU2V0LCBhZXMoeCA9IGRhdGUsCiAgICAgICAgICAgICAgICAgICAgeSA9IFJlZ2lzdHJhdGlvbnMsCiAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBldmVudF9jYW1wYWlnbiwKICAgICAgICAgICAgICAgICAgICBjb2xvciA9IGV2ZW50X2NhbXBhaWduLAogICAgICAgICAgICAgICAgICAgIGZpbGwgPSBldmVudF9jYW1wYWlnbiwKICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IFJlZ2lzdHJhdGlvbnMsCiAgICAgICAgICAgICAgICAgICAgKSkgKyAKICBnZW9tX3BhdGgoc2l6ZSA9IC4yNSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAxLjUpICsKICBnZW9tX3BvaW50KHNpemUgPSAxLCBjb2xvciA9ICJ3aGl0ZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBnZ3RpdGxlKCdUaGFuayBZb3UgMjkxOTogUmVnaXN0cmF0aW9ucyBwZXIgVGFnJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIGdlb21fdGV4dF9yZXBlbChzaXplID0gMy41LCBzaG93LmxlZ2VuZCA9IEYpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQpgYGAKCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZBTFNFfQojIyMgLS0tIEZ1bGwgRGF0YXNldCAoVGFibGUgUmVwb3J0KQpjb2xuYW1lcyhkYXRhU2V0KVsxXSA8LSAnVGFnJwpkYXRhdGFibGUoZGF0YVNldCAlPiUgYXJyYW5nZShUYWcsIGRhdGUsIGRlc2MoUmVnaXN0cmF0aW9ucykpKQpgYGAKCiMjIDMuIFVzZXIgRWRpdHMKClRoaXMgYW5hbHlzaXMgaXMgc2NoZWR1bGVkIGZvciBKYW51YXJ5IDIxc3QgYW5kIDMwdGggW1BoYWJdKGh0dHBzOi8vcGhhYnJpY2F0b3Iud2lraW1lZGlhLm9yZy9UMjExNjkwIzQ4OTE1MjgpCgojIyMgMy4xIFVzZXIgZWRpdHM6IGRpc3RyaWJ1dGlvbgoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBGLCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0V9CmRhdGFTZXQgPC0gcmVhZC5jc3YoCiAgJ19hbmFseXRpY3MvYWxsVXNlckVkaXRzX1RoYW5rWW91MjAxOS5jc3YnLAogICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSwKICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCiMgLSBFZGl0IHwgMSB8IDItNCB8IDUtOSB8IDEwLTQ5IHwgPjUwCmVkaXRCb3VuZGFyaWVzIDwtIGxpc3QoCiAgYygwLCAxKSwgCiAgYygyLCA0KSwKICBjKDUsIDkpLAogIGMoMTAsIDQ5KQopCmRhdGFTZXQkZWRpdENsYXNzIDwtIHNhcHBseShkYXRhU2V0JGVkaXRzLCBmdW5jdGlvbih4KSB7CiAgd0VDIDwtIHNhcHBseShlZGl0Qm91bmRhcmllcywgZnVuY3Rpb24oeSkgewogICAgeCA+PSB5WzFdICYgeCA8PSB5WzJdCiAgfSkKICBpZiAoc3VtKHdFQykgPT0gMCkgewogICAgcmV0dXJuKCI+PSA1MCIpCiAgfSBlbHNlIHsKICAgIHJldHVybihwYXN0ZTAoIigiLAogICAgICAgICAgICAgICAgICBlZGl0Qm91bmRhcmllc1tbd2hpY2god0VDKV1dWzFdLAogICAgICAgICAgICAgICAgICAiIC0gIiwKICAgICAgICAgICAgICAgICAgZWRpdEJvdW5kYXJpZXNbW3doaWNoKHdFQyldXVsyXSwgCiAgICAgICAgICAgICAgICAgICIpIgogICAgICAgICAgICAgICAgICApCiAgICApCiAgfQp9KQplZGl0Q2xhc3MgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShkYXRhU2V0JGVkaXRDbGFzcyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKY29sbmFtZXMoZWRpdENsYXNzKSA8LSBjKCdFZGl0IENsYXNzJywgJ051bS5Vc2VycycpCmVkaXRDbGFzcyRvcmRlciA8LSBhcy5udW1lcmljKHNhcHBseShlZGl0Q2xhc3MkYEVkaXQgQ2xhc3NgLCBmdW5jdGlvbih4KSB7CiAgbG93ZXIgPC0gc3RyX2V4dHJhY3QoeCwgJ1tbOmRpZ2l0Ol1dKycpCn0pKQplZGl0Q2xhc3MgPC0gYXJyYW5nZShlZGl0Q2xhc3MsIG9yZGVyKQplZGl0Q2xhc3Mkb3JkZXIgPC0gTlVMTApkYXRhdGFibGUoZWRpdENsYXNzKQpgYGAK