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

3.1 Collect user edits

This data acquisition chank is not reproducible from this Report; runs in production on stat1007:

### ------------------------------------------------------
### --- Collect all user edits per day from dewiki.revision
### --- from 2. January 2019, w. event_campaign
### --- update 1: one week after then end: 23th January
### --- update 2: two weeks after then end: 30th January
### ------------------------------------------------------
localDir <- '/home/goransm/Analytics/NewEditors/Campaigns/2019_ThankYou/data/'
startTimestamp <- '20190102000000' # - campaign begins
# - SQL query
sqlQuery <- paste("\"SELECT rev_user, SUBSTR(rev_timestamp, 1, 8) 
                  FROM dewiki.revision WHERE rev_timestamp >= ", startTimestamp, ";\"", sep = "")

### --- output filename
filename <- paste(localDir,'ReferenceEdits_dewiki_20190123', ".tsv", sep = "")

### --- execute sql script:
sqlArgs <- 'mysql --defaults-file=/etc/mysql/conf.d/analytics-research-client.cnf -h analytics-store.eqiad.wmnet -A -e'
sqlInput <- paste(sqlQuery,
                  " > ",
                  filename,
                  sep = "")
# - command:
sqlCommand <- paste(sqlArgs, sqlInput)
system(command = sqlCommand, wait = TRUE)

This analysis is scheduled for January 24th and 30th Phab

3.1 User edits: daily

Wrangle the dataset; visualize campaign edits per day.

allEdits <- 
  fread(
    '/home/goransm/Work/___DataKolektiv/Projects/WikimediaDEU/_WMDE_Projects/_misc/NewEditors_Team/2019_ThankYou/_analytics/ReferenceEdits_dewiki_20190131.tsv'
    )
userRegs <- read.csv(
  '/home/goransm/Work/___DataKolektiv/Projects/WikimediaDEU/_WMDE_Projects/_misc/NewEditors_Team/2019_ThankYou/_analytics/fullRegistrationDataset.csv',
                    header = T,
                    row.names = 1,
                    check.names = F,
                    stringsAsFactors = F)
# - keep only campaign registered users:
userEdits <- allEdits[allEdits$rev_user %in% unique(userRegs$event_userId), ]
rm(userRegs); rm(allEdits); gc()
          used (Mb) gc trigger (Mb) max used (Mb)
Ncells  993551 53.1    1770749 94.6  1770749 94.6
Vcells 2671840 20.4    9089513 69.4  9089499 69.4
# - wrangle timestamp
colnames(userEdits) <- c('user_id', 'date')
userEdits$date <- sapply(userEdits$date, function(x) {
  paste(
    substr(x, 1, 4),
    substr(x, 5, 6), 
    substr(x, 7, 8),
    sep = "-"
  )
})
# - aggregate per day
userEditsDay <- userEdits %>% 
  group_by(date) %>% 
  summarise(edits = n()) %>% 
  arrange(date)
# - input zero edits at missing dates
userEditsDayCopy <- userEditsDay
editDateRange <- userEditsDayCopy$date
editDateRange <- as.POSIXct(editDateRange)
editDateRange <- seq(min(editDateRange), max(editDateRange), by = "day")
userEditsDayCopy <- data.frame(date = as.character(editDateRange), edits = numeric(length(editDateRange)), 
                               stringsAsFactors = F)
userEditsDayCopy <- left_join(userEditsDayCopy, userEditsDay, by = "date")
userEditsDayCopy$edits.x <- NULL
colnames(userEditsDayCopy) <- c('date', 'edits')
userEditsDayCopy$edits[is.na(userEditsDayCopy$edits)] <- 0
userEditsDay <-userEditsDayCopy
# - visualize daily edits
# - Visualize w. {ggplot2}
ggplot(userEditsDay, aes(x = date,
                         y = edits,
                         label = edits
                    )) + 
  geom_path(size = .5, color = 'blue', group = 1) + 
  geom_point(size = 1.5, color = 'blue') + 
  geom_point(size = 1.5, color = 'white') +
  geom_text_repel(size = 3.5) + 
  ggtitle('Thank You 2919: Edits per day') +
  theme_minimal() + 
  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")

3.1 User edits: edit classes

# - Edit | 1 | 2-4 | 5-9 | 10-49 | >50
editBoundaries <- list(
  c(0, 1), 
  c(2, 4),
  c(5, 9),
  c(10, 49)
)
userEdits_user <- userEdits %>% 
  group_by(user_id) %>% 
  summarise(edits = n())
userEdits_user$editClass <- sapply(userEdits_user$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(userEdits_user$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)

3.2 User edits: when do the edits happen?

Chart 3.2.1

Q. Also it would be interesting to know when the new users edit: is there a possibility to calculate in which time interval new users are active, e.g. directly after registration, or some days later etc.? On the x-axis would be time after registration in days, with 0 being point of registration and on the y-axis number of edits. The diagram would then show average edits per user x-days after registration. Could this work? Phab

Note. The chart presents the mean number of edits calculated from the total number of edits per day divided by the number of active users (i.e. by how many users edited) on that day.

# - load user registrations per day, per user
userRegistrationsDaily <- fread("_analytics/fullRegistrationDataset.csv")
userRegistrationsDaily <- select(userRegistrationsDaily, 
                                 event_userId, date)
editsSinceReg <- userRegistrationsDaily 
editsSinceReg <- left_join(editsSinceReg, 
                           userEdits, 
                           by = c("event_userId" = "user_id")) %>% 
  arrange(event_userId)
colnames(editsSinceReg) <- c("user_id", "registration", "edit")
editsSinceReg <- filter(editsSinceReg, !is.na(edit))
editsSinceReg$registration <- as.POSIXct(editsSinceReg$registration)
editsSinceReg$edit <- as.POSIXct(editsSinceReg$edit)
editsSinceReg$diff <- (editsSinceReg$edit - editsSinceReg$registration)/(60^2*24)
avgEditsSinceReg <- editsSinceReg %>% 
  select(diff, user_id) %>% 
  group_by(diff, user_id) %>% 
  summarise(edits = n()) %>% 
  select(diff, edits) %>% 
  group_by(diff) %>% 
  summarise(meanEdits = mean(edits))
avgEditsSinceReg$diff <- as.numeric(avgEditsSinceReg$diff)
avgEditsFrame <- data.frame(diff = seq(min(avgEditsSinceReg$diff), max(avgEditsSinceReg$diff)))
avgEditsFrame <- left_join(avgEditsFrame, avgEditsSinceReg, by = "diff")
avgEditsFrame$meanEdits[is.na(avgEditsFrame$meanEdits)] <- 0
avgEditsFrame$meanEdits <- round(avgEditsFrame$meanEdits, 2)
ggplot(avgEditsFrame, aes(x = diff, 
                    y = meanEdits, 
                    label = meanEdits)) + 
  geom_path(color = "blue", size = .25) + 
  geom_point(color = "blue", size = 1.5) + 
  geom_point(color = "white", size = 1) + 
  ggtitle('Thank You 2919: Average number of edits N days after registration') + 
  xlab("Days after registration") + ylab("Mean edits per user") + 
  theme_minimal() + 
  geom_text_repel(size = 3.5, show.legend = FALSE) + 
  scale_y_continuous(labels = comma) +
  theme(axis.text.x = element_text(angle = 0, size = 8)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) + 
  theme(legend.position = "right")

Chart 3.2.2

Note. The chart presents the mean number of edits calculated from the total number of edits per day divided by the number ofusers registered until that day (i.e. by how many campaign registered users could have edited on that day) see Phab.

# - load user registrations per day, per user
userRegistrationsDaily <- fread("_analytics/fullRegistrationDataset.csv")
userRegistrationsDaily <- select(userRegistrationsDaily, 
                                 event_userId, date)
cumulativeRegistrations <- userRegistrationsDaily %>% 
  select(date) %>% 
  group_by(date) %>% 
  summarise(registrations = n())
cumulativeRegistrations$registrations <- cumsum(cumulativeRegistrations$registrations)
userEditsPerDay <- userEdits %>% 
  select(date) %>%
  group_by(date) %>% 
  summarise(edits = n())
userEditsPerDay <- left_join(userEditsPerDay, cumulativeRegistrations,
                             by = "date")
userEditsPerDay$registrations[is.na(userEditsPerDay$registrations)] <- 
  max(userEditsPerDay$registrations, na.rm = T)
userEditsPerDay$meanEdits = round(userEditsPerDay$edits/userEditsPerDay$registrations, 3)
dateRangeFrame <- data.frame(date = 
                               as.character(
                                 seq(min(as.POSIXct(userEditsPerDay$date)), 
                                     max(as.POSIXct(userEditsPerDay$date)), 
                                     by = "day")),
                             stringsAsFactors = F)
userEditsPerDay <- left_join(dateRangeFrame, userEditsPerDay, by = "date")
userEditsPerDay$meanEdits[is.na(userEditsPerDay$meanEdits)] <- 0
ggplot(userEditsPerDay, aes(x = date,
                            y = meanEdits,
                            label = meanEdits)) + 
  geom_path(color = "blue", size = .25, group = 1) + 
  geom_point(color = "blue", size = 1.5) + 
  geom_point(color = "white", size = 1) + 
  ggtitle('Thank You 2919: Average number of edits N days after registration') + 
  xlab("Days after registration") + ylab("Mean edits per user") + 
  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 = "right")

Chart 3.2.3

Note. The chart presents the mean number of edits calculated from the total number of edits per day divided by the number of active users (i.e. by how many users edited) until that day." see Phab.

# - load user registrations per day, per user
userEditsRev <- userEdits %>% 
  arrange(date)
uniqueDates <- unique(userEditsRev$date)
editors <- list()
editors <- lapply(uniqueDates, function(x) {
  unique(userEditsRev$user_id[userEditsRev$date %in% x])
})
names(editors) <- uniqueDates
editorsDaily <- list()
for (i in 1:length(editors)) {
  editorsDaily[[i]] <- c(unique(unname(unlist(editors[1:i]))))
}
editorsDaily <- lapply(editorsDaily, length)
editorsDaily <- data.frame(editors = unname(unlist(editorsDaily)), 
                           date = uniqueDates, 
                           stringsAsFactors = F)
userEditsPerDay <- userEdits %>% 
  select(date) %>%
  group_by(date) %>% 
  summarise(edits = n())
userEditsPerDay <- left_join(userEditsPerDay, editorsDaily,
                             by = "date")
userEditsPerDay$meanEdits = round(userEditsPerDay$edits/userEditsPerDay$editors, 3)
dateRangeFrame <- data.frame(date = 
                               as.character(
                                 seq(min(as.POSIXct(userEditsPerDay$date)), 
                                     max(as.POSIXct(userEditsPerDay$date)), 
                                     by = "day")),
                             stringsAsFactors = F)
userEditsPerDay <- left_join(dateRangeFrame, userEditsPerDay, by = "date")
userEditsPerDay$meanEdits[is.na(userEditsPerDay$meanEdits)] <- 0
ggplot(userEditsPerDay, aes(x = date,
                            y = meanEdits,
                            label = meanEdits)) + 
  geom_path(color = "blue", size = .25, group = 1) + 
  geom_point(color = "blue", size = 1.5) + 
  geom_point(color = "white", size = 1) + 
  ggtitle('Thank You 2919: Average number of edits N days after registration') + 
  xlab("Days after registration") + ylab("Mean edits per user") + 
  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 = "right")

4. Training Modules

4.1 Collect training modules data

The dataset is obtained directly from the maintainer.

# - training modules data
trainModules <- fread(
    '/home/goransm/Work/___DataKolektiv/Projects/WikimediaDEU/_WMDE_Projects/_misc/NewEditors_Team/2019_ThankYou/_analytics/wmde_training_data_2019-01.csv')
# - get user registrations w. user names for joins
locDir <- 
'/home/goransm/Work/___DataKolektiv/Projects/WikimediaDEU/_WMDE_Projects/_misc/NewEditors_Team/2019_ThankYou/_data/'
lF <- list.files(locDir)
lF <- lF[grepl("userRegistrations_", lF)]
userReg <- lapply(lF, function(x) {
  fread(paste0(locDir, x))
})
userReg <- rbindlist(userReg)
userReg$V1 <- NULL
trainModules <- left_join(trainModules,
                          select(userReg,  event_userId, event_userName),
                          by = c("username" = "event_userName"))
trainModules <- trainModules[!is.na(trainModules$event_userId), ]
trainModules$username <- NULL

4.2 Training modules: overview

49 new editors have started at least one training module; 32 have completed at least one the training and 19 did not.

The following table 4.2.1 gives and overview of how many users have completed a particular training module.

tmUsers <- trainModules %>% 
  select(event_userId, training_module, module_completion_date) %>% 
  mutate(module_completion_date = ifelse(module_completion_date == '', F, T)) %>% 
  group_by(event_userId, training_module, module_completion_date) %>% 
  summarise(n = n()) %>% 
  filter(module_completion_date == T) %>% 
  select(training_module) %>% 
  group_by(training_module) %>% 
  summarise(editors = n())
datatable(tmUsers)

Table 4.2.2 gives and overview of how many users have started a particular training module.

tmUsers <- trainModules %>% 
  select(training_module) %>% 
  group_by(training_module) %>% 
  summarise(editors = n())
datatable(tmUsers)

Table 4.2.3 The last slide from a training module completed by new editors; the editors column shows the number of editors who have abandoned (or completed) their traning at the respective slide.

tmLastSlide <- trainModules %>%
  group_by(training_module, last_slide_completed) %>% 
  summarise(editors = n())
datatable(tmLastSlide)

Table 4.2.4 User edits: training modules completed vs not completed.

tmEdits <- left_join(userEdits, trainModules, by = c('user_id' = 'event_userId'))
tmEdits <- tmEdits %>% 
  select(training_module, module_completion_date)
tmEdits$completed <- sapply(tmEdits$module_completion_date, function(x) {
  if(is.na(x) | x == '') {return(FALSE)} else {return(TRUE)}
})
tmEdits$started <- sapply(tmEdits$training_module, function(x) {
  if(is.na(x) | x == '') {return(FALSE)} else {return(TRUE)}
})
tmEdits$module_completion_date <- NULL
tmEdits$training_module <- NULL
tmEdits$training_outcome <- ifelse(tmEdits$completed, 'Completed', NA)
tmEdits$training_outcome <- ifelse(tmEdits$started & is.na(tmEdits$training_outcome), 
                                   'Started', tmEdits$training_outcome)
tmEdits$training_outcome[is.na(tmEdits$training_outcome)] <- 'No training' 
tmEdits <- tmEdits %>% 
  group_by(training_outcome) %>% 
  summarise(edits = n())
datatable(tmEdits)

Table 4.2.5 User edits: average number of edits per user, training modules completed vs not completed.

tmEdits <- left_join(userEdits, trainModules, by = c('user_id' = 'event_userId'))
tmEdits <- tmEdits %>% 
  select(user_id, training_module, module_completion_date)
tmEdits$completed <- sapply(tmEdits$module_completion_date, function(x) {
  if(is.na(x) | x == '') {return(FALSE)} else {return(TRUE)}
})
tmEdits$started <- sapply(tmEdits$training_module, function(x) {
  if(is.na(x) | x == '') {return(FALSE)} else {return(TRUE)}
})
tmEdits$module_completion_date <- NULL
tmEdits$training_module <- NULL
tmEdits$training_outcome <- ifelse(tmEdits$completed, 'Completed', NA)
tmEdits$training_outcome <- ifelse(tmEdits$started & is.na(tmEdits$training_outcome), 
                                   'Started', tmEdits$training_outcome)
tmEdits$training_outcome[is.na(tmEdits$training_outcome)] <- 'No training' 
tmEdits <- tmEdits %>% 
  group_by(user_id, training_outcome) %>% 
  summarise(edits = n()) %>% 
  group_by(training_outcome) %>% 
  summarise(`mean(Edits)` = round(mean(edits), 2))
datatable(tmEdits)

Do you see in the tables from Ragesoss how many people did or begin the modules in general - regardless of the fact they register or not. Question behind the question: I would like to know how many people potentially just did the module, but didn’t register beforhand. (see: Phab)

# - training modules data
trainModules <- fread(
    '/home/goransm/Work/___DataKolektiv/Projects/WikimediaDEU/_WMDE_Projects/_misc/NewEditors_Team/2019_ThankYou/_analytics/wmde_training_data_2019-01.csv')
# - Unique number of trainModules$username -2 (Stefan and Sage)
length(unique(trainModules$username)) - 2
[1] 387

387 people took the training modules irrespective of registration.

LS0tCnRpdGxlOiAnVGhhbmsgWW91IENhbXBhaWduIDIwMTk6IFJlcG9ydCcKYXV0aG9yOiAiR29yYW4gUy4gTWlsb3Zhbm92aWMsIERhdGEgU2NpZW50aXN0LCBXTURFIgpkYXRlOiAiSmFudWFyeSAyMSwgMjAxOSIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIHRoZW1lOiBzaW1wbGV4CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIHRvY19kZXB0aDogNQogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiA1Ci0tLQoKKipGZWVkYmFjayoqIHNob3VsZCBiZSBzZW5kIHRvIGBnb3Jhbi5taWxvdmFub3ZpY19leHRAd2lraW1lZGlhLmRlYC4gCgpUaGUgY2FtcGFpZ24gaXMgcnVuIGZyb20gMjAxOS8wMS8wMiB0byAyMDE5LzAxLzE2LgoKKipDVVJSRU5UIFVQREFURToqKiBDb21wbGV0ZSBkYXRhc2V0IGFzIG9mIDIwMTkvMDEvMTYuCgpgYGB7ciwgZWNobyA9IEYsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGLCByZXN1bHRzID0gJ2hpZGUnfQojICFkaWFnbm9zdGljcyBvZmYKIyMjIC0tLSBTZXR1cAprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLndpZHRoID0gMTUsIGZpZy5oZWlnaHQgPSA4KSAKcm0obGlzdCA9IGxzKCkpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dyZXBlbCkKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkocm1hcmtkb3duKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KERUKQpsaWJyYXJ5KHJlc2hhcGUyKQpsaWJyYXJ5KHB1cnJyKQpgYGAKCiMjIDAuIERhdGEgQWNxdWlzaXRvbgoKKipOT1RFOioqIHRoZSBEYXRhIEFjcXVpc2l0aW9uIGNvZGUgY2h1bmsgaXMgbm90IGZ1bGx5IHJlcHJvZHVjaWJsZSBmcm9tIHRoaXMgUmVwb3J0LiBUaGUgZGF0YSBhcmUgY29sbGVjdGVkIGJ5IHJ1bm5pbmcgYW4gUiBzY3JpcHQgb24gYHN0YXQxMDA3LmVxaWFkLndtbmV0YCwgY29sbGVjdGluZyB0aGUgZGF0YSBhcyBgLnRzdmAgYW5kIGAuY3N2YCBmaWxlcywgY29weWluZyBtYW51YWxseSwgYW5kIHByb2Nlc3NpbmcgbG9jYWxseS4gCgojIyMgMC4xIERhaWx5IFVwZGF0ZQoKYGBge3IsIGVjaG8gPSBULCBldmFsID0gRn0KCiMjIyAtLS0gRGF0YSBBY3F1aXNpdGlvbiBmb3IgdGhlIFRoYW5rIFlvdSAyMDE5IENhbXBhaWduCiMjIyAtLS0gcnVuIGZyb20gc3RhdDEwMDcKIyMjIC0tLSAvaG9tZS9nb3JhbnNtL0FuYWx5dGljcy9OZXdFZGl0b3JzL0NhbXBhaWducy8yMDE5X1RoYW5rWW91CgojIyMgLS0tIHRvIGRhdGEgZGlyZWN0b3J5CmRhdGFEaXIgPC0gJy9ob21lL2dvcmFuc20vQW5hbHl0aWNzL05ld0VkaXRvcnMvQ2FtcGFpZ25zLzIwMTlfVGhhbmtZb3UvZGF0YS8nCnNldHdkKGRhdGFEaXIpCgojIyMgLS0tIGRldGVybWluZSBjZXREYXkKbGlicmFyeShsdWJyaWRhdGUpCmNldERheSA8LSBTeXMudGltZSgpCmNldERheQphdHRyKGNldERheSwgInR6b25lIikgPC0gIkV1cm9wZS9CZXJsaW4iCiMgLSBvbmUgZGF5IGJlaGluZCBmb3IgY3JvbnRhYgojIC0gKGkuZS4gd2FpdGluZyBmb3Igd21mLndlYnJlcXVlc3QgdG8gY29tcGxldGUgaXMgZGF0YSBhY3F1aXNpdGlvbikKY2V0RGF5IDwtIHltZCgKICBzdHJzcGxpdChhcy5jaGFyYWN0ZXIoY2V0RGF5KSwgCiAgICAgICAgICAgc3BsaXQgPSAiICIsIAogICAgICAgICAgIGZpeGVkID0gVClbWzFdXVsxXQogICkgLSAxCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBDb2xsZWN0IEJhbm5lciBJbXByZXNzaW9uIERhdGEKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIC0gZnVuY3Rpb246IHdtZGVfY29sbGVjdF9iYW5uZXJfaW1wcmVzc2lvbnMKd21kZV9jb2xsZWN0X2Jhbm5lcl9pbXByZXNzaW9ucyA8LSBmdW5jdGlvbih1cmlfaG9zdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXJpX3BhdGgsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVyaV9xdWVyeSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2V0RGF5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1ZXJ5RmlsZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlTmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyKSB7CiAgCiAgIyAtIE5PVEU6CiAgIyAtIGV4cGVjdGVkIGZvcm1hdCBmb3IgY2V0RGF5IGlzOiBZWVlZLU1NLURECiAgCiAgIyAtIHRvIGRhdGFEaXIKICBzZXR3ZChkYXRhRGlyKQogIAogICMgLSBsaWJyYXJpZXMKICBsaWJyYXJ5KHN0cmluZ3IpCiAgCiAgIyAtIFdIRVJFIGNvbmRpdGlvbjogY3JlYXRlIGRhdGV0aW1lX2NvbmRpdGlvbgogIGNldF9jb25kaXRpb24gPC0gc2VxKAogICAgZnJvbSA9IGFzLlBPU0lYY3QocGFzdGUwKGNldERheSwiIDA6MDAiKSwgdHogPSAiRXVyb3BlL0JlcmxpbiIpLAogICAgdG8gPSBhcy5QT1NJWGN0KHBhc3RlMChjZXREYXksIiAyMzowMCIpLCB0eiA9ICJFdXJvcGUvQmVybGluIiksCiAgICBieSA9ICJob3VyIgogICkgCiAgYXR0cihjZXRfY29uZGl0aW9uLCAidHpvbmUiKSA8LSAiVVRDIgogIGNldF9jb25kaXRpb24gPC0gYXMuY2hhcmFjdGVyKGNldF9jb25kaXRpb24pCiAgY2V0X2NvbmRpdGlvbiA8LSB1bmxpc3Qoc3RyX2V4dHJhY3RfYWxsKGNldF9jb25kaXRpb24sICJeKFtbOmRpZ2l0Ol1dfFxcc3wtKSoiKSkKICBjZXRfeWVhcnMgPC0gc2FwcGx5KAogICAgc3Ryc3BsaXQoY2V0X2NvbmRpdGlvbiwgc3BsaXQgPSAiICIsIGZpeGVkID0gVCksIGZ1bmN0aW9uKHgpIHsKICAgICAgc3Ryc3BsaXQoeCwgc3BsaXQgPSAiLSIpW1sxXV1bMV0KICAgIH0pCiAgY2V0X21vbnRocyA8LSBzYXBwbHkoCiAgICBzdHJzcGxpdChjZXRfY29uZGl0aW9uLCBzcGxpdCA9ICIgIiwgZml4ZWQgPSBUKSwgZnVuY3Rpb24oeCkgewogICAgICBzdHJzcGxpdCh4LCBzcGxpdCA9ICItIilbWzFdXVsyXQogICAgfSkKICBjZXRfbW9udGhzIDwtIGdzdWIoIl4wIiwgIiIsIGNldF9tb250aHMpCiAgY2V0X2RheXMgPC0gc2FwcGx5KAogICAgc3Ryc3BsaXQoY2V0X2NvbmRpdGlvbiwgc3BsaXQgPSAiICIsIGZpeGVkID0gVCksIGZ1bmN0aW9uKHgpIHsKICAgICAgc3Ryc3BsaXQoeCwgc3BsaXQgPSAiLSIpW1sxXV1bM10KICAgIH0pCiAgY2V0X2RheXMgPC0gZ3N1YigiXjAiLCAiIiwgY2V0X2RheXMpCiAgY2V0X2hvdXJzIDwtIHNhcHBseShzdHJzcGxpdChjZXRfY29uZGl0aW9uLCBzcGxpdCA9ICIgIiwgZml4ZWQgPSBUKSwgCiAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHhbMl0KICAgICAgICAgICAgICAgICAgICAgIH0pCiAgY2V0X2hvdXJzIDwtIGdzdWIoIl4wIiwgIiIsIGNldF9ob3VycykKICBkYXRldGltZUNvbmRpdGlvbiA8LSBwYXN0ZTAoCiAgICAieWVhciA9ICIsIGNldF95ZWFycywgIiBBTkQgIiwKICAgICJtb250aCA9ICIsIGNldF9tb250aHMsICIgQU5EICIsCiAgICAiZGF5ID0gIiwgY2V0X2RheXMsICIgQU5EICIsIAogICAgImhvdXIgPSAiLCBjZXRfaG91cnMKICApCiAgZGF0ZXRpbWVDb25kaXRpb24gPC0gcGFzdGUoIigiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRldGltZUNvbmRpdGlvbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIiBPUiAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiIikKICAKICAjIC0gV0hFUkUgY29uZGl0aW9uOiBjcmVhdGUgdXJpX3BhdGhfY29uZGl0aW9uCiAgaWYgKGxlbmd0aCh1cmlfcGF0aCkgPiAxKSB7CiAgICB1cmlfcGF0aF9jb25kaXRpb24gPC0gcGFzdGUwKCIoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJ1cmlfcGF0aCA9ICciLCB1cmlfcGF0aCwgIiciKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9ICIgT1IgIiwgc2VwID0gIiAiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICB9IGVsc2UgewogICAgdXJpX3BhdGhfY29uZGl0aW9uID0gcGFzdGUwKCJ1cmlfcGF0aCA9ICciLCB1cmlfcGF0aCwgIiciKQogIH0KICAKICAjIC0gV0hFUkUgY29uZGl0aW9uOiBjcmVhdGUgdXJpX2hvc3RfY29uZGl0aW9uCiAgaWYgKGxlbmd0aCh1cmlfaG9zdCkgPiAxKSB7CiAgICB1cmlfaG9zdF9jb25kaXRpb24gPC0gcGFzdGUwKCIoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJ1cmlfaG9zdCA9ICciLCB1cmlfaG9zdCwgIiciKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9ICIgT1IgIiwgc2VwID0gIiAiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiCiAgICApCiAgfSBlbHNlIHsKICAgIHVyaV9ob3N0X2NvbmRpdGlvbiA9IHBhc3RlMCgidXJpX2hvc3QgPSAnIiwgdXJpX2hvc3QsICInIikKICB9CiAgCiAgIyAtIFdIRVJFIGNvbmRpdGlvbjogY3JlYXRlIHVyaV9xdWVyeV9jb25kaXRpb24KICBpZiAobGVuZ3RoKHVyaV9xdWVyeSkgPiAxKSB7CiAgICB1cmlfcXVlcnlfY29uZGl0aW9uIDwtIHBhc3RlMCgiKCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMCgidXJpX3F1ZXJ5IExJS0UgJyUiLCB1cmlfcXVlcnksICIlJyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIiBPUiAiLCBzZXAgPSAiICIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiKSIKICAgICkKICB9IGVsc2UgewogICAgdXJpX3F1ZXJ5X2NvbmRpdGlvbiA9IHBhc3RlMCgidXJpX3F1ZXJ5IExJS0UgJyUiLCB1cmlfcXVlcnksICIlJyIpCiAgfQogIAogICMgLSBjb21wb3NlIEhpdmVRTCBxdWVyeQogIGhpdmVRdWVyeSA8LSBwYXN0ZTAoCiAgICAiVVNFIHdtZjsKICAgIFNFTEVDVCB1cmlfcXVlcnkgRlJPTSB3ZWJyZXF1ZXN0CiAgICBXSEVSRSAoIiwKICAgIHVyaV9ob3N0X2NvbmRpdGlvbiwgIiBBTkQgIiwKICAgIHVyaV9wYXRoX2NvbmRpdGlvbiwgIiBBTkQgIiwKICAgIHVyaV9xdWVyeV9jb25kaXRpb24sICIgQU5EICIsCiAgICAiKCIsIGRhdGV0aW1lQ29uZGl0aW9uLCAiKSIsCiAgICAiKTsiCiAgKQogIAogICMgLSB3cml0ZSBocWwKICB3cml0ZShoaXZlUXVlcnksIHF1ZXJ5RmlsZSkKICAjIC0gZXhlY3V0ZSBocWwgc2NyaXB0OgogIGhpdmVBcmdzIDwtICcvdXNyL2xvY2FsL2Jpbi9iZWVsaW5lIC1mJwogIGhpdmVJbnB1dCA8LSBwYXN0ZTAocXVlcnlGaWxlLCAnID4gJywgcGFzdGUwKGRhdGFEaXIsIGZpbGVOYW1lKSkKICAjIC0gY29tbWFuZDoKICBoaXZlQ29tbWFuZCA8LSBwYXN0ZShoaXZlQXJncywgaGl2ZUlucHV0KQogIHJldHVybigKICAgIHN5c3RlbShjb21tYW5kID0gaGl2ZUNvbW1hbmQsIHdhaXQgPSBUUlVFKSkKfQoKIyAtIHNldCBwYXJhbXMgdG8gd21kZV9jb2xsZWN0X2Jhbm5lcl9pbXByZXNzaW9ucwojIC0gZm9yIHRoZSBUaGFuayBZb3UgMjkxOQp1cmlfaG9zdCA8LSBjKCdkZS53aWtpcGVkaWEub3JnJywgJ2RlLm0ud2lraXBlZGlhLm9yZycpCnVyaV9wYXRoICA8LSAnL2JlYWNvbi9pbXByZXNzaW9uJwp1cmlfcXVlcnkgPC0gYygnV01ERV8yMDE5X3RoeCcKICAgICAgICAgICAgICAgKQpxdWVyeUZpbGUgPC0gJ3RoYW5reW91MjAxOV9CYW5uZXJJbXByZXNzaW9ucy5ocWwnCmZpbGVOYW1lIDwtIHBhc3RlMCgiYmFubmVySW1wcmVzc2lvbnNfIiwgY2V0RGF5LCAiLnRzdiIpCgojIC0gY29sbGVjdCBCYW5uZXIgSW1wcmVzc2lvbiBkYXRhCndtZGVfY29sbGVjdF9iYW5uZXJfaW1wcmVzc2lvbnModXJpX2hvc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXJpX3BhdGgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXJpX3F1ZXJ5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNldERheSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWVyeUZpbGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsZU5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YURpcikKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFdyYW5nbGUgQmFubmVyIEltcHJlc3Npb24gRGF0YQojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMgLSBmdW5jdGlvbjogd21kZV9wcm9jZXNzX2Jhbm5lcl9pbXByZXNzaW9ucwp3bWRlX3Byb2Nlc3NfYmFubmVyX2ltcHJlc3Npb25zIDwtIGZ1bmN0aW9uKGZpbGVOYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFEaXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNldERheSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FtcGFpZ25OYW1lKSB7CiAgCiAgIyAtIHRvIGRhdGFEaXIKICBzZXR3ZChkYXRhRGlyKQogIAogICMgLSBsaWJyYXJpZXMKICBsaWJyYXJ5KHN0cmluZ3IpCiAgbGlicmFyeShkcGx5cikKICAKICAjIC0gbG9hZAogIGJhbm5lckRhdGEgPC0gcmVhZC5kZWxpbShmaWxlTmFtZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIlx0IikKICBjb2xuYW1lcyhiYW5uZXJEYXRhKSA8LSAndXJpX3F1ZXJ5JwogIAogICMgLSBjbGVhbgogIHdJbmZvIDwtIHdoaWNoKGdyZXBsKCJjYW1wYWlnbj0iLCBiYW5uZXJEYXRhJHVyaV9xdWVyeSkpCiAgYmFubmVyRGF0YSA8LSBiYW5uZXJEYXRhW3dJbmZvLCBdCiAgCiAgIyAtIHNwbGl0CiAgYmFubmVyRGF0YSA8LSBzdHJzcGxpdChiYW5uZXJEYXRhLCBzcGxpdCA9ICImIiwgZml4ZWQgPSBUKQogICMgLSBleHRyYWN0IHJlbGV2YW50IGZpZWxkcwogICMgLSBiYW5uZXI6CiAgYmFubmVyIDwtIHNhcHBseShiYW5uZXJEYXRhLCBmdW5jdGlvbih4KSB7CiAgICB4W3doaWNoKGdyZXBsKCJeYmFubmVyPSIsIHgpKV0KICB9KQogIGJhbm5lciA8LSBnc3ViKCJeYmFubmVyPSIsICIiLCBiYW5uZXIpCiAgIyAtIHJlY29yZEltcHJlc3Npb25TYW1wbGVSYXRlOgogIHJlY29yZEltcHJlc3Npb25TYW1wbGVSYXRlIDwtIHNhcHBseShiYW5uZXJEYXRhLCBmdW5jdGlvbih4KSB7CiAgICB4W3doaWNoKGdyZXBsKCJecmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGU9IiwgeCkpXQogIH0pCiAgcmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGUgPC0gYXMubnVtZXJpYygKICAgIGdzdWIoIl5yZWNvcmRJbXByZXNzaW9uU2FtcGxlUmF0ZT0iLCAiIiwgcmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGUpCiAgICApCiAgIyAtIHJlc3VsdDoKICByZXN1bHQgPC0gc2FwcGx5KGJhbm5lckRhdGEsIGZ1bmN0aW9uKHgpIHsKICAgIHhbd2hpY2goZ3JlcGwoIl5yZXN1bHQ9IiwgeCkpXQogIH0pCiAgcmVzdWx0IDwtIGdzdWIoIl5yZXN1bHQ9IiwgIiIsIHJlc3VsdCkKICAKICAjIC0gY29tcG9zZSB0YWJsZToKICBiYW5uZXJPYnNlcnZhdGlvbnMgPC0gZGF0YS5mcmFtZShiYW5uZXIgPSBiYW5uZXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlY29yZEltcHJlc3Npb25TYW1wbGVSYXRlID0gcmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdCA9IHJlc3VsdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCiAgCiAgIyAtIGZpbHRlciBmb3IgcmVzdWx0PXNob3cKICBiYW5uZXJPYnNlcnZhdGlvbnMgPC0gZHBseXI6OmZpbHRlcihiYW5uZXJPYnNlcnZhdGlvbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzdWx0ID09ICJzaG93IikKICAKICAjIC0gY29ycmVjdGlvbiBmb3IgcmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGUKICBiYW5uZXJPYnNlcnZhdGlvbnMkcmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGUgPC0gCiAgICAxL2Jhbm5lck9ic2VydmF0aW9ucyRyZWNvcmRJbXByZXNzaW9uU2FtcGxlUmF0ZQogIAogICMgLSBhZ2dyZWdhdGU6CiAgYmFubmVyT2JzZXJ2YXRpb25zIDwtIGJhbm5lck9ic2VydmF0aW9ucyAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KGJhbm5lciwgcmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGUpICU+JSAKICAgIGRwbHlyOjpncm91cF9ieShiYW5uZXIpICU+JSAKICAgIGRwbHlyOjpzdW1tYXJpc2UoaW1wcmVzc2lvbnMgPSBzdW0ocmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGUpKQogIAogICMgLSBhZGQgY2V0RGF5LCBtZQogIGJhbm5lck9ic2VydmF0aW9ucyRkYXRlIDwtIGNldERheQogIGJhbm5lck9ic2VydmF0aW9ucyRjYW1wYWlnbiA8LSBjYW1wYWlnbk5hbWUKICAKICAjIC0gc3RvcmU6CiAgd3JpdGUuY3N2KGJhbm5lck9ic2VydmF0aW9ucywgCiAgICAgICAgICAgIHBhc3RlMCgiYmFubmVySW1wcmVzc2lvbnNBZ2dyZWdhdGVkXyIsCiAgICAgICAgICAgICAgICAgICBzdHJzcGxpdCgKICAgICAgICAgICAgICAgICAgICAgc3Ryc3BsaXQoZmlsZU5hbWUsIHNwbGl0ID0gIl8iLCBmaXhlZCA9IFQpW1sxXV1bMl0sCiAgICAgICAgICAgICAgICAgICAgIHNwbGl0ID0gIi4iLCAKICAgICAgICAgICAgICAgICAgICAgZml4ZWQgPSBUKVtbMV1dWzFdLAogICAgICAgICAgICAgICAgICAgIi5jc3YiCiAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICkKICAKfQoKIyAtIHdyYW5nbGUgQmFubmVyIEltcHJlc3Npb24gZGF0YQpjYW1wYWlnbk5hbWUgPC0gInRoYW5reW91MjAxOSIKd21kZV9wcm9jZXNzX2Jhbm5lcl9pbXByZXNzaW9ucyhmaWxlTmFtZSA9IGZpbGVOYW1lLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyID0gZGF0YURpciwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2V0RGF5ID0gY2V0RGF5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbXBhaWduTmFtZSA9IGNhbXBhaWduTmFtZSkKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIENvbGxlY3QgUGFnZXZpZXdzCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyAtIGZ1bmN0aW9uOiB3bWRlX2NvbGxlY3RfcGFnZXZpZXdzCndtZGVfY29sbGVjdF9wYWdldmlld3MgPC0gZnVuY3Rpb24odXJpX2hvc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXJpX3BhdGgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2V0RGF5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1ZXJ5RmlsZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlTmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyKSB7CiAgCiAgIyAtIE5PVEU6CiAgIyAtIGV4cGVjdGVkIGZvcm1hdCBmb3IgY2V0RGF5IGlzOiBZWVlZLU1NLURECiAgCiAgIyAtIHRvIGRhdGFEaXIKICBzZXR3ZChkYXRhRGlyKQogIAogICMgLSBsaWJyYXJpZXMKICBsaWJyYXJ5KHN0cmluZ3IpCiAgCiAgIyAtIFdIRVJFIGNvbmRpdGlvbjogY3JlYXRlIGRhdGV0aW1lX2NvbmRpdGlvbgogIGNldF9jb25kaXRpb24gPC0gc2VxKAogICAgZnJvbSA9IGFzLlBPU0lYY3QocGFzdGUwKGNldERheSwiIDA6MDAiKSwgdHogPSAiRXVyb3BlL0JlcmxpbiIpLAogICAgdG8gPSBhcy5QT1NJWGN0KHBhc3RlMChjZXREYXksIiAyMzowMCIpLCB0eiA9ICJFdXJvcGUvQmVybGluIiksCiAgICBieSA9ICJob3VyIgogICkgCiAgYXR0cihjZXRfY29uZGl0aW9uLCAidHpvbmUiKSA8LSAiVVRDIgogIGNldF9jb25kaXRpb24gPC0gYXMuY2hhcmFjdGVyKGNldF9jb25kaXRpb24pCiAgY2V0X2NvbmRpdGlvbiA8LSB1bmxpc3Qoc3RyX2V4dHJhY3RfYWxsKGNldF9jb25kaXRpb24sICJeKFtbOmRpZ2l0Ol1dfFxcc3wtKSoiKSkKICBjZXRfeWVhcnMgPC0gc2FwcGx5KAogICAgc3Ryc3BsaXQoY2V0X2NvbmRpdGlvbiwgc3BsaXQgPSAiICIsIGZpeGVkID0gVCksIGZ1bmN0aW9uKHgpIHsKICAgICAgc3Ryc3BsaXQoeCwgc3BsaXQgPSAiLSIpW1sxXV1bMV0KICAgIH0pCiAgY2V0X21vbnRocyA8LSBzYXBwbHkoCiAgICBzdHJzcGxpdChjZXRfY29uZGl0aW9uLCBzcGxpdCA9ICIgIiwgZml4ZWQgPSBUKSwgZnVuY3Rpb24oeCkgewogICAgICBzdHJzcGxpdCh4LCBzcGxpdCA9ICItIilbWzFdXVsyXQogICAgfSkKICBjZXRfbW9udGhzIDwtIGdzdWIoIl4wIiwgIiIsIGNldF9tb250aHMpCiAgY2V0X2RheXMgPC0gc2FwcGx5KAogICAgc3Ryc3BsaXQoY2V0X2NvbmRpdGlvbiwgc3BsaXQgPSAiICIsIGZpeGVkID0gVCksIGZ1bmN0aW9uKHgpIHsKICAgICAgc3Ryc3BsaXQoeCwgc3BsaXQgPSAiLSIpW1sxXV1bM10KICAgIH0pCiAgY2V0X2RheXMgPC0gZ3N1YigiXjAiLCAiIiwgY2V0X2RheXMpCiAgY2V0X2hvdXJzIDwtIHNhcHBseShzdHJzcGxpdChjZXRfY29uZGl0aW9uLCBzcGxpdCA9ICIgIiwgZml4ZWQgPSBUKSwgCiAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHhbMl0KICAgICAgICAgICAgICAgICAgICAgIH0pCiAgY2V0X2hvdXJzIDwtIGdzdWIoIl4wIiwgIiIsIGNldF9ob3VycykKICBkYXRldGltZUNvbmRpdGlvbiA8LSBwYXN0ZTAoCiAgICAieWVhciA9ICIsIGNldF95ZWFycywgIiBBTkQgIiwKICAgICJtb250aCA9ICIsIGNldF9tb250aHMsICIgQU5EICIsCiAgICAiZGF5ID0gIiwgY2V0X2RheXMsICIgQU5EICIsIAogICAgImhvdXIgPSAiLCBjZXRfaG91cnMKICApCiAgZGF0ZXRpbWVDb25kaXRpb24gPC0gcGFzdGUoIigiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRldGltZUNvbmRpdGlvbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIiBPUiAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiIikKICAKICAjIC0gV0hFUkUgY29uZGl0aW9uOiBjcmVhdGUgdXJpX3BhdGhfY29uZGl0aW9uCiAgaWYgKGxlbmd0aCh1cmlfcGF0aCkgPiAxKSB7CiAgICB1cmlfcGF0aF9jb25kaXRpb24gPC0gcGFzdGUwKCIoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJ1cmlfcGF0aCA9ICciLCB1cmlfcGF0aCwgIiciKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9ICIgT1IgIiwgc2VwID0gIiAiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikiCiAgICApCiAgfSBlbHNlIHsKICAgIHVyaV9wYXRoX2NvbmRpdGlvbiA9IHBhc3RlMCgidXJpX3BhdGggPSAnIiwgdXJpX3BhdGgsICInIikKICB9CiAgCiAgIyAtIFdIRVJFIGNvbmRpdGlvbjogY3JlYXRlIHVyaV9ob3N0X2NvbmRpdGlvbgogIGlmIChsZW5ndGgodXJpX2hvc3QpID4gMSkgewogICAgdXJpX2hvc3RfY29uZGl0aW9uIDwtIHBhc3RlMCgiKCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMCgidXJpX2hvc3QgPSAnIiwgdXJpX2hvc3QsICInIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sbGFwc2UgPSAiIE9SICIsIHNlcCA9ICIgIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIpIgogICAgKQogIH0gZWxzZSB7CiAgICB1cmlfaG9zdF9jb25kaXRpb24gPSBwYXN0ZTAoInVyaV9ob3N0ID0gJyIsIHVyaV9ob3N0LCAiJyIpCiAgfQogIAogICMgLSBjb21wb3NlIEhpdmVRTCBxdWVyeQogIGhpdmVRdWVyeSA8LSBwYXN0ZTAoCiAgICAiVVNFIHdtZjsKICAgIFNFTEVDVCB1cmlfcGF0aCwgdXJpX3F1ZXJ5LCByZWZlcmVyIEZST00gd2VicmVxdWVzdAogICAgV0hFUkUgKCIsCiAgICB1cmlfaG9zdF9jb25kaXRpb24sICIgQU5EICIsCiAgICB1cmlfcGF0aF9jb25kaXRpb24sICIgQU5EICIsCiAgICAiKCIsIGRhdGV0aW1lQ29uZGl0aW9uLCAiKSIsCiAgICAiKTsiCiAgICApCiAgCiAgIyAtIHdyaXRlIGhxbAogIHdyaXRlKGhpdmVRdWVyeSwgcXVlcnlGaWxlKQogICMgLSBleGVjdXRlIGhxbCBzY3JpcHQ6CiAgaGl2ZUFyZ3MgPC0gJy91c3IvbG9jYWwvYmluL2JlZWxpbmUgLWYnCiAgaGl2ZUlucHV0IDwtIHBhc3RlMChxdWVyeUZpbGUsICcgPiAnLCBmaWxlTmFtZSkKICAjIC0gY29tbWFuZDoKICBoaXZlQ29tbWFuZCA8LSBwYXN0ZShoaXZlQXJncywgaGl2ZUlucHV0KQogIHJldHVybigKICAgIHN5c3RlbShjb21tYW5kID0gaGl2ZUNvbW1hbmQsIHdhaXQgPSBUUlVFKSkKfQoKIyAtIHNldCBwYXJhbXMgdG8gd21kZV9jb2xsZWN0X3BhZ2V2aWV3cwojIC0gZm9yIHRoZSBUaGFuayBZb3UgMjkxOQp1cmlfaG9zdCA8LSBjKCdkZS53aWtpcGVkaWEub3JnJywgJ2RlLm0ud2lraXBlZGlhLm9yZycpCnVyaV9wYXRoICA8LSBjKAogICcvd2lraS9XaWtpcGVkaWE6V2lraW1lZGlhX0RldXRzY2hsYW5kL0xlcm5lV2lraXBlZGlhJykKcXVlcnlGaWxlIDwtICd0aGFua3lvdTIwMTlfUGFnZXZpZXdzLmhxbCcKZmlsZU5hbWUgPC0gcGFzdGUwKCJwYWdldmlld3NfIiwgY2V0RGF5LCAiLnRzdiIpCgojIC0gY29sbGVjdCBQYWdldmlld3MgZGF0YQp3bWRlX2NvbGxlY3RfcGFnZXZpZXdzKHVyaV9ob3N0LAogICAgICAgICAgICAgICAgICAgICAgIHVyaV9wYXRoLAogICAgICAgICAgICAgICAgICAgICAgIGNldERheSwKICAgICAgICAgICAgICAgICAgICAgICBxdWVyeUZpbGUsCiAgICAgICAgICAgICAgICAgICAgICAgZmlsZU5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YURpcikKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFdyYW5nbGUgUGFnZXZpZXdzCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyAtIGZ1bmN0aW9uOiB3bWRlX3Byb2Nlc3NfcGFnZXZpZXdzCndtZGVfcHJvY2Vzc19wYWdldmlld3MgPC0gZnVuY3Rpb24oZmlsZU5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YURpciwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXJpX3F1ZXJ5X2ZpbHRlciwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2V0RGF5ID0gY2V0RGF5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbXBhaWduTmFtZSA9IGNhbXBhaWduTmFtZSkgewogIAogICMgLSB0byBkYXRhRGlyCiAgc2V0d2QoZGF0YURpcikKICAKICAjIC0gbGlicmFyaWVzCiAgbGlicmFyeShzdHJpbmdyKQogIGxpYnJhcnkoZHBseXIpCiAgbGlicmFyeSh0aWR5cikKICBsaWJyYXJ5KGRhdGEudGFibGUpCgogICMgLSBsb2FkCiAgcGFnZXZpZXdzRGF0YSA8LSByZWFkTGluZXMoZmlsZU5hbWUpCiAgd1N0YXJ0IDwtIHdoaWNoKGdyZXBsKCJedXJpX3BhdGgiLCBwYWdldmlld3NEYXRhKSkKICBwYWdldmlld3NEYXRhIDwtIHBhZ2V2aWV3c0RhdGFbKHdTdGFydCArIDIpOihsZW5ndGgocGFnZXZpZXdzRGF0YSkgLSAyKV0KICBwYWdldmlld3NEYXRhIDwtIGRhdGEuZnJhbWUoZGF0ID0gcGFnZXZpZXdzRGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQogIHBhZ2V2aWV3c0RhdGEgPC0gc2VwYXJhdGUocGFnZXZpZXdzRGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGludG8gPSBjKCd1cmlfcGF0aCcsICd1cmlfcXVlcnknLCAncmVmZXJlcicpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIlx0IikKICAjIC0gYXBwbHkgdXJpX3F1ZXJ5X2ZpbHRlcgogICMgLSBOT1RFOiBBdXR1bW4gMjAxOCwgbG9va2luZyBpbiBib3RoOiB1cmlfcXVlcnksIHJlZmVyZXIKICB3X3VyaV9xdWVyeSA8LSB3aGljaChncmVwbCh1cmlfcXVlcnlfZmlsdGVyLCBwYWdldmlld3NEYXRhJHVyaV9xdWVyeSkpCiAgd191cmlfcXVlcnlfcmVmZXJlciA8LSB3aGljaChncmVwbCh1cmlfcXVlcnlfZmlsdGVyLCBwYWdldmlld3NEYXRhJHJlZmVyZXIpKQogIHdfdXJpX3F1ZXJ5IDwtIHVuaXF1ZShjKHdfdXJpX3F1ZXJ5LCB3X3VyaV9xdWVyeV9yZWZlcmVyKSkKICBwYWdldmlld3NEYXRhIDwtIHBhZ2V2aWV3c0RhdGFbd191cmlfcXVlcnksIF0KICB3X3VyaV9xdWVyeV9yZWZlcmVyIDwtIHdoaWNoKGdyZXBsKHVyaV9xdWVyeV9maWx0ZXIsIHBhZ2V2aWV3c0RhdGEkcmVmZXJlcikpCiAgd191cmlfcXVlcnlfcmVmZXJlcl9kZWxldGUgPC0gc2V0ZGlmZigxOmRpbShwYWdldmlld3NEYXRhKVsxXSwgd191cmlfcXVlcnlfcmVmZXJlcikKICBwYWdldmlld3NEYXRhJHJlZmVyZXJbd191cmlfcXVlcnlfcmVmZXJlcl9kZWxldGVdIDwtICcnCiAgIyAtIHdoZW4gdGhlcmUgaXMgbm8gdXJpX3F1ZXJ5LCB1c2UgdGhlIHF1ZXJ5IGZyb20gdGhlIHJlZmVyZXIgZmllbGQgaWYgcHJlc2VudCB0aGVyZQogIHBhZ2V2aWV3c0RhdGEkcmVmZXJlciA8LSBzdHJfZXh0cmFjdChwYWdldmlld3NEYXRhJHJlZmVyZXIsICJcXD9jYW1wYWlnbj0uKiQiKQogIHBhZ2V2aWV3c0RhdGEkcmVmZXJlcltpcy5uYShwYWdldmlld3NEYXRhJHJlZmVyZXIpXSA8LSAiIgogIHBhZ2V2aWV3c0RhdGEkcmVmZXJlciA8LSBnc3ViKCI/Y2FtcGFpZ249IiwgIiIsIHBhZ2V2aWV3c0RhdGEkcmVmZXJlciwgZml4ZWQgPSBUKQogIHBhZ2V2aWV3c0RhdGEkdXJpX3F1ZXJ5IDwtIGdzdWIoIj9jYW1wYWlnbj0iLCAiIiwgcGFnZXZpZXdzRGF0YSR1cmlfcXVlcnksIGZpeGVkID0gVCkKICBwYWdldmlld3NEYXRhJHVyaV9xdWVyeVtwYWdldmlld3NEYXRhJHVyaV9xdWVyeSA9PSAiIl0gPC0gCiAgICBwYWdldmlld3NEYXRhJHJlZmVyZXJbcGFnZXZpZXdzRGF0YSR1cmlfcXVlcnkgPT0gIiJdCiAgcGFnZXZpZXdzRGF0YSA8LSBkcGx5cjo6ZmlsdGVyKHBhZ2V2aWV3c0RhdGEsIAogICAgICAgICAgICAgICAgICAgICAgICAgIHVyaV9xdWVyeSAhPSAiIikKICBwYWdldmlld3NEYXRhJHJlZmVyZXIgPC0gTlVMTAogICMgLSBjbGVhbiB1cCBhIGJpdDoKICBwYWdldmlld3NEYXRhJHVyaV9xdWVyeSA8LSBnc3ViKCIvLiokIiwgIiIsIHBhZ2V2aWV3c0RhdGEkdXJpX3F1ZXJ5KQogIAogICMgLSBhZ2dyZWdhdGU6CiAgcGFnZXZpZXdzRGF0YSA8LSBwYWdldmlld3NEYXRhICU+JSAKICAgIGRwbHlyOjpncm91cF9ieSh1cmlfcXVlcnksIHVyaV9wYXRoKSAlPiUgCiAgICBkcGx5cjo6c3VtbWFyaXNlKHBhZ2V2aWV3cyA9IG4oKSkKICBjb2xuYW1lcyhwYWdldmlld3NEYXRhKSA8LSBjKCdUYWcnLCAnUGFnZScsICdQYWdldmlld3MnKQogIAogICMgLSBhZGQgY2V0RGF5LCBjYW1wYWlnbk5hbWUKICBwYWdldmlld3NEYXRhJGRhdGUgPC0gY2V0RGF5CiAgcGFnZXZpZXdzRGF0YSRjYW1wYWlnbiA8LSBjYW1wYWlnbk5hbWUKCiAgIyAtIHN0b3JlOgogIHdyaXRlLmNzdihwYWdldmlld3NEYXRhLCAKICAgICAgICAgICAgcGFzdGUwKCJwYWdldmlld3NBZ2dyZWdhdGVkXyIsCiAgICAgICAgICAgICAgICAgICBzdHJzcGxpdCgKICAgICAgICAgICAgICAgICAgICAgc3Ryc3BsaXQoZmlsZU5hbWUsIHNwbGl0ID0gIl8iLCBmaXhlZCA9IFQpW1sxXV1bMl0sCiAgICAgICAgICAgICAgICAgICAgIHNwbGl0ID0gIi4iLCAKICAgICAgICAgICAgICAgICAgICAgZml4ZWQgPSBUKVtbMV1dWzFdLAogICAgICAgICAgICAgICAgICAgIi5jc3YiCiAgICAgICAgICAgICkKICApCiAgCn0KCiMgLSBzZXQgcGFyYW1zIHRvIHdtZGVfcHJvY2Vzc19wYWdldmlld3MKIyAtIGZvciB0aGUgVGhhbmsgWW91IDI5MTkKdXJpX3F1ZXJ5X2ZpbHRlciA8LSAnV01ERV8yMDE5X3RoeCcKCiMgLSB3cmFuZ2xlIHBhZ2V2aWV3cwp3bWRlX3Byb2Nlc3NfcGFnZXZpZXdzKGZpbGVOYW1lID0gZmlsZU5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YURpciA9IGRhdGFEaXIsCiAgICAgICAgICAgICAgICAgICAgICAgdXJpX3F1ZXJ5X2ZpbHRlciA9IHVyaV9xdWVyeV9maWx0ZXIsIAogICAgICAgICAgICAgICAgICAgICAgIGNldERheSA9IGNldERheSwKICAgICAgICAgICAgICAgICAgICAgICBjYW1wYWlnbk5hbWUgPSBjYW1wYWlnbk5hbWUpIAoKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIENvbGxlY3QgVXNlciBSZWdpc3RyYXRpb25zCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyAtIGZ1bmN0aW9uOiB3bWRlX2NvbGxlY3RfcmVnaXN0cmF0aW9ucwp3bWRlX2NvbGxlY3RfcmVnaXN0cmF0aW9ucyA8LSBmdW5jdGlvbihsb2dTY2hlbWEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWJfaG9zdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnRfY2FtcGFpZ24sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZXREYXksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFEaXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlTmFtZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbXBhaWduTmFtZSkgewogIAogICMgLSBXSEVSRSBjb25kaXRpb246IGNyZWF0ZSBzdGFydF90aW1lc3RhbXAsIHN0b3BfdGltZXN0YW1wCiAgY2V0X2NvbmRpdGlvbiA8LSBzZXEoCiAgICBmcm9tID0gYXMuUE9TSVhjdChwYXN0ZTAoY2V0RGF5LCIgMDowMCIpLCB0eiA9ICJFdXJvcGUvQmVybGluIiksCiAgICB0byA9IGFzLlBPU0lYY3QocGFzdGUwKGNldERheSwiIDI0OjAwIiksIHR6ID0gIkV1cm9wZS9CZXJsaW4iKSwKICAgIGJ5ID0gImhvdXIiCiAgKSAKICBhdHRyKGNldF9jb25kaXRpb24sICJ0em9uZSIpIDwtICJVVEMiCiAgY2V0X2NvbmRpdGlvbiA8LSBhcy5jaGFyYWN0ZXIoY2V0X2NvbmRpdGlvbikKICBzdGFydF90aW1lc3RhbXAgPC0gcGFzdGUoCiAgICB1bmxpc3Qoc3RyX2V4dHJhY3RfYWxsKGNldF9jb25kaXRpb25bMV0sCiAgICAgICAgICAgICAgICAgICAgIltbOmRpZ2l0Ol1dIikpLAogICAgY29sbGFwc2UgPSAiIikKICBzdG9wX3RpbWVzdGFtcCA8LSBwYXN0ZSgKICAgIHVubGlzdChzdHJfZXh0cmFjdF9hbGwodGFpbChjZXRfY29uZGl0aW9uLCAxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIltbOmRpZ2l0Ol1dIikpLAogICAgY29sbGFwc2UgPSAiIikKICAKICAjIC0gV0hFUkUgY29uZGl0aW9uOiBjcmVhdGUgZXZlbnRfY2FtcGFpZ25fY29uZGl0aW9uCiAgaWYgKGxlbmd0aChldmVudF9jYW1wYWlnbikgPiAxKSB7CiAgICBldmVudF9jYW1wYWlnbl9jb25kaXRpb24gPC0gcGFzdGUwKCIoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJldmVudF9jYW1wYWlnbiBMSUtFICclIiwgZXZlbnRfY2FtcGFpZ24sICIlJyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIiBPUiAiLCBzZXAgPSAiICIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiKSIKICAgICkKICB9IGVsc2UgewogICAgZXZlbnRfY2FtcGFpZ25fY29uZGl0aW9uID0gcGFzdGUwKCJldmVudF9jYW1wYWlnbiBMSUtFICclIiwgZXZlbnRfY2FtcGFpZ24sICIlJyIpCiAgfQogIAoKICAjIC0gY29tcG9zZSBTUUwgcXVlcnk6CiAgc3FsUGFyYW1zIDwtICdteXNxbCAtLWRlZmF1bHRzLWZpbGU9L2V0Yy9teXNxbC9jb25mLmQvYW5hbHl0aWNzLXJlc2VhcmNoLWNsaWVudC5jbmYgLWggYW5hbHl0aWNzLXNsYXZlLmVxaWFkLndtbmV0IC1BIC1lJwogIHF1ZXJ5IDwtIHBhc3RlMCgKICAgICJcIlNFTEVDVCAqIEZST00gIiwgCiAgICBwYXN0ZTAoImxvZy4iLCBsb2dTY2hlbWEpLCAKICAgICIgV0hFUkUgKCh3ZWJIb3N0ID0gJyIsIAogICAgd2ViX2hvc3QsIAogICAgIicpIEFORCAodGltZXN0YW1wID4gIiwgCiAgICBzdGFydF90aW1lc3RhbXAsIAogICAgIikgQU5EICh0aW1lc3RhbXAgPD0gIiwgCiAgICBzdG9wX3RpbWVzdGFtcCwgCiAgICAiKSBBTkQgKCIsIAogICAgZXZlbnRfY2FtcGFpZ25fY29uZGl0aW9uLAogICAgIikpO1wiIikKICBzcWxPdXRwdXQgPC0gcGFzdGUwKCI+ICIsIHBhc3RlMChkYXRhRGlyLCAiLyIsIGZpbGVOYW1lKSkKICAgIAogICMgLSBydW4gY29tbWFuZAogIHFDb21tYW5kIDwtIHBhc3RlKHNxbFBhcmFtcywgcXVlcnksIHNxbE91dHB1dCwgc2VwID0gIiAiKQogIHN5c3RlbShjb21tYW5kID0gcUNvbW1hbmQsIHdhaXQgPSBUUlVFKQogIAogICMgLSB0byBkYXRhRGlyCiAgc2V0d2QoZGF0YURpcikKICAKICAjIC0gbGlicmFyaWVzCiAgbGlicmFyeShzdHJpbmdyKQogIGxpYnJhcnkoZHBseXIpCiAgbGlicmFyeSh0aWR5cikKICBsaWJyYXJ5KGRhdGEudGFibGUpCiAgCiAgIyAtIGxvYWQKICB1c2VyUmVnIDwtIGZyZWFkKGZpbGVOYW1lLCBzZXAgPSAiXHQiKQogIAogICMgLSBmaWx0ZXIgYm90cwogIHdCb3QgPC0gd2hpY2goZ3JlcGwoIlwiaXNfYm90XCI6IHRydWUiLCB1c2VyUmVnJHVzZXJBZ2VudCkpCiAgaWYgKGxlbmd0aCh3Qm90KSA+IDApIHsKICAgIHVzZXJSZWcgPC0gdXNlclJlZ1std0JvdCwgXQogIH0KICAKICAjIC0gc2VsZWN0IGZpZWxkcwogIHVzZXJSZWcgPC0gdXNlclJlZyAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KGV2ZW50X3VzZXJJZCwgCiAgICAgICAgICAgICAgICAgIGV2ZW50X3VzZXJOYW1lLCAKICAgICAgICAgICAgICAgICAgZXZlbnRfaXNTZWxmTWFkZSwgCiAgICAgICAgICAgICAgICAgIGV2ZW50X2NhbXBhaWduLCAKICAgICAgICAgICAgICAgICAgdGltZXN0YW1wKQogIAogICMgLSBhZGQgY2V0RGF5LCBjYW1wYWlnbk5hbWUKICB1c2VyUmVnJGRhdGUgPC0gY2V0RGF5CiAgdXNlclJlZyRjYW1wYWlnbiA8LSBjYW1wYWlnbk5hbWUKICAKICAjIC0gc3RvcmU6CiAgd3JpdGUuY3N2KHVzZXJSZWcsIAogICAgICAgICAgICBwYXN0ZTAoCiAgICAgICAgICAgICAgc3Ryc3BsaXQoZmlsZU5hbWUsIHNwbGl0ID0gIi4iLCBmaXhlZCA9IFQpW1sxXV1bMV0sCiAgICAgICAgICAgICIuY3N2IikKICApCiAgCiAgIyAtIHJlbW92ZSB0ZW1wIC50c3YgZmlsZQogIGZpbGUucmVtb3ZlKGZpbGVOYW1lKQogIAp9CgojIC0gc2V0IHBhcmFtcyBmb3I6IHdtZGVfY29sbGVjdF9yZWdpc3RyYXRpb25zCmxvZ1NjaGVtYSA8LSAnU2VydmVyU2lkZUFjY291bnRDcmVhdGlvbl8xNzcxOTIzNycgCndlYl9ob3N0IDwtICdkZS53aWtpcGVkaWEub3JnJwpldmVudF9jYW1wYWlnbiA8LSAnV01ERV8yMDE5X3RoeCcKZmlsZU5hbWUgPC0gcGFzdGUwKCJ1c2VyUmVnaXN0cmF0aW9uc18iLCBjZXREYXksICIudHN2IikKCiMgLSBjb2xsZWN0IHVzZXIgcmVnaXN0cmF0aW9ucwp3bWRlX2NvbGxlY3RfcmVnaXN0cmF0aW9ucyhsb2dTY2hlbWEgPSBsb2dTY2hlbWEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlYl9ob3N0ID0gd2ViX2hvc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50X2NhbXBhaWduID0gZXZlbnRfY2FtcGFpZ24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNldERheSA9IGNldERheSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YURpciA9IGRhdGFEaXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVOYW1lID0gZmlsZU5hbWUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBjYW1wYWlnbk5hbWUgPSBjYW1wYWlnbk5hbWUpCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBXcmFuZ2xlIFVzZXIgUmVnaXN0cmF0aW9ucwojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMgLSBmdW5jdGlvbjogd21kZV9wcm9jZXNzX3JlZ2lzdHJhdGlvbnMKd21kZV9wcm9jZXNzX3JlZ2lzdHJhdGlvbnMgPC0gZnVuY3Rpb24oZmlsZU5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFEaXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZXREYXksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW1wYWlnbk5hbWUpIHsKICAKICAjIC0gdG8gZGF0YURpcgogIHNldHdkKGRhdGFEaXIpCiAgCiAgIyAtIGxpYnJhcmllcwogIGxpYnJhcnkoZHBseXIpCiAgbGlicmFyeShkYXRhLnRhYmxlKQoKICAjIC0gbG9hZAogIHVzZXJSZWcgPC0gZnJlYWQoZmlsZU5hbWUpCiAgCiAgIyAtIGFncmVnYXRlCiAgdXNlclJlZyA8LSB1c2VyUmVnICU+JSAKICAgIGRwbHlyOjpzZWxlY3QoZXZlbnRfY2FtcGFpZ24pICU+JSAKICAgIGRwbHlyOjpncm91cF9ieShldmVudF9jYW1wYWlnbikgJT4lIAogICAgZHBseXI6OnN1bW1hcmlzZShSZWdpc3RyYXRpb25zID0gbigpKQoKICAjIC0gYWRkIGNldERheSwgY2FtcGFpZ25OYW1lCiAgdXNlclJlZyRkYXRlIDwtIGNldERheQogIHVzZXJSZWckY2FtcGFpZ24gPC0gY2FtcGFpZ25OYW1lCiAgCiAgIyAtIHN0b3JlOgogIHdyaXRlLmNzdih1c2VyUmVnLCAKICAgICAgICAgICAgcGFzdGUwKCd1c2VyUmVnaXN0cmF0aW9uc0FnZ3JlYWd0ZWRfJywgY2V0RGF5LCAiLmNzdiIpCiAgKQogIAp9CgojIC0gc2V0IHBhcmFtcyBmb3I6IHdtZGVfcHJvY2Vzc19yZWdpc3RyYXRpb25zCmZpbGVOYW1lIDwtIHBhc3RlMCgidXNlclJlZ2lzdHJhdGlvbnNfIiwgY2V0RGF5LCAiLmNzdiIpCgojIC0gd3JhbmdsZSB1c2VyIHJlZ2lzdHJhdGlvbnM6CndtZGVfcHJvY2Vzc19yZWdpc3RyYXRpb25zKGZpbGVOYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyLAogICAgICAgICAgICAgICAgICAgICAgICAgICBjZXREYXksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbXBhaWduTmFtZSkKCiMjIyAtLS0gQ29sbGVjdCBOZXdzbGV0dGVyIHJlZ2lzdHJhdGlvbnMgLSBmb3IgMjAxOF9BdUJDIGV4Y2wuCiMgLSBTZXJ2ZXJTaWRlQWNjb3VudENyZWF0aW9uXzE3NzE5MjM3IHNjaGVtYQpxQ29tbWFuZCA8LSAibXlzcWwgLS1kZWZhdWx0cy1maWxlPS9ldGMvbXlzcWwvY29uZi5kL2FuYWx5dGljcy1yZXNlYXJjaC1jbGllbnQuY25mIC1oIGFuYWx5dGljcy1zbGF2ZS5lcWlhZC53bW5ldCAtQSAtZSBcInNlbGVjdCAqIGZyb20gbG9nLlNlcnZlclNpZGVBY2NvdW50Q3JlYXRpb25fMTc3MTkyMzcgd2hlcmUgKCh3ZWJIb3N0ID0gJ2RlLndpa2lwZWRpYS5vcmcnKSBhbmQgKHRpbWVzdGFtcCA+PSAyMDE4MTAxMTAwMDAwMCkgYW5kIChldmVudF9jYW1wYWlnbiBsaWtlICclV01ERV9uZXdlZGl0b3JzX2F1dHVtbl8yMDE4X2xwbiUnKSk7XCIgPiAvaG9tZS9nb3JhbnNtL1JTY3JpcHRzL05ld0VkaXRvcnMvMjAxOF9BdXR1bW5CYW5uZXJDYW1wYWlnbi9fZGF0YS9OZXdzbGV0dGVyX3VzZXJSZWdpc3RyYXRpb25zLnRzdiIKc3lzdGVtKGNvbW1hbmQgPSBxQ29tbWFuZCwgd2FpdCA9IFRSVUUpCiMjIyAtLS0gV3JhbmdsZSBOZXdzbGV0dGVyIHJlZ2lzdHJhdGlvbnMgLSBmb3IgMjAxOF9BdUJDIGV4Y2wuCiMgLSBmdW5jdGlvbjogd21kZV9wcm9jZXNzX3JlZ2lzdHJhdGlvbnNfZ2VuZXJhbAp3bWRlX3Byb2Nlc3NfcmVnaXN0cmF0aW9uc19nZW5lcmFsIDwtIGZ1bmN0aW9uKGZpbGVOYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhRGlyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2V0RGF5LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FtcGFpZ25OYW1lLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0RmlsZU5hbWUpIHsKICAKICAjIC0gdG8gZGF0YURpcgogIHNldHdkKGRhdGFEaXIpCiAgCiAgIyAtIGxpYnJhcmllcwogIGxpYnJhcnkoZHBseXIpCiAgbGlicmFyeShkYXRhLnRhYmxlKQogIAogICMgLSBsb2FkCiAgdXNlclJlZyA8LSBmcmVhZChmaWxlTmFtZSkKICAKICAjIC0gYWdyZWdhdGUKICB1c2VyUmVnIDwtIHVzZXJSZWcgJT4lIAogICAgZHBseXI6OnNlbGVjdChldmVudF9jYW1wYWlnbikgJT4lIAogICAgZHBseXI6Omdyb3VwX2J5KGV2ZW50X2NhbXBhaWduKSAlPiUgCiAgICBkcGx5cjo6c3VtbWFyaXNlKFJlZ2lzdHJhdGlvbnMgPSBuKCkpCiAgCiAgIyAtIGFkZCBjZXREYXksIGNhbXBhaWduTmFtZQogIHVzZXJSZWckZGF0ZSA8LSBjZXREYXkKICB1c2VyUmVnJGNhbXBhaWduIDwtIGNhbXBhaWduTmFtZQogIAogICMgLSBzdG9yZToKICB3cml0ZS5jc3YodXNlclJlZywgCiAgICAgICAgICAgIG91dEZpbGVOYW1lCiAgKQogIAp9CgpgYGAKCiMjIyAwLjIgRGF0YSBBZ2dyZWdhdGlvbgoKKipOT1RFOioqIE5vdCBydW4gZnJvbSB0aGlzIHJlcG9ydDsgdGhlIGRhdGEgd2VyZSBhbHJlYWR5IHByZS1wcm9jZXNzZWQgYW5kIGFnZ3JlZ2F0ZWQgYnkgdGhlIGZvbGxvd2luZyBgUmAgc2NyaXB0IGJlZm9yZSBiZWluZyBzdWJtaXR0ZWQgdG8gYW5hbHl0aWNhbCBwcm9jZWR1cmVzOgoKYGBge3IsIGVjaG8gPSBULCBldmFsID0gRn0KIyMjIC0tLSBSZXBvcnQgR2VuZXJhdGlvbiBmb3IgdGhlIFRoYW5rIFlvdSAyOTE5CiMjIyAtLS0gcnVuIGxvY2FsbHkKCiMjIyAtLS0gdG8gZGF0YSBkaXJlY3RvcnkKZGF0YURpciA8LSAKICAnL2hvbWUvZ29yYW5zbS9Xb3JrL19fX0RhdGFLb2xla3Rpdi9Qcm9qZWN0cy9XaWtpbWVkaWFERVUvX1dNREVfUHJvamVjdHMvX21pc2MvTmV3RWRpdG9yc19UZWFtLzIwMTlfVGhhbmtZb3UvX2RhdGEvJwphbmFseXRpY3NEaXIgPC0gCiAgJy9ob21lL2dvcmFuc20vV29yay9fX19EYXRhS29sZWt0aXYvUHJvamVjdHMvV2lraW1lZGlhREVVL19XTURFX1Byb2plY3RzL19taXNjL05ld0VkaXRvcnNfVGVhbS8yMDE5X1RoYW5rWW91L19hbmFseXRpY3MvJwpzZXR3ZChhbmFseXRpY3NEaXIpCgojIyMgLS0tIFJlcG9ydCBCYW5uZXIgSW1wcmVzc2lvbiBEYXRhCgojIC0gZnVuY3Rpb246IHdtZGVfcmVwb3J0X2Jhbm5lcl9pbXByZXNzaW9ucwp3bWRlX3JlcG9ydF9iYW5uZXJfaW1wcmVzc2lvbnMgPC0gZnVuY3Rpb24oZGF0YURpcikgewogIAogICMgLSBTZXR1cAogIGxpYnJhcnkoZGF0YS50YWJsZSkKICBsaWJyYXJ5KGRwbHlyKQoKICAjIC0gbGlzdCBmaWxlczoKICBsRiA8LSBsaXN0LmZpbGVzKGRhdGFEaXIpCiAgCiAgIyAtIGZpbHRlciBhZ2dyZWdhdGVkIGJhbm5lciBpbXByZXNzaW9uIGRhdGEKICBsRiA8LSBsRltncmVwbCgiYmFubmVySW1wcmVzc2lvbnNBZ2dyZWdhdGVkXyIsIGxGLCBmaXhlZCA9IFQpXQogIAogICMgLSBsb2FkIGZpbGVzIGFuZCBtZXJnZQogIGJhbm5lckRhdGEgPC0gdmVjdG9yKG1vZGUgPSAibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChsRikpCiAgZm9yIChpIGluIDE6bGVuZ3RoKGxGKSkgewogICAgaWYgKGdyZXBsKCJjc3YkfHRzdiQiLCBsRltpXSkpIHsKICAgICAgYmFubmVyRGF0YVtbaV1dIDwtIGZyZWFkKHBhc3RlMChkYXRhRGlyLCBsRltpXSkpCiAgICB9IGVsc2UgewogICAgICBiYW5uZXJEYXRhW1tpXV0gPC0gTlVMTAogICAgfQogIH0KICBiYW5uZXJEYXRhIDwtIHJiaW5kbGlzdChiYW5uZXJEYXRhKQogIGJhbm5lckRhdGEkVjEgPC0gTlVMTAogIAogICMgLSBhZ2dyZWdhdGVzCiAgcGVyQmFubmVyVG90YWxzIDwtIGJhbm5lckRhdGEgJT4lIAogICAgc2VsZWN0KGJhbm5lciwgaW1wcmVzc2lvbnMpICU+JSAKICAgIGdyb3VwX2J5KGJhbm5lcikgJT4lIAogICAgc3VtbWFyaXNlKHRvdGFsSW1wcmVzc2lvbnMgPSBzdW0oaW1wcmVzc2lvbnMpKQogIHBlckRheVRvdGFscyA8LSBiYW5uZXJEYXRhICU+JSAKICAgIHNlbGVjdChkYXRlLCBpbXByZXNzaW9ucykgJT4lIAogICAgZ3JvdXBfYnkoZGF0ZSkgJT4lIAogICAgc3VtbWFyaXNlKHRvdGFsSW1wcmVzc2lvbnMgPSBzdW0oaW1wcmVzc2lvbnMpKQogIAogICMgLSBvdXRwdXQKICByZXR1cm4oCiAgICBsaXN0KGJhbm5lckltcHJlc3Npb25zUmVwb3J0ID0gYmFubmVyRGF0YSwgCiAgICAgICAgIHBlckJhbm5lclRvdGFscyA9IHBlckJhbm5lclRvdGFscywgCiAgICAgICAgIHBlckRheVRvdGFscyA9IHBlckRheVRvdGFscykKICApCiAgICAKfQoKIyAtIFJlcG9ydCBiYW5uZXIgaW1wcmVzc2lvbnMKYmFubmVySW1wcmVzc2lvbnNEYXRhIDwtIHdtZGVfcmVwb3J0X2Jhbm5lcl9pbXByZXNzaW9ucyhkYXRhRGlyKQpiYW5uZXJJbXByZXNzaW9uc0ZpbGUgPC0gYmFubmVySW1wcmVzc2lvbnNEYXRhJGJhbm5lckltcHJlc3Npb25zUmVwb3J0CndyaXRlLmNzdihiYW5uZXJJbXByZXNzaW9uc0ZpbGUsICJiYW5uZXJJbXByZXNzaW9uc0ZpbGUuY3N2IikKYmFubmVyVG90YWxzIDwtIGJhbm5lckltcHJlc3Npb25zRGF0YSRwZXJCYW5uZXJUb3RhbHMKd3JpdGUuY3N2KGJhbm5lclRvdGFscywgImJhbm5lclRvdGFscy5jc3YiKQpiYW5uZXJEYXlUb3RhbHMgPC0gYmFubmVySW1wcmVzc2lvbnNEYXRhJHBlckRheVRvdGFscwp3cml0ZS5jc3YoYmFubmVyRGF5VG90YWxzLCAiYmFubmVyRGF5VG90YWxzLmNzdiIpCgoKIyMjIC0tLSBSZXBvcnQgUGFnZXZpZXdzIERhdGEKCiMgLSBmdW5jdGlvbjogd21kZV9yZXBvcnRfcGFnZXZpZXdzCndtZGVfcmVwb3J0X3BhZ2V2aWV3cyA8LSBmdW5jdGlvbihkYXRhRGlyKSB7CiAgCiAgIyAtIFNldHVwCiAgbGlicmFyeShkYXRhLnRhYmxlKQogIGxpYnJhcnkoZHBseXIpCiAgCiAgIyAtIGxpc3QgZmlsZXM6CiAgbEYgPC0gbGlzdC5maWxlcyhkYXRhRGlyKQogIAogICMgLSBmaWx0ZXIgYWdncmVnYXRlZCBiYW5uZXIgaW1wcmVzc2lvbiBkYXRhCiAgbEYgPC0gbEZbZ3JlcGwoInBhZ2V2aWV3c0FnZ3JlZ2F0ZWRfIiwgbEYsIGZpeGVkID0gVCldCiAgCiAgIyAtIGxvYWQgZmlsZXMgYW5kIG1lcmdlCiAgcGFnZXZpZXdzRGF0YSA8LSB2ZWN0b3IobW9kZSA9ICJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKGxGKSkKICBmb3IgKGkgaW4gMTpsZW5ndGgobEYpKSB7CiAgICBpZiAoZ3JlcGwoImNzdiR8dHN2JCIsIGxGW2ldKSkgewogICAgICBwYWdldmlld3NEYXRhW1tpXV0gPC0gZnJlYWQocGFzdGUwKGRhdGFEaXIsIGxGW2ldKSkKICAgIH0gZWxzZSB7CiAgICAgIHBhZ2V2aWV3c0RhdGFbW2ldXSA8LSBOVUxMCiAgICB9CiAgfQogIHBhZ2V2aWV3c0RhdGEgPC0gcmJpbmRsaXN0KHBhZ2V2aWV3c0RhdGEpCiAgcGFnZXZpZXdzRGF0YSRWMSA8LSBOVUxMCiAgCiAgIyAtIGFnZ3JlZ2F0ZXMKICBwZXJEYXlUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSAlPiUgCiAgICBzZWxlY3QoZGF0ZSwgUGFnZXZpZXdzKSAlPiUgCiAgICBncm91cF9ieShkYXRlKSAlPiUgCiAgICBzdW1tYXJpc2UodG90YWxQYWdldmlld3MgPSBzdW0oUGFnZXZpZXdzKSkKICBwZXJUYWdUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSAlPiUgCiAgICBzZWxlY3QoVGFnLCBQYWdldmlld3MpICU+JSAKICAgIGdyb3VwX2J5KFRhZykgJT4lIAogICAgc3VtbWFyaXNlKHRvdGFsUGFnZXZpZXdzID0gc3VtKFBhZ2V2aWV3cykpCiAgcGVyUGFnZVRvdGFscyA8LSBwYWdldmlld3NEYXRhICU+JSAKICAgIHNlbGVjdChQYWdlLCBQYWdldmlld3MpICU+JSAKICAgIGdyb3VwX2J5KFBhZ2UpICU+JSAKICAgIHN1bW1hcmlzZSh0b3RhbFBhZ2V2aWV3cyA9IHN1bShQYWdldmlld3MpKQogIHBlclBhZ2VEYXlUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSAlPiUgCiAgICBzZWxlY3QoUGFnZSwgZGF0ZSwgUGFnZXZpZXdzKSAlPiUKICAgIGdyb3VwX2J5KFBhZ2UsIGRhdGUpICU+JSAKICAgIHN1bW1hcmlzZSh0b3RhbFBhZ2V2aWV3cyA9IHN1bShQYWdldmlld3MpKQogIHBlclRhZ0RheVRvdGFscyA8LSBwYWdldmlld3NEYXRhICU+JSAKICAgIHNlbGVjdChUYWcsIGRhdGUsIFBhZ2V2aWV3cykgJT4lCiAgICBncm91cF9ieShUYWcsIGRhdGUpICU+JSAKICAgIHN1bW1hcmlzZSh0b3RhbFBhZ2V2aWV3cyA9IHN1bShQYWdldmlld3MpKQogIHBlclRhZ1BhZ2VUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSAlPiUgCiAgICBzZWxlY3QoVGFnLCBQYWdlLCBQYWdldmlld3MpICU+JQogICAgZ3JvdXBfYnkoVGFnLCBQYWdlKSAlPiUgCiAgICBzdW1tYXJpc2UodG90YWxQYWdldmlld3MgPSBzdW0oUGFnZXZpZXdzKSkKICAgIAogICMgLSBvdXRwdXQKICByZXR1cm4oCiAgICBsaXN0KHBhZ2V2aWV3c0RhdGFSZXBvcnQgPSBwYWdldmlld3NEYXRhLCAKICAgICAgICAgcGVyRGF5VG90YWxzID0gcGVyRGF5VG90YWxzLCAKICAgICAgICAgcGVyVGFnVG90YWxzID0gcGVyVGFnVG90YWxzLCAKICAgICAgICAgcGVyUGFnZVRvdGFscyA9IHBlclBhZ2VUb3RhbHMsIAogICAgICAgICBwZXJQYWdlRGF5VG90YWxzID0gcGVyUGFnZURheVRvdGFscywgCiAgICAgICAgIHBlclRhZ0RheVRvdGFscyA9IHBlclRhZ0RheVRvdGFscywKICAgICAgICAgcGVyVGFnUGFnZVRvdGFscyA9IHBlclRhZ1BhZ2VUb3RhbHMpCiAgKQogIAp9CgojIC0gUmVwb3J0IHBhZ2V2aWV3czoKcGFnZXZpZXdzRGF0YSA8LSB3bWRlX3JlcG9ydF9wYWdldmlld3MoZGF0YURpcikKcGFnZXZpZXdzUmVwb3J0RmlsZSA8LSBwYWdldmlld3NEYXRhJHBhZ2V2aWV3c0RhdGFSZXBvcnQKd3JpdGUuY3N2KHBhZ2V2aWV3c1JlcG9ydEZpbGUsICJwYWdldmlld3NSZXBvcnRGaWxlLmNzdiIpCnBhZ2V2aWV3c19wZXJEYXlUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSRwZXJEYXlUb3RhbHMKd3JpdGUuY3N2KHBhZ2V2aWV3c19wZXJEYXlUb3RhbHMsICJwYWdldmlld3NfcGVyRGF5VG90YWxzLmNzdiIpCnBhZ2V2aWV3c19wZXJUYWdUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSRwZXJUYWdUb3RhbHMKd3JpdGUuY3N2KHBhZ2V2aWV3c19wZXJUYWdUb3RhbHMsICJwYWdldmlld3NfcGVyVGFnVG90YWxzLmNzdiIpCnBhZ2V2aWV3c19wZXJQYWdlVG90YWxzIDwtIHBhZ2V2aWV3c0RhdGEkcGVyUGFnZVRvdGFscwp3cml0ZS5jc3YocGFnZXZpZXdzX3BlclBhZ2VUb3RhbHMsICJwYWdldmlld3NfcGVyUGFnZVRvdGFscy5jc3YiKQpwYWdldmlld3NfcGVyUGFnZURheVRvdGFscyA8LSBwYWdldmlld3NEYXRhJHBlclBhZ2VEYXlUb3RhbHMKd3JpdGUuY3N2KHBhZ2V2aWV3c19wZXJQYWdlRGF5VG90YWxzLCAicGFnZXZpZXdzX3BlclBhZ2VEYXlUb3RhbHMuY3N2IikKcGFnZXZpZXdzX3BlclRhZ0RheVRvdGFscyA8LSBwYWdldmlld3NEYXRhJHBlclRhZ0RheVRvdGFscwp3cml0ZS5jc3YocGFnZXZpZXdzX3BlclRhZ0RheVRvdGFscywgInBhZ2V2aWV3c19wZXJUYWdEYXlUb3RhbHMuY3N2IikKcGFnZXZpZXdzX3BlclRhZ1BhZ2VUb3RhbHMgPC0gcGFnZXZpZXdzRGF0YSRwZXJUYWdQYWdlVG90YWxzCndyaXRlLmNzdihwYWdldmlld3NfcGVyVGFnUGFnZVRvdGFscywgInBlclRhZ1BhZ2VUb3RhbHMuY3N2IikKCiMjIyAtLS0gUmVwb3J0IFVzZXIgUmVnaXN0cmF0aW9ucwoKIyAtIGZ1bmN0aW9uOiB3bWRlX3JlcG9ydF9yZWdpc3RyYXRpb25zCndtZGVfcmVwb3J0X3JlZ2lzdHJhdGlvbnMgPC0gZnVuY3Rpb24oZGF0YURpcikgewogIAogICMgLSBTZXR1cAogIGxpYnJhcnkoZGF0YS50YWJsZSkKICBsaWJyYXJ5KGRwbHlyKQogIAogICMgLSBsaXN0IGZpbGVzOgogIGxGIDwtIGxpc3QuZmlsZXMoZGF0YURpcikKICAKICAjIC0gZmlsdGVyIGFnZ3JlZ2F0ZWQgdXNlciByZWdpc3RyYXRpb24gZGF0YQogIGxGIDwtIGxGW2dyZXBsKCJ1c2VyUmVnaXN0cmF0aW9uc0FnZ3JlYWd0ZWRfIiwgbEYsIGZpeGVkID0gVCldCiAgCiAgIyAtIGxvYWQgZmlsZXMgYW5kIG1lcmdlCiAgcmVnaXN0cmF0aW9uRGF0YSA8LSB2ZWN0b3IobW9kZSA9ICJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKGxGKSkKICBmb3IgKGkgaW4gMTpsZW5ndGgobEYpKSB7CiAgICBpZiAoZ3JlcGwoImNzdiR8dHN2JCIsIGxGW2ldKSkgewogICAgICByZWdpc3RyYXRpb25EYXRhW1tpXV0gPC0gZnJlYWQocGFzdGUwKGRhdGFEaXIsIGxGW2ldKSkKICAgIH0gZWxzZSB7CiAgICAgIHJlZ2lzdHJhdGlvbkRhdGFbW2ldXSA8LSBOVUxMCiAgICB9CiAgfQogIHJlZ2lzdHJhdGlvbkRhdGEgPC0gcmJpbmRsaXN0KHJlZ2lzdHJhdGlvbkRhdGEpCiAgcmVnaXN0cmF0aW9uRGF0YSRWMSA8LSBOVUxMCiAgCiAgIyAtIGFnZ3JlZ2F0ZXMKICBwZXJEYXlUb3RhbHMgPC0gcmVnaXN0cmF0aW9uRGF0YSAlPiUgCiAgICBzZWxlY3QoZGF0ZSwgUmVnaXN0cmF0aW9ucykgJT4lIAogICAgZ3JvdXBfYnkoZGF0ZSkgJT4lIAogICAgc3VtbWFyaXNlKHRvdGFsUmVnaXN0cmF0aW9ucyA9IHN1bShSZWdpc3RyYXRpb25zKSkKICBwZXJUYWdUb3RhbHMgPC0gcmVnaXN0cmF0aW9uRGF0YSAlPiUgCiAgICBzZWxlY3QoZXZlbnRfY2FtcGFpZ24sIFJlZ2lzdHJhdGlvbnMpICU+JSAKICAgIGdyb3VwX2J5KGV2ZW50X2NhbXBhaWduKSAlPiUgCiAgICBzdW1tYXJpc2UodG90YWxSZWdpc3RyYXRpb25zID0gc3VtKFJlZ2lzdHJhdGlvbnMpKQogIHBlclRhZ0RheVRvdGFscyA8LSByZWdpc3RyYXRpb25EYXRhICU+JSAKICAgIHNlbGVjdChldmVudF9jYW1wYWlnbiwgZGF0ZSwgUmVnaXN0cmF0aW9ucykgJT4lIAogICAgZ3JvdXBfYnkoZXZlbnRfY2FtcGFpZ24sIGRhdGUpICU+JSAKICAgIHN1bW1hcmlzZSh0b3RhbFJlZ2lzdHJhdGlvbnMgPSBzdW0oUmVnaXN0cmF0aW9ucykpCiAgCiAgIyAtIG91dHB1dAogIHJldHVybigKICAgIGxpc3QocmVnaXN0cmF0aW9uc0RhdGFSZXBvcnQgPSByZWdpc3RyYXRpb25EYXRhLCAKICAgICAgICAgcGVyRGF5VG90YWxzID0gcGVyRGF5VG90YWxzLCAKICAgICAgICAgcGVyVGFnVG90YWxzID0gcGVyVGFnVG90YWxzLCAKICAgICAgICAgcGVyVGFnRGF5VG90YWxzID0gcGVyVGFnRGF5VG90YWxzKQogICkKICAKfQoKIyAtIFJlcG9ydCB1cG9uIHVzZXIgcmVnaXN0cmF0aW9ucwp1c2VyUmVnRGF0YSA8LSB3bWRlX3JlcG9ydF9yZWdpc3RyYXRpb25zKGRhdGFEaXIpCnVzZXJSZWdpc3RyYXRpb25zUmVwb3J0RmlsZSA8LSB1c2VyUmVnRGF0YSRyZWdpc3RyYXRpb25zRGF0YVJlcG9ydAp3cml0ZS5jc3YodXNlclJlZ2lzdHJhdGlvbnNSZXBvcnRGaWxlLCAidXNlclJlZ2lzdHJhdGlvbnNSZXBvcnRGaWxlLmNzdiIpCnVzZXJSZWdpc3RyYXRpb25zX3BlckRheVRvdGFscyA8LSB1c2VyUmVnRGF0YSRwZXJEYXlUb3RhbHMKd3JpdGUuY3N2KHVzZXJSZWdpc3RyYXRpb25zX3BlckRheVRvdGFscywgInVzZXJSZWdpc3RyYXRpb25zX3BlckRheVRvdGFscy5jc3YiKQp1c2VyUmVnaXN0cmF0aW9uc19wZXJUYWdUb3RhbHMgPC0gdXNlclJlZ0RhdGEkcGVyVGFnVG90YWxzCndyaXRlLmNzdih1c2VyUmVnaXN0cmF0aW9uc19wZXJUYWdUb3RhbHMsICJ1c2VyUmVnaXN0cmF0aW9uc19wZXJUYWdUb3RhbHMuY3N2IikKdXNlclJlZ2lzdHJhdGlvbnNfcGVyVGFnRGF5VG90YWxzIDwtIHVzZXJSZWdEYXRhJHBlclRhZ0RheVRvdGFscwp3cml0ZS5jc3YodXNlclJlZ2lzdHJhdGlvbnNfcGVyVGFnRGF5VG90YWxzLCAidXNlclJlZ2lzdHJhdGlvbnNfcGVyVGFnRGF5VG90YWxzLmNzdiIpCmBgYAoKIyMgMS4gQ2FtcGFpZ24gQmFubmVycyBhbmQgUGFnZXMKClRoaXMgc2VjdGlvbiBwcmVzZW50cyBhbGwgZGF0YSBhbmQgc3RhdGlzdGljcyBvbiB0aGUgY2FtcGFpZ24gYmFubmVycyBhbmQgcGFnZXMuCgojIyMgMS4xIEJhbm5lciBJbXByZXNzaW9ucwojIyMjIDEuMS4xIEJhbm5lciBJbXByZXNzaW9ucyBPdmVydmlldwoKKipDaGFydCAxLjEuMSoqIERhaWx5IEJhbm5lciBJbXByZXNzaW9ucwoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0V9CmRhdGFTZXQgPC0gcmVhZC5jc3YoCiAgJ19hbmFseXRpY3MvYmFubmVySW1wcmVzc2lvbnNGaWxlLmNzdicsCiAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLAogICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKIyAtIFZpc3VhbGl6ZSB3LiB7Z2dwbG90Mn0KZ2dwbG90KGRhdGFTZXQsIGFlcyh4ID0gZGF0ZSwKICAgICAgICAgICAgICAgICAgICB5ID0gaW1wcmVzc2lvbnMsCiAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBpbXByZXNzaW9ucywKICAgICAgICAgICAgICAgICAgICBncm91cCA9IGJhbm5lciwKICAgICAgICAgICAgICAgICAgICBjb2xvciA9IGJhbm5lciwKICAgICAgICAgICAgICAgICAgICBmaWxsID0gYmFubmVyCiAgICAgICAgICAgICAgICAgICAgKSkgKyAKICBnZW9tX3BhdGgoc2l6ZSA9IC41KSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDEuNSkgKwogIGZhY2V0X3dyYXAofmJhbm5lciwgbmNvbCA9IDIpICsgCiAgZ2d0aXRsZSgnVGhhbmsgWW91IDI5MTk6IEJhbm5lciBJbXByZXNzaW9ucycpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBzaXplID0gOCkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpCmBgYAoKIyMjIyAxLjEuMSBCYW5uZXIgSW1wcmVzc2lvbnMgT3ZlcnZpZXc6IFRhYmxlCgoqKlRhYmxlIDEuMS4xLioqIERhaWx5IEJhbm5lciBJbXByZXNzaW9ucwoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0V9CiMjIyAtLS0gRnVsbCBEYXRhc2V0IChUYWJsZSBSZXBvcnQpCmRhdGF0YWJsZShkYXRhU2V0KQpgYGAKCiMjIyMgMS4xLjIgVG90YWwgQmFubmVyIEltcHJlc3Npb25zCioqQ2hhcnQgMS4xLjIuKiogVG90YWwgQmFubmVyIEltcHJlc3Npb25zCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGQUxTRX0KZGF0YVNldCA8LSByZWFkLmNzdigKICAnX2FuYWx5dGljcy9iYW5uZXJUb3RhbHMuY3N2JywKICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULAogICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsCiAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpnZ3Bsb3QoZGF0YVNldCwgYWVzKHggPSBiYW5uZXIsIAogICAgICAgICAgICAgICAgICAgIHkgPSB0b3RhbEltcHJlc3Npb25zLCAKICAgICAgICAgICAgICAgICAgICBjb2xvciA9IGJhbm5lciwgCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGJhbm5lciwgCiAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSB0b3RhbEltcHJlc3Npb25zKSkgKyAKICBnZW9tX2Jhcih3aWR0aCA9IC41LCBzdGF0ID0gImlkZW50aXR5IikgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBnZ3RpdGxlKCdUaGFuayBZb3UgMjkxOTogQmFubmVyIEltcHJlc3Npb25zJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIGdlb21fbGFiZWxfcmVwZWwoc2l6ZSA9IDMuNSwgY29sb3IgPSAid2hpdGUiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4KSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCiMjIyMgMS4xLjMgQmFubmVyIEltcHJlc3Npb25zIHBlciBEYXkKCioqQ2hhcnQgMS4xLjMuKiogQmFubmVyIEltcHJlc3Npb25zIHBlciBEYXkKCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZBTFNFfQpkYXRhU2V0IDwtIHJlYWQuY3N2KAogICdfYW5hbHl0aWNzL2Jhbm5lckRheVRvdGFscy5jc3YnLAogICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSwKICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmdncGxvdChkYXRhU2V0LCBhZXMoeCA9IGRhdGUsIAogICAgICAgICAgICAgICAgICAgIHkgPSB0b3RhbEltcHJlc3Npb25zLCAKICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IHRvdGFsSW1wcmVzc2lvbnMpKSArCiAgZ2VvbV9wYXRoKHNpemUgPSAuMjUsIGdyb3VwID0gMSwgY29sb3IgPSAiZGFya2JsdWUiKSArCiAgZ2VvbV9wb2ludChzaXplID0gMS41LCBjb2xvciA9ICJkYXJrYmx1ZSIpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMSwgY29sb3IgPSAid2hpdGUiKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGdndGl0bGUoJ1RoYW5rIFlvdSAyOTE5OiBCYW5uZXIgSW1wcmVzc2lvbnMnKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgZ2VvbV9sYWJlbF9yZXBlbChzaXplID0gMy41LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4KSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQpgYGAKCiMjIyAxLjIgUGFnZXZpZXdzCgojIyMjIDEuMi4xIFBhZ2V2aWV3cyBPdmVydmlldwoKKipDaGFydCAxLjIuMS4qKiBQYWdldmlld3MgT3ZlcnZpZXcuIAoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0V9CmRhdGFTZXQgPC0gcmVhZC5jc3YoCiAgJ19hbmFseXRpY3MvcGFnZXZpZXdzX3BlclBhZ2VEYXlUb3RhbHMuY3N2JywKICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULAogICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsCiAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpkYXRhU2V0JFBhZ2UgPC0gZ3N1YigiL3dpa2kvV2lraXBlZGlhOldpa2lwZWRpYV98L3dpa2kvV2lraXBlZGlhOldpa2ltZWRpYV8iLCAiIiwgZGF0YVNldCRQYWdlKQojIC0gVmlzdWFsaXplIHcuIHtnZ3Bsb3QyfQpnZ3Bsb3QoZGF0YVNldCwgYWVzKHggPSBkYXRlLAogICAgICAgICAgICAgICAgICAgIHkgPSB0b3RhbFBhZ2V2aWV3cywKICAgICAgICAgICAgICAgICAgICBncm91cCA9IFBhZ2UsCiAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBQYWdlLAogICAgICAgICAgICAgICAgICAgIGZpbGwgPSBQYWdlLAogICAgICAgICAgICAgICAgICAgIGxhYmVsID0gdG90YWxQYWdldmlld3MsCiAgICAgICAgICAgICAgICAgICAgKSkgKyAKICBnZW9tX3BhdGgoc2l6ZSA9IC4yNSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAxLjUpICsKICBnZW9tX3BvaW50KHNpemUgPSAxLCBjb2xvciA9ICJ3aGl0ZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBnZ3RpdGxlKCdUaGFuayBZb3UgMjkxOTogUGFnZXZpZXdzJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIGdlb21fdGV4dF9yZXBlbChzaXplID0gMy41LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4KSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikKYGBgCgoKIyMjIyAxLjIuMSBQYWdldmlld3MgT3ZlcnZpZXc6IFRhYmxlCgoqKlRhYmxlIDEuMi4xLioqIFBhZ2V2aWV3cyBPdmVydmlldwoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0V9CiMjIyAtLS0gRnVsbCBEYXRhc2V0IChUYWJsZSBSZXBvcnQpCmRhdGF0YWJsZShkYXRhU2V0ICU+JSBhcnJhbmdlKGRlc2ModG90YWxQYWdldmlld3MpKSkKYGBgCgoKIyMgMi4gVXNlciBSZWdpc3RyYXRpb25zCgpBbGwgZGF0YSBvbiB1c2VyIHJlZ2lzdHJhdGlvbnMgYXJlIHByZXNlbnRlZCBpbiB0aGlzIHNlY3Rpb24uCgojIyMgMi4xIFJlZ2lzdHJhdGlvbnMgcGVyIHRhZyBhbmQgZGF5CgoqKkNoYXJ0IDIuMS4qKiBSZWdpc3RyYXRpb25zIHBlciB0YWcgYW5kIGRheS4gUGxlYXNlIG5vdGU6IHBvaW50cyB3aXRoIG5vIGRhdGEgbGFiZWxzIHNpZ25pZnkgMCB1c2VyIHJlZ2lzdHJhdGlvbnMuCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGQUxTRX0KIyAtIFN0YW5kYXJkIHJlZ2lzdHJhdGlvbnMKZGF0YVNldCA8LSByZWFkLmNzdigKICAnX2FuYWx5dGljcy91c2VyUmVnaXN0cmF0aW9uc1JlcG9ydEZpbGUuY3N2JywKICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULAogICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsCiAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpkYXRhU2V0IDwtIGRhdGFTZXQgJT4lIAogIGZpbHRlcighKGV2ZW50X2NhbXBhaWduICVpbiUgJ1dNREVfbmV3ZWRpdG9yc19hdXR1bW5fMjAxOF9scG4nKSkKZGF0YVNldCRjYW1wYWlnbiA8LSBOVUxMCmdncGxvdChkYXRhU2V0LCBhZXMoeCA9IGRhdGUsCiAgICAgICAgICAgICAgICAgICAgeSA9IFJlZ2lzdHJhdGlvbnMsCiAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBldmVudF9jYW1wYWlnbiwKICAgICAgICAgICAgICAgICAgICBjb2xvciA9IGV2ZW50X2NhbXBhaWduLAogICAgICAgICAgICAgICAgICAgIGZpbGwgPSBldmVudF9jYW1wYWlnbiwKICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IFJlZ2lzdHJhdGlvbnMsCiAgICAgICAgICAgICAgICAgICAgKSkgKyAKICBnZW9tX3BhdGgoc2l6ZSA9IC4yNSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAxLjUpICsKICBnZW9tX3BvaW50KHNpemUgPSAxLCBjb2xvciA9ICJ3aGl0ZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBnZ3RpdGxlKCdUaGFuayBZb3UgMjkxOTogUmVnaXN0cmF0aW9ucyBwZXIgVGFnJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIGdlb21fdGV4dF9yZXBlbChzaXplID0gMy41LCBzaG93LmxlZ2VuZCA9IEYpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQpgYGAKCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZBTFNFfQojIyMgLS0tIEZ1bGwgRGF0YXNldCAoVGFibGUgUmVwb3J0KQpjb2xuYW1lcyhkYXRhU2V0KVsxXSA8LSAnVGFnJwpkYXRhdGFibGUoZGF0YVNldCAlPiUgYXJyYW5nZShUYWcsIGRhdGUsIGRlc2MoUmVnaXN0cmF0aW9ucykpKQpgYGAKCiMjIDMuIFVzZXIgRWRpdHMKCiMjIyAzLjEgQ29sbGVjdCB1c2VyIGVkaXRzCgpUaGlzIGRhdGEgYWNxdWlzaXRpb24gY2hhbmsgaXMgbm90IHJlcHJvZHVjaWJsZSBmcm9tIHRoaXMgUmVwb3J0OyBydW5zIGluIHByb2R1Y3Rpb24gb24gYHN0YXQxMDA3YDoKCmBgYHtyLCBlY2hvID0gVCwgZXZhbCA9IEZ9CiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBDb2xsZWN0IGFsbCB1c2VyIGVkaXRzIHBlciBkYXkgZnJvbSBkZXdpa2kucmV2aXNpb24KIyMjIC0tLSBmcm9tIDIuIEphbnVhcnkgMjAxOSwgdy4gZXZlbnRfY2FtcGFpZ24KIyMjIC0tLSB1cGRhdGUgMTogb25lIHdlZWsgYWZ0ZXIgdGhlbiBlbmQ6IDIzdGggSmFudWFyeQojIyMgLS0tIHVwZGF0ZSAyOiB0d28gd2Vla3MgYWZ0ZXIgdGhlbiBlbmQ6IDMwdGggSmFudWFyeQojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCmxvY2FsRGlyIDwtICcvaG9tZS9nb3JhbnNtL0FuYWx5dGljcy9OZXdFZGl0b3JzL0NhbXBhaWducy8yMDE5X1RoYW5rWW91L2RhdGEvJwpzdGFydFRpbWVzdGFtcCA8LSAnMjAxOTAxMDIwMDAwMDAnICMgLSBjYW1wYWlnbiBiZWdpbnMKIyAtIFNRTCBxdWVyeQpzcWxRdWVyeSA8LSBwYXN0ZSgiXCJTRUxFQ1QgcmV2X3VzZXIsIFNVQlNUUihyZXZfdGltZXN0YW1wLCAxLCA4KSAKICAgICAgICAgICAgICAgICAgRlJPTSBkZXdpa2kucmV2aXNpb24gV0hFUkUgcmV2X3RpbWVzdGFtcCA+PSAiLCBzdGFydFRpbWVzdGFtcCwgIjtcIiIsIHNlcCA9ICIiKQoKIyMjIC0tLSBvdXRwdXQgZmlsZW5hbWUKZmlsZW5hbWUgPC0gcGFzdGUobG9jYWxEaXIsJ1JlZmVyZW5jZUVkaXRzX2Rld2lraV8yMDE5MDEyMycsICIudHN2Iiwgc2VwID0gIiIpCgojIyMgLS0tIGV4ZWN1dGUgc3FsIHNjcmlwdDoKc3FsQXJncyA8LSAnbXlzcWwgLS1kZWZhdWx0cy1maWxlPS9ldGMvbXlzcWwvY29uZi5kL2FuYWx5dGljcy1yZXNlYXJjaC1jbGllbnQuY25mIC1oIGFuYWx5dGljcy1zdG9yZS5lcWlhZC53bW5ldCAtQSAtZScKc3FsSW5wdXQgPC0gcGFzdGUoc3FsUXVlcnksCiAgICAgICAgICAgICAgICAgICIgPiAiLAogICAgICAgICAgICAgICAgICBmaWxlbmFtZSwKICAgICAgICAgICAgICAgICAgc2VwID0gIiIpCiMgLSBjb21tYW5kOgpzcWxDb21tYW5kIDwtIHBhc3RlKHNxbEFyZ3MsIHNxbElucHV0KQpzeXN0ZW0oY29tbWFuZCA9IHNxbENvbW1hbmQsIHdhaXQgPSBUUlVFKQpgYGAKClRoaXMgYW5hbHlzaXMgaXMgc2NoZWR1bGVkIGZvciBKYW51YXJ5IDI0dGggYW5kIDMwdGggW1BoYWJdKGh0dHBzOi8vcGhhYnJpY2F0b3Iud2lraW1lZGlhLm9yZy9UMjExNjkwIzQ4OTE1MjgpCgojIyMgMy4xIFVzZXIgZWRpdHM6IGRhaWx5CgpXcmFuZ2xlIHRoZSBkYXRhc2V0OyB2aXN1YWxpemUgY2FtcGFpZ24gZWRpdHMgcGVyIGRheS4KCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZBTFNFfQphbGxFZGl0cyA8LSAKICBmcmVhZCgKICAgICcvaG9tZS9nb3JhbnNtL1dvcmsvX19fRGF0YUtvbGVrdGl2L1Byb2plY3RzL1dpa2ltZWRpYURFVS9fV01ERV9Qcm9qZWN0cy9fbWlzYy9OZXdFZGl0b3JzX1RlYW0vMjAxOV9UaGFua1lvdS9fYW5hbHl0aWNzL1JlZmVyZW5jZUVkaXRzX2Rld2lraV8yMDE5MDEzMS50c3YnCiAgICApCnVzZXJSZWdzIDwtIHJlYWQuY3N2KAogICcvaG9tZS9nb3JhbnNtL1dvcmsvX19fRGF0YUtvbGVrdGl2L1Byb2plY3RzL1dpa2ltZWRpYURFVS9fV01ERV9Qcm9qZWN0cy9fbWlzYy9OZXdFZGl0b3JzX1RlYW0vMjAxOV9UaGFua1lvdS9fYW5hbHl0aWNzL2Z1bGxSZWdpc3RyYXRpb25EYXRhc2V0LmNzdicsCiAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLAogICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKIyAtIGtlZXAgb25seSBjYW1wYWlnbiByZWdpc3RlcmVkIHVzZXJzOgp1c2VyRWRpdHMgPC0gYWxsRWRpdHNbYWxsRWRpdHMkcmV2X3VzZXIgJWluJSB1bmlxdWUodXNlclJlZ3MkZXZlbnRfdXNlcklkKSwgXQpybSh1c2VyUmVncyk7IHJtKGFsbEVkaXRzKTsgZ2MoKQojIC0gd3JhbmdsZSB0aW1lc3RhbXAKY29sbmFtZXModXNlckVkaXRzKSA8LSBjKCd1c2VyX2lkJywgJ2RhdGUnKQp1c2VyRWRpdHMkZGF0ZSA8LSBzYXBwbHkodXNlckVkaXRzJGRhdGUsIGZ1bmN0aW9uKHgpIHsKICBwYXN0ZSgKICAgIHN1YnN0cih4LCAxLCA0KSwKICAgIHN1YnN0cih4LCA1LCA2KSwgCiAgICBzdWJzdHIoeCwgNywgOCksCiAgICBzZXAgPSAiLSIKICApCn0pCiMgLSBhZ2dyZWdhdGUgcGVyIGRheQp1c2VyRWRpdHNEYXkgPC0gdXNlckVkaXRzICU+JSAKICBncm91cF9ieShkYXRlKSAlPiUgCiAgc3VtbWFyaXNlKGVkaXRzID0gbigpKSAlPiUgCiAgYXJyYW5nZShkYXRlKQojIC0gaW5wdXQgemVybyBlZGl0cyBhdCBtaXNzaW5nIGRhdGVzCnVzZXJFZGl0c0RheUNvcHkgPC0gdXNlckVkaXRzRGF5CmVkaXREYXRlUmFuZ2UgPC0gdXNlckVkaXRzRGF5Q29weSRkYXRlCmVkaXREYXRlUmFuZ2UgPC0gYXMuUE9TSVhjdChlZGl0RGF0ZVJhbmdlKQplZGl0RGF0ZVJhbmdlIDwtIHNlcShtaW4oZWRpdERhdGVSYW5nZSksIG1heChlZGl0RGF0ZVJhbmdlKSwgYnkgPSAiZGF5IikKdXNlckVkaXRzRGF5Q29weSA8LSBkYXRhLmZyYW1lKGRhdGUgPSBhcy5jaGFyYWN0ZXIoZWRpdERhdGVSYW5nZSksIGVkaXRzID0gbnVtZXJpYyhsZW5ndGgoZWRpdERhdGVSYW5nZSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQp1c2VyRWRpdHNEYXlDb3B5IDwtIGxlZnRfam9pbih1c2VyRWRpdHNEYXlDb3B5LCB1c2VyRWRpdHNEYXksIGJ5ID0gImRhdGUiKQp1c2VyRWRpdHNEYXlDb3B5JGVkaXRzLnggPC0gTlVMTApjb2xuYW1lcyh1c2VyRWRpdHNEYXlDb3B5KSA8LSBjKCdkYXRlJywgJ2VkaXRzJykKdXNlckVkaXRzRGF5Q29weSRlZGl0c1tpcy5uYSh1c2VyRWRpdHNEYXlDb3B5JGVkaXRzKV0gPC0gMAp1c2VyRWRpdHNEYXkgPC11c2VyRWRpdHNEYXlDb3B5CiMgLSB2aXN1YWxpemUgZGFpbHkgZWRpdHMKIyAtIFZpc3VhbGl6ZSB3LiB7Z2dwbG90Mn0KZ2dwbG90KHVzZXJFZGl0c0RheSwgYWVzKHggPSBkYXRlLAogICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGVkaXRzLAogICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBlZGl0cwogICAgICAgICAgICAgICAgICAgICkpICsgCiAgZ2VvbV9wYXRoKHNpemUgPSAuNSwgY29sb3IgPSAnYmx1ZScsIGdyb3VwID0gMSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAxLjUsIGNvbG9yID0gJ2JsdWUnKSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDEuNSwgY29sb3IgPSAnd2hpdGUnKSArCiAgZ2VvbV90ZXh0X3JlcGVsKHNpemUgPSAzLjUpICsgCiAgZ2d0aXRsZSgnVGhhbmsgWW91IDI5MTk6IEVkaXRzIHBlciBkYXknKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQpgYGAKCiMjIyAzLjEgVXNlciBlZGl0czogZWRpdCBjbGFzc2VzCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGQUxTRX0KIyAtIEVkaXQgfCAxIHwgMi00IHwgNS05IHwgMTAtNDkgfCA+NTAKZWRpdEJvdW5kYXJpZXMgPC0gbGlzdCgKICBjKDAsIDEpLCAKICBjKDIsIDQpLAogIGMoNSwgOSksCiAgYygxMCwgNDkpCikKdXNlckVkaXRzX3VzZXIgPC0gdXNlckVkaXRzICU+JSAKICBncm91cF9ieSh1c2VyX2lkKSAlPiUgCiAgc3VtbWFyaXNlKGVkaXRzID0gbigpKQp1c2VyRWRpdHNfdXNlciRlZGl0Q2xhc3MgPC0gc2FwcGx5KHVzZXJFZGl0c191c2VyJGVkaXRzLCBmdW5jdGlvbih4KSB7CiAgd0VDIDwtIHNhcHBseShlZGl0Qm91bmRhcmllcywgZnVuY3Rpb24oeSkgewogICAgeCA+PSB5WzFdICYgeCA8PSB5WzJdCiAgfSkKICBpZiAoc3VtKHdFQykgPT0gMCkgewogICAgcmV0dXJuKCI+PSA1MCIpCiAgfSBlbHNlIHsKICAgIHJldHVybihwYXN0ZTAoIigiLAogICAgICAgICAgICAgICAgICBlZGl0Qm91bmRhcmllc1tbd2hpY2god0VDKV1dWzFdLAogICAgICAgICAgICAgICAgICAiIC0gIiwKICAgICAgICAgICAgICAgICAgZWRpdEJvdW5kYXJpZXNbW3doaWNoKHdFQyldXVsyXSwgCiAgICAgICAgICAgICAgICAgICIpIgogICAgICAgICAgICAgICAgICApCiAgICApCiAgfQp9KQplZGl0Q2xhc3MgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZSh1c2VyRWRpdHNfdXNlciRlZGl0Q2xhc3MpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmNvbG5hbWVzKGVkaXRDbGFzcykgPC0gYygnRWRpdCBDbGFzcycsICdOdW0uVXNlcnMnKQplZGl0Q2xhc3Mkb3JkZXIgPC0gYXMubnVtZXJpYyhzYXBwbHkoZWRpdENsYXNzJGBFZGl0IENsYXNzYCwgZnVuY3Rpb24oeCkgewogIGxvd2VyIDwtIHN0cl9leHRyYWN0KHgsICdbWzpkaWdpdDpdXSsnKQp9KSkKZWRpdENsYXNzIDwtIGFycmFuZ2UoZWRpdENsYXNzLCBvcmRlcikKZWRpdENsYXNzJG9yZGVyIDwtIE5VTEwKZGF0YXRhYmxlKGVkaXRDbGFzcykKYGBgCgojIyMgMy4yIFVzZXIgZWRpdHM6IHdoZW4gZG8gdGhlIGVkaXRzIGhhcHBlbj8gCgoqKkNoYXJ0IDMuMi4xKioKCioqUS4qKiBBbHNvIGl0IHdvdWxkIGJlIGludGVyZXN0aW5nIHRvIGtub3cgd2hlbiB0aGUgbmV3IHVzZXJzIGVkaXQ6IGlzIHRoZXJlIGEgcG9zc2liaWxpdHkgdG8gY2FsY3VsYXRlIGluIHdoaWNoIHRpbWUgaW50ZXJ2YWwgbmV3IHVzZXJzIGFyZSBhY3RpdmUsIGUuZy4gZGlyZWN0bHkgYWZ0ZXIgcmVnaXN0cmF0aW9uLCBvciBzb21lIGRheXMgbGF0ZXIgZXRjLj8gT24gdGhlIHgtYXhpcyB3b3VsZCBiZSB0aW1lIGFmdGVyIHJlZ2lzdHJhdGlvbiBpbiBkYXlzLCB3aXRoIDAgYmVpbmcgcG9pbnQgb2YgcmVnaXN0cmF0aW9uIGFuZCBvbiB0aGUgeS1heGlzIG51bWJlciBvZiBlZGl0cy4gVGhlIGRpYWdyYW0gd291bGQgdGhlbiBzaG93IGF2ZXJhZ2UgZWRpdHMgcGVyIHVzZXIgeC1kYXlzIGFmdGVyIHJlZ2lzdHJhdGlvbi4gQ291bGQgdGhpcyB3b3JrPyBbUGhhYl0oaHR0cHM6Ly9waGFicmljYXRvci53aWtpbWVkaWEub3JnL1QyMTE2OTAjNDkzNDAzMCkKCioqTm90ZS4qKiBUaGUgY2hhcnQgcHJlc2VudHMgdGhlIG1lYW4gbnVtYmVyIG9mIGVkaXRzIGNhbGN1bGF0ZWQgZnJvbSB0aGUgdG90YWwgbnVtYmVyIG9mIGVkaXRzIHBlciBkYXkgZGl2aWRlZCBieSB0aGUgbnVtYmVyIG9mIGFjdGl2ZSB1c2VycyAoaS5lLiBieSBob3cgbWFueSB1c2VycyBlZGl0ZWQpIG9uIHRoYXQgZGF5LgoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0V9CiMgLSBsb2FkIHVzZXIgcmVnaXN0cmF0aW9ucyBwZXIgZGF5LCBwZXIgdXNlcgp1c2VyUmVnaXN0cmF0aW9uc0RhaWx5IDwtIGZyZWFkKCJfYW5hbHl0aWNzL2Z1bGxSZWdpc3RyYXRpb25EYXRhc2V0LmNzdiIpCnVzZXJSZWdpc3RyYXRpb25zRGFpbHkgPC0gc2VsZWN0KHVzZXJSZWdpc3RyYXRpb25zRGFpbHksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldmVudF91c2VySWQsIGRhdGUpCmVkaXRzU2luY2VSZWcgPC0gdXNlclJlZ2lzdHJhdGlvbnNEYWlseSAKZWRpdHNTaW5jZVJlZyA8LSBsZWZ0X2pvaW4oZWRpdHNTaW5jZVJlZywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZXJFZGl0cywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gYygiZXZlbnRfdXNlcklkIiA9ICJ1c2VyX2lkIikpICU+JSAKICBhcnJhbmdlKGV2ZW50X3VzZXJJZCkKY29sbmFtZXMoZWRpdHNTaW5jZVJlZykgPC0gYygidXNlcl9pZCIsICJyZWdpc3RyYXRpb24iLCAiZWRpdCIpCmVkaXRzU2luY2VSZWcgPC0gZmlsdGVyKGVkaXRzU2luY2VSZWcsICFpcy5uYShlZGl0KSkKZWRpdHNTaW5jZVJlZyRyZWdpc3RyYXRpb24gPC0gYXMuUE9TSVhjdChlZGl0c1NpbmNlUmVnJHJlZ2lzdHJhdGlvbikKZWRpdHNTaW5jZVJlZyRlZGl0IDwtIGFzLlBPU0lYY3QoZWRpdHNTaW5jZVJlZyRlZGl0KQplZGl0c1NpbmNlUmVnJGRpZmYgPC0gKGVkaXRzU2luY2VSZWckZWRpdCAtIGVkaXRzU2luY2VSZWckcmVnaXN0cmF0aW9uKS8oNjBeMioyNCkKYXZnRWRpdHNTaW5jZVJlZyA8LSBlZGl0c1NpbmNlUmVnICU+JSAKICBzZWxlY3QoZGlmZiwgdXNlcl9pZCkgJT4lIAogIGdyb3VwX2J5KGRpZmYsIHVzZXJfaWQpICU+JSAKICBzdW1tYXJpc2UoZWRpdHMgPSBuKCkpICU+JSAKICBzZWxlY3QoZGlmZiwgZWRpdHMpICU+JSAKICBncm91cF9ieShkaWZmKSAlPiUgCiAgc3VtbWFyaXNlKG1lYW5FZGl0cyA9IG1lYW4oZWRpdHMpKQphdmdFZGl0c1NpbmNlUmVnJGRpZmYgPC0gYXMubnVtZXJpYyhhdmdFZGl0c1NpbmNlUmVnJGRpZmYpCmF2Z0VkaXRzRnJhbWUgPC0gZGF0YS5mcmFtZShkaWZmID0gc2VxKG1pbihhdmdFZGl0c1NpbmNlUmVnJGRpZmYpLCBtYXgoYXZnRWRpdHNTaW5jZVJlZyRkaWZmKSkpCmF2Z0VkaXRzRnJhbWUgPC0gbGVmdF9qb2luKGF2Z0VkaXRzRnJhbWUsIGF2Z0VkaXRzU2luY2VSZWcsIGJ5ID0gImRpZmYiKQphdmdFZGl0c0ZyYW1lJG1lYW5FZGl0c1tpcy5uYShhdmdFZGl0c0ZyYW1lJG1lYW5FZGl0cyldIDwtIDAKYXZnRWRpdHNGcmFtZSRtZWFuRWRpdHMgPC0gcm91bmQoYXZnRWRpdHNGcmFtZSRtZWFuRWRpdHMsIDIpCmdncGxvdChhdmdFZGl0c0ZyYW1lLCBhZXMoeCA9IGRpZmYsIAogICAgICAgICAgICAgICAgICAgIHkgPSBtZWFuRWRpdHMsIAogICAgICAgICAgICAgICAgICAgIGxhYmVsID0gbWVhbkVkaXRzKSkgKyAKICBnZW9tX3BhdGgoY29sb3IgPSAiYmx1ZSIsIHNpemUgPSAuMjUpICsgCiAgZ2VvbV9wb2ludChjb2xvciA9ICJibHVlIiwgc2l6ZSA9IDEuNSkgKyAKICBnZW9tX3BvaW50KGNvbG9yID0gIndoaXRlIiwgc2l6ZSA9IDEpICsgCiAgZ2d0aXRsZSgnVGhhbmsgWW91IDI5MTk6IEF2ZXJhZ2UgbnVtYmVyIG9mIGVkaXRzIE4gZGF5cyBhZnRlciByZWdpc3RyYXRpb24nKSArIAogIHhsYWIoIkRheXMgYWZ0ZXIgcmVnaXN0cmF0aW9uIikgKyB5bGFiKCJNZWFuIGVkaXRzIHBlciB1c2VyIikgKyAKICB0aGVtZV9taW5pbWFsKCkgKyAKICBnZW9tX3RleHRfcmVwZWwoc2l6ZSA9IDMuNSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIHNpemUgPSA4KSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKQpgYGAKCioqQ2hhcnQgMy4yLjIqKgoKKipOb3RlLioqIFRoZSBjaGFydCBwcmVzZW50cyB0aGUgbWVhbiBudW1iZXIgb2YgZWRpdHMgY2FsY3VsYXRlZCBmcm9tIHRoZSB0b3RhbCBudW1iZXIgb2YgZWRpdHMgcGVyIGRheSBkaXZpZGVkIGJ5IHRoZSBudW1iZXIgb2Z1c2VycyByZWdpc3RlcmVkIHVudGlsIHRoYXQgZGF5IChpLmUuIGJ5IGhvdyBtYW55IGNhbXBhaWduIHJlZ2lzdGVyZWQgdXNlcnMgY291bGQgaGF2ZSBlZGl0ZWQgb24gdGhhdCBkYXkpIFtzZWUgUGhhYl0oaHR0cHM6Ly9waGFicmljYXRvci53aWtpbWVkaWEub3JnL1QyMTE2OTAjNDk2Nzc1NykuCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGQUxTRX0KIyAtIGxvYWQgdXNlciByZWdpc3RyYXRpb25zIHBlciBkYXksIHBlciB1c2VyCnVzZXJSZWdpc3RyYXRpb25zRGFpbHkgPC0gZnJlYWQoIl9hbmFseXRpY3MvZnVsbFJlZ2lzdHJhdGlvbkRhdGFzZXQuY3N2IikKdXNlclJlZ2lzdHJhdGlvbnNEYWlseSA8LSBzZWxlY3QodXNlclJlZ2lzdHJhdGlvbnNEYWlseSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50X3VzZXJJZCwgZGF0ZSkKY3VtdWxhdGl2ZVJlZ2lzdHJhdGlvbnMgPC0gdXNlclJlZ2lzdHJhdGlvbnNEYWlseSAlPiUgCiAgc2VsZWN0KGRhdGUpICU+JSAKICBncm91cF9ieShkYXRlKSAlPiUgCiAgc3VtbWFyaXNlKHJlZ2lzdHJhdGlvbnMgPSBuKCkpCmN1bXVsYXRpdmVSZWdpc3RyYXRpb25zJHJlZ2lzdHJhdGlvbnMgPC0gY3Vtc3VtKGN1bXVsYXRpdmVSZWdpc3RyYXRpb25zJHJlZ2lzdHJhdGlvbnMpCnVzZXJFZGl0c1BlckRheSA8LSB1c2VyRWRpdHMgJT4lIAogIHNlbGVjdChkYXRlKSAlPiUKICBncm91cF9ieShkYXRlKSAlPiUgCiAgc3VtbWFyaXNlKGVkaXRzID0gbigpKQp1c2VyRWRpdHNQZXJEYXkgPC0gbGVmdF9qb2luKHVzZXJFZGl0c1BlckRheSwgY3VtdWxhdGl2ZVJlZ2lzdHJhdGlvbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSAiZGF0ZSIpCnVzZXJFZGl0c1BlckRheSRyZWdpc3RyYXRpb25zW2lzLm5hKHVzZXJFZGl0c1BlckRheSRyZWdpc3RyYXRpb25zKV0gPC0gCiAgbWF4KHVzZXJFZGl0c1BlckRheSRyZWdpc3RyYXRpb25zLCBuYS5ybSA9IFQpCnVzZXJFZGl0c1BlckRheSRtZWFuRWRpdHMgPSByb3VuZCh1c2VyRWRpdHNQZXJEYXkkZWRpdHMvdXNlckVkaXRzUGVyRGF5JHJlZ2lzdHJhdGlvbnMsIDMpCmRhdGVSYW5nZUZyYW1lIDwtIGRhdGEuZnJhbWUoZGF0ZSA9IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMuY2hhcmFjdGVyKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXEobWluKGFzLlBPU0lYY3QodXNlckVkaXRzUGVyRGF5JGRhdGUpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXgoYXMuUE9TSVhjdCh1c2VyRWRpdHNQZXJEYXkkZGF0ZSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gImRheSIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKdXNlckVkaXRzUGVyRGF5IDwtIGxlZnRfam9pbihkYXRlUmFuZ2VGcmFtZSwgdXNlckVkaXRzUGVyRGF5LCBieSA9ICJkYXRlIikKdXNlckVkaXRzUGVyRGF5JG1lYW5FZGl0c1tpcy5uYSh1c2VyRWRpdHNQZXJEYXkkbWVhbkVkaXRzKV0gPC0gMApnZ3Bsb3QodXNlckVkaXRzUGVyRGF5LCBhZXMoeCA9IGRhdGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gbWVhbkVkaXRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBtZWFuRWRpdHMpKSArIAogIGdlb21fcGF0aChjb2xvciA9ICJibHVlIiwgc2l6ZSA9IC4yNSwgZ3JvdXAgPSAxKSArIAogIGdlb21fcG9pbnQoY29sb3IgPSAiYmx1ZSIsIHNpemUgPSAxLjUpICsgCiAgZ2VvbV9wb2ludChjb2xvciA9ICJ3aGl0ZSIsIHNpemUgPSAxKSArIAogIGdndGl0bGUoJ1RoYW5rIFlvdSAyOTE5OiBBdmVyYWdlIG51bWJlciBvZiBlZGl0cyBOIGRheXMgYWZ0ZXIgcmVnaXN0cmF0aW9uJykgKyAKICB4bGFiKCJEYXlzIGFmdGVyIHJlZ2lzdHJhdGlvbiIpICsgeWxhYigiTWVhbiBlZGl0cyBwZXIgdXNlciIpICsgCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgZ2VvbV90ZXh0X3JlcGVsKHNpemUgPSAzLjUsIHNob3cubGVnZW5kID0gRkFMU0UpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpCmBgYAoKKipDaGFydCAzLjIuMyoqCgoqKk5vdGUuKiogVGhlIGNoYXJ0IHByZXNlbnRzIHRoZSBtZWFuIG51bWJlciBvZiBlZGl0cyBjYWxjdWxhdGVkIGZyb20gdGhlIHRvdGFsIG51bWJlciBvZiBlZGl0cyBwZXIgZGF5IGRpdmlkZWQgYnkgdGhlIG51bWJlciBvZiBhY3RpdmUgdXNlcnMgKGkuZS4gYnkgaG93IG1hbnkgdXNlcnMgZWRpdGVkKSB1bnRpbCB0aGF0IGRheS4iIFtzZWUgUGhhYl0oaHR0cHM6Ly9waGFicmljYXRvci53aWtpbWVkaWEub3JnL1QyMTE2OTAjNDk2ODU1MikuCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGQUxTRX0KIyAtIGxvYWQgdXNlciByZWdpc3RyYXRpb25zIHBlciBkYXksIHBlciB1c2VyCnVzZXJFZGl0c1JldiA8LSB1c2VyRWRpdHMgJT4lIAogIGFycmFuZ2UoZGF0ZSkKdW5pcXVlRGF0ZXMgPC0gdW5pcXVlKHVzZXJFZGl0c1JldiRkYXRlKQplZGl0b3JzIDwtIGxpc3QoKQplZGl0b3JzIDwtIGxhcHBseSh1bmlxdWVEYXRlcywgZnVuY3Rpb24oeCkgewogIHVuaXF1ZSh1c2VyRWRpdHNSZXYkdXNlcl9pZFt1c2VyRWRpdHNSZXYkZGF0ZSAlaW4lIHhdKQp9KQpuYW1lcyhlZGl0b3JzKSA8LSB1bmlxdWVEYXRlcwplZGl0b3JzRGFpbHkgPC0gbGlzdCgpCmZvciAoaSBpbiAxOmxlbmd0aChlZGl0b3JzKSkgewogIGVkaXRvcnNEYWlseVtbaV1dIDwtIGModW5pcXVlKHVubmFtZSh1bmxpc3QoZWRpdG9yc1sxOmldKSkpKQp9CmVkaXRvcnNEYWlseSA8LSBsYXBwbHkoZWRpdG9yc0RhaWx5LCBsZW5ndGgpCmVkaXRvcnNEYWlseSA8LSBkYXRhLmZyYW1lKGVkaXRvcnMgPSB1bm5hbWUodW5saXN0KGVkaXRvcnNEYWlseSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0ZSA9IHVuaXF1ZURhdGVzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCnVzZXJFZGl0c1BlckRheSA8LSB1c2VyRWRpdHMgJT4lIAogIHNlbGVjdChkYXRlKSAlPiUKICBncm91cF9ieShkYXRlKSAlPiUgCiAgc3VtbWFyaXNlKGVkaXRzID0gbigpKQp1c2VyRWRpdHNQZXJEYXkgPC0gbGVmdF9qb2luKHVzZXJFZGl0c1BlckRheSwgZWRpdG9yc0RhaWx5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gImRhdGUiKQp1c2VyRWRpdHNQZXJEYXkkbWVhbkVkaXRzID0gcm91bmQodXNlckVkaXRzUGVyRGF5JGVkaXRzL3VzZXJFZGl0c1BlckRheSRlZGl0b3JzLCAzKQpkYXRlUmFuZ2VGcmFtZSA8LSBkYXRhLmZyYW1lKGRhdGUgPSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmNoYXJhY3RlcigKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VxKG1pbihhcy5QT1NJWGN0KHVzZXJFZGl0c1BlckRheSRkYXRlKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4KGFzLlBPU0lYY3QodXNlckVkaXRzUGVyRGF5JGRhdGUpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9ICJkYXkiKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCnVzZXJFZGl0c1BlckRheSA8LSBsZWZ0X2pvaW4oZGF0ZVJhbmdlRnJhbWUsIHVzZXJFZGl0c1BlckRheSwgYnkgPSAiZGF0ZSIpCnVzZXJFZGl0c1BlckRheSRtZWFuRWRpdHNbaXMubmEodXNlckVkaXRzUGVyRGF5JG1lYW5FZGl0cyldIDwtIDAKZ2dwbG90KHVzZXJFZGl0c1BlckRheSwgYWVzKHggPSBkYXRlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IG1lYW5FZGl0cywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gbWVhbkVkaXRzKSkgKyAKICBnZW9tX3BhdGgoY29sb3IgPSAiYmx1ZSIsIHNpemUgPSAuMjUsIGdyb3VwID0gMSkgKyAKICBnZW9tX3BvaW50KGNvbG9yID0gImJsdWUiLCBzaXplID0gMS41KSArIAogIGdlb21fcG9pbnQoY29sb3IgPSAid2hpdGUiLCBzaXplID0gMSkgKyAKICBnZ3RpdGxlKCdUaGFuayBZb3UgMjkxOTogQXZlcmFnZSBudW1iZXIgb2YgZWRpdHMgTiBkYXlzIGFmdGVyIHJlZ2lzdHJhdGlvbicpICsgCiAgeGxhYigiRGF5cyBhZnRlciByZWdpc3RyYXRpb24iKSArIHlsYWIoIk1lYW4gZWRpdHMgcGVyIHVzZXIiKSArIAogIHRoZW1lX21pbmltYWwoKSArIAogIGdlb21fdGV4dF9yZXBlbChzaXplID0gMy41LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4KSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKQpgYGAKCiMjIDQuIFRyYWluaW5nIE1vZHVsZXMKCiMjIyA0LjEgQ29sbGVjdCB0cmFpbmluZyBtb2R1bGVzIGRhdGEKClRoZSBkYXRhc2V0IGlzIG9idGFpbmVkIGRpcmVjdGx5IGZyb20gdGhlIG1haW50YWluZXIuCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGQUxTRX0KIyAtIHRyYWluaW5nIG1vZHVsZXMgZGF0YQp0cmFpbk1vZHVsZXMgPC0gZnJlYWQoCiAgICAnL2hvbWUvZ29yYW5zbS9Xb3JrL19fX0RhdGFLb2xla3Rpdi9Qcm9qZWN0cy9XaWtpbWVkaWFERVUvX1dNREVfUHJvamVjdHMvX21pc2MvTmV3RWRpdG9yc19UZWFtLzIwMTlfVGhhbmtZb3UvX2FuYWx5dGljcy93bWRlX3RyYWluaW5nX2RhdGFfMjAxOS0wMS5jc3YnKQojIC0gZ2V0IHVzZXIgcmVnaXN0cmF0aW9ucyB3LiB1c2VyIG5hbWVzIGZvciBqb2lucwpsb2NEaXIgPC0gCicvaG9tZS9nb3JhbnNtL1dvcmsvX19fRGF0YUtvbGVrdGl2L1Byb2plY3RzL1dpa2ltZWRpYURFVS9fV01ERV9Qcm9qZWN0cy9fbWlzYy9OZXdFZGl0b3JzX1RlYW0vMjAxOV9UaGFua1lvdS9fZGF0YS8nCmxGIDwtIGxpc3QuZmlsZXMobG9jRGlyKQpsRiA8LSBsRltncmVwbCgidXNlclJlZ2lzdHJhdGlvbnNfIiwgbEYpXQp1c2VyUmVnIDwtIGxhcHBseShsRiwgZnVuY3Rpb24oeCkgewogIGZyZWFkKHBhc3RlMChsb2NEaXIsIHgpKQp9KQp1c2VyUmVnIDwtIHJiaW5kbGlzdCh1c2VyUmVnKQp1c2VyUmVnJFYxIDwtIE5VTEwKdHJhaW5Nb2R1bGVzIDwtIGxlZnRfam9pbih0cmFpbk1vZHVsZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KHVzZXJSZWcsICBldmVudF91c2VySWQsIGV2ZW50X3VzZXJOYW1lKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoInVzZXJuYW1lIiA9ICJldmVudF91c2VyTmFtZSIpKQp0cmFpbk1vZHVsZXMgPC0gdHJhaW5Nb2R1bGVzWyFpcy5uYSh0cmFpbk1vZHVsZXMkZXZlbnRfdXNlcklkKSwgXQp0cmFpbk1vZHVsZXMkdXNlcm5hbWUgPC0gTlVMTApgYGAKCiMjIyA0LjIgVHJhaW5pbmcgbW9kdWxlczogb3ZlcnZpZXcKCioqNDkgbmV3IGVkaXRvcnMqKiBoYXZlIHN0YXJ0ZWQgYXQgbGVhc3Qgb25lIHRyYWluaW5nIG1vZHVsZTsgKiozMioqIGhhdmUgY29tcGxldGVkIGF0IGxlYXN0IG9uZSB0aGUgdHJhaW5pbmcgYW5kICoqMTkqKiBkaWQgbm90LgoKVGhlIGZvbGxvd2luZyB0YWJsZSA0LjIuMSBnaXZlcyBhbmQgb3ZlcnZpZXcgb2YgaG93IG1hbnkgdXNlcnMgaGF2ZSAqKmNvbXBsZXRlZCoqIGEgcGFydGljdWxhciB0cmFpbmluZyBtb2R1bGUuCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGQUxTRX0KdG1Vc2VycyA8LSB0cmFpbk1vZHVsZXMgJT4lIAogIHNlbGVjdChldmVudF91c2VySWQsIHRyYWluaW5nX21vZHVsZSwgbW9kdWxlX2NvbXBsZXRpb25fZGF0ZSkgJT4lIAogIG11dGF0ZShtb2R1bGVfY29tcGxldGlvbl9kYXRlID0gaWZlbHNlKG1vZHVsZV9jb21wbGV0aW9uX2RhdGUgPT0gJycsIEYsIFQpKSAlPiUgCiAgZ3JvdXBfYnkoZXZlbnRfdXNlcklkLCB0cmFpbmluZ19tb2R1bGUsIG1vZHVsZV9jb21wbGV0aW9uX2RhdGUpICU+JSAKICBzdW1tYXJpc2UobiA9IG4oKSkgJT4lIAogIGZpbHRlcihtb2R1bGVfY29tcGxldGlvbl9kYXRlID09IFQpICU+JSAKICBzZWxlY3QodHJhaW5pbmdfbW9kdWxlKSAlPiUgCiAgZ3JvdXBfYnkodHJhaW5pbmdfbW9kdWxlKSAlPiUgCiAgc3VtbWFyaXNlKGVkaXRvcnMgPSBuKCkpCmRhdGF0YWJsZSh0bVVzZXJzKQpgYGAgCgpUYWJsZSA0LjIuMiBnaXZlcyBhbmQgb3ZlcnZpZXcgb2YgaG93IG1hbnkgdXNlcnMgaGF2ZSAqKnN0YXJ0ZWQqKiBhIHBhcnRpY3VsYXIgdHJhaW5pbmcgbW9kdWxlLgoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0V9CnRtVXNlcnMgPC0gdHJhaW5Nb2R1bGVzICU+JSAKICBzZWxlY3QodHJhaW5pbmdfbW9kdWxlKSAlPiUgCiAgZ3JvdXBfYnkodHJhaW5pbmdfbW9kdWxlKSAlPiUgCiAgc3VtbWFyaXNlKGVkaXRvcnMgPSBuKCkpCmRhdGF0YWJsZSh0bVVzZXJzKQpgYGAKClRhYmxlIDQuMi4zIFRoZSAqKmxhc3Qgc2xpZGUqKiBmcm9tIGEgdHJhaW5pbmcgbW9kdWxlIGNvbXBsZXRlZCBieSBuZXcgZWRpdG9yczsgdGhlIGBlZGl0b3JzYCBjb2x1bW4gc2hvd3MgdGhlIG51bWJlciBvZiBlZGl0b3JzIHdobyBoYXZlIGFiYW5kb25lZCAob3IgY29tcGxldGVkKSB0aGVpciB0cmFuaW5nIGF0IHRoZSByZXNwZWN0aXZlIHNsaWRlLgoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0V9CnRtTGFzdFNsaWRlIDwtIHRyYWluTW9kdWxlcyAlPiUKICBncm91cF9ieSh0cmFpbmluZ19tb2R1bGUsIGxhc3Rfc2xpZGVfY29tcGxldGVkKSAlPiUgCiAgc3VtbWFyaXNlKGVkaXRvcnMgPSBuKCkpCmRhdGF0YWJsZSh0bUxhc3RTbGlkZSkKYGBgCgpUYWJsZSA0LjIuNCBVc2VyIGVkaXRzOiB0cmFpbmluZyBtb2R1bGVzIGNvbXBsZXRlZCB2cyBub3QgY29tcGxldGVkLgoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0V9CnRtRWRpdHMgPC0gbGVmdF9qb2luKHVzZXJFZGl0cywgdHJhaW5Nb2R1bGVzLCBieSA9IGMoJ3VzZXJfaWQnID0gJ2V2ZW50X3VzZXJJZCcpKQp0bUVkaXRzIDwtIHRtRWRpdHMgJT4lIAogIHNlbGVjdCh0cmFpbmluZ19tb2R1bGUsIG1vZHVsZV9jb21wbGV0aW9uX2RhdGUpCnRtRWRpdHMkY29tcGxldGVkIDwtIHNhcHBseSh0bUVkaXRzJG1vZHVsZV9jb21wbGV0aW9uX2RhdGUsIGZ1bmN0aW9uKHgpIHsKICBpZihpcy5uYSh4KSB8IHggPT0gJycpIHtyZXR1cm4oRkFMU0UpfSBlbHNlIHtyZXR1cm4oVFJVRSl9Cn0pCnRtRWRpdHMkc3RhcnRlZCA8LSBzYXBwbHkodG1FZGl0cyR0cmFpbmluZ19tb2R1bGUsIGZ1bmN0aW9uKHgpIHsKICBpZihpcy5uYSh4KSB8IHggPT0gJycpIHtyZXR1cm4oRkFMU0UpfSBlbHNlIHtyZXR1cm4oVFJVRSl9Cn0pCnRtRWRpdHMkbW9kdWxlX2NvbXBsZXRpb25fZGF0ZSA8LSBOVUxMCnRtRWRpdHMkdHJhaW5pbmdfbW9kdWxlIDwtIE5VTEwKdG1FZGl0cyR0cmFpbmluZ19vdXRjb21lIDwtIGlmZWxzZSh0bUVkaXRzJGNvbXBsZXRlZCwgJ0NvbXBsZXRlZCcsIE5BKQp0bUVkaXRzJHRyYWluaW5nX291dGNvbWUgPC0gaWZlbHNlKHRtRWRpdHMkc3RhcnRlZCAmIGlzLm5hKHRtRWRpdHMkdHJhaW5pbmdfb3V0Y29tZSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdTdGFydGVkJywgdG1FZGl0cyR0cmFpbmluZ19vdXRjb21lKQp0bUVkaXRzJHRyYWluaW5nX291dGNvbWVbaXMubmEodG1FZGl0cyR0cmFpbmluZ19vdXRjb21lKV0gPC0gJ05vIHRyYWluaW5nJyAKdG1FZGl0cyA8LSB0bUVkaXRzICU+JSAKICBncm91cF9ieSh0cmFpbmluZ19vdXRjb21lKSAlPiUgCiAgc3VtbWFyaXNlKGVkaXRzID0gbigpKQpkYXRhdGFibGUodG1FZGl0cykKYGBgCgpUYWJsZSA0LjIuNSBVc2VyIGVkaXRzOiBhdmVyYWdlIG51bWJlciBvZiBlZGl0cyBwZXIgdXNlciwgdHJhaW5pbmcgbW9kdWxlcyBjb21wbGV0ZWQgdnMgbm90IGNvbXBsZXRlZC4KCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZBTFNFfQp0bUVkaXRzIDwtIGxlZnRfam9pbih1c2VyRWRpdHMsIHRyYWluTW9kdWxlcywgYnkgPSBjKCd1c2VyX2lkJyA9ICdldmVudF91c2VySWQnKSkKdG1FZGl0cyA8LSB0bUVkaXRzICU+JSAKICBzZWxlY3QodXNlcl9pZCwgdHJhaW5pbmdfbW9kdWxlLCBtb2R1bGVfY29tcGxldGlvbl9kYXRlKQp0bUVkaXRzJGNvbXBsZXRlZCA8LSBzYXBwbHkodG1FZGl0cyRtb2R1bGVfY29tcGxldGlvbl9kYXRlLCBmdW5jdGlvbih4KSB7CiAgaWYoaXMubmEoeCkgfCB4ID09ICcnKSB7cmV0dXJuKEZBTFNFKX0gZWxzZSB7cmV0dXJuKFRSVUUpfQp9KQp0bUVkaXRzJHN0YXJ0ZWQgPC0gc2FwcGx5KHRtRWRpdHMkdHJhaW5pbmdfbW9kdWxlLCBmdW5jdGlvbih4KSB7CiAgaWYoaXMubmEoeCkgfCB4ID09ICcnKSB7cmV0dXJuKEZBTFNFKX0gZWxzZSB7cmV0dXJuKFRSVUUpfQp9KQp0bUVkaXRzJG1vZHVsZV9jb21wbGV0aW9uX2RhdGUgPC0gTlVMTAp0bUVkaXRzJHRyYWluaW5nX21vZHVsZSA8LSBOVUxMCnRtRWRpdHMkdHJhaW5pbmdfb3V0Y29tZSA8LSBpZmVsc2UodG1FZGl0cyRjb21wbGV0ZWQsICdDb21wbGV0ZWQnLCBOQSkKdG1FZGl0cyR0cmFpbmluZ19vdXRjb21lIDwtIGlmZWxzZSh0bUVkaXRzJHN0YXJ0ZWQgJiBpcy5uYSh0bUVkaXRzJHRyYWluaW5nX291dGNvbWUpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnU3RhcnRlZCcsIHRtRWRpdHMkdHJhaW5pbmdfb3V0Y29tZSkKdG1FZGl0cyR0cmFpbmluZ19vdXRjb21lW2lzLm5hKHRtRWRpdHMkdHJhaW5pbmdfb3V0Y29tZSldIDwtICdObyB0cmFpbmluZycgCnRtRWRpdHMgPC0gdG1FZGl0cyAlPiUgCiAgZ3JvdXBfYnkodXNlcl9pZCwgdHJhaW5pbmdfb3V0Y29tZSkgJT4lIAogIHN1bW1hcmlzZShlZGl0cyA9IG4oKSkgJT4lIAogIGdyb3VwX2J5KHRyYWluaW5nX291dGNvbWUpICU+JSAKICBzdW1tYXJpc2UoYG1lYW4oRWRpdHMpYCA9IHJvdW5kKG1lYW4oZWRpdHMpLCAyKSkKZGF0YXRhYmxlKHRtRWRpdHMpCmBgYAoKRG8geW91IHNlZSBpbiB0aGUgdGFibGVzIGZyb20gUmFnZXNvc3MgaG93IG1hbnkgcGVvcGxlIGRpZCBvciBiZWdpbiB0aGUgbW9kdWxlcyBpbiBnZW5lcmFsIC0gcmVnYXJkbGVzcyBvZiB0aGUgZmFjdCB0aGV5IHJlZ2lzdGVyIG9yIG5vdC4gUXVlc3Rpb24gYmVoaW5kIHRoZSBxdWVzdGlvbjogSSB3b3VsZCBsaWtlIHRvIGtub3cgaG93IG1hbnkgcGVvcGxlIHBvdGVudGlhbGx5IGp1c3QgZGlkIHRoZSBtb2R1bGUsIGJ1dCBkaWRuJ3QgcmVnaXN0ZXIgYmVmb3JoYW5kLiAoc2VlOiAgW1BoYWJdKGh0dHBzOi8vcGhhYnJpY2F0b3Iud2lraW1lZGlhLm9yZy9UMjExNjkwIzQ5NTEwMzIpKQoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0V9CiMgLSB0cmFpbmluZyBtb2R1bGVzIGRhdGEKdHJhaW5Nb2R1bGVzIDwtIGZyZWFkKAogICAgJy9ob21lL2dvcmFuc20vV29yay9fX19EYXRhS29sZWt0aXYvUHJvamVjdHMvV2lraW1lZGlhREVVL19XTURFX1Byb2plY3RzL19taXNjL05ld0VkaXRvcnNfVGVhbS8yMDE5X1RoYW5rWW91L19hbmFseXRpY3Mvd21kZV90cmFpbmluZ19kYXRhXzIwMTktMDEuY3N2JykKIyAtIFVuaXF1ZSBudW1iZXIgb2YgdHJhaW5Nb2R1bGVzJHVzZXJuYW1lIC0yIChTdGVmYW4gYW5kIFNhZ2UpCmxlbmd0aCh1bmlxdWUodHJhaW5Nb2R1bGVzJHVzZXJuYW1lKSkgLSAyCmBgYAoKMzg3IHBlb3BsZSB0b29rIHRoZSB0cmFpbmluZyBtb2R1bGVzIGlycmVzcGVjdGl2ZSBvZiByZWdpc3RyYXRpb24uCg==