In this last part of scraping names, we will show how to mimic a human webbrowsing activity. We will use RSelenium / Selenium for this. This can be a bit complicated to get up and running. For example, you need to hava Java installed.

Especially the R documentation is not great. In R, there is a relatively new package selendir which looks promising.

Because we will be interacting with websites, we did not show our output on this page.

1 Preparation

1.1 clean up

rm(list = ls())
gc()


1.2 general custom R functions

  • fpackage.check: Check if packages are installed (and install if not) in R
  • fsave: save data with time stamp in correct directory
  • fload: load R-objects under new names
  • fshowdf: Print objects (tibble / data.frame) nicely on screen in .Rmd.
fpackage.check <- function(packages) {
    lapply(packages, FUN = function(x) {
        if (!require(x, character.only = TRUE)) {
            install.packages(x, dependencies = TRUE)
            library(x, character.only = TRUE)
        }
    })
}

fsave <- function(x, file, location = "./local/", ...) {
    if (!dir.exists(location))
        dir.create(location)
    datename <- substr(gsub("[:-]", "", Sys.time()), 1, 8)
    totalname <- paste(location, datename, file, sep = "")
    print(paste("SAVED: ", totalname, sep = ""))
    save(x, file = totalname)
}

fload <- function(fileName) {
    load(fileName)
    get(ls()[ls() != "fileName"])
}

fshowdf <- function(x, digits = 2, ...) {
    knitr::kable(x, digits = digits, "html", ...) %>%
        kableExtra::kable_styling(bootstrap_options = c("striped", "hover")) %>%
        kableExtra::scroll_box(width = "100%", height = "300px")
}


1.3 necessary packages

1.3.1 R

packages = c("RSelenium", "rvest", "tidyverse", "netstat", "pingr", "stringr")
fpackage.check(packages)
rm(packages)

1.3.2 Python

Install the newest version of the selenium module inside a virtual environment, by typing in your terminal the below command:

pip install -U selenium


and import necessary modules

import selenium
import time

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By


1.4 set up Selenium

1.4.1 R

# find a free port
port <- netstat::free_port(random = TRUE)

# ping port for confirmation
pingr::ping_port("www.jochemtolsma.nl", port = port)
ping_port("r-project.org")

Set up selenium server and browser (firefox is recommended) An issue with RSelenium::rsDriver is that it is configured to launch the Selenium server with the latest versions of Chrome and Firefox drivers, even if you have specified the other browser to run. To solve this, pass in NULL in the Chrome driver version chromever!

rD <- rsDriver(browser = "firefox", port = port, chromever = NULL, verbose = FALSE)
remDr <- rD[["client"]]


If at any time you wish to terminate the process, use the code:

pid <- rD$server$process$get_pid()  #get process id
system(paste0("Taskkill /F /T", " /PID ", pid))

1.4.2 Python

remDr = webdriver.Firefox()


If at any time you wish to terminate the process, use the code:

remDr.close()



2 Scraping scholar names

2.1 R

# navigate to RU staff page
remDr$navigate("https://www.ru.nl/en/search/scope/staff/")

# handle cookies
remDr$findElement(using = "css", value = ".agree-button")$highlightElement()
remDr$findElement(using = "css", value = ".agree-button")$clickElement()

cookies <- remDr$getAllCookies()
saveRDS(cookies, "cookies.rds")
remDr$addCookie(name = cookies[[1]][["name"]], value = cookies[[1]][["value"]])

remDr$maxWindowSize()

We want to get scholars from the sociology department; so we use the website’s collapsible lists and expand content under Radboud University.

We use the highlightElement() function to demonstrate where we are on the website.

remDr$findElement(using = "class name", value = "facet-list__expand-sub")$highlightElement()
remDr$findElement(using = "class name", value = "facet-list__expand-sub")$clickElement()

# we have to expand the list
remDr$findElement(using = "class name", value = "btn--show-hide")$highlightElement()
remDr$findElement(using = "class name", value = "btn--show-hide")$clickElement()

Expand content under Faculty of social science (dep id 961) using its xpath.
Note, this is the html:

<button class="facet-list__expand-sub is-active" aria-controls="toggle-staff-department-961" aria-expanded="false" data-once="ruHierarchicalFacetsClick" style=""><span class="visually-hidden">Hide filters</span>-</button>

This translates into the xpath shown below. Note here we use a compound class!
Difficulties with xpath? Have a look here

There seems to be a small bug in the website which breaks our code. Let us try to fix this in class.

remDr$findElement(using = "xpath", value = "//button[@class='facet-list__expand-sub' and @aria-controls='toggle-staff-department-961']")$highlightElement()

# If you would follow the complete html structure you will arrive at the same point.
remDr$findElement(using = "xpath", value = "/html/body/div[1]/div/div[1]/aside/div/div[27]/div/ul/li/div/ul/li[9]/button")$highlightElement()

# and click
remDr$findElement(using = "xpath", value = "//button[@class='facet-list__expand-sub' and @aria-controls='toggle-staff-department-961']")$clickElement()

Here we encounter the bug: not all departments are shown. You have to click on less and more.

Assignment: Let your crawler click on less and more (of fix the bug in a better way)

Once done, we can continue

#click on sociology
remDr$findElement(using = "xpath", value = "//input[@type='checkbox' and @class='facets-checkbox' and @id='staff-department-992']")$clickElement()

#among all staff members, select researchers

remDr$findElement(using = "xpath", value = "//input[@type='checkbox' and @class='facets-checkbox' and @id='staff-staff-researchers']")$highlightElement()

#more complicated but insightful 
remDr$findElement(using = "id", value = "block-staffstaff")$highlightElement()

remDr$findElement(using = "id", value = "block-staffstaff")$getElementText()

#first get categories:
remDr$findElement(using = "id", value = "block-staffstaff")$getElementText() %>% #get element text;
  unlist(.) %>% #unlist
  strsplit(., "\n") %>% #split by \n
  .[[1]] %>% #take the first element from the list
  .[-1] -> staff_cats #remove the header "staff"

#take the indicator of researchers
researchers <- grep("Researchers", staff_cats)   

#check the corresponding checkbox
remDr$findElements(using = "class name", value = "facets-checkbox")[[researchers]]$clickElement()

Naturally, this can also be achieved by using the URL https://www.ru.nl/en/search/scope/staff/staff-department/X/staff-staff/researchers, where X should be replaced by department-id, which can be stored in a list beforehand (this is useful if we want multiple departments of a faculty)

Now we have all sociology researchers (or of any other department) but these are spread across multiple pages.
To know how many pages there are we navigate to the “last” page first, save the last page, go back and loop through all pages:

remDr$findElements("class name", "pager__link-text")[[2]]$clickElement()

#get the page number of this page
npages <- remDr$findElement(using = "xpath", "//a[@aria-current='page']")$getElementText() %>%
  unlist(.) %>% #unlist
  strsplit(., "\n") %>% #split by \n
  .[[1]] %>%
  .[2] %>%
  as.numeric(.)

#go back to the first page
remDr$findElements("class name", "pager__link-text")[[1]]$clickElement()

#get all elements with class name "card__title"; these refer to names of researchers, on the first page. Retrieve the text:
names_page <- unlist(lapply(remDr$findElements("class name", "card__title"),
                     function(x) x$getElementText()))

#now repeat this for the other pages in 1:npages-1
page1 <- remDr$getCurrentUrl()

for ( i in 1:(npages-1)) { 
  #increment page 
  newpage <- sub("page=0$", paste0("page=", i), page1)
  #navigate to the page
  remDr$navigate(newpage)
  #retrieve names
  new_names <- unlist(lapply(remDr$findElements("class name", "card__title"),
                     function(x) x$getElementText()))
  #and add to the names_page object:
  names_page <- c(names_page, new_names)
  #add sleeper:
  Sys.sleep(2)
}

#all names!
print(names_page)
length(names_page)

2.2 Python

#navigate to RU staff page
remDr.get("https://www.ru.nl/en/search/scope/staff/")

remDr.maximize_window()

#we want to get scholars from the sociology department; so we use the website's collapsible lists
#expand content under Radboud University
remDr.find_element(By.CLASS_NAME, "facet-list__expand-sub").click()

#expand content under Faculty of social science (dep id 961)
#using its xpath
remDr.find_element(By.XPATH, "//button[@class='facet-list__expand-sub' and @aria-controls='toggle-staff-department-961']").click()

#click on sociology
remDr.find_element(By.XPATH, "//input[@type='checkbox' and @class='facets-checkbox' and @id='staff-department-992']").click()

#among all staff members, select researchers
#get the indicator of the checkbox that is "Researchers";

#first get categories:
staff_cats = remDr.find_element(By.ID, "block-staffstaff").text.split("\n")[1:]

#take the indicator of "researchers"
researchers = [i for i, cat in enumerate(staff_cats) if "Researchers" in cat][0]

#check the corresponding checkbox
remDr.find_elements(By.CLASS_NAME, "facets-checkbox")[researchers].click()

#naturally this can also be achieved by using the URL https://www.ru.nl/en/search/scope/staff/staff-department/X/staff-staff/researchers, where X should be replaced by department-id, which can be stored in a list beforehand (this is useful if we want multiple departments of a faculty)

##################################################################################################

#now we have all sociology researchers (or of any other department);
#but this is spread across multiple pages;
#to know how many pages there are - and thus through how many we must scrape - we navigate to the "last" page first;
remDr.find_elements(By.CLASS_NAME, "pager__link-text")[1].click()

#get the page number of this page
npages = int(remDr.find_element(By.XPATH, "//a[@aria-current='page']").text.split("\n")[1])
  
#go back to the first page
remDr.find_elements(By.CLASS_NAME, "pager__link-text")[0].click()

#get all elements with class name "card__title"; these refer to names of researchers, on the first page. Retrieve the text:
names_page = [elem.text for elem in remDr.find_elements(By.CLASS_NAME, "card__title")]

#now repeat this for the other pages
page1 = remDr.current_url

for i in range(1, npages):
  #increment page
  newpage = page1.replace("page=0", f"page={i}")
  print(str("navigating to: ") + newpage)
  time.sleep(0.5)
  #navigate to the page
  remDr.get(newpage)
  time.sleep(0.5)
  #retrieve names
  new_names = [elem.text for elem in remDr.find_elements(By.CLASS_NAME, "card__title")]
  #and add to the names_page object:
  names_page.extend(new_names)
  #add sleeper:
  time.sleep(2)

#all names!
print(names_page)
len(names_page)
LS0tDQp0aXRsZTogIlNjcmFwaW5nIFJVIFNvY2lvbG9neSBkZXBhcnRtZW50Ig0KYmlibGlvZ3JhcGh5OiByZWZlcmVuY2VzLmJpYg0KbGluay1jaXRhdGlvbnM6IHllcw0KLS0tDQoNCg0KDQpgYGB7ciwgZ2xvYmFsc2V0dGluZ3MsIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkodGlkeXZlcnNlKSANCmxpYnJhcnkocmV0aWN1bGF0ZSkNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCm9wdHNfY2h1bmskc2V0KHRpZHkub3B0cz1saXN0KHdpZHRoLmN1dG9mZj0xMDApLHRpZHk9VFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGV2YWwgPSBGQUxTRSwgY29tbWVudCA9ICIjPiIsIGNhY2hlPVRSVUUsIGNsYXNzLnNvdXJjZT1jKCJ0ZXN0IiksIGNsYXNzLm91dHB1dD1jKCJ0ZXN0MyIpKQ0Kb3B0aW9ucyh3aWR0aCA9IDEwMCkNCnJnbDo6c2V0dXBLbml0cigpDQoNCmNvbG9yaXplIDwtIGZ1bmN0aW9uKHgsIGNvbG9yKSB7c3ByaW50ZigiPHNwYW4gc3R5bGU9J2NvbG9yOiAlczsnPiVzPC9zcGFuPiIsIGNvbG9yLCB4KSB9DQpgYGANCg0KYGBge3Iga2xpcHB5LCBlY2hvPUZBTFNFLCBpbmNsdWRlPVRSVUV9DQojaW5zdGFsbC5wYWNrYWdlcygicmVtb3RlcyIpDQojcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoInJsZXN1ci9rbGlwcHkiKQ0Ka2xpcHB5OjprbGlwcHkobGFuZyA9IGMoInIiLCAicHl0aG9uIiksIHBvc2l0aW9uID0gYygndG9wJywgJ3JpZ2h0JykpDQoNCiNrbGlwcHk6OmtsaXBweShjb2xvciA9ICdkYXJrcmVkJykNCiNrbGlwcHk6OmtsaXBweSh0b29sdGlwX21lc3NhZ2UgPSAnQ2xpY2sgdG8gY29weScsIHRvb2x0aXBfc3VjY2VzcyA9ICdEb25lJykNCmBgYA0KDQpJbiB0aGlzIGxhc3QgcGFydCBvZiBzY3JhcGluZyBuYW1lcywgd2Ugd2lsbCBzaG93IGhvdyB0byBtaW1pYyBhIGh1bWFuIHdlYmJyb3dzaW5nIGFjdGl2aXR5LiBXZSB3aWxsIHVzZSBSU2VsZW5pdW0gLyBTZWxlbml1bSBmb3IgdGhpcy4gVGhpcyBjYW4gYmUgYSBiaXQgY29tcGxpY2F0ZWQgdG8gZ2V0IHVwIGFuZCBydW5uaW5nLiBGb3IgZXhhbXBsZSwgeW91IG5lZWQgdG8gaGF2YSBKYXZhIGluc3RhbGxlZC4gDQoNCkVzcGVjaWFsbHkgdGhlIFIgZG9jdW1lbnRhdGlvbiBpcyBub3QgZ3JlYXQuIA0KSW4gUiwgdGhlcmUgaXMgYSByZWxhdGl2ZWx5IG5ldyBwYWNrYWdlIGBzZWxlbmRpcmAgd2hpY2ggbG9va3MgcHJvbWlzaW5nLiANCg0KQmVjYXVzZSB3ZSB3aWxsIGJlIGludGVyYWN0aW5nIHdpdGggd2Vic2l0ZXMsIHdlIGRpZCBub3Qgc2hvdyBvdXIgb3V0cHV0IG9uIHRoaXMgcGFnZS4gDQoNCg0KIyBQcmVwYXJhdGlvbg0KDQojIyBjbGVhbiB1cA0KYGBge3IsIGNsZWFudXAsIHJlc3VsdHM9J2hpZGUnfQ0Kcm0obGlzdD1scygpKQ0KZ2MoKQ0KYGBgDQoNCjxicj4NCg0KIyMgZ2VuZXJhbCBjdXN0b20gUiBmdW5jdGlvbnMNCg0KLSBgZnBhY2thZ2UuY2hlY2tgOiBDaGVjayBpZiBwYWNrYWdlcyBhcmUgaW5zdGFsbGVkIChhbmQgaW5zdGFsbCBpZiBub3QpIGluIFINCi0gYGZzYXZlYDogc2F2ZSBkYXRhIHdpdGggdGltZSBzdGFtcCBpbiBjb3JyZWN0IGRpcmVjdG9yeQ0KLSBgZmxvYWRgOiBsb2FkIFItb2JqZWN0cyB1bmRlciBuZXcgbmFtZXMNCi0gYGZzaG93ZGZgOiBQcmludCBvYmplY3RzIChgdGliYmxlYCAvIGBkYXRhLmZyYW1lYCkgbmljZWx5IG9uIHNjcmVlbiBpbiBgLlJtZGAuDQoNCg0KYGBge3IsIGN1c3RvbWZ1bmN9DQpmcGFja2FnZS5jaGVjayA8LSBmdW5jdGlvbihwYWNrYWdlcykgew0KICAgIGxhcHBseShwYWNrYWdlcywgRlVOID0gZnVuY3Rpb24oeCkgew0KICAgICAgICBpZiAoIXJlcXVpcmUoeCwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkgew0KICAgICAgICAgICAgaW5zdGFsbC5wYWNrYWdlcyh4LCBkZXBlbmRlbmNpZXMgPSBUUlVFKQ0KICAgICAgICAgICAgbGlicmFyeSh4LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpDQogICAgICAgIH0NCiAgICB9KQ0KfQ0KDQpmc2F2ZSA8LSBmdW5jdGlvbih4LCBmaWxlLCBsb2NhdGlvbiA9ICIuL2xvY2FsLyIsIC4uLikgew0KICAgIGlmICghZGlyLmV4aXN0cyhsb2NhdGlvbikpDQogICAgICAgIGRpci5jcmVhdGUobG9jYXRpb24pDQogICAgZGF0ZW5hbWUgPC0gc3Vic3RyKGdzdWIoIls6LV0iLCAiIiwgU3lzLnRpbWUoKSksIDEsIDgpDQogICAgdG90YWxuYW1lIDwtIHBhc3RlKGxvY2F0aW9uLCBkYXRlbmFtZSwgZmlsZSwgc2VwID0gIiIpDQogICAgcHJpbnQocGFzdGUoIlNBVkVEOiAiLCB0b3RhbG5hbWUsIHNlcCA9ICIiKSkNCiAgICBzYXZlKHgsIGZpbGUgPSB0b3RhbG5hbWUpDQp9DQoNCmZsb2FkICA8LSBmdW5jdGlvbihmaWxlTmFtZSl7DQogIGxvYWQoZmlsZU5hbWUpDQogIGdldChscygpW2xzKCkgIT0gImZpbGVOYW1lIl0pDQp9DQoNCmZzaG93ZGYgPC0gZnVuY3Rpb24oeCwgZGlnaXRzID0gMiwgLi4uKSB7DQogICAga25pdHI6OmthYmxlKHgsIGRpZ2l0cyA9IGRpZ2l0cywgImh0bWwiLCAuLi4pICU+JQ0KICAgICAgICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIpKSAlPiUNCiAgICAgICAga2FibGVFeHRyYTo6c2Nyb2xsX2JveCh3aWR0aCA9ICIxMDAlIiwgaGVpZ2h0ID0gIjMwMHB4IikNCn0NCmBgYA0KDQo8YnI+DQoNCiMjIG5lY2Vzc2FyeSBwYWNrYWdlcyB7LnRhYnNldCAudGFic2V0LWZhZGV9DQoNCiMjIyBSDQoNCmBgYHtyLCBwYWNrYWdlcywgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpwYWNrYWdlcyA9IGMoIlJTZWxlbml1bSIsICJydmVzdCIsICJ0aWR5dmVyc2UiLCAibmV0c3RhdCIsICJwaW5nciIsICJzdHJpbmdyIikNCmZwYWNrYWdlLmNoZWNrKHBhY2thZ2VzKQ0Kcm0ocGFja2FnZXMpDQpgYGANCg0KIyMjIFB5dGhvbg0KDQpJbnN0YWxsIHRoZSBuZXdlc3QgdmVyc2lvbiBvZiB0aGUgYHNlbGVuaXVtYCBtb2R1bGUgaW5zaWRlIGEgdmlydHVhbCBlbnZpcm9ubWVudCwgYnkgdHlwaW5nIGluIHlvdXIgdGVybWluYWwgdGhlIGJlbG93IGNvbW1hbmQ6DQoNCg0KDQpgYGB7YmFzaCwgZXZhbCA9IEZBTFNFfQ0KcGlwIGluc3RhbGwgLVUgc2VsZW5pdW0NCmBgYCANCg0KPGJyPg0KDQphbmQgaW1wb3J0IG5lY2Vzc2FyeSBtb2R1bGVzDQoNCmBgYHtweXRob24sIGV2YWwgPSBGQUxTRX0NCmltcG9ydCBzZWxlbml1bQ0KaW1wb3J0IHRpbWUNCg0KZnJvbSBzZWxlbml1bSBpbXBvcnQgd2ViZHJpdmVyDQpmcm9tIHNlbGVuaXVtLndlYmRyaXZlci5jb21tb24ua2V5cyBpbXBvcnQgS2V5cw0KZnJvbSBzZWxlbml1bS53ZWJkcml2ZXIuY29tbW9uLmJ5IGltcG9ydCBCeQ0KYGBgIA0KDQo8YnI+DQoNCiMjIHNldCB1cCBTZWxlbml1bSB7LnRhYnNldCAudGFic2V0LWZhZGV9DQoNCiMjIyBSDQoNCmBgYHtyLCBzZWxlbml1bSwgZXZhbCA9IEZBTFNFfQ0KI2ZpbmQgYSBmcmVlIHBvcnQNCnBvcnQgPC0gbmV0c3RhdDo6ZnJlZV9wb3J0KHJhbmRvbSA9IFRSVUUpDQoNCiNwaW5nIHBvcnQgZm9yIGNvbmZpcm1hdGlvbg0KcGluZ3I6OnBpbmdfcG9ydCgid3d3LmpvY2hlbXRvbHNtYS5ubCIsIHBvcnQgPSBwb3J0KQ0KcGluZ19wb3J0KCJyLXByb2plY3Qub3JnIikNCmBgYA0KDQpTZXQgdXAgc2VsZW5pdW0gc2VydmVyIGFuZCBicm93c2VyIChmaXJlZm94IGlzIHJlY29tbWVuZGVkKQ0KQW4gaXNzdWUgd2l0aCBgUlNlbGVuaXVtOjpyc0RyaXZlcmAgaXMgdGhhdCBpdCBpcyBjb25maWd1cmVkIHRvIGxhdW5jaCB0aGUgU2VsZW5pdW0gc2VydmVyIHdpdGggdGhlIGxhdGVzdCB2ZXJzaW9ucyBvZiBDaHJvbWUgKmFuZCogRmlyZWZveCBkcml2ZXJzLCAqZXZlbiBpZiB5b3UgaGF2ZSBzcGVjaWZpZWQgdGhlIG90aGVyIGJyb3dzZXIgdG8gcnVuKi4gVG8gc29sdmUgdGhpcywgcGFzcyBpbiBgTlVMTGAgaW4gdGhlIENocm9tZSBkcml2ZXIgdmVyc2lvbiBgY2hyb21ldmVyYCENCmBgYHtyLCBldmFsID0gRkFMU0V9DQoNCnJEIDwtIHJzRHJpdmVyKGJyb3dzZXI9ImZpcmVmb3giLCBwb3J0PXBvcnQsIGNocm9tZXZlciA9IE5VTEwsIHZlcmJvc2UgPSBGQUxTRSkNCnJlbURyIDwtIHJEW1siY2xpZW50Il1dDQpgYGANCjxicj4NCg0KDQoNCklmIGF0IGFueSB0aW1lIHlvdSB3aXNoIHRvIHRlcm1pbmF0ZSB0aGUgcHJvY2VzcywgdXNlIHRoZSBjb2RlOg0KDQpgYGB7ciwgdGFza2tpbGwsIGV2YWwgPSBGQUxTRX0NCnBpZCA8LSByRCRzZXJ2ZXIkcHJvY2VzcyRnZXRfcGlkKCkjZ2V0IHByb2Nlc3MgaWQNCnN5c3RlbShwYXN0ZTAoIlRhc2traWxsIC9GIC9UIiAsIiAvUElEICIsIHBpZCkpDQpgYGANCg0KIyMjIFB5dGhvbg0KDQpgYGB7cHl0aG9uLCBldmFsID0gRkFMU0V9DQpyZW1EciA9IHdlYmRyaXZlci5GaXJlZm94KCkNCmBgYCANCg0KPGJyPg0KDQpJZiBhdCBhbnkgdGltZSB5b3Ugd2lzaCB0byB0ZXJtaW5hdGUgdGhlIHByb2Nlc3MsIHVzZSB0aGUgY29kZToNCg0KYGBge3B5dGhvbiwgZXZhbCA9IEZBTFNFfQ0KcmVtRHIuY2xvc2UoKQ0KYGBgIA0KDQojIyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQoNCjxicj4NCg0KLS0tDQoNCiMgU2NyYXBpbmcgc2Nob2xhciBuYW1lcyB7LnRhYnNldCAudGFic2V0LWZhZGV9DQoNCiMjIFINCg0KYGBge3IsIG5hbWVzLCBldmFsID0gRkFMU0V9DQojbmF2aWdhdGUgdG8gUlUgc3RhZmYgcGFnZQ0KcmVtRHIkbmF2aWdhdGUoImh0dHBzOi8vd3d3LnJ1Lm5sL2VuL3NlYXJjaC9zY29wZS9zdGFmZi8iKQ0KDQojaGFuZGxlIGNvb2tpZXMNCnJlbURyJGZpbmRFbGVtZW50KHVzaW5nID0gImNzcyIsIHZhbHVlID0gIi5hZ3JlZS1idXR0b24iKSRoaWdobGlnaHRFbGVtZW50KCkNCnJlbURyJGZpbmRFbGVtZW50KHVzaW5nID0gImNzcyIsIHZhbHVlID0gIi5hZ3JlZS1idXR0b24iKSRjbGlja0VsZW1lbnQoKQ0KDQpjb29raWVzIDwtIHJlbURyJGdldEFsbENvb2tpZXMoKQ0Kc2F2ZVJEUyhjb29raWVzLCAiY29va2llcy5yZHMiKQ0KcmVtRHIkYWRkQ29va2llKG5hbWUgPSBjb29raWVzW1sxXV1bWyJuYW1lIl1dLCB2YWx1ZSA9IGNvb2tpZXNbWzFdXVtbInZhbHVlIl1dKQ0KDQpyZW1EciRtYXhXaW5kb3dTaXplKCkNCg0KDQpgYGANCg0KV2Ugd2FudCB0byBnZXQgc2Nob2xhcnMgZnJvbSB0aGUgc29jaW9sb2d5IGRlcGFydG1lbnQ7IHNvIHdlIHVzZSB0aGUgd2Vic2l0ZSdzIGNvbGxhcHNpYmxlIGxpc3RzDQphbmQgZXhwYW5kIGNvbnRlbnQgdW5kZXIgUmFkYm91ZCBVbml2ZXJzaXR5LiANCg0KV2UgdXNlIHRoZSBgaGlnaGxpZ2h0RWxlbWVudCgpYCBmdW5jdGlvbiB0byBkZW1vbnN0cmF0ZSB3aGVyZSB3ZSBhcmUgb24gdGhlIHdlYnNpdGUuDQoNCmBgYHtyLCBldmFsID0gRkFMU0V9DQpyZW1EciRmaW5kRWxlbWVudCh1c2luZyA9ICJjbGFzcyBuYW1lIiwgdmFsdWUgPSAiZmFjZXQtbGlzdF9fZXhwYW5kLXN1YiIpJGhpZ2hsaWdodEVsZW1lbnQoKQ0KcmVtRHIkZmluZEVsZW1lbnQodXNpbmcgPSAiY2xhc3MgbmFtZSIsIHZhbHVlID0gImZhY2V0LWxpc3RfX2V4cGFuZC1zdWIiKSRjbGlja0VsZW1lbnQoKQ0KDQojd2UgaGF2ZSB0byBleHBhbmQgdGhlIGxpc3QNCnJlbURyJGZpbmRFbGVtZW50KHVzaW5nID0gImNsYXNzIG5hbWUiLCB2YWx1ZSA9ICJidG4tLXNob3ctaGlkZSIpJGhpZ2hsaWdodEVsZW1lbnQoKQ0KcmVtRHIkZmluZEVsZW1lbnQodXNpbmcgPSAiY2xhc3MgbmFtZSIsIHZhbHVlID0gImJ0bi0tc2hvdy1oaWRlIikkY2xpY2tFbGVtZW50KCkNCmBgYA0KDQpFeHBhbmQgY29udGVudCB1bmRlciBGYWN1bHR5IG9mIHNvY2lhbCBzY2llbmNlIChkZXAgaWQgOTYxKSB1c2luZyBpdHMgeHBhdGguICANCk5vdGUsIHRoaXMgaXMgdGhlIGh0bWw6IA0KDQpgYGANCjxidXR0b24gY2xhc3M9ImZhY2V0LWxpc3RfX2V4cGFuZC1zdWIgaXMtYWN0aXZlIiBhcmlhLWNvbnRyb2xzPSJ0b2dnbGUtc3RhZmYtZGVwYXJ0bWVudC05NjEiIGFyaWEtZXhwYW5kZWQ9ImZhbHNlIiBkYXRhLW9uY2U9InJ1SGllcmFyY2hpY2FsRmFjZXRzQ2xpY2siIHN0eWxlPSIiPjxzcGFuIGNsYXNzPSJ2aXN1YWxseS1oaWRkZW4iPkhpZGUgZmlsdGVyczwvc3Bhbj4tPC9idXR0b24+DQpgYGANCg0KVGhpcyB0cmFuc2xhdGVzIGludG8gdGhlIHhwYXRoIHNob3duIGJlbG93LiBOb3RlIGhlcmUgd2UgdXNlIGEgY29tcG91bmQgY2xhc3MhICAgDQpEaWZmaWN1bHRpZXMgd2l0aCB4cGF0aD8gSGF2ZSBhIGxvb2sgW2hlcmVdKGh0dHBzOi8vd3d3Lnczc2Nob29scy5jb20veG1sL3hwYXRoX2ludHJvLmFzcCkNCg0KVGhlcmUgc2VlbXMgdG8gYmUgYSBzbWFsbCBidWcgaW4gdGhlIHdlYnNpdGUgd2hpY2ggYnJlYWtzIG91ciBjb2RlLiBMZXQgdXMgdHJ5IHRvIGZpeCB0aGlzIGluIGNsYXNzLiANCg0KYGBge3IsIGV2YWwgPSBGQUxTRX0NCnJlbURyJGZpbmRFbGVtZW50KHVzaW5nID0gInhwYXRoIiwgdmFsdWUgPSAiLy9idXR0b25bQGNsYXNzPSdmYWNldC1saXN0X19leHBhbmQtc3ViJyBhbmQgQGFyaWEtY29udHJvbHM9J3RvZ2dsZS1zdGFmZi1kZXBhcnRtZW50LTk2MSddIikkaGlnaGxpZ2h0RWxlbWVudCgpDQoNCiNJZiB5b3Ugd291bGQgZm9sbG93IHRoZSBjb21wbGV0ZSBodG1sIHN0cnVjdHVyZSB5b3Ugd2lsbCBhcnJpdmUgYXQgdGhlIHNhbWUgcG9pbnQuIA0KcmVtRHIkZmluZEVsZW1lbnQodXNpbmcgPSAieHBhdGgiLCB2YWx1ZSA9ICIvaHRtbC9ib2R5L2RpdlsxXS9kaXYvZGl2WzFdL2FzaWRlL2Rpdi9kaXZbMjddL2Rpdi91bC9saS9kaXYvdWwvbGlbOV0vYnV0dG9uIikkaGlnaGxpZ2h0RWxlbWVudCgpDQoNCiNhbmQgY2xpY2sNCnJlbURyJGZpbmRFbGVtZW50KHVzaW5nID0gInhwYXRoIiwgdmFsdWUgPSAiLy9idXR0b25bQGNsYXNzPSdmYWNldC1saXN0X19leHBhbmQtc3ViJyBhbmQgQGFyaWEtY29udHJvbHM9J3RvZ2dsZS1zdGFmZi1kZXBhcnRtZW50LTk2MSddIikkY2xpY2tFbGVtZW50KCkNCmBgYA0KDQpIZXJlIHdlIGVuY291bnRlciB0aGUgYnVnOiBub3QgYWxsIGRlcGFydG1lbnRzIGFyZSBzaG93bi4gWW91IGhhdmUgdG8gY2xpY2sgb24gbGVzcyBhbmQgbW9yZS4gDQoNCj4gKipBc3NpZ25tZW50OioqDQo+ICBMZXQgeW91ciBjcmF3bGVyIGNsaWNrIG9uIGxlc3MgYW5kIG1vcmUgKG9mIGZpeCB0aGUgYnVnIGluIGEgYmV0dGVyIHdheSkgIA0KDQpPbmNlIGRvbmUsIHdlIGNhbiBjb250aW51ZQ0KYGBge3IsIGV2YWwgPSBGQUxTRX0NCiNjbGljayBvbiBzb2Npb2xvZ3kNCnJlbURyJGZpbmRFbGVtZW50KHVzaW5nID0gInhwYXRoIiwgdmFsdWUgPSAiLy9pbnB1dFtAdHlwZT0nY2hlY2tib3gnIGFuZCBAY2xhc3M9J2ZhY2V0cy1jaGVja2JveCcgYW5kIEBpZD0nc3RhZmYtZGVwYXJ0bWVudC05OTInXSIpJGNsaWNrRWxlbWVudCgpDQoNCiNhbW9uZyBhbGwgc3RhZmYgbWVtYmVycywgc2VsZWN0IHJlc2VhcmNoZXJzDQoNCnJlbURyJGZpbmRFbGVtZW50KHVzaW5nID0gInhwYXRoIiwgdmFsdWUgPSAiLy9pbnB1dFtAdHlwZT0nY2hlY2tib3gnIGFuZCBAY2xhc3M9J2ZhY2V0cy1jaGVja2JveCcgYW5kIEBpZD0nc3RhZmYtc3RhZmYtcmVzZWFyY2hlcnMnXSIpJGhpZ2hsaWdodEVsZW1lbnQoKQ0KDQojbW9yZSBjb21wbGljYXRlZCBidXQgaW5zaWdodGZ1bCANCnJlbURyJGZpbmRFbGVtZW50KHVzaW5nID0gImlkIiwgdmFsdWUgPSAiYmxvY2stc3RhZmZzdGFmZiIpJGhpZ2hsaWdodEVsZW1lbnQoKQ0KDQpyZW1EciRmaW5kRWxlbWVudCh1c2luZyA9ICJpZCIsIHZhbHVlID0gImJsb2NrLXN0YWZmc3RhZmYiKSRnZXRFbGVtZW50VGV4dCgpDQoNCiNmaXJzdCBnZXQgY2F0ZWdvcmllczoNCnJlbURyJGZpbmRFbGVtZW50KHVzaW5nID0gImlkIiwgdmFsdWUgPSAiYmxvY2stc3RhZmZzdGFmZiIpJGdldEVsZW1lbnRUZXh0KCkgJT4lICNnZXQgZWxlbWVudCB0ZXh0Ow0KICB1bmxpc3QoLikgJT4lICN1bmxpc3QNCiAgc3Ryc3BsaXQoLiwgIlxuIikgJT4lICNzcGxpdCBieSBcbg0KICAuW1sxXV0gJT4lICN0YWtlIHRoZSBmaXJzdCBlbGVtZW50IGZyb20gdGhlIGxpc3QNCiAgLlstMV0gLT4gc3RhZmZfY2F0cyAjcmVtb3ZlIHRoZSBoZWFkZXIgInN0YWZmIg0KDQojdGFrZSB0aGUgaW5kaWNhdG9yIG9mIHJlc2VhcmNoZXJzDQpyZXNlYXJjaGVycyA8LSBncmVwKCJSZXNlYXJjaGVycyIsIHN0YWZmX2NhdHMpICAgDQoNCiNjaGVjayB0aGUgY29ycmVzcG9uZGluZyBjaGVja2JveA0KcmVtRHIkZmluZEVsZW1lbnRzKHVzaW5nID0gImNsYXNzIG5hbWUiLCB2YWx1ZSA9ICJmYWNldHMtY2hlY2tib3giKVtbcmVzZWFyY2hlcnNdXSRjbGlja0VsZW1lbnQoKQ0KYGBgDQoNCg0KTmF0dXJhbGx5LCB0aGlzIGNhbiBhbHNvIGJlIGFjaGlldmVkIGJ5IHVzaW5nIHRoZSBVUkwgaHR0cHM6Ly93d3cucnUubmwvZW4vc2VhcmNoL3Njb3BlL3N0YWZmL3N0YWZmLWRlcGFydG1lbnQvWC9zdGFmZi1zdGFmZi9yZXNlYXJjaGVycywgd2hlcmUgWCBzaG91bGQgYmUgcmVwbGFjZWQgYnkgZGVwYXJ0bWVudC1pZCwgd2hpY2ggY2FuIGJlIHN0b3JlZCBpbiBhIGxpc3QgYmVmb3JlaGFuZCAodGhpcyBpcyB1c2VmdWwgaWYgd2Ugd2FudCBtdWx0aXBsZSBkZXBhcnRtZW50cyBvZiBhIGZhY3VsdHkpDQoNCk5vdyB3ZSBoYXZlIGFsbCBzb2Npb2xvZ3kgcmVzZWFyY2hlcnMgKG9yIG9mIGFueSBvdGhlciBkZXBhcnRtZW50KSBidXQgdGhlc2UgYXJlIHNwcmVhZCBhY3Jvc3MgbXVsdGlwbGUgcGFnZXMuICANClRvIGtub3cgaG93IG1hbnkgcGFnZXMgdGhlcmUgYXJlIHdlIG5hdmlnYXRlIHRvIHRoZSAibGFzdCIgcGFnZSBmaXJzdCwgc2F2ZSB0aGUgbGFzdCBwYWdlLCBnbyBiYWNrIGFuZCBsb29wIHRocm91Z2ggYWxsIHBhZ2VzOiANCg0KYGBge3J9DQpyZW1EciRmaW5kRWxlbWVudHMoImNsYXNzIG5hbWUiLCAicGFnZXJfX2xpbmstdGV4dCIpW1syXV0kY2xpY2tFbGVtZW50KCkNCg0KI2dldCB0aGUgcGFnZSBudW1iZXIgb2YgdGhpcyBwYWdlDQpucGFnZXMgPC0gcmVtRHIkZmluZEVsZW1lbnQodXNpbmcgPSAieHBhdGgiLCAiLy9hW0BhcmlhLWN1cnJlbnQ9J3BhZ2UnXSIpJGdldEVsZW1lbnRUZXh0KCkgJT4lDQogIHVubGlzdCguKSAlPiUgI3VubGlzdA0KICBzdHJzcGxpdCguLCAiXG4iKSAlPiUgI3NwbGl0IGJ5IFxuDQogIC5bWzFdXSAlPiUNCiAgLlsyXSAlPiUNCiAgYXMubnVtZXJpYyguKQ0KDQojZ28gYmFjayB0byB0aGUgZmlyc3QgcGFnZQ0KcmVtRHIkZmluZEVsZW1lbnRzKCJjbGFzcyBuYW1lIiwgInBhZ2VyX19saW5rLXRleHQiKVtbMV1dJGNsaWNrRWxlbWVudCgpDQoNCiNnZXQgYWxsIGVsZW1lbnRzIHdpdGggY2xhc3MgbmFtZSAiY2FyZF9fdGl0bGUiOyB0aGVzZSByZWZlciB0byBuYW1lcyBvZiByZXNlYXJjaGVycywgb24gdGhlIGZpcnN0IHBhZ2UuIFJldHJpZXZlIHRoZSB0ZXh0Og0KbmFtZXNfcGFnZSA8LSB1bmxpc3QobGFwcGx5KHJlbURyJGZpbmRFbGVtZW50cygiY2xhc3MgbmFtZSIsICJjYXJkX190aXRsZSIpLA0KICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeCkgeCRnZXRFbGVtZW50VGV4dCgpKSkNCg0KI25vdyByZXBlYXQgdGhpcyBmb3IgdGhlIG90aGVyIHBhZ2VzIGluIDE6bnBhZ2VzLTENCnBhZ2UxIDwtIHJlbURyJGdldEN1cnJlbnRVcmwoKQ0KDQpmb3IgKCBpIGluIDE6KG5wYWdlcy0xKSkgeyANCiAgI2luY3JlbWVudCBwYWdlIA0KICBuZXdwYWdlIDwtIHN1YigicGFnZT0wJCIsIHBhc3RlMCgicGFnZT0iLCBpKSwgcGFnZTEpDQogICNuYXZpZ2F0ZSB0byB0aGUgcGFnZQ0KICByZW1EciRuYXZpZ2F0ZShuZXdwYWdlKQ0KICAjcmV0cmlldmUgbmFtZXMNCiAgbmV3X25hbWVzIDwtIHVubGlzdChsYXBwbHkocmVtRHIkZmluZEVsZW1lbnRzKCJjbGFzcyBuYW1lIiwgImNhcmRfX3RpdGxlIiksDQogICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSB4JGdldEVsZW1lbnRUZXh0KCkpKQ0KICAjYW5kIGFkZCB0byB0aGUgbmFtZXNfcGFnZSBvYmplY3Q6DQogIG5hbWVzX3BhZ2UgPC0gYyhuYW1lc19wYWdlLCBuZXdfbmFtZXMpDQogICNhZGQgc2xlZXBlcjoNCiAgU3lzLnNsZWVwKDIpDQp9DQoNCiNhbGwgbmFtZXMhDQpwcmludChuYW1lc19wYWdlKQ0KbGVuZ3RoKG5hbWVzX3BhZ2UpDQpgYGANCg0KIyMgUHl0aG9uDQoNCmBgYHtweXRob24sIGV2YWwgPSBGQUxTRX0NCiNuYXZpZ2F0ZSB0byBSVSBzdGFmZiBwYWdlDQpyZW1Eci5nZXQoImh0dHBzOi8vd3d3LnJ1Lm5sL2VuL3NlYXJjaC9zY29wZS9zdGFmZi8iKQ0KDQpyZW1Eci5tYXhpbWl6ZV93aW5kb3coKQ0KDQojd2Ugd2FudCB0byBnZXQgc2Nob2xhcnMgZnJvbSB0aGUgc29jaW9sb2d5IGRlcGFydG1lbnQ7IHNvIHdlIHVzZSB0aGUgd2Vic2l0ZSdzIGNvbGxhcHNpYmxlIGxpc3RzDQojZXhwYW5kIGNvbnRlbnQgdW5kZXIgUmFkYm91ZCBVbml2ZXJzaXR5DQpyZW1Eci5maW5kX2VsZW1lbnQoQnkuQ0xBU1NfTkFNRSwgImZhY2V0LWxpc3RfX2V4cGFuZC1zdWIiKS5jbGljaygpDQoNCiNleHBhbmQgY29udGVudCB1bmRlciBGYWN1bHR5IG9mIHNvY2lhbCBzY2llbmNlIChkZXAgaWQgOTYxKQ0KI3VzaW5nIGl0cyB4cGF0aA0KcmVtRHIuZmluZF9lbGVtZW50KEJ5LlhQQVRILCAiLy9idXR0b25bQGNsYXNzPSdmYWNldC1saXN0X19leHBhbmQtc3ViJyBhbmQgQGFyaWEtY29udHJvbHM9J3RvZ2dsZS1zdGFmZi1kZXBhcnRtZW50LTk2MSddIikuY2xpY2soKQ0KDQojY2xpY2sgb24gc29jaW9sb2d5DQpyZW1Eci5maW5kX2VsZW1lbnQoQnkuWFBBVEgsICIvL2lucHV0W0B0eXBlPSdjaGVja2JveCcgYW5kIEBjbGFzcz0nZmFjZXRzLWNoZWNrYm94JyBhbmQgQGlkPSdzdGFmZi1kZXBhcnRtZW50LTk5MiddIikuY2xpY2soKQ0KDQojYW1vbmcgYWxsIHN0YWZmIG1lbWJlcnMsIHNlbGVjdCByZXNlYXJjaGVycw0KI2dldCB0aGUgaW5kaWNhdG9yIG9mIHRoZSBjaGVja2JveCB0aGF0IGlzICJSZXNlYXJjaGVycyI7DQoNCiNmaXJzdCBnZXQgY2F0ZWdvcmllczoNCnN0YWZmX2NhdHMgPSByZW1Eci5maW5kX2VsZW1lbnQoQnkuSUQsICJibG9jay1zdGFmZnN0YWZmIikudGV4dC5zcGxpdCgiXG4iKVsxOl0NCg0KI3Rha2UgdGhlIGluZGljYXRvciBvZiAicmVzZWFyY2hlcnMiDQpyZXNlYXJjaGVycyA9IFtpIGZvciBpLCBjYXQgaW4gZW51bWVyYXRlKHN0YWZmX2NhdHMpIGlmICJSZXNlYXJjaGVycyIgaW4gY2F0XVswXQ0KDQojY2hlY2sgdGhlIGNvcnJlc3BvbmRpbmcgY2hlY2tib3gNCnJlbURyLmZpbmRfZWxlbWVudHMoQnkuQ0xBU1NfTkFNRSwgImZhY2V0cy1jaGVja2JveCIpW3Jlc2VhcmNoZXJzXS5jbGljaygpDQoNCiNuYXR1cmFsbHkgdGhpcyBjYW4gYWxzbyBiZSBhY2hpZXZlZCBieSB1c2luZyB0aGUgVVJMIGh0dHBzOi8vd3d3LnJ1Lm5sL2VuL3NlYXJjaC9zY29wZS9zdGFmZi9zdGFmZi1kZXBhcnRtZW50L1gvc3RhZmYtc3RhZmYvcmVzZWFyY2hlcnMsIHdoZXJlIFggc2hvdWxkIGJlIHJlcGxhY2VkIGJ5IGRlcGFydG1lbnQtaWQsIHdoaWNoIGNhbiBiZSBzdG9yZWQgaW4gYSBsaXN0IGJlZm9yZWhhbmQgKHRoaXMgaXMgdXNlZnVsIGlmIHdlIHdhbnQgbXVsdGlwbGUgZGVwYXJ0bWVudHMgb2YgYSBmYWN1bHR5KQ0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KDQojbm93IHdlIGhhdmUgYWxsIHNvY2lvbG9neSByZXNlYXJjaGVycyAob3Igb2YgYW55IG90aGVyIGRlcGFydG1lbnQpOw0KI2J1dCB0aGlzIGlzIHNwcmVhZCBhY3Jvc3MgbXVsdGlwbGUgcGFnZXM7DQojdG8ga25vdyBob3cgbWFueSBwYWdlcyB0aGVyZSBhcmUgLSBhbmQgdGh1cyB0aHJvdWdoIGhvdyBtYW55IHdlIG11c3Qgc2NyYXBlIC0gd2UgbmF2aWdhdGUgdG8gdGhlICJsYXN0IiBwYWdlIGZpcnN0Ow0KcmVtRHIuZmluZF9lbGVtZW50cyhCeS5DTEFTU19OQU1FLCAicGFnZXJfX2xpbmstdGV4dCIpWzFdLmNsaWNrKCkNCg0KI2dldCB0aGUgcGFnZSBudW1iZXIgb2YgdGhpcyBwYWdlDQpucGFnZXMgPSBpbnQocmVtRHIuZmluZF9lbGVtZW50KEJ5LlhQQVRILCAiLy9hW0BhcmlhLWN1cnJlbnQ9J3BhZ2UnXSIpLnRleHQuc3BsaXQoIlxuIilbMV0pDQogIA0KI2dvIGJhY2sgdG8gdGhlIGZpcnN0IHBhZ2UNCnJlbURyLmZpbmRfZWxlbWVudHMoQnkuQ0xBU1NfTkFNRSwgInBhZ2VyX19saW5rLXRleHQiKVswXS5jbGljaygpDQoNCiNnZXQgYWxsIGVsZW1lbnRzIHdpdGggY2xhc3MgbmFtZSAiY2FyZF9fdGl0bGUiOyB0aGVzZSByZWZlciB0byBuYW1lcyBvZiByZXNlYXJjaGVycywgb24gdGhlIGZpcnN0IHBhZ2UuIFJldHJpZXZlIHRoZSB0ZXh0Og0KbmFtZXNfcGFnZSA9IFtlbGVtLnRleHQgZm9yIGVsZW0gaW4gcmVtRHIuZmluZF9lbGVtZW50cyhCeS5DTEFTU19OQU1FLCAiY2FyZF9fdGl0bGUiKV0NCg0KI25vdyByZXBlYXQgdGhpcyBmb3IgdGhlIG90aGVyIHBhZ2VzDQpwYWdlMSA9IHJlbURyLmN1cnJlbnRfdXJsDQoNCmZvciBpIGluIHJhbmdlKDEsIG5wYWdlcyk6DQogICNpbmNyZW1lbnQgcGFnZQ0KICBuZXdwYWdlID0gcGFnZTEucmVwbGFjZSgicGFnZT0wIiwgZiJwYWdlPXtpfSIpDQogIHByaW50KHN0cigibmF2aWdhdGluZyB0bzogIikgKyBuZXdwYWdlKQ0KICB0aW1lLnNsZWVwKDAuNSkNCiAgI25hdmlnYXRlIHRvIHRoZSBwYWdlDQogIHJlbURyLmdldChuZXdwYWdlKQ0KICB0aW1lLnNsZWVwKDAuNSkNCiAgI3JldHJpZXZlIG5hbWVzDQogIG5ld19uYW1lcyA9IFtlbGVtLnRleHQgZm9yIGVsZW0gaW4gcmVtRHIuZmluZF9lbGVtZW50cyhCeS5DTEFTU19OQU1FLCAiY2FyZF9fdGl0bGUiKV0NCiAgI2FuZCBhZGQgdG8gdGhlIG5hbWVzX3BhZ2Ugb2JqZWN0Og0KICBuYW1lc19wYWdlLmV4dGVuZChuZXdfbmFtZXMpDQogICNhZGQgc2xlZXBlcjoNCiAgdGltZS5zbGVlcCgyKQ0KDQojYWxsIG5hbWVzIQ0KcHJpbnQobmFtZXNfcGFnZSkNCmxlbihuYW1lc19wYWdlKQ0KYGBgIA0KDQo=


Copyright © 2024 Jochem Tolsma