In this typical exercise, we will learn how to read multiple files and plot them all together. This is the basic step of data analysis.

1 Reading the data files

  • Load the tidyverse and readxl libraries
library(tidyverse)
library(readxl)
  • Store into flist the list of all .xls files in the Data/ folder. Take a look at the list.files() function for this, and especially at its parameters path, pattern, and full.names.
flist <- list.files(path="Data", pattern = ".xls", full.names = TRUE)
  • Open one of the .xls files with Excel and study the structure of the file. At the top of the file, the first group of 2-column data is the blank measurement (columns A and B, lines 8 through 3408), while the second group under the blank is the actual raw measurement (columns A and B, lines 3413 through 6813). Working on the first file in the list, successively:
    • Read and store the blank data in a tibble called blank.df
    • Read and store the raw data in a tibble called raw.df
    • Combine them into a tibble called alldata containing the columns w, blank, raw, and transmittance, the later being the transmittance (in percent) with respect to the blank measurement (i.e. transmittance = raw / blank * 100).

The final table should look like this:

blank.df <- read_excel(flist[1], 
            range = "A8:B3408", 
            col_names = c("w","blank"))
raw.df   <- read_excel(flist[1], 
            range = "A3413:B6813", 
            col_names = c("w","raw"))
# Base R version
alldata <- tibble(blank.df, raw=raw.df$raw)
alldata$transmittance <- alldata$raw/alldata$blank*100
alldata
## # A tibble: 3,401 × 4
##        w blank   raw transmittance
##    <dbl> <dbl> <dbl>         <dbl>
##  1  4000   100  94.2          94.2
##  2  3999   100  94.3          94.3
##  3  3998   100  94.2          94.2
##  4  3997   100  94.2          94.2
##  5  3996   100  94.2          94.2
##  6  3995   100  94.2          94.2
##  7  3994   100  94.2          94.2
##  8  3993   100  94.2          94.2
##  9  3992   100  94.2          94.2
## 10  3991   100  94.2          94.2
## # … with 3,391 more rows
# Tidyverse version:
# alldata <- inner_join(blank.df, raw.df, by = "w") %>% 
#     mutate(transmittance=raw/blank*100)
  • Wrap this procedure into a function readFTIR() that, given a file name, returns a tibble with the columns w, blank, raw, and transmittance. This means you just need to do the same thing as before but providing the file name as a variable, and finish the function by printing the alldata tibble – if you don’t print it as the last action of your function, it won’t return the tibble. Test it on the first file in the list.
readFTIR <- function(filename) {
    blank.df <- read_excel(filename, 
                range = "A8:B3408", 
                col_names = c("w","blank"))
    raw.df   <- read_excel(filename, 
                range = "A3413:B6813", 
                col_names = c("w","raw"))
    # Base R version
    alldata <- tibble(blank.df, raw=raw.df$raw)
    alldata$transmittance <- alldata$raw/alldata$blank*100
    alldata
    # Tidyverse version:
    # inner_join(blank.df, raw.df, by = "w") %>% 
    #     mutate(transmittance=raw/blank*100)
}
  • Plot the first file in flist, and try to make your plot look like this:
readFTIR(flist[1]) %>% 
    ggplot(aes(x=w, y=raw))+
        geom_line(size=1)+
        geom_line(aes(y=blank), lty=2, col="gray")+
        labs(x=expression(paste("Wavenumber [", cm^{-1},"]")),
             y="Transmittance [%]")+
        scale_x_continuous(breaks=seq(0,5000,500))+
        theme_bw()+
        theme(panel.grid=element_blank(),
              text=element_text(size=16))

  • Now we want to read all files in the list and store them in a tidy tibble. For this we will start easy and use a for loop. This is the step-by-step procedure:
    • Define an empty tibble called df
    • Define a for loop on all elements of flist
    • Then, within this for loop, successively:
      • Read the current file with the above-defined readFTIR() function and store it in tempdf
      • Add the column name to tempdf containing the current file name. If needed, trim the path from the name string using basename().
      • Stack tempdf below df by binding them row-wise (look at bind_rows()).

The final table should look like this:

df <- tibble()
for(file in flist){
    tempdf <- readFTIR(file)
    tempdf$name <- basename(file)
    df <- bind_rows(df, tempdf)
}
df
## # A tibble: 34,010 × 5
##        w blank   raw transmittance name            
##    <dbl> <dbl> <dbl>         <dbl> <chr>           
##  1  4000   100  94.2          94.2 sample_0_25C.xls
##  2  3999   100  94.3          94.3 sample_0_25C.xls
##  3  3998   100  94.2          94.2 sample_0_25C.xls
##  4  3997   100  94.2          94.2 sample_0_25C.xls
##  5  3996   100  94.2          94.2 sample_0_25C.xls
##  6  3995   100  94.2          94.2 sample_0_25C.xls
##  7  3994   100  94.2          94.2 sample_0_25C.xls
##  8  3993   100  94.2          94.2 sample_0_25C.xls
##  9  3992   100  94.2          94.2 sample_0_25C.xls
## 10  3991   100  94.2          94.2 sample_0_25C.xls
## # … with 34,000 more rows
  • More advanced: you can do this without using a for loop using the tidyverse. For this, look into the map() function as described here.
df  <- tibble(name = flist) %>% 
    mutate(data=map(name, ~readFTIR(.)),
           name=basename(name)) %>% 
    unnest(data)
write.csv(df,"Data/alldata.csv", row.names=FALSE)

2 Plotting

In case you didn’t manage to do the above procedure, just load the alldata.csv file in the Data folder by running df <- read_csv("Data/alldata.csv").

We will now plot all these files together using ggplot2.

  • First, you can try several things, like:
    • Plot them all together in one frame with a color depending on the file name.
    • Plot them all on a different frame (look at facet_wrap()) for each file.

The final plots should look like this:

theme_set(theme_bw()+
          theme(strip.background = element_blank(),
                strip.text = element_text(face="bold")))
ggplot(data=df, aes(x=w, y=transmittance, color=name))+
    geom_line()+
    labs(x=expression(paste("Wavenumber [", cm^{-1},"]")),
         y="Transmittance [%]")

ggplot(data=df, aes(x=w, y=transmittance))+
    geom_line()+
    labs(x=expression(paste("Wavenumber [", cm^{-1},"]")),
         y="Transmittance [%]")+
    facet_wrap(~name)

  • You see in the file names that there are actually 5 samples with 2 temperatures for each sample. Based on the column name of the df tibble, create the columns sample and temperature that contain numerical values of the sample number and temperature. Think about using the function gsub() to replace (delete) text in a string, the substr(string, start, end) function to get a portion of a string. You can also take a look at the separate() function from the tidyverse (see here).
# Base version
df$sample      <- as.numeric(substr(df$name,8,8))
df$temperature <- substr(df$name,10,20)
df$temperature <- as.numeric(gsub("C.xls","",df$temperature))
# tidyverse version
df  <- tibble(name = flist) %>% 
    mutate(data=map(name, ~readFTIR(.)),
           name=basename(name)) %>% 
    separate(name, c(NA, "sample","temperature",NA), convert = TRUE) %>% 
    mutate(temperature=as.numeric(gsub("C","",temperature))) %>% 
    unnest(data)
  • Now it is time to make nicer plots:
    • Make a grid plot showing the data evolution for each sample as a function of temperature
    • Make a grid plot with one frame per sample, and the two temperatures in different colors.

The final plots should look like this:

ggplot(data=df, aes(x=w, y=transmittance))+
    geom_line()+
    labs(x=expression(paste("Wavenumber [", cm^{-1},"]")),
         y="Transmittance [%]")+
    facet_grid(paste("Sample", sample) ~ paste(temperature,"ËšC"))

ggplot(data=df, aes(x=w, y=transmittance, color=factor(temperature)))+
    geom_line()+
    labs(x=expression(paste("Wavenumber [", cm^{-1},"]")),
         y="Transmittance [%]",
         color="Temperature [ËšC]")+
    facet_wrap(~paste("Sample",sample))

LS0tCnRpdGxlIDogIlIgRXhlcmNpc2VzIC0gTG9hZCBhbmQgcGxvdCBtdWx0aXBsZSBmaWxlcyIKZGF0ZSAgOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDogCiAgICBodG1sX2RvY3VtZW50OgogICAgICAgIHRvYyAgICAgICAgICAgIDogeWVzCiAgICAgICAgdG9jX2Zsb2F0ICAgICAgOiB5ZXMKICAgICAgICB0b2NfZGVwdGggICAgICA6IDQKICAgICAgICBoaWdobGlnaHQgICAgICA6IHRhbmdvCiAgICAgICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgICAgICBjb2RlX2Rvd25sb2FkICA6IFRSVUUKcGFyYW1zOiAKICAgIHNvbHV0aW9uOgogICAgICAgIHZhbHVlOiBUUlVFCi0tLQoKYGBge3IgZWNobz1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgZmlnLmFsaWduPSJjZW50ZXIifQpsaWJyYXJ5KGRvd25sb2FkdGhpcykKZG93bmxvYWRfbGluaygKICBsaW5rID0gIi4vQXJjaGl2ZS56aXAiLAogIG91dHB1dF9uYW1lID0gIkRhdGEgRmlsZXMiLAogIGJ1dHRvbl9sYWJlbCA9ICJEb3dubG9hZCBEYXRhIEZpbGVzIiwKICBidXR0b25fdHlwZSA9ICJkZWZhdWx0IiwKICBoYXNfaWNvbiA9IFRSVUUsCiAgaWNvbiA9ICJmYSBmYS1zYXZlIiwKICBzZWxmX2NvbnRhaW5lZCA9IEZBTFNFCikKYGBgCjxicj4KCmBgYHtyIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChjYWNoZSA9IEZBTFNFLCBvdXQud2lkdGg9JzEwMCUnLCB3YXJuaW5ncz1GQUxTRSwgbWVzc2FnZT1GQUxTRSkKb3B0aW9ucyh3aWR0aCA9IDgwKQpgYGAKCi0tLS0KCkluIHRoaXMgdHlwaWNhbCBleGVyY2lzZSwgd2Ugd2lsbCBsZWFybiBob3cgdG8gcmVhZCBtdWx0aXBsZSBmaWxlcyBhbmQgcGxvdCB0aGVtIGFsbCB0b2dldGhlci4gVGhpcyBpcyB0aGUgYmFzaWMgc3RlcCBvZiBkYXRhIGFuYWx5c2lzLgoKIyBSZWFkaW5nIHRoZSBkYXRhIGZpbGVzCgotIExvYWQgdGhlIGB0aWR5dmVyc2VgIGFuZCBgcmVhZHhsYCBsaWJyYXJpZXMKCmBgYHtyIGVjaG89cGFyYW1zJHNvbHV0aW9uLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShyZWFkeGwpCmBgYAoKLSBTdG9yZSBpbnRvIGBmbGlzdGAgdGhlIGxpc3Qgb2YgYWxsIGAueGxzYCBmaWxlcyBpbiB0aGUgYERhdGEvYCBmb2xkZXIuIFRha2UgYSBsb29rIGF0IHRoZSBgbGlzdC5maWxlcygpYHsuUn0gZnVuY3Rpb24gZm9yIHRoaXMsIGFuZCBlc3BlY2lhbGx5IGF0IGl0cyBwYXJhbWV0ZXJzIGBwYXRoYCwgYHBhdHRlcm5gLCBhbmQgYGZ1bGwubmFtZXNgLgoKYGBge3IgZWNobz1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0V9CmZsaXN0IDwtIGxpc3QuZmlsZXMocGF0aD0iRGF0YSIsIHBhdHRlcm4gPSAiLnhscyIsIGZ1bGwubmFtZXMgPSBUUlVFKQpgYGAKCi0gT3BlbiBvbmUgb2YgdGhlIC54bHMgZmlsZXMgd2l0aCBFeGNlbCBhbmQgc3R1ZHkgdGhlIHN0cnVjdHVyZSBvZiB0aGUgZmlsZS4gQXQgdGhlIHRvcCBvZiB0aGUgZmlsZSwgdGhlIGZpcnN0IGdyb3VwIG9mIDItY29sdW1uIGRhdGEgaXMgdGhlIGJsYW5rIG1lYXN1cmVtZW50IChjb2x1bW5zIEEgYW5kIEIsIGxpbmVzIDggdGhyb3VnaCAzNDA4KSwgd2hpbGUgdGhlIHNlY29uZCBncm91cCB1bmRlciB0aGUgYmxhbmsgaXMgdGhlIGFjdHVhbCByYXcgbWVhc3VyZW1lbnQgKGNvbHVtbnMgQSBhbmQgQiwgbGluZXMgMzQxMyB0aHJvdWdoIDY4MTMpLiBXb3JraW5nIG9uIHRoZSBmaXJzdCBmaWxlIGluIHRoZSBsaXN0LCBzdWNjZXNzaXZlbHk6CiAgICArIFJlYWQgYW5kIHN0b3JlIHRoZSBibGFuayBkYXRhIGluIGEgdGliYmxlIGNhbGxlZCBgYmxhbmsuZGZgCiAgICArIFJlYWQgYW5kIHN0b3JlIHRoZSByYXcgZGF0YSBpbiBhIHRpYmJsZSBjYWxsZWQgYHJhdy5kZmAKICAgICsgQ29tYmluZSB0aGVtIGludG8gYSB0aWJibGUgY2FsbGVkICBgYWxsZGF0YWAgY29udGFpbmluZyB0aGUgY29sdW1ucyBgd2AsIGBibGFua2AsIGByYXdgLCBhbmQgYHRyYW5zbWl0dGFuY2VgLCB0aGUgbGF0ZXIgYmVpbmcgdGhlIHRyYW5zbWl0dGFuY2UgKGluIHBlcmNlbnQpIHdpdGggcmVzcGVjdCB0byB0aGUgYmxhbmsgbWVhc3VyZW1lbnQgKCppLmUuKiBgdHJhbnNtaXR0YW5jZSA9IHJhdyAvIGJsYW5rICogMTAwYCkuCgpUaGUgZmluYWwgdGFibGUgc2hvdWxkIGxvb2sgbGlrZSB0aGlzOgoKYGBge3IgZWNobz1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0V9CmJsYW5rLmRmIDwtIHJlYWRfZXhjZWwoZmxpc3RbMV0sIAogICAgICAgICAgICByYW5nZSA9ICJBODpCMzQwOCIsIAogICAgICAgICAgICBjb2xfbmFtZXMgPSBjKCJ3IiwiYmxhbmsiKSkKcmF3LmRmICAgPC0gcmVhZF9leGNlbChmbGlzdFsxXSwgCiAgICAgICAgICAgIHJhbmdlID0gIkEzNDEzOkI2ODEzIiwgCiAgICAgICAgICAgIGNvbF9uYW1lcyA9IGMoInciLCJyYXciKSkKIyBCYXNlIFIgdmVyc2lvbgphbGxkYXRhIDwtIHRpYmJsZShibGFuay5kZiwgcmF3PXJhdy5kZiRyYXcpCmFsbGRhdGEkdHJhbnNtaXR0YW5jZSA8LSBhbGxkYXRhJHJhdy9hbGxkYXRhJGJsYW5rKjEwMAphbGxkYXRhCiMgVGlkeXZlcnNlIHZlcnNpb246CiMgYWxsZGF0YSA8LSBpbm5lcl9qb2luKGJsYW5rLmRmLCByYXcuZGYsIGJ5ID0gInciKSAlPiUgCiMgICAgIG11dGF0ZSh0cmFuc21pdHRhbmNlPXJhdy9ibGFuayoxMDApCmBgYAoKLSBXcmFwIHRoaXMgcHJvY2VkdXJlIGludG8gYSBmdW5jdGlvbiBgcmVhZEZUSVIoKWB7LlJ9IHRoYXQsIGdpdmVuIGEgZmlsZSBuYW1lLCByZXR1cm5zIGEgdGliYmxlIHdpdGggdGhlIGNvbHVtbnMgYHdgLCBgYmxhbmtgLCBgcmF3YCwgYW5kIGB0cmFuc21pdHRhbmNlYC4gVGhpcyBtZWFucyB5b3UganVzdCBuZWVkIHRvIGRvIHRoZSBzYW1lIHRoaW5nIGFzIGJlZm9yZSBidXQgcHJvdmlkaW5nIHRoZSBmaWxlIG5hbWUgYXMgYSB2YXJpYWJsZSwgYW5kIGZpbmlzaCB0aGUgZnVuY3Rpb24gYnkgcHJpbnRpbmcgdGhlIGBhbGxkYXRhYCB0aWJibGUgLS0gaWYgeW91IGRvbid0IHByaW50IGl0IGFzIHRoZSBsYXN0IGFjdGlvbiBvZiB5b3VyIGZ1bmN0aW9uLCBpdCB3b24ndCByZXR1cm4gdGhlIHRpYmJsZS4gVGVzdCBpdCBvbiB0aGUgZmlyc3QgZmlsZSBpbiB0aGUgbGlzdC4KCmBgYHtyIGVjaG89cGFyYW1zJHNvbHV0aW9uLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPUZBTFNFfQpyZWFkRlRJUiA8LSBmdW5jdGlvbihmaWxlbmFtZSkgewogICAgYmxhbmsuZGYgPC0gcmVhZF9leGNlbChmaWxlbmFtZSwgCiAgICAgICAgICAgICAgICByYW5nZSA9ICJBODpCMzQwOCIsIAogICAgICAgICAgICAgICAgY29sX25hbWVzID0gYygidyIsImJsYW5rIikpCiAgICByYXcuZGYgICA8LSByZWFkX2V4Y2VsKGZpbGVuYW1lLCAKICAgICAgICAgICAgICAgIHJhbmdlID0gIkEzNDEzOkI2ODEzIiwgCiAgICAgICAgICAgICAgICBjb2xfbmFtZXMgPSBjKCJ3IiwicmF3IikpCiAgICAjIEJhc2UgUiB2ZXJzaW9uCiAgICBhbGxkYXRhIDwtIHRpYmJsZShibGFuay5kZiwgcmF3PXJhdy5kZiRyYXcpCiAgICBhbGxkYXRhJHRyYW5zbWl0dGFuY2UgPC0gYWxsZGF0YSRyYXcvYWxsZGF0YSRibGFuayoxMDAKICAgIGFsbGRhdGEKICAgICMgVGlkeXZlcnNlIHZlcnNpb246CiAgICAjIGlubmVyX2pvaW4oYmxhbmsuZGYsIHJhdy5kZiwgYnkgPSAidyIpICU+JSAKICAgICMgICAgIG11dGF0ZSh0cmFuc21pdHRhbmNlPXJhdy9ibGFuayoxMDApCn0KYGBgCgoKLSBQbG90IHRoZSBmaXJzdCBmaWxlIGluIGBmbGlzdGAsIGFuZCB0cnkgdG8gbWFrZSB5b3VyIHBsb3QgbG9vayBsaWtlIHRoaXM6CgpgYGB7ciBlY2hvPXBhcmFtcyRzb2x1dGlvbiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1GQUxTRX0KcmVhZEZUSVIoZmxpc3RbMV0pICU+JSAKICAgIGdncGxvdChhZXMoeD13LCB5PXJhdykpKwogICAgICAgIGdlb21fbGluZShzaXplPTEpKwogICAgICAgIGdlb21fbGluZShhZXMoeT1ibGFuayksIGx0eT0yLCBjb2w9ImdyYXkiKSsKICAgICAgICBsYWJzKHg9ZXhwcmVzc2lvbihwYXN0ZSgiV2F2ZW51bWJlciBbIiwgY21eey0xfSwiXSIpKSwKICAgICAgICAgICAgIHk9IlRyYW5zbWl0dGFuY2UgWyVdIikrCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMCw1MDAwLDUwMCkpKwogICAgICAgIHRoZW1lX2J3KCkrCiAgICAgICAgdGhlbWUocGFuZWwuZ3JpZD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgdGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xNikpCmBgYAoKLSBOb3cgd2Ugd2FudCB0byByZWFkIGFsbCBmaWxlcyBpbiB0aGUgbGlzdCBhbmQgc3RvcmUgdGhlbSBpbiBhIHRpZHkgdGliYmxlLiBGb3IgdGhpcyB3ZSB3aWxsIHN0YXJ0IGVhc3kgYW5kIHVzZSBhIGBmb3JgIGxvb3AuIFRoaXMgaXMgdGhlIHN0ZXAtYnktc3RlcCBwcm9jZWR1cmU6CiAgICArIERlZmluZSBhbiBlbXB0eSB0aWJibGUgY2FsbGVkIGBkZmAKICAgICsgRGVmaW5lIGEgYGZvcmAgbG9vcCBvbiBhbGwgZWxlbWVudHMgb2YgYGZsaXN0YAogICAgKyBUaGVuLCB3aXRoaW4gdGhpcyBgZm9yYCBsb29wLCBzdWNjZXNzaXZlbHk6CiAgICAgICAgKyBSZWFkIHRoZSBjdXJyZW50IGZpbGUgd2l0aCB0aGUgYWJvdmUtZGVmaW5lZCBgcmVhZEZUSVIoKWAgZnVuY3Rpb24gYW5kIHN0b3JlIGl0IGluIGB0ZW1wZGZgCiAgICAgICAgKyBBZGQgdGhlIGNvbHVtbiBgbmFtZWAgdG8gYHRlbXBkZmAgY29udGFpbmluZyB0aGUgY3VycmVudCBmaWxlIG5hbWUuIElmIG5lZWRlZCwgdHJpbSB0aGUgcGF0aCBmcm9tIHRoZSBuYW1lIHN0cmluZyB1c2luZyBgYmFzZW5hbWUoKWB7LlJ9LgogICAgICAgICsgU3RhY2sgYHRlbXBkZmAgYmVsb3cgYGRmYCBieSBiaW5kaW5nIHRoZW0gcm93LXdpc2UgKGxvb2sgYXQgYGJpbmRfcm93cygpYHsuUn0pLgoKVGhlIGZpbmFsIHRhYmxlIHNob3VsZCBsb29rIGxpa2UgdGhpczoKCmBgYHtyIGVjaG89cGFyYW1zJHNvbHV0aW9uLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPUZBTFNFfQpkZiA8LSB0aWJibGUoKQpmb3IoZmlsZSBpbiBmbGlzdCl7CiAgICB0ZW1wZGYgPC0gcmVhZEZUSVIoZmlsZSkKICAgIHRlbXBkZiRuYW1lIDwtIGJhc2VuYW1lKGZpbGUpCiAgICBkZiA8LSBiaW5kX3Jvd3MoZGYsIHRlbXBkZikKfQpkZgpgYGAKCi0gTW9yZSBhZHZhbmNlZDogeW91IGNhbiBkbyB0aGlzIHdpdGhvdXQgdXNpbmcgYSBgZm9yYCBsb29wIHVzaW5nIHRoZSBgdGlkeXZlcnNlYC4gRm9yIHRoaXMsIGxvb2sgaW50byB0aGUgYG1hcCgpYHsuUn0gZnVuY3Rpb24gYXMgZGVzY3JpYmVkIFtoZXJlXShodHRwczovL2xtaS5jbnJzLmZyL3IvZGF0YS1mcmFtZXMuaHRtbCNhcHBseS1hLWZ1bmN0aW9uLXJlY3Vyc2l2ZWx5LW9uLWVhY2gtZWxlbWVudC1vZi1hLWNvbHVtbikuCgpgYGB7ciBlY2hvPXBhcmFtcyRzb2x1dGlvbiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1GQUxTRX0KZGYgIDwtIHRpYmJsZShuYW1lID0gZmxpc3QpICU+JSAKICAgIG11dGF0ZShkYXRhPW1hcChuYW1lLCB+cmVhZEZUSVIoLikpLAogICAgICAgICAgIG5hbWU9YmFzZW5hbWUobmFtZSkpICU+JSAKICAgIHVubmVzdChkYXRhKQp3cml0ZS5jc3YoZGYsIkRhdGEvYWxsZGF0YS5jc3YiLCByb3cubmFtZXM9RkFMU0UpCmBgYAoKCiMgUGxvdHRpbmcKCkluIGNhc2UgeW91IGRpZG4ndCBtYW5hZ2UgdG8gZG8gdGhlIGFib3ZlIHByb2NlZHVyZSwganVzdCBsb2FkIHRoZSBgYWxsZGF0YS5jc3ZgIGZpbGUgaW4gdGhlIGBEYXRhYCBmb2xkZXIgYnkgcnVubmluZyBgZGYgPC0gcmVhZF9jc3YoIkRhdGEvYWxsZGF0YS5jc3YiKWB7LlJ9LgoKV2Ugd2lsbCBub3cgcGxvdCBhbGwgdGhlc2UgZmlsZXMgdG9nZXRoZXIgdXNpbmcgYGdncGxvdDJgLgoKLSBGaXJzdCwgeW91IGNhbiB0cnkgc2V2ZXJhbCB0aGluZ3MsIGxpa2U6CiAgICArIFBsb3QgdGhlbSBhbGwgdG9nZXRoZXIgaW4gb25lIGZyYW1lIHdpdGggYSBjb2xvciBkZXBlbmRpbmcgb24gdGhlIGZpbGUgbmFtZS4KICAgICsgUGxvdCB0aGVtIGFsbCBvbiBhIGRpZmZlcmVudCBmcmFtZSAobG9vayBhdCBgZmFjZXRfd3JhcCgpYHsuUn0pIGZvciBlYWNoIGZpbGUuCgpUaGUgZmluYWwgcGxvdHMgc2hvdWxkIGxvb2sgbGlrZSB0aGlzOgoKYGBge3IgZWNobz1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0V9CnRoZW1lX3NldCh0aGVtZV9idygpKwogICAgICAgICAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoZmFjZT0iYm9sZCIpKSkKZ2dwbG90KGRhdGE9ZGYsIGFlcyh4PXcsIHk9dHJhbnNtaXR0YW5jZSwgY29sb3I9bmFtZSkpKwogICAgZ2VvbV9saW5lKCkrCiAgICBsYWJzKHg9ZXhwcmVzc2lvbihwYXN0ZSgiV2F2ZW51bWJlciBbIiwgY21eey0xfSwiXSIpKSwKICAgICAgICAgeT0iVHJhbnNtaXR0YW5jZSBbJV0iKQpnZ3Bsb3QoZGF0YT1kZiwgYWVzKHg9dywgeT10cmFuc21pdHRhbmNlKSkrCiAgICBnZW9tX2xpbmUoKSsKICAgIGxhYnMoeD1leHByZXNzaW9uKHBhc3RlKCJXYXZlbnVtYmVyIFsiLCBjbV57LTF9LCJdIikpLAogICAgICAgICB5PSJUcmFuc21pdHRhbmNlIFslXSIpKwogICAgZmFjZXRfd3JhcCh+bmFtZSkKYGBgCgotIFlvdSBzZWUgaW4gdGhlIGZpbGUgbmFtZXMgdGhhdCB0aGVyZSBhcmUgYWN0dWFsbHkgNSBzYW1wbGVzIHdpdGggMiB0ZW1wZXJhdHVyZXMgZm9yIGVhY2ggc2FtcGxlLiBCYXNlZCBvbiB0aGUgY29sdW1uIGBuYW1lYCBvZiB0aGUgYGRmYCB0aWJibGUsIGNyZWF0ZSB0aGUgY29sdW1ucyBgc2FtcGxlYCBhbmQgYHRlbXBlcmF0dXJlYCB0aGF0IGNvbnRhaW4gKm51bWVyaWNhbCB2YWx1ZXMqIG9mIHRoZSBzYW1wbGUgbnVtYmVyIGFuZCB0ZW1wZXJhdHVyZS4gVGhpbmsgYWJvdXQgdXNpbmcgdGhlIGZ1bmN0aW9uIGBnc3ViKClgey5SfSB0byByZXBsYWNlIChkZWxldGUpIHRleHQgaW4gYSBzdHJpbmcsIHRoZSBgc3Vic3RyKHN0cmluZywgc3RhcnQsIGVuZClgey5SfSBmdW5jdGlvbiB0byBnZXQgYSBwb3J0aW9uIG9mIGEgc3RyaW5nLiBZb3UgY2FuIGFsc28gdGFrZSBhIGxvb2sgYXQgdGhlIGBzZXBhcmF0ZSgpYHsuUn0gZnVuY3Rpb24gZnJvbSB0aGUgYHRpZHl2ZXJzZWAgKHNlZSBbaGVyZV0oaHR0cHM6Ly9sbWkuY25ycy5mci9yL2RhdGEtZnJhbWVzLmh0bWwjc2VwYXJhdGUtY29sdW1ucykpLgoKYGBge3IgZWNobz1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0V9CiMgQmFzZSB2ZXJzaW9uCmRmJHNhbXBsZSAgICAgIDwtIGFzLm51bWVyaWMoc3Vic3RyKGRmJG5hbWUsOCw4KSkKZGYkdGVtcGVyYXR1cmUgPC0gc3Vic3RyKGRmJG5hbWUsMTAsMjApCmRmJHRlbXBlcmF0dXJlIDwtIGFzLm51bWVyaWMoZ3N1YigiQy54bHMiLCIiLGRmJHRlbXBlcmF0dXJlKSkKIyB0aWR5dmVyc2UgdmVyc2lvbgpkZiAgPC0gdGliYmxlKG5hbWUgPSBmbGlzdCkgJT4lIAogICAgbXV0YXRlKGRhdGE9bWFwKG5hbWUsIH5yZWFkRlRJUiguKSksCiAgICAgICAgICAgbmFtZT1iYXNlbmFtZShuYW1lKSkgJT4lIAogICAgc2VwYXJhdGUobmFtZSwgYyhOQSwgInNhbXBsZSIsInRlbXBlcmF0dXJlIixOQSksIGNvbnZlcnQgPSBUUlVFKSAlPiUgCiAgICBtdXRhdGUodGVtcGVyYXR1cmU9YXMubnVtZXJpYyhnc3ViKCJDIiwiIix0ZW1wZXJhdHVyZSkpKSAlPiUgCiAgICB1bm5lc3QoZGF0YSkKYGBgCgotIE5vdyBpdCBpcyB0aW1lIHRvIG1ha2UgbmljZXIgcGxvdHM6CiAgICAtIE1ha2UgYSBncmlkIHBsb3Qgc2hvd2luZyB0aGUgZGF0YSBldm9sdXRpb24gZm9yIGVhY2ggc2FtcGxlIGFzIGEgZnVuY3Rpb24gb2YgdGVtcGVyYXR1cmUKICAgIC0gTWFrZSBhIGdyaWQgcGxvdCB3aXRoIG9uZSBmcmFtZSBwZXIgc2FtcGxlLCBhbmQgdGhlIHR3byB0ZW1wZXJhdHVyZXMgaW4gZGlmZmVyZW50IGNvbG9ycy4KClRoZSBmaW5hbCBwbG90cyBzaG91bGQgbG9vayBsaWtlIHRoaXM6CgpgYGB7ciBlY2hvPXBhcmFtcyRzb2x1dGlvbiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1GQUxTRX0KZ2dwbG90KGRhdGE9ZGYsIGFlcyh4PXcsIHk9dHJhbnNtaXR0YW5jZSkpKwogICAgZ2VvbV9saW5lKCkrCiAgICBsYWJzKHg9ZXhwcmVzc2lvbihwYXN0ZSgiV2F2ZW51bWJlciBbIiwgY21eey0xfSwiXSIpKSwKICAgICAgICAgeT0iVHJhbnNtaXR0YW5jZSBbJV0iKSsKICAgIGZhY2V0X2dyaWQocGFzdGUoIlNhbXBsZSIsIHNhbXBsZSkgfiBwYXN0ZSh0ZW1wZXJhdHVyZSwiy5pDIikpCmdncGxvdChkYXRhPWRmLCBhZXMoeD13LCB5PXRyYW5zbWl0dGFuY2UsIGNvbG9yPWZhY3Rvcih0ZW1wZXJhdHVyZSkpKSsKICAgIGdlb21fbGluZSgpKwogICAgbGFicyh4PWV4cHJlc3Npb24ocGFzdGUoIldhdmVudW1iZXIgWyIsIGNtXnstMX0sIl0iKSksCiAgICAgICAgIHk9IlRyYW5zbWl0dGFuY2UgWyVdIiwKICAgICAgICAgY29sb3I9IlRlbXBlcmF0dXJlIFvLmkNdIikrCiAgICBmYWNldF93cmFwKH5wYXN0ZSgiU2FtcGxlIixzYW1wbGUpKQpgYGAK