devtools::load_all()

1 Introduzione

Per operazioni semplici o per esercizi si possono creare strutture dati come vettori, liste o dataframe direttamente in R. Tuttavia per lavorare con R è solito importare i dati da fonti esterne come file e database.

In R sono disponibili diverse funzioni per importare i dati nei principali formati come .csv o .txt assieme a diversi pacchetti con funzioni per importare dati nei formati provenienti da altri software come SPSS, Excel, etc. Insomma se esiste un formato specifico probabilmente esiste anche una funzione in R per importarlo 😄

1.1 La logica

A prescindere dal formato specifico, la logica è che un certo tipo di file ha un’estensione che identifica la tipologia come file di testo .txt, comma-separated values .csv, Excel .xlsx. Questo file organizza i dati in un modo specifico e quindi è importante utilizzare la funzione giusta con i giusti argomenti per poter far interpretare ad R i dati in modo corretto.

1.2 RStudio

RStudio permette di importare qualsiasi tipo di dato tramite interfaccia grafica. All’inizio può essere utile ma quando si lavora all’interno di uno script è meglio automatizzare tramite comandi qualsiasi operazione sia per salvare che per importare i dati. Potete trovare il menù di importazione con file >. import dataset come in Figura 1.1.

knitr::include_graphics("img/rstudio_import.png")
Menù di importazione dati di RStudio

Figure 1.1: Menù di importazione dati di RStudio

1.3 Tipi di formato

Le tipologie di formato dati sono tantissime ma possiamo organizzarle in 2 categorie:

  • formati esterni: sono tutti quei formati non R-specifici
  • formato R: sono quei formati che sono creati da R e direttamente compatibili

1.4 Formato R

Partiamo dai formati specifici di R perchè sono quelli più semplici da creare e importare. Il principale difetto è quello che sono appunto leggibili (principalmente) solo all’interno dell’ambiente R. Se abbiamo la necessità di lavorare con software o linguaggi diversi è probabilmente meglio usare un formato più generico.

1.4.1 .RData

Questo è il formato più generico e permette di salvare l’intera sessione di lavoro in un file unico per poi essere eventualmente ripristinata. Non è un vero e proprio formato specifico e corrisponde più a fare una fotografia (~backup) alla sessione di lavoro. In generale non consiglio di usare questo formato ma di lavorare con singoli file da salvare/importare ma in generale più essere utile a volte.

Per salvare si più usare save.image() semplicemente specificando il percorso e il nome del file con estensione .RData:

save.image(file = "data/sessione.RData")

Per ripristinare la sessione si può usare la funzione load() specificando il percorso del file.

load(file = "data/sessione.RData")

1.4.2 .rda

Il formato .rda è quello più comune in R e permette di salvare uno o più oggetti all’interno di un unico file per poi ricaricarlo/i direttamente. Il principio è lo stesso dei file .RData ma al contrario del precedente permette di salvare solo alcuni oggetti. Il comando per salvare è save() dove vanno inseriti gli oggetti separati da virgola e poi il nome ed il percorso del file da salvare. Salviamo il dataset iris, un vettore my_vect e la stringa "hello world" dentro un unico oggetto my_obj.rda:

iris <- iris
my_vect <- 1:10
my_string <- "hello world"

save(iris, my_vect, my_string, file = "data/my_obj.rda")

Se vogliamo ricaricare questo insieme di oggetti all’interno di R possiamo usare la funzione load() specificando il percorso del file. Gli oggetti saranno non solo caricati ma salvati direttamente nel global environment:

load(file = "data/my_obj.rda")

I lati positivi di questo approccio sono l’estrema semplicità nel salvare e caricare i file mentre l’aspetto negativo è che non possiamo cambiare i nomi con cui i file vengono importati.

1.4.3 .rds

Il formato .rds è molto simile al precedente ma leggermente meno flessibile. In particolare è possibile salvare un solo oggetto all’interno di un file .rds. Il lato negativo è quindi che non possiamo salvare una sessione intera di oggetti mentre il lato positivo è che possiamo rinominare l’oggetto quando viene importato. In generale ritengo migliore il formato .rds perchè nonostante la necessità di più file permette una gestione più flessibile di cosa importare e come viene rappresentato nell’ambiente di lavoro. La funzione per salvare è saveRDS() specificando l’oggetto e il percorso/nome e per importare readRDS() specificando il percorso del file. Salviamo il dataset iris come iris.rds:

saveRDS(iris, file = "data/iris.rds")

Re-importiamo il dataset iris ma chiamandolo my_iris:

my_iris <- readRDS("data/iris.rds")

Come vedete è necessario assegnare <- il risultato ad un oggetto altrimenti non è possibile avere disponibile l’oggetto nell’ambiente di lavoro.

1.4.4 Tabella riassuntiva

Formato Estensione Salvataggio Importazione Pro Contro
RDS .rds saveRDS(oggetto, file) readRDS(file) Importando si decide il nome Un solo oggetto per file
RDA .rda save(oggetto/i, file) load(file) Salvare insieme più file Carica direttamente i file con i nomi salvati
RData .RData save.image(file) load(file) Salvare tutta la sessione Carica direttamente i file con i nomi salvati

1.5 Formato esterno

Trattare i formati esterni è sicuramente complicato sia per la differenza tra i vari formati che la quantità di formati diversi. L’idea di base è che solitamente per importare i dati bisogna capire come il formato specifico rappresenta dati, caratteri speciali, elementi che separano un singolo dato dall’altro e così via.

Se prendiamo il seguente vettore x <- c(1,2,3) noi capiamo che x ha 3 elementi essendo la virgola non un dato ma un modo per separare dati. Ecco anche i vari formati hanno delle modalità per separare e organizzare i dati ed R deve capire (lo dobbiamo dire noi) quale modalità specifica è utilizzata nel tipo di dato che stiamo cercando di importare.

1.5.1 .txt

Partiamo dal formato più semplice .txt e proviamo ad importare un singolo vettore da .txt a R come oggetto.

Vediamo il file di testo comma_vector.txt:

1,2,3,4,5,6,7,8,9,10

Come vedete ci sono dei numeri separati da virgole. Per leggere questo dato in R possiamo usare la funzione scan(file, sep). In particolare dobbiamo specificare il file ovviamente e l’argomento sep = per specificare quale carattere venga interpretato come separatore:

right <- scan('data/comma_vector.txt', sep=",", quiet = TRUE)
right
##  [1]  1  2  3  4  5  6  7  8  9 10
wrong <- scan('data/comma_vector.txt', sep=";", quiet = TRUE)
## Error in scan("data/comma_vector.txt", sep = ";", quiet = TRUE): scan() expected 'a real', got '1,2,3,4,5,6,7,8,9,10'
wrong
## Error in eval(expr, envir, enclos): object 'wrong' not found

Come vedete specificando un separatore errato dice a R di importare anche , e ovviamente non è compatibile con il resto dei dati.

Solitamente è inutile importare un singolo vettore ma questo concetto di capire come è codificato il file da importare è alla base di tutti i tipi di importazione.

Un modo più complesso di organizzare i dati sempre nei file .txt è quello di ricreare un vero e proprio dataset. Il file comma_table.txt rappresenta un esempio più realistico (in piccolo) di quello che potrebbe essere un dataset vero da importare:

id, nome, age, corso
1,filippo,20,psicologia
2,andrea,22,ingegneria
3,anna,30,informatica
4,francesca,23,dams

Come vedete abbiamo delle stringhe che idealmente rappresentano i nomi delle colonne e poi i dati effettivi. Come in precedenza in questo caso abbiamo la virgola come separatore di dato ma dobbiamo in qualche modo dire ad R che la prima riga non fa parte dei dati sono i nomi delle colonne. Per importare questo tipo di dato possiamo usare il comando read.table(file, sep, header):

read.table("data/comma_table.txt", sep = ",")
##   V1        V2   V3          V4
## 1 id      nome  age       corso
## 2  1   filippo   20  psicologia
## 3  2    andrea   22  ingegneria
## 4  3      anna   30 informatica
## 5  4 francesca   23        dams

Come vedete R intepreta la prima riga come parte dei dati e aggiunge dei nomi standard per le colonne V1, V2, etc.. Aggiungendo l’argomento header = TRUE diciamo proprio questo a R.

my_data <- read.table("data/comma_table.txt", sep = ",", header = TRUE)
my_data
##   id      nome age       corso
## 1  1   filippo  20  psicologia
## 2  2    andrea  22  ingegneria
## 3  3      anna  30 informatica
## 4  4 francesca  23        dams

E’ importante notare che il carattere di separazione può essere qualsiasi altro carattere rispetto alla virgola come il punto e virgola ";" o uno spazio " ". E’ solo importante capire il carattere corretto e impostare la funzione di importazione.

1.5.2 .csv

I file csv sono più comuni per gestire dataset sopratutto perchè sono facilmente importabili e creabili da software come Excel. Il nome significa comma separated values perchè in modo simile ai file che abbiamo visto in precedenza si utilizza la virgola "," per separare i dati. Un file csv si presenta in questo modo:

"Sepal.Length","Sepal.Width","Petal.Length","Petal.Width","Species"
5.1,3.5,1.4,0.2,"setosa"
4.9,3,1.4,0.2,"setosa"
4.7,3.2,1.3,0.2,"setosa"
4.6,3.1,1.5,0.2,"setosa"
5,3.6,1.4,0.2,"setosa"
5.4,3.9,1.7,0.4,"setosa"
4.6,3.4,1.4,0.3,"setosa"
5,3.4,1.5,0.2,"setosa"
4.4,2.9,1.4,0.2,"setosa"
4.9,3.1,1.5,0.1,"setosa"

Come vedete il file è estremamente simile al txt che abbiamo importato in precedenza con la differenza che le stringe sono racchiuse tra virgolette. Il comando per leggere file csv è read.csv(file, sep, header) dove il sep = "," predefinito è la virgola (ma si può cambiare) e anche qui dobbiamo specificare il parametro header = TRUE se vogliamo importare la prima riga come nomi delle colonne. Assegnando ad un oggetto come per read.table() avremo in automatico un dataframe.

my_csv <- read.csv("data/csv_example.csv", header = T) # sep di default è virgola
my_csv
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1           5.1         3.5          1.4         0.2  setosa
## 2           4.9         3.0          1.4         0.2  setosa
## 3           4.7         3.2          1.3         0.2  setosa
## 4           4.6         3.1          1.5         0.2  setosa
## 5           5.0         3.6          1.4         0.2  setosa
## 6           5.4         3.9          1.7         0.4  setosa
## 7           4.6         3.4          1.4         0.3  setosa
## 8           5.0         3.4          1.5         0.2  setosa
## 9           4.4         2.9          1.4         0.2  setosa
## 10          4.9         3.1          1.5         0.1  setosa

1.5.3 .xsls

Esistono diverse funzioni per importare file direttamente dal formato Excel. Solitamente è possibile evitare di utilizzare funzioni o pacchetti aggiuntivi semplicemente salvando il file Excel come csv e usare le funzioni di importazione precedenti. Tuttavia a volte è comodo importare direttamente il file nel formato originale. Sono disponibili diversi pacchetti e funzioni:

Il pacchetto readxl con le funzioni readxl::read_excel(), readxl::read_xls() e readxl::read_xlsx(). Rispetto ai csv i file Excel sono più complessi e permettono di avere più “fogli”. Queste funzioni permettono anche di importare un foglio specifico (argomento sheet =) oppure un range specifico nel foglio di destinazione (argomento range =) Excel identifica infatti le righe in modo numerico 1,2,3,4... e le colonne con le lettere dell’alfabeto in modo progressivo A, B, C ...

my_excel <- readxl::read_xlsx("data/excel_example.xlsx", range = "A1:D10")
my_excel
## # A tibble: 9 × 4
##   Sepal.Length Sepal.Width Petal.Length Petal.Width
##          <dbl>       <dbl>        <dbl>       <dbl>
## 1          5.1         3.5          1.4         0.2
## 2          4.9         3            1.4         0.2
## 3          4.7         3.2          1.3         0.2
## 4          4.6         3.1          1.5         0.2
## 5          5           3.6          1.4         0.2
## 6          5.4         3.9          1.7         0.4
## 7          4.6         3.4          1.4         0.3
## 8          5           3.4          1.5         0.2
## 9          4.4         2.9          1.4         0.2

In alternativa è presente anche il pacchetto xlsx che permette le stesse funzionalità ma in generale il pacchetto readxl è il migliore. L’unica differenza riguarda la capacità di scrivere quindi creare file excel da un oggetto R. Finora abbiamo solo parlato di lettura di file ma R permette anche di creare tutti i formati che legge. In questo caso il pacchetto readxl permette solo di leggere mentre xlsx anche di scrivere. Il pacchetto WriteXLS come dice il nome invece è specifico per la scrittura di file.

1.5.4 .sav

Il formato .sav è specifico del software SPSS e può essere gestito con il pacchetto haven che permette di leggere e scrivere file in questo formato. In questo caso, in modo simile all’excel, non ci sono particolari opzioni per leggere/scrivere se non specificare il percorso del file.

my_sav <- haven::read_sav(file = "data/sav_example.sav")
my_sav
## # A tibble: 150 × 5
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species   
##           <dbl>       <dbl>        <dbl>       <dbl> <dbl+lbl> 
##  1          5.1         3.5          1.4         0.2 1 [setosa]
##  2          4.9         3            1.4         0.2 1 [setosa]
##  3          4.7         3.2          1.3         0.2 1 [setosa]
##  4          4.6         3.1          1.5         0.2 1 [setosa]
##  5          5           3.6          1.4         0.2 1 [setosa]
##  6          5.4         3.9          1.7         0.4 1 [setosa]
##  7          4.6         3.4          1.4         0.3 1 [setosa]
##  8          5           3.4          1.5         0.2 1 [setosa]
##  9          4.4         2.9          1.4         0.2 1 [setosa]
## 10          4.9         3.1          1.5         0.1 1 [setosa]
## # ℹ 140 more rows

1.6 Elementi comuni

A prescindere dal formato o dalla funzione utilizzata, abbiamo visto che generalmente sono importanti alcune informazioni:

  • tipo di separatore: ovvero il carattere utilizzato per separare un’informazione dall’altra. Nel caso dei file csv ad esempio è la virgola.
  • NA: quando importiamo dei dati possiamo usare qualsiasi modalità per identificare dati mancanti (in R NA). E’ importante dire alla funzione di importazione in che modo noi abbiamo rappresentato gli NA nel file di origine in modo che vengano gestiti direttamente in R. Se non facciamo questo R intepreterà come dati normali questi valori creando degli ipotetici problemi. Vediamo un esempio:

Immaginiamo di avere il dataset my_iris con dei valori NA:

my_iris <- iris[1:10, ]
my_iris <- filor::add_random_na(my_iris, 10)
my_iris
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1            NA         3.5          1.4         0.2  setosa
## 2           4.9          NA          1.4          NA  setosa
## 3           4.7         3.2          1.3          NA  setosa
## 4           4.6         3.1          1.5         0.2  setosa
## 5           5.0         3.6          1.4         0.2  setosa
## 6           5.4          NA           NA          NA  setosa
## 7           4.6         3.4          1.4          NA  setosa
## 8           5.0         3.4          1.5         0.2  setosa
## 9           4.4         2.9          1.4         0.2  setosa
## 10          4.9         3.1          1.5          NA    <NA>

Ora scriviamo il file come csv specificando un certo tipo di stringa per gli NA:

write.csv(my_iris, file = "data/na_csv_example.csv", na = "NA", row.names = FALSE)

Ora vediamo il risultato:

"Sepal.Length","Sepal.Width","Petal.Length","Petal.Width","Species"
NA,3.5,1.4,0.2,"setosa"
4.9,NA,1.4,NA,"setosa"
4.7,3.2,1.3,NA,"setosa"
4.6,3.1,1.5,0.2,"setosa"
5,3.6,1.4,0.2,"setosa"
5.4,NA,NA,NA,"setosa"
4.6,3.4,1.4,NA,"setosa"
5,3.4,1.5,0.2,"setosa"
4.4,2.9,1.4,0.2,"setosa"
4.9,3.1,1.5,NA,NA

Come vedete abbiamo il dataset e in mezzo dei valori NA. Proviamo a immaginare di non aver creato questo file e dobbiamo importarlo per la prima volta in R:

my_csv <- read.csv(file = "data/na_csv_example.csv", header = TRUE)
sapply(my_csv, typeof)
## Sepal.Length  Sepal.Width Petal.Length  Petal.Width      Species 
##     "double"     "double"     "double"     "double"  "character"

Come vedete R interpreta correttamente i valori NA mantenendo il tipo di colonna (quindi non intepretando NA come stringa) perchè è il modo di default di gestione. Proviamo a scrivere un’altro file usando un modo diverso di gestire gli NA:

write.csv(my_iris, file = "data/na_csv_example.csv", na = "NO", row.names = FALSE)

Ora gli NA sono rappresentati come NO:

"Sepal.Length","Sepal.Width","Petal.Length","Petal.Width","Species"
NO,3.5,1.4,0.2,"setosa"
4.9,NO,1.4,NO,"setosa"
4.7,3.2,1.3,NO,"setosa"
4.6,3.1,1.5,0.2,"setosa"
5,3.6,1.4,0.2,"setosa"
5.4,NO,NO,NO,"setosa"
4.6,3.4,1.4,NO,"setosa"
5,3.4,1.5,0.2,"setosa"
4.4,2.9,1.4,0.2,"setosa"
4.9,3.1,1.5,NO,NO

Infatti se importiamo con la stessa funzione abbiamo un risultato strano:

my_csv <- read.csv(file = "data/na_csv_example.csv", header = TRUE)
my_csv
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1            NO         3.5          1.4         0.2  setosa
## 2           4.9          NO          1.4          NO  setosa
## 3           4.7         3.2          1.3          NO  setosa
## 4           4.6         3.1          1.5         0.2  setosa
## 5             5         3.6          1.4         0.2  setosa
## 6           5.4          NO           NO          NO  setosa
## 7           4.6         3.4          1.4          NO  setosa
## 8             5         3.4          1.5         0.2  setosa
## 9           4.4         2.9          1.4         0.2  setosa
## 10          4.9         3.1          1.5          NO      NO
sapply(my_csv, typeof)
## Sepal.Length  Sepal.Width Petal.Length  Petal.Width      Species 
##  "character"  "character"  "character"  "character"  "character"

Tutte le colonne sono intepretate come stringhe perche "NO" è gestito come tale. Per importare correttamente dobbiamo specificare la stringa giusta:

my_csv <- read.csv(file = "data/na_csv_example.csv", header = TRUE, na.strings = "NO")
my_csv
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1            NA         3.5          1.4         0.2  setosa
## 2           4.9          NA          1.4          NA  setosa
## 3           4.7         3.2          1.3          NA  setosa
## 4           4.6         3.1          1.5         0.2  setosa
## 5           5.0         3.6          1.4         0.2  setosa
## 6           5.4          NA           NA          NA  setosa
## 7           4.6         3.4          1.4          NA  setosa
## 8           5.0         3.4          1.5         0.2  setosa
## 9           4.4         2.9          1.4         0.2  setosa
## 10          4.9         3.1          1.5          NA    <NA>
sapply(my_csv, typeof)
## Sepal.Length  Sepal.Width Petal.Length  Petal.Width      Species 
##     "double"     "double"     "double"     "double"  "character"
LS0tCnRpdGxlOiAiSW1wb3J0YXJlIGkgZGF0aSBpbiBSIgpvdXRwdXQ6IAogICAgYm9va2Rvd246Omh0bWxfZG9jdW1lbnQyOgogICAgICAgIHRvYzogdHJ1ZQogICAgICAgIHRvY19mbG9hdDogdHJ1ZQogICAgICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKY3NzOiBbIi4uL2ZpbGVzL2Nzcy9jb3Vyc2VfaHRtbC5jc3MiXQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduID0gImNlbnRlciIsIAogICAgICAgICAgICAgICAgICAgICAgZmlnLnJldGluYSA9IDIsIAogICAgICAgICAgICAgICAgICAgICAgZHBpID0gMzAwKQpgYGAKCmBgYHtyIHBhY2thZ2VzfQpkZXZ0b29sczo6bG9hZF9hbGwoKQpgYGAKCiMgSW50cm9kdXppb25lCgpQZXIgb3BlcmF6aW9uaSBzZW1wbGljaSBvIHBlciBlc2VyY2l6aSBzaSBwb3Nzb25vIGNyZWFyZSBzdHJ1dHR1cmUgZGF0aSBjb21lIGB2ZXR0b3JpYCwgYGxpc3RlYCBvIGBkYXRhZnJhbWVgIGRpcmV0dGFtZW50ZSBpbiBSLiBUdXR0YXZpYSBwZXIgbGF2b3JhcmUgY29uIFIgw6ggc29saXRvICoqaW1wb3J0YXJlKiogaSBkYXRpIGRhIGZvbnRpIGVzdGVybmUgY29tZSBmaWxlIGUgZGF0YWJhc2UuCgpJbiBSIHNvbm8gZGlzcG9uaWJpbGkgZGl2ZXJzZSBmdW56aW9uaSBwZXIgaW1wb3J0YXJlIGkgZGF0aSBuZWkgcHJpbmNpcGFsaSBmb3JtYXRpIGNvbWUgYC5jc3ZgIG8gYC50eHRgIGFzc2llbWUgYSBkaXZlcnNpIHBhY2NoZXR0aSBjb24gZnVuemlvbmkgcGVyIGltcG9ydGFyZSBkYXRpIG5laSBmb3JtYXRpIHByb3ZlbmllbnRpIGRhIGFsdHJpIHNvZnR3YXJlIGNvbWUgU1BTUywgRXhjZWwsIGV0Yy4gSW5zb21tYSBzZSBlc2lzdGUgdW4gZm9ybWF0byBzcGVjaWZpY28gcHJvYmFiaWxtZW50ZSBlc2lzdGUgYW5jaGUgdW5hIGZ1bnppb25lIGluIFIgcGVyIGltcG9ydGFybG8gYHIgZW1vOjpqaSgic21pbGUiKWAKCiMjIExhIGxvZ2ljYQoKQSBwcmVzY2luZGVyZSBkYWwgZm9ybWF0byBzcGVjaWZpY28sIGxhIGxvZ2ljYSDDqCBjaGUgdW4gY2VydG8gdGlwbyBkaSBmaWxlIGhhIHVuJ2VzdGVuc2lvbmUgY2hlIGlkZW50aWZpY2EgbGEgdGlwb2xvZ2lhIGNvbWUgKmZpbGUgZGkgdGVzdG8qIGAudHh0YCwgKmNvbW1hLXNlcGFyYXRlZCB2YWx1ZXMqIGAuY3N2YCwgKkV4Y2VsKiBgLnhsc3hgLiBRdWVzdG8gZmlsZSBvcmdhbml6emEgaSBkYXRpIGluIHVuIG1vZG8gc3BlY2lmaWNvIGUgcXVpbmRpIMOoIGltcG9ydGFudGUgdXRpbGl6emFyZSBsYSBmdW56aW9uZSBnaXVzdGEgY29uIGkgZ2l1c3RpIGFyZ29tZW50aSBwZXIgcG90ZXIgZmFyIGludGVycHJldGFyZSBhZCBSIGkgZGF0aSBpbiBtb2RvIGNvcnJldHRvLgoKIyMgUlN0dWRpbwoKUlN0dWRpbyBwZXJtZXR0ZSBkaSBpbXBvcnRhcmUgcXVhbHNpYXNpIHRpcG8gZGkgZGF0byB0cmFtaXRlIGludGVyZmFjY2lhIGdyYWZpY2EuIEFsbCdpbml6aW8gcHXDsiBlc3NlcmUgdXRpbGUgbWEgcXVhbmRvIHNpIGxhdm9yYSBhbGwnaW50ZXJubyBkaSB1bm8gc2NyaXB0IMOoIG1lZ2xpbyBhdXRvbWF0aXp6YXJlIHRyYW1pdGUgY29tYW5kaSBxdWFsc2lhc2kgb3BlcmF6aW9uZSBzaWEgcGVyIHNhbHZhcmUgY2hlIHBlciBpbXBvcnRhcmUgaSBkYXRpLiBQb3RldGUgdHJvdmFyZSBpbCBtZW7DuSBkaSBpbXBvcnRhemlvbmUgY29uIGBmaWxlID4uIGltcG9ydCBkYXRhc2V0YCBjb21lIGluIEZpZ3VyYSBcQHJlZihmaWc6cnN0dWRpby1pbXBvcnQpLgoKYGBge3IgcnN0dWRpby1pbXBvcnQsIGZpZy5jYXA9Ik1lbsO5IGRpIGltcG9ydGF6aW9uZSBkYXRpIGRpIFJTdHVkaW8iLCBvdXQud2lkdGg9IjUwJSJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWcvcnN0dWRpb19pbXBvcnQucG5nIikKYGBgCgojIyBUaXBpIGRpIGZvcm1hdG8KCkxlIHRpcG9sb2dpZSBkaSBmb3JtYXRvIGRhdGkgc29ubyB0YW50aXNzaW1lIG1hIHBvc3NpYW1vIG9yZ2FuaXp6YXJsZSBpbiAyIGNhdGVnb3JpZToKCi0gYGZvcm1hdGkgZXN0ZXJuaWA6IHNvbm8gdHV0dGkgcXVlaSBmb3JtYXRpIG5vbiBSLXNwZWNpZmljaQotIGBmb3JtYXRvIFJgOiBzb25vIHF1ZWkgZm9ybWF0aSBjaGUgc29ubyBjcmVhdGkgZGEgUiBlIGRpcmV0dGFtZW50ZSBjb21wYXRpYmlsaQoKIyMgRm9ybWF0byBSCgpQYXJ0aWFtbyBkYWkgZm9ybWF0aSBzcGVjaWZpY2kgZGkgUiBwZXJjaMOoIHNvbm8gcXVlbGxpIHBpw7kgc2VtcGxpY2kgZGEgY3JlYXJlIGUgaW1wb3J0YXJlLiBJbCBwcmluY2lwYWxlIGRpZmV0dG8gw6ggcXVlbGxvIGNoZSBzb25vIGFwcHVudG8gbGVnZ2liaWxpIChwcmluY2lwYWxtZW50ZSkgc29sbyBhbGwnaW50ZXJubyBkZWxsJ2FtYmllbnRlIFIuIFNlIGFiYmlhbW8gbGEgbmVjZXNzaXTDoCBkaSBsYXZvcmFyZSBjb24gc29mdHdhcmUgbyBsaW5ndWFnZ2kgZGl2ZXJzaSDDqCBwcm9iYWJpbG1lbnRlIG1lZ2xpbyB1c2FyZSB1biBmb3JtYXRvIHBpw7kgZ2VuZXJpY28uCgojIyMgYC5SRGF0YWAKClF1ZXN0byDDqCBpbCBmb3JtYXRvIHBpw7kgZ2VuZXJpY28gZSBwZXJtZXR0ZSBkaSBzYWx2YXJlIGwnaW50ZXJhIHNlc3Npb25lIGRpIGxhdm9ybyBpbiB1biBmaWxlIHVuaWNvIHBlciBwb2kgZXNzZXJlIGV2ZW50dWFsbWVudGUgcmlwcmlzdGluYXRhLiBOb24gw6ggdW4gdmVybyBlIHByb3ByaW8gZm9ybWF0byBzcGVjaWZpY28gZSBjb3JyaXNwb25kZSBwacO5IGEgZmFyZSB1bmEgZm90b2dyYWZpYSAofmJhY2t1cCkgYWxsYSBzZXNzaW9uZSBkaSBsYXZvcm8uIEluIGdlbmVyYWxlIG5vbiBjb25zaWdsaW8gZGkgdXNhcmUgcXVlc3RvIGZvcm1hdG8gbWEgZGkgbGF2b3JhcmUgY29uIHNpbmdvbGkgZmlsZSBkYSBzYWx2YXJlL2ltcG9ydGFyZSBtYSBpbiBnZW5lcmFsZSBwacO5IGVzc2VyZSB1dGlsZSBhIHZvbHRlLgoKUGVyIHNhbHZhcmUgc2kgcGnDuSB1c2FyZSBgc2F2ZS5pbWFnZSgpYCBzZW1wbGljZW1lbnRlIHNwZWNpZmljYW5kbyBpbCBwZXJjb3JzbyBlIGlsIG5vbWUgZGVsIGZpbGUgY29uIGVzdGVuc2lvbmUgYC5SRGF0YWA6CgpgYGB7cn0Kc2F2ZS5pbWFnZShmaWxlID0gImRhdGEvc2Vzc2lvbmUuUkRhdGEiKQpgYGAKClBlciByaXByaXN0aW5hcmUgbGEgc2Vzc2lvbmUgc2kgcHXDsiB1c2FyZSBsYSBmdW56aW9uZSBgbG9hZCgpYCBzcGVjaWZpY2FuZG8gaWwgcGVyY29yc28gZGVsIGZpbGUuCgpgYGB7cn0KbG9hZChmaWxlID0gImRhdGEvc2Vzc2lvbmUuUkRhdGEiKQpgYGAKCiMjIyBgLnJkYWAKCklsIGZvcm1hdG8gYC5yZGFgIMOoIHF1ZWxsbyBwacO5IGNvbXVuZSBpbiBSIGUgcGVybWV0dGUgZGkgc2FsdmFyZSAqKnVubyBvIHBpw7kgb2dnZXR0aSoqIGFsbCdpbnRlcm5vIGRpIHVuIHVuaWNvIGZpbGUgcGVyIHBvaSByaWNhcmljYXJsby9pIGRpcmV0dGFtZW50ZS4gSWwgcHJpbmNpcGlvIMOoIGxvIHN0ZXNzbyBkZWkgZmlsZSBgLlJEYXRhYCBtYSBhbCBjb250cmFyaW8gZGVsIHByZWNlZGVudGUgcGVybWV0dGUgZGkgc2FsdmFyZSBzb2xvIGFsY3VuaSBvZ2dldHRpLgpJbCBjb21hbmRvIHBlciBzYWx2YXJlIMOoIGBzYXZlKClgIGRvdmUgdmFubm8gaW5zZXJpdGkgZ2xpIG9nZ2V0dGkgc2VwYXJhdGkgZGEgdmlyZ29sYSBlIHBvaSBpbCBub21lIGVkIGlsIHBlcmNvcnNvIGRlbCBmaWxlIGRhIHNhbHZhcmUuIFNhbHZpYW1vIGlsIGRhdGFzZXQgYGlyaXNgLCB1biB2ZXR0b3JlIGBteV92ZWN0YCBlIGxhIHN0cmluZ2EgYCJoZWxsbyB3b3JsZCJgIGRlbnRybyB1biB1bmljbyBvZ2dldHRvIGBteV9vYmoucmRhYDoKCmBgYHtyfQppcmlzIDwtIGlyaXMKbXlfdmVjdCA8LSAxOjEwCm15X3N0cmluZyA8LSAiaGVsbG8gd29ybGQiCgpzYXZlKGlyaXMsIG15X3ZlY3QsIG15X3N0cmluZywgZmlsZSA9ICJkYXRhL215X29iai5yZGEiKQpgYGAKClNlIHZvZ2xpYW1vIHJpY2FyaWNhcmUgcXVlc3RvIGluc2llbWUgZGkgb2dnZXR0aSBhbGwnaW50ZXJubyBkaSBSIHBvc3NpYW1vIHVzYXJlIGxhIGZ1bnppb25lIGBsb2FkKClgIHNwZWNpZmljYW5kbyBpbCBwZXJjb3JzbyBkZWwgZmlsZS4gR2xpIG9nZ2V0dGkgc2FyYW5ubyBub24gc29sbyBjYXJpY2F0aSBtYSBzYWx2YXRpIGRpcmV0dGFtZW50ZSBuZWwgZ2xvYmFsIGVudmlyb25tZW50OgoKYGBge3J9CmxvYWQoZmlsZSA9ICJkYXRhL215X29iai5yZGEiKQpgYGAKCkkgbGF0aSBwb3NpdGl2aSBkaSBxdWVzdG8gYXBwcm9jY2lvIHNvbm8gbCdlc3RyZW1hIHNlbXBsaWNpdMOgIG5lbCBzYWx2YXJlIGUgY2FyaWNhcmUgaSBmaWxlIG1lbnRyZSBsJ2FzcGV0dG8gbmVnYXRpdm8gw6ggY2hlIG5vbiBwb3NzaWFtbyBjYW1iaWFyZSBpIG5vbWkgY29uIGN1aSBpIGZpbGUgdmVuZ29ubyBpbXBvcnRhdGkuCgojIyMgYC5yZHNgCgpJbCBmb3JtYXRvIGAucmRzYCDDqCBtb2x0byBzaW1pbGUgYWwgcHJlY2VkZW50ZSBtYSBsZWdnZXJtZW50ZSBtZW5vIGZsZXNzaWJpbGUuIEluIHBhcnRpY29sYXJlIMOoIHBvc3NpYmlsZSBzYWx2YXJlICoqdW4gc29sbyBvZ2dldHRvKiogYWxsJ2ludGVybm8gZGkgdW4gZmlsZSBgLnJkc2AuIElsIGxhdG8gbmVnYXRpdm8gw6ggcXVpbmRpIGNoZSBub24gcG9zc2lhbW8gc2FsdmFyZSB1bmEgc2Vzc2lvbmUgaW50ZXJhIGRpIG9nZ2V0dGkgbWVudHJlIGlsIGxhdG8gcG9zaXRpdm8gw6ggY2hlIHBvc3NpYW1vIHJpbm9taW5hcmUgbCdvZ2dldHRvIHF1YW5kbyB2aWVuZSBpbXBvcnRhdG8uIEluIGdlbmVyYWxlIHJpdGVuZ28gbWlnbGlvcmUgaWwgZm9ybWF0byBgLnJkc2AgcGVyY2jDqCBub25vc3RhbnRlIGxhIG5lY2Vzc2l0w6AgZGkgcGnDuSBmaWxlIHBlcm1ldHRlIHVuYSBnZXN0aW9uZSBwacO5IGZsZXNzaWJpbGUgZGkgY29zYSBpbXBvcnRhcmUgZSBjb21lIHZpZW5lIHJhcHByZXNlbnRhdG8gbmVsbCdhbWJpZW50ZSBkaSBsYXZvcm8uIExhIGZ1bnppb25lIHBlciBzYWx2YXJlIMOoIGBzYXZlUkRTKClgIHNwZWNpZmljYW5kbyBsJ29nZ2V0dG8gZSBpbCBwZXJjb3Jzby9ub21lIGUgcGVyIGltcG9ydGFyZSBgcmVhZFJEUygpYCBzcGVjaWZpY2FuZG8gaWwgcGVyY29yc28gZGVsIGZpbGUuClNhbHZpYW1vIGlsIGRhdGFzZXQgYGlyaXNgIGNvbWUgYGlyaXMucmRzYDoKCmBgYHtyfQpzYXZlUkRTKGlyaXMsIGZpbGUgPSAiZGF0YS9pcmlzLnJkcyIpCmBgYAoKUmUtaW1wb3J0aWFtbyBpbCBkYXRhc2V0IGBpcmlzYCBtYSBjaGlhbWFuZG9sbyBgbXlfaXJpc2A6CgpgYGB7cn0KbXlfaXJpcyA8LSByZWFkUkRTKCJkYXRhL2lyaXMucmRzIikKYGBgCgpDb21lIHZlZGV0ZSDDqCBuZWNlc3NhcmlvIGFzc2VnbmFyZSBgPC1gIGlsIHJpc3VsdGF0byBhZCB1biBvZ2dldHRvIGFsdHJpbWVudGkgbm9uIMOoIHBvc3NpYmlsZSBhdmVyZSBkaXNwb25pYmlsZSBsJ29nZ2V0dG8gbmVsbCdhbWJpZW50ZSBkaSBsYXZvcm8uCgojIyMgVGFiZWxsYSByaWFzc3VudGl2YQoKfCBGb3JtYXRvIHwgRXN0ZW5zaW9uZSB8IFNhbHZhdGFnZ2lvICAgICAgICAgICAgfCBJbXBvcnRhemlvbmUgIHwgUHJvICAgICAgICAgICAgICAgICAgICAgICAgICB8IENvbnRybyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgLS0tLS0tLSB8IC0tLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tIHwgLS0tLS0tLS0tLS0tLSB8IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gfAp8IFJEUyAgICAgfCBgLnJkc2AgICB8IGBzYXZlUkRTKG9nZ2V0dG8sIGZpbGUpYCB8IGByZWFkUkRTKGZpbGUpYCB8IEltcG9ydGFuZG8gc2kgZGVjaWRlIGlsIG5vbWUgfCBVbiBzb2xvIG9nZ2V0dG8gcGVyIGZpbGUgICAgICAgICAgICAgICAgICAgICAgfAp8IFJEQSAgICAgfCBgLnJkYWAgICB8IGBzYXZlKG9nZ2V0dG8vaSwgZmlsZSlgICB8IGBsb2FkKGZpbGUpYCAgICB8IFNhbHZhcmUgaW5zaWVtZSBwacO5IGZpbGUgICAgIHwgQ2FyaWNhIGRpcmV0dGFtZW50ZSBpIGZpbGUgY29uIGkgbm9taSBzYWx2YXRpIHwKfCBSRGF0YSAgIHwgYC5SRGF0YWAgfCBgc2F2ZS5pbWFnZShmaWxlKWAgICAgICAgfCBgbG9hZChmaWxlKWAgICAgfCBTYWx2YXJlIHR1dHRhIGxhIHNlc3Npb25lICAgIHwgQ2FyaWNhIGRpcmV0dGFtZW50ZSBpIGZpbGUgY29uIGkgbm9taSBzYWx2YXRpIHwKCiMjIEZvcm1hdG8gZXN0ZXJubwoKVHJhdHRhcmUgaSBmb3JtYXRpIGVzdGVybmkgw6ggc2ljdXJhbWVudGUgY29tcGxpY2F0byBzaWEgcGVyIGxhIGRpZmZlcmVuemEgdHJhIGkgdmFyaSBmb3JtYXRpIGNoZSBsYSBxdWFudGl0w6AgZGkgZm9ybWF0aSBkaXZlcnNpLiBMJ2lkZWEgZGkgYmFzZSDDqCBjaGUgc29saXRhbWVudGUgcGVyIGltcG9ydGFyZSBpIGRhdGkgYmlzb2duYSBjYXBpcmUgY29tZSBpbCBmb3JtYXRvIHNwZWNpZmljbyByYXBwcmVzZW50YSBkYXRpLCBjYXJhdHRlcmkgc3BlY2lhbGksIGVsZW1lbnRpIGNoZSBzZXBhcmFubyB1biBzaW5nb2xvIGRhdG8gZGFsbCdhbHRybyBlIGNvc8OsIHZpYS4KClNlIHByZW5kaWFtbyBpbCBzZWd1ZW50ZSB2ZXR0b3JlIGB4IDwtIGMoMSwyLDMpYCBub2kgY2FwaWFtbyBjaGUgYHhgIGhhIDMgZWxlbWVudGkgZXNzZW5kbyBsYSB2aXJnb2xhIG5vbiB1biBkYXRvIG1hIHVuIG1vZG8gcGVyIHNlcGFyYXJlIGRhdGkuIEVjY28gYW5jaGUgaSB2YXJpIGZvcm1hdGkgaGFubm8gZGVsbGUgbW9kYWxpdMOgIHBlciBzZXBhcmFyZSBlIG9yZ2FuaXp6YXJlIGkgZGF0aSBlZCBSIGRldmUgY2FwaXJlIChsbyBkb2JiaWFtbyBkaXJlIG5vaSkgcXVhbGUgbW9kYWxpdMOgIHNwZWNpZmljYSDDqCB1dGlsaXp6YXRhIG5lbCB0aXBvIGRpIGRhdG8gY2hlIHN0aWFtbyBjZXJjYW5kbyBkaSBpbXBvcnRhcmUuCgojIyMgYC50eHRgCgpQYXJ0aWFtbyBkYWwgZm9ybWF0byBwacO5IHNlbXBsaWNlIGAudHh0YCBlIHByb3ZpYW1vIGFkIGltcG9ydGFyZSB1biBzaW5nb2xvIHZldHRvcmUgZGEgYC50eHRgIGEgUiBjb21lIG9nZ2V0dG8uCgpWZWRpYW1vIGlsIGZpbGUgZGkgdGVzdG8gYGNvbW1hX3ZlY3Rvci50eHRgOgoKYGBge3IgY29tbWVudD0nJywgZWNobz1GQUxTRX0KY2F0KHJlYWRMaW5lcygnZGF0YS9jb21tYV92ZWN0b3IudHh0JyksIHNlcCA9ICdcbicpCmBgYAoKQ29tZSB2ZWRldGUgY2kgc29ubyBkZWkgbnVtZXJpIHNlcGFyYXRpIGRhIHZpcmdvbGUuIFBlciBsZWdnZXJlIHF1ZXN0byBkYXRvIGluIGBSYCBwb3NzaWFtbyB1c2FyZSBsYSBmdW56aW9uZSBgc2NhbihmaWxlLCBzZXApYC4gSW4gcGFydGljb2xhcmUgZG9iYmlhbW8gc3BlY2lmaWNhcmUgaWwgZmlsZSBvdnZpYW1lbnRlIGUgbCdhcmdvbWVudG8gYHNlcCA9YCBwZXIgc3BlY2lmaWNhcmUgcXVhbGUgY2FyYXR0ZXJlIHZlbmdhIGludGVycHJldGF0byBjb21lICoqc2VwYXJhdG9yZSoqOgoKYGBge3IsIGVycm9yPVRSVUV9CnJpZ2h0IDwtIHNjYW4oJ2RhdGEvY29tbWFfdmVjdG9yLnR4dCcsIHNlcD0iLCIsIHF1aWV0ID0gVFJVRSkKcmlnaHQKd3JvbmcgPC0gc2NhbignZGF0YS9jb21tYV92ZWN0b3IudHh0Jywgc2VwPSI7IiwgcXVpZXQgPSBUUlVFKQp3cm9uZwpgYGAKQ29tZSB2ZWRldGUgc3BlY2lmaWNhbmRvIHVuIHNlcGFyYXRvcmUgZXJyYXRvIGRpY2UgYSBSIGRpIGltcG9ydGFyZSBhbmNoZSBgLGAgZSBvdnZpYW1lbnRlIG5vbiDDqCBjb21wYXRpYmlsZSBjb24gaWwgcmVzdG8gZGVpIGRhdGkuCgpTb2xpdGFtZW50ZSDDqCBpbnV0aWxlIGltcG9ydGFyZSB1biBzaW5nb2xvIHZldHRvcmUgbWEgcXVlc3RvIGNvbmNldHRvIGRpIGNhcGlyZSBjb21lIMOoIGNvZGlmaWNhdG8gaWwgZmlsZSBkYSBpbXBvcnRhcmUgw6ggYWxsYSBiYXNlIGRpIHR1dHRpIGkgdGlwaSBkaSBpbXBvcnRhemlvbmUuCgpVbiBtb2RvIHBpw7kgY29tcGxlc3NvIGRpIG9yZ2FuaXp6YXJlIGkgZGF0aSBzZW1wcmUgbmVpIGZpbGUgYC50eHRgIMOoIHF1ZWxsbyBkaSByaWNyZWFyZSB1biB2ZXJvIGUgcHJvcHJpbyBkYXRhc2V0LiBJbCBmaWxlIGBjb21tYV90YWJsZS50eHRgIHJhcHByZXNlbnRhIHVuIGVzZW1waW8gcGnDuSByZWFsaXN0aWNvIChpbiBwaWNjb2xvKSBkaSBxdWVsbG8gY2hlIHBvdHJlYmJlIGVzc2VyZSB1biBkYXRhc2V0IHZlcm8gZGEgaW1wb3J0YXJlOgoKYGBge3IgY29tbWVudD0nJywgZWNobz1GQUxTRX0KY2F0KHJlYWRMaW5lcygnZGF0YS9jb21tYV90YWJsZS50eHQnKSwgc2VwID0gJ1xuJykKYGBgCgpDb21lIHZlZGV0ZSBhYmJpYW1vIGRlbGxlIHN0cmluZ2hlIGNoZSBpZGVhbG1lbnRlIHJhcHByZXNlbnRhbm8gaSBub21pIGRlbGxlIGNvbG9ubmUgZSBwb2kgaSBkYXRpIGVmZmV0dGl2aS4gQ29tZSBpbiBwcmVjZWRlbnphIGluIHF1ZXN0byBjYXNvIGFiYmlhbW8gbGEgdmlyZ29sYSBjb21lIHNlcGFyYXRvcmUgZGkgZGF0byBtYSBkb2JiaWFtbyBpbiBxdWFsY2hlIG1vZG8gZGlyZSBhZCBSIGNoZSBsYSBwcmltYSByaWdhIG5vbiBmYSBwYXJ0ZSBkZWkgZGF0aSBzb25vIGkgbm9taSBkZWxsZSBjb2xvbm5lLiBQZXIgaW1wb3J0YXJlIHF1ZXN0byB0aXBvIGRpIGRhdG8gcG9zc2lhbW8gdXNhcmUgaWwgY29tYW5kbyBgcmVhZC50YWJsZShmaWxlLCBzZXAsIGhlYWRlcilgOgoKYGBge3J9CnJlYWQudGFibGUoImRhdGEvY29tbWFfdGFibGUudHh0Iiwgc2VwID0gIiwiKQpgYGAKCkNvbWUgdmVkZXRlIFIgaW50ZXByZXRhIGxhIHByaW1hIHJpZ2EgY29tZSBwYXJ0ZSBkZWkgZGF0aSBlIGFnZ2l1bmdlIGRlaSBub21pIHN0YW5kYXJkIHBlciBsZSBjb2xvbm5lIGBWMSwgVjIsIGV0Yy5gLiBBZ2dpdW5nZW5kbyBsJ2FyZ29tZW50byBgaGVhZGVyID0gVFJVRWAgZGljaWFtbyBwcm9wcmlvIHF1ZXN0byBhIFIuCgpgYGB7cn0KbXlfZGF0YSA8LSByZWFkLnRhYmxlKCJkYXRhL2NvbW1hX3RhYmxlLnR4dCIsIHNlcCA9ICIsIiwgaGVhZGVyID0gVFJVRSkKbXlfZGF0YQpgYGAKCkUnIGltcG9ydGFudGUgbm90YXJlIGNoZSBpbCBjYXJhdHRlcmUgZGkgc2VwYXJhemlvbmUgcHXDsiBlc3NlcmUgcXVhbHNpYXNpIGFsdHJvIGNhcmF0dGVyZSByaXNwZXR0byBhbGxhIHZpcmdvbGEgY29tZSBpbCBwdW50byBlIHZpcmdvbGEgYCI7ImAgbyB1bm8gc3BhemlvIGAiICJgLiBFJyBzb2xvIGltcG9ydGFudGUgY2FwaXJlIGlsIGNhcmF0dGVyZSBjb3JyZXR0byBlIGltcG9zdGFyZSBsYSBmdW56aW9uZSBkaSBpbXBvcnRhemlvbmUuCgojIyMgYC5jc3ZgCgpJIGZpbGUgYGNzdmAgc29ubyBwacO5IGNvbXVuaSBwZXIgZ2VzdGlyZSBkYXRhc2V0IHNvcHJhdHV0dG8gcGVyY2jDqCBzb25vIGZhY2lsbWVudGUgaW1wb3J0YWJpbGkgZSBjcmVhYmlsaSBkYSBzb2Z0d2FyZSBjb21lIEV4Y2VsLiBJbCBub21lIHNpZ25pZmljYSAqKmNvbW1hIHNlcGFyYXRlZCB2YWx1ZXMqKiBwZXJjaMOoIGluIG1vZG8gc2ltaWxlIGFpIGZpbGUgY2hlIGFiYmlhbW8gdmlzdG8gaW4gcHJlY2VkZW56YSBzaSB1dGlsaXp6YSBsYSB2aXJnb2xhIGAiLCJgIHBlciBzZXBhcmFyZSBpIGRhdGkuIFVuIGZpbGUgYGNzdmAgc2kgcHJlc2VudGEgaW4gcXVlc3RvIG1vZG86CgpgYGB7ciBjb21tZW50PScnLCBlY2hvPUZBTFNFfQpjYXQocmVhZExpbmVzKCdkYXRhL2Nzdl9leGFtcGxlLmNzdicpLCBzZXAgPSAnXG4nKQpgYGAKCkNvbWUgdmVkZXRlIGlsIGZpbGUgw6ggZXN0cmVtYW1lbnRlIHNpbWlsZSBhbCBgdHh0YCBjaGUgYWJiaWFtbyBpbXBvcnRhdG8gaW4gcHJlY2VkZW56YSBjb24gbGEgZGlmZmVyZW56YSBjaGUgbGUgc3RyaW5nZSBzb25vIHJhY2NoaXVzZSB0cmEgdmlyZ29sZXR0ZS4gSWwgY29tYW5kbyBwZXIgbGVnZ2VyZSBmaWxlIGBjc3ZgIMOoIGByZWFkLmNzdihmaWxlLCBzZXAsIGhlYWRlcilgIGRvdmUgaWwgYHNlcCA9ICIsImAgcHJlZGVmaW5pdG8gw6ggbGEgdmlyZ29sYSAobWEgc2kgcHXDsiBjYW1iaWFyZSkgZSBhbmNoZSBxdWkgZG9iYmlhbW8gc3BlY2lmaWNhcmUgaWwgcGFyYW1ldHJvIGBoZWFkZXIgPSBUUlVFYCBzZSB2b2dsaWFtbyBpbXBvcnRhcmUgbGEgcHJpbWEgcmlnYSBjb21lIG5vbWkgZGVsbGUgY29sb25uZS4gQXNzZWduYW5kbyBhZCB1biBvZ2dldHRvIGNvbWUgcGVyIGByZWFkLnRhYmxlKClgIGF2cmVtbyBpbiBhdXRvbWF0aWNvIHVuIGRhdGFmcmFtZS4KCmBgYHtyfQpteV9jc3YgPC0gcmVhZC5jc3YoImRhdGEvY3N2X2V4YW1wbGUuY3N2IiwgaGVhZGVyID0gVCkgIyBzZXAgZGkgZGVmYXVsdCDDqCB2aXJnb2xhCm15X2NzdgpgYGAKCiMjIyBgLnhzbHNgCgpFc2lzdG9ubyBkaXZlcnNlIGZ1bnppb25pIHBlciBpbXBvcnRhcmUgZmlsZSBkaXJldHRhbWVudGUgZGFsIGZvcm1hdG8gRXhjZWwuIFNvbGl0YW1lbnRlIMOoIHBvc3NpYmlsZSBldml0YXJlIGRpIHV0aWxpenphcmUgZnVuemlvbmkgbyBwYWNjaGV0dGkgYWdnaXVudGl2aSBzZW1wbGljZW1lbnRlIHNhbHZhbmRvIGlsIGZpbGUgRXhjZWwgY29tZSBgY3N2YCBlIHVzYXJlIGxlIGZ1bnppb25pIGRpIGltcG9ydGF6aW9uZSBwcmVjZWRlbnRpLiBUdXR0YXZpYSBhIHZvbHRlIMOoIGNvbW9kbyBpbXBvcnRhcmUgZGlyZXR0YW1lbnRlIGlsIGZpbGUgbmVsIGZvcm1hdG8gb3JpZ2luYWxlLiBTb25vIGRpc3BvbmliaWxpIGRpdmVyc2kgcGFjY2hldHRpIGUgZnVuemlvbmk6CgpJbCBwYWNjaGV0dG8gW2ByZWFkeGxgXShodHRwczovL3JlYWR4bC50aWR5dmVyc2Uub3JnLykgY29uIGxlIGZ1bnppb25pIGByZWFkeGw6OnJlYWRfZXhjZWwoKWAsIGByZWFkeGw6OnJlYWRfeGxzKClgIGUgYHJlYWR4bDo6cmVhZF94bHN4KClgLiBSaXNwZXR0byBhaSBgY3N2YCBpIGZpbGUgRXhjZWwgc29ubyBwacO5IGNvbXBsZXNzaSBlIHBlcm1ldHRvbm8gZGkgYXZlcmUgcGnDuSAiZm9nbGkiLiBRdWVzdGUgZnVuemlvbmkgcGVybWV0dG9ubyBhbmNoZSBkaSBpbXBvcnRhcmUgdW4gZm9nbGlvIHNwZWNpZmljbyAoYXJnb21lbnRvIGBzaGVldCA9YCkgb3BwdXJlIHVuIHJhbmdlIHNwZWNpZmljbyBuZWwgZm9nbGlvIGRpIGRlc3RpbmF6aW9uZSAoYXJnb21lbnRvIGByYW5nZSA9YCkgRXhjZWwgaWRlbnRpZmljYSBpbmZhdHRpIGxlIHJpZ2hlIGluIG1vZG8gbnVtZXJpY28gYDEsMiwzLDQuLi5gIGUgbGUgY29sb25uZSBjb24gbGUgbGV0dGVyZSBkZWxsJ2FsZmFiZXRvIGluIG1vZG8gcHJvZ3Jlc3Npdm8gYEEsIEIsIEMgLi4uYAoKYGBge3J9Cm15X2V4Y2VsIDwtIHJlYWR4bDo6cmVhZF94bHN4KCJkYXRhL2V4Y2VsX2V4YW1wbGUueGxzeCIsIHJhbmdlID0gIkExOkQxMCIpCm15X2V4Y2VsCmBgYAoKSW4gYWx0ZXJuYXRpdmEgw6ggcHJlc2VudGUgYW5jaGUgaWwgcGFjY2hldHRvIFtgeGxzeGBdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy94bHN4L3hsc3gucGRmKSBjaGUgcGVybWV0dGUgbGUgc3Rlc3NlIGZ1bnppb25hbGl0w6AgbWEgaW4gZ2VuZXJhbGUgaWwgcGFjY2hldHRvIGByZWFkeGxgIMOoIGlsIG1pZ2xpb3JlLiBMJ3VuaWNhIGRpZmZlcmVuemEgcmlndWFyZGEgbGEgY2FwYWNpdMOgIGRpICpzY3JpdmVyZSogcXVpbmRpIGNyZWFyZSBmaWxlIGV4Y2VsIGRhIHVuIG9nZ2V0dG8gUi4gRmlub3JhIGFiYmlhbW8gc29sbyBwYXJsYXRvIGRpIGxldHR1cmEgZGkgZmlsZSBtYSBSIHBlcm1ldHRlIGFuY2hlIGRpIGNyZWFyZSB0dXR0aSBpIGZvcm1hdGkgY2hlIGxlZ2dlLiBJbiBxdWVzdG8gY2FzbyBpbCBwYWNjaGV0dG8gYHJlYWR4bGAgcGVybWV0dGUgc29sbyBkaSBsZWdnZXJlIG1lbnRyZSBgeGxzeGAgYW5jaGUgZGkgc2NyaXZlcmUuIElsIHBhY2NoZXR0byBbYFdyaXRlWExTYF0oV3JpdGVYTFMpIGNvbWUgZGljZSBpbCBub21lIGludmVjZSDDqCBzcGVjaWZpY28gcGVyIGxhIHNjcml0dHVyYSBkaSBmaWxlLgoKIyMjIGAuc2F2YAoKSWwgZm9ybWF0byBgLnNhdmAgw6ggc3BlY2lmaWNvIGRlbCBzb2Z0d2FyZSBTUFNTIGUgcHXDsiBlc3NlcmUgZ2VzdGl0byBjb24gaWwgcGFjY2hldHRvIFtgaGF2ZW5gXShodHRwczovL2hhdmVuLnRpZHl2ZXJzZS5vcmcvKSBjaGUgcGVybWV0dGUgZGkgbGVnZ2VyZSBlIHNjcml2ZXJlIGZpbGUgaW4gcXVlc3RvIGZvcm1hdG8uIEluIHF1ZXN0byBjYXNvLCBpbiBtb2RvIHNpbWlsZSBhbGwnZXhjZWwsIG5vbiBjaSBzb25vIHBhcnRpY29sYXJpIG9wemlvbmkgcGVyIGxlZ2dlcmUvc2NyaXZlcmUgc2Ugbm9uIHNwZWNpZmljYXJlIGlsIHBlcmNvcnNvIGRlbCBmaWxlLgoKYGBge3J9Cm15X3NhdiA8LSBoYXZlbjo6cmVhZF9zYXYoZmlsZSA9ICJkYXRhL3Nhdl9leGFtcGxlLnNhdiIpCm15X3NhdgpgYGAKCiMjIEVsZW1lbnRpIGNvbXVuaQoKQSBwcmVzY2luZGVyZSBkYWwgZm9ybWF0byBvIGRhbGxhIGZ1bnppb25lIHV0aWxpenphdGEsIGFiYmlhbW8gdmlzdG8gY2hlIGdlbmVyYWxtZW50ZSBzb25vIGltcG9ydGFudGkgYWxjdW5lIGluZm9ybWF6aW9uaToKCi0gYHRpcG8gZGkgc2VwYXJhdG9yZWA6IG92dmVybyBpbCBjYXJhdHRlcmUgdXRpbGl6emF0byBwZXIgc2VwYXJhcmUgdW4naW5mb3JtYXppb25lIGRhbGwnYWx0cmEuIE5lbCBjYXNvIGRlaSBmaWxlIGBjc3ZgIGFkIGVzZW1waW8gw6ggbGEgdmlyZ29sYS4KLSBgTkFgOiBxdWFuZG8gaW1wb3J0aWFtbyBkZWkgZGF0aSBwb3NzaWFtbyB1c2FyZSBxdWFsc2lhc2kgbW9kYWxpdMOgIHBlciBpZGVudGlmaWNhcmUgZGF0aSBtYW5jYW50aSAoaW4gUiBgTkFgKS4gRScgaW1wb3J0YW50ZSBkaXJlIGFsbGEgZnVuemlvbmUgZGkgaW1wb3J0YXppb25lIGluIGNoZSBtb2RvIG5vaSBhYmJpYW1vIHJhcHByZXNlbnRhdG8gZ2xpIGBOQWAgbmVsIGZpbGUgZGkgb3JpZ2luZSBpbiBtb2RvIGNoZSB2ZW5nYW5vIGdlc3RpdGkgZGlyZXR0YW1lbnRlIGluIFIuIFNlIG5vbiBmYWNjaWFtbyBxdWVzdG8gUiBpbnRlcHJldGVyw6AgY29tZSBkYXRpIG5vcm1hbGkgcXVlc3RpIHZhbG9yaSBjcmVhbmRvIGRlZ2xpIGlwb3RldGljaSBwcm9ibGVtaS4gVmVkaWFtbyB1biBlc2VtcGlvOgoKSW1tYWdpbmlhbW8gZGkgYXZlcmUgaWwgZGF0YXNldCBgbXlfaXJpc2AgY29uIGRlaSB2YWxvcmkgYE5BYDoKCmBgYHtyfQpteV9pcmlzIDwtIGlyaXNbMToxMCwgXQpteV9pcmlzIDwtIGZpbG9yOjphZGRfcmFuZG9tX25hKG15X2lyaXMsIDEwKQpteV9pcmlzCmBgYAoKT3JhIHNjcml2aWFtbyBpbCBmaWxlIGNvbWUgYGNzdmAgc3BlY2lmaWNhbmRvIHVuIGNlcnRvIHRpcG8gZGkgc3RyaW5nYSBwZXIgZ2xpIGBOQWA6CgpgYGB7cn0Kd3JpdGUuY3N2KG15X2lyaXMsIGZpbGUgPSAiZGF0YS9uYV9jc3ZfZXhhbXBsZS5jc3YiLCBuYSA9ICJOQSIsIHJvdy5uYW1lcyA9IEZBTFNFKQpgYGAKCk9yYSB2ZWRpYW1vIGlsIHJpc3VsdGF0bzoKCmBgYHtyIGNvbW1lbnQ9JycsIGVjaG89RkFMU0V9CmNhdChyZWFkTGluZXMoJ2RhdGEvbmFfY3N2X2V4YW1wbGUuY3N2JyksIHNlcCA9ICdcbicpCmBgYAoKQ29tZSB2ZWRldGUgYWJiaWFtbyBpbCBkYXRhc2V0IGUgaW4gbWV6em8gZGVpIHZhbG9yaSBgTkFgLiBQcm92aWFtbyBhIGltbWFnaW5hcmUgZGkgbm9uIGF2ZXIgY3JlYXRvIHF1ZXN0byBmaWxlIGUgZG9iYmlhbW8gaW1wb3J0YXJsbyBwZXIgbGEgcHJpbWEgdm9sdGEgaW4gUjoKCmBgYHtyfQpteV9jc3YgPC0gcmVhZC5jc3YoZmlsZSA9ICJkYXRhL25hX2Nzdl9leGFtcGxlLmNzdiIsIGhlYWRlciA9IFRSVUUpCnNhcHBseShteV9jc3YsIHR5cGVvZikKYGBgCgpDb21lIHZlZGV0ZSBSIGludGVycHJldGEgY29ycmV0dGFtZW50ZSBpIHZhbG9yaSBgTkFgIG1hbnRlbmVuZG8gaWwgdGlwbyBkaSBjb2xvbm5hIChxdWluZGkgbm9uIGludGVwcmV0YW5kbyBgTkFgIGNvbWUgc3RyaW5nYSkgcGVyY2jDqCDDqCBpbCBtb2RvIGRpIGRlZmF1bHQgZGkgZ2VzdGlvbmUuIFByb3ZpYW1vIGEgc2NyaXZlcmUgdW4nYWx0cm8gZmlsZSB1c2FuZG8gdW4gbW9kbyBkaXZlcnNvIGRpIGdlc3RpcmUgZ2xpIGBOQWA6CgpgYGB7cn0Kd3JpdGUuY3N2KG15X2lyaXMsIGZpbGUgPSAiZGF0YS9uYV9jc3ZfZXhhbXBsZS5jc3YiLCBuYSA9ICJOTyIsIHJvdy5uYW1lcyA9IEZBTFNFKQpgYGAKCk9yYSBnbGkgYE5BYCBzb25vIHJhcHByZXNlbnRhdGkgY29tZSBgTk9gOgoKYGBge3IgY29tbWVudD0nJywgZWNobz1GQUxTRX0KY2F0KHJlYWRMaW5lcygnZGF0YS9uYV9jc3ZfZXhhbXBsZS5jc3YnKSwgc2VwID0gJ1xuJykKYGBgCgpJbmZhdHRpIHNlIGltcG9ydGlhbW8gY29uIGxhIHN0ZXNzYSBmdW56aW9uZSBhYmJpYW1vIHVuIHJpc3VsdGF0byBzdHJhbm86CgpgYGB7cn0KbXlfY3N2IDwtIHJlYWQuY3N2KGZpbGUgPSAiZGF0YS9uYV9jc3ZfZXhhbXBsZS5jc3YiLCBoZWFkZXIgPSBUUlVFKQpteV9jc3YKc2FwcGx5KG15X2NzdiwgdHlwZW9mKQpgYGAKClR1dHRlIGxlIGNvbG9ubmUgc29ubyBpbnRlcHJldGF0ZSBjb21lIHN0cmluZ2hlIHBlcmNoZSBgIk5PImAgw6ggZ2VzdGl0byBjb21lIHRhbGUuIFBlciBpbXBvcnRhcmUgY29ycmV0dGFtZW50ZSBkb2JiaWFtbyBzcGVjaWZpY2FyZSBsYSBzdHJpbmdhIGdpdXN0YToKCmBgYHtyfQpteV9jc3YgPC0gcmVhZC5jc3YoZmlsZSA9ICJkYXRhL25hX2Nzdl9leGFtcGxlLmNzdiIsIGhlYWRlciA9IFRSVUUsIG5hLnN0cmluZ3MgPSAiTk8iKQpteV9jc3YKc2FwcGx5KG15X2NzdiwgdHlwZW9mKQpgYGAK