Ce tutoriel est le fruit de mes découvertes sur le portail R et n’engage que moi. Ce n’est évidemment pas la seule manière de travailler sur le portail R. Tous les éléments de code devraient fonctionner à condition que vous ayez accès à l’Echantillon SNDS (ESND) qui est utilisé comme exemple. C’est le cas de tous les accès permanents au SNDS.

Vous pouvez télécharger le .Rmd pour exécuter le code vous-même en sélectionnant le bouton en haut à droite de cette page “Code ▾” puis “Download Rmd”

1 Comment accéder à R ?

La démarche d’accès est expliquée dans le document R - Accès à R sur portail SNDS.pdf que l’on trouve dans l’onglet Accueil du portail.

2 Ressources à lire

  • (indispensable) guide utilisateur de la Cnam, R - Guide_utilisateur_RStudio_ V1.2.pdf que l’on trouve dans l’onglet Accueil du portail. Les informations les plus importantes s’y trouvent et ce tutoriel ne fait que compléter.
    Ce tutoriel n’est pas compréhensible sans le guide de la Cnam.
  • (utile) les conseils de R - Accès à R sur portail SNDS.pdf que l’on trouve dans l’onglet Accueil du portail.
  • (utile si on ne connaît pas bien R, peu utile sinon) le support de la formation R proposée par la Cnam que l’on trouvera sur le portail (“Tableaux et requêtes” > SAP BusinesObjects > Documents > Dossiers > SNIIRAM > _Documentation SNIIRAM-SNDS > Formation > support des formations SNIIRAM > 11-Logiciels R). Plutôt généraliste, pour apprendre R, très peu de points spécifiques au SNDS.
  • (utile) le Guide de bonnes pratiques SAS - v2.1.pdf qui se trouve sur la page d’accueil du portail. Un certain nombre de points sur l’optimisation des codes valent aussi pour R.
  • (utile) le support de formation Données individuelles bénéficiaires, à relire deux fois par semaine, et que l’on trouvera sur le portail (“Tableaux et requêtes” > SAP BusinesObjects > Documents > Dossiers > SNIIRAM > _Documentation SNIIRAM-SNDS > Formation > support des formations SNIIRAM > 7-DIB).

3 Paramétrage de RStudio

La Cnam a rappelé par mail quelques paramètres importants pour RStudio, qui valent d’ailleurs au-delà du portail de la Cnam. Ils évitent de recharger les objets au démarrage de la session, ce qui peut être très long.

Dans le menu Tools > Global Options :

  • Désactiver « Restore most recently open project at startup »
  • Désactiver « Restore .RData into workspace at startup »
  • Désactiver « Save workspace to .RData on exit »

4 Connexion à la base de données

On apprend dans R - Guide_utilisateur_RStudio_ V1.2.pdf que ce bloc est indispensable :

library(ROracle) # charger le package Roracle
drv <- dbDriver("Oracle") # obtenir le pilote pour se connecter à une base oracle
conn <- dbConnect(drv, dbname = "IPIAMPR2.WORLD") # se connecter à la base IPIAMPR2.WORLD
Sys.setenv(TZ = "Europe/Paris") # fuseaux horaires
Sys.setenv(ORA_SDTZ = "Europe/Paris")

Il permet de se connecter à la base de données Oracle de la Cnam. Vous pourriez être tenté par une connexion via le pannel Connections de RStudio mais cela n’est pas possible. Pour l’explication technique, RStudio ne supporte à ce jour que les connexions ODBC et Spark tandis que ROracle utilise une connexion DBI.

On crée une fausse table pour le tutoriel.

# Créer un jeu ex nihilo 
faux0 <- data.frame(x = 1:4, y = c(1, NA), z = c(rep("A",3), "B"))
try(dbRemoveTable(conn, "PROV"), silent = T) # supprimer la table si elle existe déjà, sinon erreur
dbWriteTable(conn, "PROV", faux0) # écrire la table
faux <- tbl(conn, "PROV") # charger la table

5 Les deux manières : SQL ou dplyr

La guide de la Cnam propose de travailler soit en SQL Oracle soit en syntaxe dplyr. Illustration et comparaison :

5.1 Exemple SQL Oracle

Exemple : on récupère le référentiel de biologie.

dbGetQuery(conn, "select * from ir_bio_r")[1:5,]

ne pas utiliser fetch first 100 rows only qui n’est pas optimisé et n’aboutit pas (explications) comme dans la commande suivante :

# NE PAS EXECUTER, N'ABOUTIT PAS
# dbGetQuery(conn, "select * from ER_PRS_F fetch first 100 rows only")

Alors que celle-ci est immédiate (commentée car il s’agit de résultats individuels non publiables) :

# dbGetQuery(conn, "SELECT * FROM ER_PRS_F WHERE ROWNUM <= 100.0")

5.2 Exemple Dplyr

Exemple : on récupère le référentiel de biologie.

library(dplyr) # le paquet dplyr est nécessaire pour cette méthode
library(dbplyr) # je recommande de charger également dbplyr pour quelques fonctions suppémentaires

irbior <- tbl(conn, "IR_BIO_R")
irbior %>% collect %>% head(5)

5.3 Quelle manière choisir ?

Les habitués de SAS préféreront peut-être travailler directement en SQL en envoyant les requêtes via dbGetQuery(). Personnellement, et même si je n’aime pas dplyr en temps normal, je trouve la manière dplyr beaucoup plus souple. En particulier elle permet de faire les choses progressivement grâce à l’évaluation paresseuse (lazy). Le logiciel n’exécute pas directement chaque étape mais les enregistre jusqu’au moment où il n’a plus le choix, par exemple lorsqu’on lui demande un comptage.

Exemple : on peut créer un objet erprsf avec des filtres usuels sur ESND_ER_PRS_F (échantillon SNDS, 2% des bénéficiaires). Si l’on ne collecte pas le résultat, l’opération est immédiate puisqu’on ne réalise pas réellement le filtre. Ensuite, on a tout le loisir de repartir de cet objet pour filtrer sur telle et telle prestation. Un dbGetQuery sur ERE_PRS_F avec des filtres usuels serait impossible car demanderait trop de ressources en mémoire vive. Donc il faudrait, pour chaque ciblage de prestation, remettre tous les filtres depuis ERE_PRS_F si l’on passait par SQL Oracle. Traduction en code :

# Immédiat (évaluation paresseuse)
erprsf <- tbl(conn, in_schema("MEPSGP_156", "ESND_ER_PRS_F")) %>%
    filter(
        FLX_DIS_DTD - EXE_SOI_DTD < 183 & # on enlève
        # les soins remontés 6 mois après la date de début pour raisonner à durée de flux contante
        DPN_QLF != 71 &
        PRS_DPN_QLP != 71 &
        CPL_MAJ_TOP < 2 &
        CPL_AFF_COD != 16 &
        (!BEN_SEX_COD %in% c("0")) &
        BEN_NAI_ANN > 1800 &
        BEN_NAI_ANN <= 2016
    ) %>%
  select(BEN_NIR_PSA, BEN_RNG_GEM, BEN_SEX_COD, BEN_AMA_COD, BEN_RES_DPT, BEN_RES_COM, FLX_DIS_DTD, EXE_SOI_AMD, EXE_SOI_DTD, PRS_NAT_REF, PSE_ACT_NAT, PSE_SPE_COD, PRS_ACT_QTE)

Filtrer les vaccins contre la grippe (codes prestations 3331) en repartant de l’objet erprsf :

erprsf %>% filter(PRS_NAT_REF == "3331") %>% tally

Filtrer les soins des médecins généralistes (codes spécialité 1, 22 ou 23) en repartant de l’objet erprsf :

erprsf %>% filter(PSE_SPE_COD %in% c(1, 22, 23)) %>% tally

La plupart des commandes présentées ensuite concernent la manière dplyr.

5.4 REGLE D’OR

Il faut bien choisir le moment du rapatriement en R (collect ou dbGetQuery). Globalement, plus ce moment vient tard, mieux c’est. Autrement dit plus on fait travailler Oracle au lieu de R, mieux c’est. C’est le même principe si on travaillait sur un autre moteur de base de données ou sur Spark. Ne jamais rapatrier des données de prestations (par exemple un bout de ER_PRS_F), cela va être très long tout en risquant de surcharger la mémoire vive du serveur, conduisant à un mail de la Cnam et l’arrêt de la session. On peut éventuellement rapatrier une table avec autant de lignes que d’individus, à condition de sélectionner peu de colonnes. Ensuite finir en R.

5.5 Traduire une commande dplyr en SQL Oracle

Dans les coulisses, le paquet dbplyr traduit le code type dplyr en SQL Oracle. Pour s’en convaincre, on peut montrer la requête effectivement exécutée avec show_query() ou remote_query()

tbl(conn, "IR_BIO_R") %>% show_query # du paquet dplyr
<SQL>
SELECT *
FROM "IR_BIO_R"
tbl(conn, "IR_BIO_R") %>% remote_query # du paquet dbplyr
<SQL> SELECT *
FROM "IR_BIO_R"

show_query affiche la commande sous la forme d’un message tandis que remote_query renvoie la commande sous la forme d’un résultat, ce qui peut permettre d’enregistrer le code de la requête comme on le verra plus bas.

show_query() et remote_query() peuvent être pratiques si l’on veut travailler directement avec le SQL Oracle (via dbGetQuery()) mais que l’on est rouillé. Ces commandes nous donnent la traduction SQL oubliée d’une commande dplyr que l’on connait.

Astuce : on peut montrer la traduction en même temps qu’on demande l’exécution de la requête en enchainant par de nouvelles commandes après show_query :

tbl(conn, "IR_BIO_R") %>%
  show_query  %>%
  head(1) %>%
  collect
<SQL>
SELECT *
FROM "IR_BIO_R"

5.6 Commandes dplyr non supportées

Toutes les commandes dplyr ne fonctionneront pas car le paquet dbplyr ne les traduit pas toutes.

Par exemple, la commande slice qui doit permettre de sélectionner une ligne par son numéro, ne peut pas être traduite :

# Ne fonctionne PAS sur une connexion à une base Oracle :
# tbl(conn, "IR_BIO_R") %>% slice(100)

# Alors qu'elle fonctionne sur un tableau déjà rappatrié en R :
tbl(conn, "IR_BIO_R") %>% collect %>% slice(100)

5.7 Fonctions courantes

Principalement des fonctions dbplyr.

5.7.1 Créer une colonne : mutate

On peut modifier une colonne ou la créer si elle n’existe pas encore.

Exemple sur le faux jeu : passer la colonne x au carré.

faux %>% mutate(x = x^2)

Créer une nouvelle colonne avec le carré de la colonne x.

faux %>% mutate(x2 = x^2)

Pour éviter les problèmes, respecter la casse ; si on crée une colonne sous le nom de “NoM”, il vaut mieux l’appeler par “NoM”. “nom” marchera dans certains cas (en SQL) et pas dans d’autres.

quelques raccourcis habituels en R ne fonctionnent plus

Par exemple, sur un objet R (ici rappatrié via collect), on peut créer une colonne avec une condition.

faux %>% collect %>% mutate(x2 = x > 2)

Sur une base Oracle (sans le collect), cela générera une erreur :

faux %>% mutate(x2 = x > 2) %>% show_query() %>% collect
<SQL>
SELECT "x", "y", "z", "x" > 2.0 AS "x2"
FROM ("PROV") 
Erreur dans .oci.SendQuery(conn, statement, data = data, prefetch = prefetch,  : 
  ORA-00923: mot-clé FROM absent à l'emplacement prévu

Et il faudra expliciter avec ifelse, qui sera traduit par CASE WHEN en SQL :

faux %>% mutate(x2 = ifelse(x > 2, 1, 0)) %>% show_query() %>% collect # fonctionne
<SQL>
SELECT "x", "y", "z", CASE WHEN ("x" > 2.0) THEN (1.0) WHEN NOT("x" > 2.0) THEN (0.0) END AS "x2"
FROM ("PROV") 

5.7.1.1 Astuce : manipuler une colonne juste après sa création

On peut utiliser dans le même mutate une colonne qui vient d’être créée.

On crée une variable x3 qui dépend d’une variable x2 créée par la même occasion.

faux %>% mutate(x2 = x^2,
                x3 = x2 + 10)

5.7.2 Ordre : arrange

On peut ordonner avec arrange qui va créer un ORDER BY en SQL :

faux %>% arrange(-x) %>% show_query() %>% collect
<SQL>
SELECT *
FROM ("PROV") 
ORDER BY -"x"

-x permet d’obtenir un ordre décroissant tout comme desc(x).

5.7.3 Dédoublonnage : distinct

La fonction dbplyr distinct correspond à la fonction R base unique, elle élimine les lignes en doublons.

Exemple avec notre faux jeu de données.

faux %>% select(y) %>% distinct

5.7.4 Comptes : tally

tally permet de compter le nombre de lignes et va être traduit par un COUNT(*) en SQL :

mcoc <- tbl(conn, in_schema("MEPSGP_156", "ESND_T_MCO19C"))
mcoc %>%
  select("NIR_ANO_17") %>%
  distinct %>%
  tally %>%
  show_query %>%
  collect
<SQL>
SELECT COUNT(*) AS "n"
FROM (SELECT DISTINCT "NIR_ANO_17"
FROM ("MEPSGP_156"."ESND_T_MCO19C") ) "q01"

5.7.5 Groupages : group_by

tally fonctionne aussi avec group_by si l’on souhaite grouper par une variable.

Exemple on veut compter le nombre de séjours finis chaque mois de l’année 2019.

mcoc %>%
  group_by(EXE_SOI_AMF) %>%
  tally %>%
  show_query %>%
  collect
<SQL>
SELECT "EXE_SOI_AMF", COUNT(*) AS "n"
FROM ("MEPSGP_156"."ESND_T_MCO19C") 
GROUP BY "EXE_SOI_AMF"

count est un raccourci qui fait la même chose. Notez que c’est exactement la même traduction SQL :

mcoc %>%
  count(EXE_SOI_AMF) %>%
  show_query %>%
  collect
<SQL>
SELECT "EXE_SOI_AMF", COUNT(*) AS "n"
FROM ("MEPSGP_156"."ESND_T_MCO19C") 
GROUP BY "EXE_SOI_AMF"

5.7.6 Agrégations

tally est un raccourci pour summarise(n=n())

faux %>%
  group_by(y) %>%
  summarise(n = n()) %>% # au lieu de tally 
  show_query %>%
  collect
<SQL>
SELECT "y", COUNT(*) AS "n"
FROM ("PROV") 
GROUP BY "y"

On peut choisir d’agréger d’une autre manière, en prenant un compte unique, la moyenne, la somme, le max :

faux %>% summarise(n = n(),
                   n_unique = n_distinct(y),
                   moy = mean(x),
                   tot = sum(x),
                   max = max(x))

Parfois il est pratique d’ordonner et de prendre la première (first()) ou la dernière valeur (last()). Malheureusement, ces deux commandes ne fonctionnent qu’en tant que fonction de fenêtrage (avec mutate()) et non en fonction d’agrégation (avec summarize). Ainsi cette commande échoue :

faux %>% summarise(premiere = first(x))
Erreur : `first()` is only available in a windowed (`mutate()`) context

Une astuce consiste à coupler mutate() avec select() et distinct() :

faux %>%
  group_by(z) %>%
  mutate(premiere = first(x)) %>%
  select(z, premiere) %>%
  distinct

Mais il faut veiller à choisir les bonnes colonnes dans le select pour arriver au résultat escompté.

5.7.7 Fonctions de fenêtrage

5.7.7.1 Première observation

On peut utiliser group_by puis arrange (ou dans l’autre sens) pour ensuite créer une variable de rang ou filtrer directement sur le rang 1.

faux %>%
  arrange(-x) %>%
  group_by(y) %>%
  filter(row_number() == 1)
Avis : ORDER BY is ignored in subqueries without LIMIT
ℹ Do you need to move arrange() later in the pipeline or use window_order() instead?

On reçoit un avertissement ORDER BY is ignored in subqueries without LIMIT mais cela semble pourtant bien fonctionner, comme on peut le voir si on remplace -x par x puisque le résultat est cohérent. À manipuler avec précaution.

row_number accepte un argument d’ordre, ce qui permet de gagner en concision :

faux %>%
  group_by(y) %>%
  filter(row_number(-x) == 1)

Bémol : row_number n’accepte qu’une variable d’ordre, à la différence de arrange.

La fonction window_order() pourrait être la solution :

faux %>%
  group_by(y) %>%
  window_order(-x, z) %>%
  filter(row_number() == 1)

En plus le tibble garde les variables de groupage et d’ordre en attributs et les affiche, ce qui est pratique.

5.7.7.2 Agrégation par recyclage

L’agrégation par recyclage est un type particulier de fonction de fenêtrage. A la différence des fonctions d’agrégation, on garde le même nombre de lignes mais on réplique l’information au sein de chaque groupe. Il suffit de remplacer summarise() par mutate().

Exemple :

faux %>%
  group_by(z) %>%
  mutate(som = n_distinct(x))

5.7.7.3 Pour les amateurs

Pour des fonctions de fenêtrage plus complexes, on est obligés de passer par des méthodes plus proches de SQL. Un option est la fonction win_over qui aide à construire son fenêtrage.

faux %>%
  mutate(rang = dbplyr::win_over(con = conn, expr = sql("RANK()"), partition = "y", order = "x"))

Mais il n’est pas possible d’utiliser un ordre décroissant à ma connaissance.

Rien ne vaut le 100% SQL dans ce cas :

faux %>%
  mutate(rang = sql('RANK() OVER (PARTITION BY "y" ORDER BY "x" DESC)'))

5.7.8 Valeurs manquantes : NVL

On peut facilement remplacer les valeurs manquantes dans Oracle avec la fonction NVL (no value).

Exemple avec notre faux jeu de données.

faux %>%
  mutate(y2 = NVL(y, NA), # ne change rien
         y3 = NVL(y, 0)) # remplace par 0

# NVL(y, "A") ne fonctionne pas car y est de type numeric

# Exemple réel (commenté car résultats individuels)
# irbenr <- tbl(conn, "IR_BEN_R")
# irbenr %>%
#   select(IND_RNM_BEN) %>%
#   mutate(IND_RNM_BEN2 = NVL(IND_RNM_BEN, "NA")) # On peut mettre ce que l'on veut à la place de "NA"

5.7.9 Empiler des tables : union_all

Equivalent de la fonction R rbind :

union_all(faux, faux) %>% show_query() %>% collect
<SQL>
(SELECT *
FROM "PROV")
UNION ALL
(SELECT *
FROM "PROV")

5.7.10 Jointures

D’après le manuel de dbplyr, toutes les jointures possibles et leur équivalent en SQL :

  • inner_join(x, y) : SELECT * FROM x JOIN y ON x.a = y.a
  • left_join(x, y) : SELECT * FROM x LEFT JOIN y ON x.a = y.a
  • right_join(x, y) : SELECT * FROM x RIGHT JOIN y ON x.a = y.a
  • full_join(x, y) : SELECT * FROM x FULL JOIN y ON x.a = y.a
  • semi_join(x, y) : SELECT * FROM x WHERE EXISTS (SELECT 1 FROM y WHERE x.a = y.a)
  • anti_join(x, y) : SELECT * FROM x WHERE NOT EXISTS (SELECT 1 FROM y WHERE x.a = y.a)

Pour chercher plus de fonctions, le mieux est encore de consulter le manuel de dbplyr.

5.8 Utiliser SQL dans dplyr

Il est possible d’utiliser des commandes SQL directement dans la syntaxe dplyr lorsqu’un besoin n’est pas couvert par une commande dplyr.

Il y a trois manières de faire : avec des fonctions SQL, avec des opérateurs SQL, avec des bouts de code SQL.

5.8.1 Fonctions

Si une fonction n’est pas reconnue par R, elle va être laissée telle quelle pour Oracle, ce qui est très pratique. A vrai dire, c’était le cas de la fonction NVL présentée ci-dessus.

Exemple : la fonction CONCAT pour concaténer n’existe pas en R mais en Oracle :

tbl(conn, "T_MCO15C") %>% mutate(ano_retour = CONCAT(NIR_RET, NAI_RET)) %>% show_query
<SQL>
SELECT "EXE_SOI_AMD", "EXE_SOI_DTD", "EXE_SOI_AMF", "EXE_SOI_DTF", "COH_NAI_RET", "COH_SEX_RET", "DAT_RET", "ENT_DAT", "ETA_NUM", "FHO_RET", "FOR_NUM", "HOS_NNE_RET", "HOS_NN_MAM", "HOS_ORG_RET", "HOS_PLO", "NAI_RET", "NIR_ANO_17", "NIR_ANO_MAM", "NIR_RET", "NUM_DAT_AT", "NUM_DAT_AT_RET", "ORG_CPL_NUM_RET", "PMS_RET", "RNG_BEN", "RNG_BEN_RET", "RNG_NAI", "RNG_NAI_RET", "RSA_NUM", "SEJ_MER_RET", "SEJ_NUM", "SEJ_RET", "SEX_RET", "SOR_ANN", "SOR_DAT", "SOR_MOI", "VID_HOSP_FOR", CONCAT("NIR_RET", "NAI_RET") AS "ano_retour"
FROM ("T_MCO15C") 

L’inconvénient de ces fonctions est qu’elles ne s’auto-complètent pas en appuyant sur TAB et qu’on ne dispose pas d’une documentation intégrée. Il faut utiliser son navigateur pour les découvrir et apprendre à les utiliser. Exemple de documentation pour CONCAT.

5.8.2 Opérateurs

Si un opérateur entre % n’est pas reconnu par R, il va être laissé tel quel et Oracle pourra le comprendre.

Exemple sur un filtre classique pour retirer les séjours de MCO avec une erreur sur le NIR, on utilise %||% qui va devenir simplement || et qui permet la concaténation de plusieurs variables :

mcoc %>%
  mutate(ano_retour = NIR_RET %||% NAI_RET %||% SEX_RET %||%
  SEJ_RET %||% FHO_RET %||% PMS_RET %||% DAT_RET %||% COH_NAI_RET %||% COH_SEX_RET) %>%
  filter(ano_retour == "000000000") %>%
  tally %>%
  show_query %>%
  collect
<SQL>
SELECT COUNT(*) AS "n"
FROM (SELECT "EXE_SOI_AMD", "EXE_SOI_DTD", "EXE_SOI_AMF", "EXE_SOI_DTF", "COH_NAI_RET", "COH_SEX_RET", "DAT_RET", "ENT_ANN", "ENT_DAT", "ENT_MOI", "ETA_NUM", "ETA_NUM_RET", "FHO_RET", "FOR_NUM", "HOS_NNE_RET", "HOS_NN_MAM", "HOS_ORG_RET", "HOS_PLO", "ID_MAM_ENF", "IPP_BEN_ANO", "IPP_BEN_ANO_RET", "NAI_RET", "NIR_ANO_17", "NIR_RET", "NUM_DAT_AT", "NUM_DAT_AT_RET", "ORG_CPL_NUM", "ORG_CPL_NUM_RET", "PMS_RET", "RNG_BEN", "RNG_BEN_RET", "RNG_NAI", "RNG_NAI_RET", "RSA_NUM", "SEJ_MER_RET", "SEJ_NUM", "SEJ_RET", "SEX_RET", "SOR_DAT", "VID_HOSP_FOR", "NIR_RET" || "NAI_RET" || "SEX_RET" || "SEJ_RET" || "FHO_RET" || "PMS_RET" || "DAT_RET" || "COH_NAI_RET" || "COH_SEX_RET" AS "ano_retour"
FROM ("MEPSGP_156"."ESND_T_MCO19C") ) "q01"
WHERE ("ano_retour" = '000000000')

L’exemple le plus pratique est l’opérateur LIKE qui permet d’utiliser les expressions régulières (regex) Oracle pour décrire des chaînes de caractères complexes. Si on veut que PRS_NAT_REF commence par 31 :

erprsf %>% filter(PRS_NAT_REF %LIKE% "31%") %>% show_query
<SQL>
SELECT *
FROM (SELECT "BEN_NIR_PSA", "BEN_RNG_GEM", "BEN_SEX_COD", "BEN_AMA_COD", "BEN_RES_DPT", "BEN_RES_COM", "FLX_DIS_DTD", "EXE_SOI_AMD", "EXE_SOI_DTD", "PRS_NAT_REF", "PSE_ACT_NAT", "PSE_SPE_COD", "PRS_ACT_QTE"
FROM ("MEPSGP_156"."ESND_ER_PRS_F") 
WHERE ("FLX_DIS_DTD" - "EXE_SOI_DTD" < 183.0 AND "DPN_QLF" != 71.0 AND "PRS_DPN_QLP" != 71.0 AND "CPL_MAJ_TOP" < 2.0 AND "CPL_AFF_COD" != 16.0 AND (NOT("BEN_SEX_COD" IN ('0'))) AND "BEN_NAI_ANN" > 1800.0 AND "BEN_NAI_ANN" <= 2016.0)) "q01"
WHERE ("PRS_NAT_REF" LIKE '31%')

les regex Oracle ne fonctionnent pas de la même manière que les regex courantes sur Linux.

5.8.3 Bouts de code SQL

On peut toujours glisser des bouts de code SQL s’il n’y a quelque chose que l’on ne sait pas faire en syntaxe dplyr. Pour ça on utilisera la commande sql.

erprsf %>% filter(PSE_SPE_COD == sql("ANY (1,2,3)")) %>% show_query()
<SQL>
SELECT *
FROM (SELECT "BEN_NIR_PSA", "BEN_RNG_GEM", "BEN_SEX_COD", "BEN_AMA_COD", "BEN_RES_DPT", "BEN_RES_COM", "FLX_DIS_DTD", "EXE_SOI_AMD", "EXE_SOI_DTD", "PRS_NAT_REF", "PSE_ACT_NAT", "PSE_SPE_COD", "PRS_ACT_QTE"
FROM ("MEPSGP_156"."ESND_ER_PRS_F") 
WHERE ("FLX_DIS_DTD" - "EXE_SOI_DTD" < 183.0 AND "DPN_QLF" != 71.0 AND "PRS_DPN_QLP" != 71.0 AND "CPL_MAJ_TOP" < 2.0 AND "CPL_AFF_COD" != 16.0 AND (NOT("BEN_SEX_COD" IN ('0'))) AND "BEN_NAI_ANN" > 1800.0 AND "BEN_NAI_ANN" <= 2016.0)) "q01"
WHERE ("PSE_SPE_COD" = ANY (1,2,3))

Dans cet exemple, on aurait facilement pu remplacer la condition par PSE_SPE_COD %in% (1,2,3), mais dans quelques cas le SQL est la seule solution, en particulier pour les fonctions de fenêtrage.

Autre cas d’usage : pour éviter des ifelse imbriqués, on utilisera directement la fonction SQL CASE WHEN. Par exemple si on veut remplir BEN_RES_DPT2016_COMP avec le département au début 2016, BEN_RES_DPT2016, préalablement extrait. Lorsqu’il est absent on va chercher dans 2017 (BEN_RES_DPT2017), 2015, 2018, 2014, 2019, 2013, 2020. Si on a rien dans toutes ces années (pour un non consommant), on prend ces infos dans IR_BEN_R (BEN_RES_DPT).

# Ce code vient du projet beneficiairesSNDS (02_production.R), où il fonctionne
# irbenr4 %>%
#   mutate(BEN_RES_DPT2016_COMP = sql('CASE
#                                     WHEN NOT((("BEN_RES_DPT2016") IS NULL)) THEN ("BEN_RES_DPT2016")
#                                     WHEN NOT((("BEN_RES_DPT2017") IS NULL)) THEN ("BEN_RES_DPT2017")
#                                     WHEN NOT((("BEN_RES_DPT2015") IS NULL)) THEN ("BEN_RES_DPT2015")
#                                     WHEN NOT((("BEN_RES_DPT2018") IS NULL)) THEN ("BEN_RES_DPT2018")
#                                     WHEN NOT((("BEN_RES_DPT2019") IS NULL)) THEN ("BEN_RES_DPT2019")
#                                     WHEN NOT((("BEN_RES_DPT2013") IS NULL)) THEN ("BEN_RES_DPT2013")
#                                     WHEN NOT((("BEN_RES_DPT2020") IS NULL)) THEN ("BEN_RES_DPT2020")
#                                     ELSE "BEN_RES_DPT"
#                                     END')

5.9 Les dates

Pas vraiment facile à gérer. Le guide CNAM donne l’indication pour un filtre en utilisant la fonction Oracle TO_DATE :

irbior %>%
filter(BIO_CRE_DAT >= TO_DATE('2017-01-01','yyyy-MM-dd')) %>%
  head(5) %>%
  collect

il s’agit de dateHeure. Deux dates peuvent ne différer que de quelques heures. Le référentiel des bénéficiaires est un bon exemple quand on compare la date d’insertion dans le référentiel (BEN_DTE_INS) et la date de mise à jour (BEN_IDT_MAJ) :

irbenr <- tbl(conn, "IR_BEN_R")

irbenr %>%
  select(BEN_DTE_INS, BEN_IDT_MAJ) %>%
  filter(BEN_DTE_INS == TO_DATE('2020-03-11','yyyy-MM-dd')
        & BEN_IDT_MAJ < TO_DATE('2020-03-12','yyyy-MM-dd')
         ) %>%
  mutate(identiques = ifelse(BEN_DTE_INS == BEN_IDT_MAJ, 1, 0)) %>%
  head(5) %>%
  collect

Remarque 1 : il y a un problème de fuseau horaire à l’affichage des résultats (2020-03-10 23:00:00 et non 2020-03-11 00:00:00). Ce problème se trouve du côté de l’affichage en R et non du côté d’Oracle car le filtre a bien porté sur le 11 mars et non le 10.

Remarque 2 : pour ces lignes l’insertion et la mise à jour ont été faites le même jour mais à des heures différentes, d’où la colonne identiques à 0. Il faut donc être vigilant quand on compare des dates si l’on ne souhaite une précision qu’au jour près.

5.9.1 De dateHeure à date

Pour perdre le détail de l’heure, on peut utiliser la fonction SQL TRUNC :

irbenr %>%
  filter(BEN_DTE_INS == TO_DATE('2020-03-11','yyyy-MM-dd')
        & BEN_IDT_MAJ < TO_DATE('2020-03-12','yyyy-MM-dd')
         ) %>%
  select(BEN_IDT_MAJ) %>%
  mutate(BEN_IDT_MAJ_trunc = TRUNC(BEN_IDT_MAJ)) %>% # équivalent :
  # mutate(BEN_IDT_MAJ_trunc = sql("TRUNC(BEN_IDT_MAJ)")) %>%
  head(5) %>%
  collect

Pour une comparaison de deux dates, une autre solution aurait été de s’assurer que la valeur absolue de la différence est inférieure à 1 (abs(BEN_DTE_INS - BEN_IDT_MAJ)<1) mais cela ne revient pas exactement au même car on vérifie alors que moins de 24h se sont écoulées entre les deux dates et non qu’elles tombent sur le même jour.

5.9.2 Extraire le jour, le mois, l’année

Il faut de nouveau recourir à la fonction Oracle dédiée, EXTRACT :

res <- irbenr %>%
  select(BEN_DTE_INS) %>%
  filter(BEN_DTE_INS == TO_DATE('2020-03-11','yyyy-MM-dd')) %>%
  head(1) %>%
  mutate(ANNEE = sql("EXTRACT(YEAR FROM BEN_DTE_INS)"), MOIS = sql("EXTRACT(MONTH FROM BEN_DTE_INS)")) %>%
  collect()
res

On peut extraire les éléments suivants :

  • YEAR
  • MONTH
  • DAY
  • HOUR
  • MINUTE
  • SECOND
  • TIMEZONE_HOUR
  • TIMEZONE_MINUTE
  • TIMEZONE_REGION
  • TIMEZONE_ABBR

5.10 Analogie entre dbplyr et SparkR

Il y a une claire analogie entre SparkR et ROracle. Dans les deux cas R envoie des requêtes à un logiciel externe. Les paquets SparkR et ROracle font la traduction, Spark ou Oracle font le travail. Ensuite le résultat est rapatrié en R.

L’analogie va plus loin puisque SparkR a repris le vocabulaire de dplyr (select, filter…), ce qui est globalement assez pratique. Il y a quelques subtilités qu’on découvre par la pratique. Par exemple le head de dbplyr ne rapatrie pas en R à la différence de SparkR qui intègre un collect dans son head. Autre subtilité, les premières lignes sont toujours les mêmes avec Oracle, c’est-à-dire que deux head donnent le même résultat. En Spark les lignes ne sont pas vraiment ordonnées et deux head donnent des résultats différents.

En SparkR, il faut nommer les colonnes via donnees$colonne alors qu’on peut utiliser colonne en dbplyr.

En SparkR, on peut utiliser les fonctions temporelles pratiques year ou month que l’on ne trouve pas en ROracle où il faudra par exemple passer par sql("EXTRACT(YEAR FROM BEN_DCD_DTE)").

5.11 Un nom de variable variable

Lorsque l’on souhaite utiliser une variable au lieu du nom en dur d’une variable, on peut utiliser les opérateurs dplyr !! (bang-bang operator) et !!! (big bang operator), qui servent à pré-évaluer un objet.

Au lieu de

faux %>% summarize(moy = mean(x))

Je peux utiliser :

nomVariable <- "x"
faux %>% summarize(moy = mean(!!sym(nomVariable)))

Ce qui peut être pratique pour boucler sur des colonnes.

Une variante pour certains cas simples consiste à utiliser la fonction sql() qui injecte du code SQL (présentée ici):

nomVariable <- "Z"
faux %>%
  mutate(Z = z) %>% # contrainte supplémentaire :
  # sql va passer le code en majuscules, il faut donc des noms
  # de colonne en majuscules pour que ça fonctionne
  group_by(grp = sql(nomVariable)) %>% tally

Si vous êtes habitué à utiliser get() pour ce genre d’usages, cela ne fonctionnera pas ici car le get sera transmis à Oracle :

faux %>% summarize(moy = mean(get(nomVariable))) %>% show_query %>% collect
<SQL>
SELECT AVG(get('Z')) AS "moy"
FROM ("PROV") 
Erreur dans .oci.SendQuery(conn, statement, data = data, prefetch = prefetch,  : 
  ORA-00904: "GET" : identificateur non valide

Exemple réel pour exporter les effectifs de chaque pathologie de la cartographie des pathologies en 2018 (30s).

ctind <- tbl(conn, in_schema("MEPSGP_156", "ESND_CT_IND_G8_2016"))

# Les variables qui nous intéressent commencent par le mot TOP ou SUP
tops <- colnames(ctind)[colnames(ctind) %like% "^(TOP|SUP)"]

# On va enregistrer les résultats dans une liste qu'on initie
liste <- list()

for (top in tops){
  print(top)
  prov <- ctind %>%
    filter(!!sym(top) == 1) %>% # technique pour avoir un nom de colonne variable
    tally %>%
    mutate(top = top) %>% # renommer la colonne avec le nom de la pathologie
    collect %>%
    setDT
  liste <- append(liste, list(prov)) # ajouter à la liste
}
tab <- rbindlist(liste) # empiler la liste
setcolorder(tab, "top") # réordonner les colonnes avec top en premier

# Ecriture pour export
# fwrite(tab, "~/EXPORT/effectifs_cartoPathos.csv")
tab[1:10,]

Pour déployer plusieurs variables dans une fonction, il faudra se tourner vers le big-bang operator, !!! :

variables <- c("x", "z")
faux %>% select(!!!variables)

5.12 Forcer l’évaluation en R

La ligne de partage entre ce qui est évalué en R et ce qui est évalué en SQL est ténue. La preuve par l’exemple : dans la commande suivante id est bien remplacé par sa valeur, tout fonctionne :

id <- 3
faux %>% filter(x == id)

Alors que si on cherche à prendre un élément dans un vecteur, il fait une traduction SQL étrange avec un CASE WHEN qui génère une erreur :

ids <- 3:4
faux %>% filter(x == ids[1]) %>% show_query %>% collect
<SQL>
SELECT *
FROM ("PROV") 
WHERE ("x" = CASE WHEN (1.0) THEN ((3, 4)) END)
Erreur dans .oci.SendQuery(conn, statement, data = data, prefetch = prefetch,  : 
  ORA-00920: opérateur relationnel non valide

La pré-évaluation par l’opérateur bang-bang !! nous sortira de ce mauvais pas :

ids <- 3:4
faux %>% filter(x == !!ids[1]) %>% show_query %>% collect
<SQL>
SELECT *
FROM ("PROV") 
WHERE ("x" = 3)

Cela vaut aussi pour des fonctions qui générerait du code SQL :

contenuSQL <- function() sql('POWER("x", 2.0)')
faux %>% mutate(x2 = contenuSQL()) # ERREUR
Erreur dans .oci.SendQuery(conn, statement, data = data, prefetch = prefetch,  : 
  ORA-00904: "CONTENUSQL" : identificateur non valide
faux %>% mutate(x2 = !!contenuSQL()) # FONCTIONNE

6 Tables et vues

Il n’y a pas de manière graphique pour naviguer dans les tables disponibles comme sur SAS. Si on connait le nom de la table, on l’appelle directement. Sinon, pour retrouver une table, on peut toujours aller chercher son nom dans SAS. Si l’on ne souhaite pas lancer SAS, on peut toujours utiliser les commandes ci-dessous.

N.B. : la plupart des noms de tables que l’on connaît ne renvoient pas vraiment à des tables mais à des vues, c’est-à-dire des enregistrements de requêtes, ou à des synonymes, c’est-à-dire des surnoms.

6.1 Lister les tables publiques

Dans une base Oracle, il existe des tables spéciales qui listent les objets :

  • all_tables liste les tables ;
  • all_views liste les vues ;
  • all_synonyms les synonymes ;
  • all_objects tous les objets (tables, vues, synonymes, index, séquences, éditions, paquets…).

Je déconseille le chargement en mémoire d’une de ses tables, assez long. Par exemple, la table all_tables se charge en 15 minutes alors que la plupart des objets qui nous intéressent (des vues et des synonymes) en sont absents.

Mieux vaut faire la recherche directement en Oracle sur la table all_objects. Imaginons que l’on cherche tous les objets qui contiennent CT_IND pour la table centrale de la cartographie des pathologies.

system.time({
  tables <- dbGetQuery(conn,
  "SELECT owner, object_name, object_id, object_type, created FROM all_objects WHERE object_name LIKE '%CT_IND%'")
  # '%CT_IND%' est une expression régulière Oracle où % est remplacé par autant de caractères que nécessaire
  }) # 8 secondes
utilisateur     système      écoulé 
      0.004       0.001       3.117 
tables$OWNER <- nettoy(tables$OWNER) # retrait de l'identifiant utilisateur
tables[, c("OWNER", "OBJECT_NAME", "OBJECT_TYPE")]

Certes avec R, nous n’avons pas l’arborescence comme dans SAS pour chercher les tables mais cela est aussi un atout. Par exemple ici j’ai pu lister toutes les tables relatives à la cartographie alors que les anciennes versions se trouvent dans ORAVUE et les nouvelles dans ORAMEPS. Sur SAS, il faut savoir où chercher pour dérouler la bonne bibliothèque.

On découvre aussi le type de l’objet et on peut ajouter sa date de création (created).

6.2 Lister les tables privées

Pour lister les tables de l’utilisateur, il suffit d’utiliser user_tables au lieu de all_tables. En l’occurrence, il s’agira normalement de tables et non de vues et synonymes donc il n’est pas nécessaire d’utiliser user_objects.

system.time({user_tables <- dbGetQuery(conn, "SELECT TABLE_NAME FROM user_tables")}) # 2
utilisateur     système      écoulé 
      0.002       0.001       2.581 
if (nrow(user_tables) == 1) {
  user_tables
} else {
  warning("Il y a d'autres tables que le faux jeu de données, on affiche rien.")
}
Avis : Il y a d'autres tables que le faux jeu de données, on affiche rien.

Les tables listées ici se trouvent dans la bibliothèque ORAUSER sur SAS. On retrouve bien la table PROV qui est notre faux jeu de données.

6.3 Charger une table d’un autre schéma

En Oracle, un schéma est un ensemble d’objets d’une base de données. Un schéma correspond à un propriétaire (owner) et a le même nom que lui. Est-ce utile de savoir cela ? Oui car si vous voulez travailler sur l’ESND, vous allez avoir un problème :

tbl(conn, "ESND_ER_PRS_F")
Erreur dans .oci.GetQuery(conn, statement, data = data, prefetch = prefetch,  : 
  ORA-00942: Table ou vue inexistante

La vue n’est pas trouvée. On vérifie qu’elle existe bien :

tables <- dbGetQuery(conn, "SELECT owner, object_name, object_type FROM all_objects WHERE object_name LIKE '%ESND_ER_PRS%'")
tables$OWNER <- nettoy(tables$OWNER) # retrait de l'identifiant utilisateur
tables

Elle existe mais elle appartient au schéma/propriétaire MEPSGP_156 (dans le cas du profil 156), qui diffère du schéma ADGPP de ER_PRS_F. Le schéma de ER_PRS_Fdoit être celui par défaut de telle sorte que tbl(conn, "ER_PRS_F") fonctionne alors que tbl(conn, "ESND_ER_PRS_F") ne fonctionne pas. On peut préciser un schéma différent du schéma par défaut grâce à in_schema() :

# erprsf <- tbl(conn, in_schema("PREGP_156", "ESND_ER_PRS_F")) # ne fonctionne pas
erprsf <- tbl(conn, in_schema("MEPSGP_156", "ESND_ER_PRS_F")) # fonctionne

6.4 Les bibliothèques SAS

Les tables SAS ne sont pas accessibles. Sur SAS elles portent un pictogramme de loupe.

Pour les ramener dans Oracle (dans Orauser) et ainsi pouvoir les manipuler avec ROracle, il faut exécuter une requête sur SAS.

Par exemple si l’on veut récupérer la table EXTRACTION_PATIENTS2019TR de consopat :

# Code optimisé qui vient du GUIDE DES BONNES PRATIQUES SAS - v2.0.pdf
proc sql;
    drop table orauser.tableoracle;
    create table orauser.tableoracle (BULKLOAD=yes BL_DATAFILE="%sysfunc(pathname(work))/ttt.xyz" BL_DELETE_DATAFILE=yes) as select * from CONSOPAT.EXTRACTION_PATIENTS2019TR;
quit;

Il y a un fort intérêt à sélectionner quelques variables au lieu de select * pour accélerer l’exécution et réduire la place prise sur ORAUSER.

Une autre solution consiste à charger les tables SAS avec le paquet haven. Cela fonctionne avec les vraies tables :

haven::read_sas("~/consopat/es_mts_v.sas7bdat")[1:4,]

Mais pas avec les vues SAS (.sas7bvew) :

haven::read_sas("~/consopat/extraction_patients2021tr.sas7bvew")
Erreur : Failed to parse /sasdata/prd/commun/data/consopat/extraction_patients2021tr.sas7bvew: Invalid file, or file has unsupported features.

Pas de solution apparente (voir cette discussion sur Stackoverflow), il faut recourir au rapatriement en SAS sur Orauser.

6.5 Sauvegarder une table intermédiaire

Il faut bien distinguer les tableaux R des pointeurs vers les tables Oracle :

a <- tbl(conn, "IR_BIO_R") # a est un pointeur vers la table Oracle
a <- tbl(conn, "IR_BIO_R") %>% collect # a est une table R

Si on possède une table R, on peut suivre le guide de la Cnam pour l’enregistrer sous Oracle avec :

# On supprime la table si elle existe déjà, sinon erreur
try(dbRemoveTable(conn, "PROV"), silent = T) # le nom doit être en majuscule

# Ecriture à proprement parler
dbWriteTable(conn, "PROV", faux0) # faux0 est un data.frame

Mais cela implique un collect auparavant. Le circuit est alors Oracle > R > Oracle ce qui est sous-optimal puisque le collect est une opération coûteuse. Une meilleure solution est de ne pas rapatrier l’objet en R, de tout faire en Oracle.

Théoriquement la fonction dbplyr compute réalise cela et doit être très pratique :

# ERREUR A CE JOUR
# a <- tbl(conn, "IR_PHA_R") %>%
#   filter(PHA_ATC_C03 == "A10") %>% # Médicaments du diabète
#   compute

Elle doit enregistrer le résultat du calcul en tant que table Oracle et la relire pour faire de a un pointeur. Cela permettrait donc de faire un point d’arrêt qui évite de reprendre tous les calculs depuis le début.

Hélas la fonction compute ne marche pas avec une base Oracle pour l’instant car la traduction oublie le mot clé GLOBAL dans la requête. Le bug concerne aussi la fonction copy_to, qui fait le contraire du collect, c’est-à-dire envoyer à Oracle un objet R. J’ai enregistré un ticket sur le Github du projet dbplyr, le bug a été traité et le compute devrait bientôt de nouveau fonctionner.

En attendant je propose cette fonction artisanale qui fait la même chose et je la couple avec une fonction de nettoyage pour supprimer les tables provisoires de temps en temps :

# Supprimer des tables :
# - lesquelles = "prov" : les tables provisoires
# - lesquelles = "provRecentes" : les tables provisoires avant aujourd'hui
# - lesquelles = "toutes" : toutes les tables de l'utilisateur
supprTables <- function(lesquelles = "provRecentes"){
  vect0 <- dbGetQuery(conn, "SELECT TABLE_NAME FROM user_tables")$TABLE_NAME
  
  # On filtre les tables provisoires 
  vect1 <- vect0[grepl("PROV[0-9]{14}", vect0, perl = T)]
  
  vect2 <- gsub("PROV([0-9]{14})", "\\1", vect1, perl = T)
  vect2 <- as.Date(vect2, "%Y%m%d")
  
  if (lesquelles == "toutes") {
    vectAsupp <- vect0
  } else if (lesquelles == "prov"){
    vectAsupp <- vect1
  } else {
    vectAsupp <- vect1[vect2 < Sys.Date()]
  }
  
  if (length(vectAsupp)) sprintf("Tables supprimées : %s", paste(vectAsupp, collapse = ", "))
  
  for (nomTable in vectAsupp) {
    try(dbRemoveTable(conn, nomTable), silent = T)
  }
}

# Equivalent fonctionnel de la fonction compute()
compute2 <- function(tbl_oracle) {
  # On commence par un nettoyage des anciennes tables provisoires
  # si on a oublié de le faire
  supprTables(lesquelles = "provRecentes")
  
  # On choisir un nom daté pour enregistrer la table
  nomTable <-  paste0("PROV", format(Sys.time(), "%Y%m%d%H%M%S"))
  
  # renvoie une erreur si la table n'existe pas
  system.time({
  #
  dbExecute(conn, paste0("CREATE TABLE ", nomTable," AS (", remote_query(tbl_oracle), ")"))
  })
  tbl(conn, nomTable)
}

Exemple d’utilisation

# Longs calculs (ici courts pour l'exemple)
a <- tbl(conn, "IR_PHA_R") %>%
  filter(PHA_ATC_C03 == "A10")

# On écrit une table intermédiaire
a <- compute2(a)

Preuve que a est un pointeur vers une table intermédiaire et non une suite d’instructions :

a %>% show_query
<SQL>
SELECT *
FROM "PROV20230213145207"

Pour nettoyer les tables intermédiaires écrites, portant le nom de “PROV”, vous pouvez exécuter de temps en temps la fonction supprTables :

supprTables(lesquelles = "prov")

Après nettoyage, le pointeur a est cassé :

a # "ORA-00942: Table ou vue inexistante"
Erreur dans .oci.SendQuery(conn, statement, data = data, prefetch = prefetch,  : 
  ORA-00942: Table ou vue inexistante

6.6 Orauser

Que ce soit via dbWriteTable(conn, "nomTABLE", unDataFrameR) ou la fonction ci-dessus compute2, on écrit sur l’espace Orauser, qui est un espace partagé. Contrairement à SASDATA1, il n’y a pas de quotas individuels. L’espace est quand même contraint et la Cnam envoie régulièrement des mails pour faire supprimer des tables et éviter la saturation de l’espace partagé. La bonne pratique est de supprimer ses tables dès que possible. Le nettoyage peut se faire de façon visuelle sur SAS ou avec la fonction supprTables définie ci-dessus.

7 Gérer sa session RStudio

7.1 Que faire en cas de perte de contrôle ?

Arrêter une requête en cours d’exécution n’est pas une chose simple. Souvent le bouton rouge Stop ne fonctionne pas car cela envoie un signal d’arrêt à la commande R et non à la requête Oracle.

On peut toujours essayer mais si ça ne fonctionne pas, la meilleure solution est de forcer l’arrêt de la session :

  1. sélectionner le code s’il n’est pas enregistré, le copier-coller quelque part, en sécurité ;
  2. cliquer sur le gros R bleu en haut à gauche pour rejoindre le menu ;
  3. cocher la session en question ;
  4. sélectionner “force quit” ;
  5. réouvrir une session / le projet ;
  6. reprendre son code non-enregistré.

Si l’étape 2 est impossible car le gros R bleu n’est pas visible, vous pouvez directement copier-coller l’URL https://rstudio-prod.local/R/ dans le navigateur chromium (celui ouvert pour R).

7.2 Coupures

Lors des pauses, Citrix peut se déconnecter ce qui ferme RStudio. Néanmoins les calculs en cours ne sont pas arrêtés, même en pleine nuit, à la différence de SAS qui est redémarré à 2h. Lorsqu’on lance à nouveau “RStudio Workbench” depuis le portail, la session s’ouvre dans le même état qu’on l’a laissée, avec tous les objets chargés. Attention, si la session compte de gros objets, l’ouverture de la session peut être long. Il est conseillé autant que possible de fermer sa session lorsqu’on a fini de travailler et d’en ouvrir une nouvelle. Cela libère de la mémoire tout en garantissant la réplicabilité de ses résultats.

Lorsqu’une session est inactive depuis trop longtemps, elle peut être supprimée automatiquement. Il est plus prudent de ne jamais compter sur les objets R de la session.

7.3 Copier-coller

Depuis la mise en production de l’interface RStudio, le collage depuis le presse-papier échoue parfois. Lorsqu’on copie depuis RStudio et qu’on colle dans RStudio, aucun problème. Lorsqu’on copie un bout de code hors de RStudio et qu’on le colle dans RStudio, cela échoue si l’on avait déjà sélectionné un bout de texte que l’on comptait remplacer. Il faut donc supprimer le texte avant, puis copier le contenu et le coller ensuite sans avoir sélectionné de texte. Attention cela échoue si on inverse l’ordre des opérations (par exemple : copier-supprimer le texte-coller).

8 Git

Logiquement, il n’est pas possible de connecter son projet à un dépôt Git sur Github ou Gitlab puisqu’il s’agit d’une bulle sécurisée. Cela n’empêche pas l’utilisation de Git en local. Ça ne permet pas de travailler à plusieurs sur le même code (quoi que ça s’envisage avec un espace projet) mais cela permet tout de même de faire le versionnage de son code tout en le sécurisant.

Pour initialiser un dossier Git : File > New project > New Directory > New Project puis choisir un nom de projet, un emplacement et cocher Create a git repository. Enfin valider avec Create project as subdirectory.

Ensuite on peut utiliser Git normalement à partir du panneau en haut à droite. Si l’on veut faire certaines opérations non incluses dans le panneau, on peut toujours utiliser la commande system pour lancer une commande système.

Quelques exemples :

system(
  # Décommenter la ligne intéressante
  
  # Configurer son nom
  # "git config --global user.name 'Samuel Allain'"
  # Vérifier
  # "git config --global user.name"
   
  # Configurer son adresse mail
  # "git config --global user.email 'samuel.allain@sante.gouv.fr'"
  # Vérifier
   # "git config --global user.email"
   
  # Etat du dépôt
  # "git status"
  
  # Ajouter une étiquette
  # "git tag v0.2"
  
  # Supprimer une étiquette
  # "git tag -d v0.2"
  
  # Version de Git
  "git --version"
)
git version 1.8.3.1

Bémol : lors de la pseudonymisation, il est difficile de gérer l’historique Git, constitué d’une foule de fichiers dans le dossier .git. En effet l’utilitaire fourni par la Cnam, BRUS, demande de valider chaque fichier, un à un.

9 Configuration RStudio

9.1 Dictionnaire français pour la correction orthographique

Le dictionnaire français n’est pas présent par défaut. Il n’est pas possible de le télécharger directement en utilisant Tools > Global Options > Spelling > Main dictionary language > Install more language car RStudio est coupé du reste d’internet. On peut en revanche récupérer l’URL indiquée par l’erreur :

https://s3.amazonaws.com/rstudio-buildtools/dictionaries/all-dictionaries.zip

Puis télécharger le zip, le décompresser, et importer les fichiers fr_FR.dic (dictionnaire) et fr_FR.aff (affixes pour les accords etc.) dans son Home. Une fois cela fait, il faut déplacer les fichiers au bon endroit, ce que devraient faire ces commandes :

# On crée le dossier pour accueillir les fichiers de dictionnaires
system("mkdir -p ~/.config/rstudio/dictionaries/languages-system")

# Copier les fichiers dans ce nouveau dossier
system("cp ~/fr_FR.dic ~/.config/rstudio/dictionaries/languages-system")
system("cp ~/fr_FR.aff ~/.config/rstudio/dictionaries/languages-system")

Ensuite redémarrer RStudio (Session > Terminate R) pour prendre en compte les changements. Vérifier dans Tools > Global Options > Spelling que la manipulation a fonctionné (“French”).

LS0tCnRpdGxlOiAiUHJhdGlxdWVyIFIgc3VyIGxlIHBvcnRhaWwgU05EUyIKc3VidGl0bGU6ICJDb21wbMOpbWVudHMgZCd1biB1dGlsaXNhdGV1ciBhdSBndWlkZSBDbmFtIgphdXRob3I6ICJTYW11ZWwgQUxMQUlOIChEUkVFUykiCmRhdGUgOiAiT2N0b2JyZSAyMDIxIC0gRGVybmnDqHJlIE1BSiBmw6l2cmllciAyMDIzIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgICMgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2Zsb2F0OiB5ZXMKICBodG1sX2RvY3VtZW50OgogICAgIyBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6IHllcwpmb250c2l6ZTogMTJwdAotLS0KCkNlIHR1dG9yaWVsIGVzdCBsZSBmcnVpdCBkZSBtZXMgZMOpY291dmVydGVzIHN1ciBsZSBwb3J0YWlsIFIgZXQgbidlbmdhZ2UgcXVlIG1vaS4gQ2Ugbidlc3Qgw6l2aWRlbW1lbnQgcGFzIGxhIHNldWxlIG1hbmnDqHJlIGRlIHRyYXZhaWxsZXIgc3VyIGxlIHBvcnRhaWwgUi4KVG91cyBsZXMgw6lsw6ltZW50cyBkZSBjb2RlIGRldnJhaWVudCBmb25jdGlvbm5lciDDoCBjb25kaXRpb24gcXVlIHZvdXMgYXlleiBhY2PDqHMgw6AgbCdFY2hhbnRpbGxvbiBTTkRTIChFU05EKSBxdWkgZXN0IHV0aWxpc8OpIGNvbW1lIGV4ZW1wbGUuIEMnZXN0IGxlIGNhcyBkZSB0b3VzIGxlcyBhY2PDqHMgcGVybWFuZW50cyBhdSBTTkRTLgoKKlZvdXMgcG91dmV6IHTDqWzDqWNoYXJnZXIgbGUgLlJtZCBwb3VyIGV4w6ljdXRlciBsZSBjb2RlIHZvdXMtbcOqbWUgZW4gc8OpbGVjdGlvbm5hbnQgbGUgYm91dG9uIGVuIGhhdXQgw6AgZHJvaXRlIGRlIGNldHRlIHBhZ2UgIkNvZGUmbmJzcDvilr4iIHB1aXMgIkRvd25sb2FkIFJtZCIqCgojIENvbW1lbnQgYWNjw6lkZXIgw6AgUiA/CgpMYSBkw6ltYXJjaGUgZCdhY2PDqHMgZXN0IGV4cGxpcXXDqWUgZGFucyBsZSBkb2N1bWVudCAqKipSIC0gQWNjw6hzIMOgIFIgc3VyIHBvcnRhaWwgU05EUy5wZGYqKiogcXVlIGwnb24gdHJvdXZlIGRhbnMgbCdvbmdsZXQgKkFjY3VlaWwqIGR1IFtwb3J0YWlsXShodHRwczovL3BvcnRhaWwuc25paXJhbS5hbWVsaS5mci8pLgoKIyBSZXNzb3VyY2VzIMOgIGxpcmUKCisgKihpbmRpc3BlbnNhYmxlKSogZ3VpZGUgdXRpbGlzYXRldXIgZGUgbGEgQ25hbSwgKioqUiAtIEd1aWRlX3V0aWxpc2F0ZXVyX1JTdHVkaW9fIFYxLjIucGRmKioqIHF1ZSBsJ29uIHRyb3V2ZSBkYW5zIGwnb25nbGV0ICpBY2N1ZWlsKiBkdSBbcG9ydGFpbF0oaHR0cHM6Ly9wb3J0YWlsLnNuaWlyYW0uYW1lbGkuZnIvKS4KTGVzIGluZm9ybWF0aW9ucyBsZXMgcGx1cyBpbXBvcnRhbnRlcyBzJ3kgdHJvdXZlbnQgZXQgY2UgdHV0b3JpZWwgbmUgZmFpdCBxdWUgY29tcGzDqXRlci48YnI+Kio8Zm9udCBjb2xvcj0iI2NmMDUwNSI+Q2UgdHV0b3JpZWwgbidlc3QgcGFzIGNvbXByw6loZW5zaWJsZSBzYW5zIGxlIGd1aWRlIGRlIGxhIENuYW0uPC9mb250PioqCisgKih1dGlsZSkqIGxlcyBjb25zZWlscyBkZSAqKipSIC0gQWNjw6hzIMOgIFIgc3VyIHBvcnRhaWwgU05EUy5wZGYqKiogcXVlIGwnb24gdHJvdXZlIGRhbnMgbCdvbmdsZXQgKkFjY3VlaWwqIGR1IFtwb3J0YWlsXShodHRwczovL3BvcnRhaWwuc25paXJhbS5hbWVsaS5mci8pLgorICoodXRpbGUgc2kgb24gbmUgY29ubmHDrnQgcGFzIGJpZW4gUiwgcGV1IHV0aWxlIHNpbm9uKSogbGUgKipzdXBwb3J0IGRlIGxhIGZvcm1hdGlvbiBSIHByb3Bvc8OpZSBwYXIgbGEgQ25hbSoqIHF1ZSBsJ29uIHRyb3V2ZXJhIHN1ciBsZSBbcG9ydGFpbF0oaHR0cHM6Ly9wb3J0YWlsLnNuaWlyYW0uYW1lbGkuZnIvKSAoIipUYWJsZWF1eCBldCByZXF1w6p0ZXMqIiA+IFNBUCBCdXNpbmVzT2JqZWN0cyA+IERvY3VtZW50cyA+IERvc3NpZXJzID4gU05JSVJBTSA+IF9Eb2N1bWVudGF0aW9uIFNOSUlSQU0tU05EUyA+IEZvcm1hdGlvbiA+IHN1cHBvcnQgZGVzIGZvcm1hdGlvbnMgU05JSVJBTSA+IDExLUxvZ2ljaWVscyBSKS4gUGx1dMO0dCBnw6luw6lyYWxpc3RlLCBwb3VyIGFwcHJlbmRyZSBSLCB0csOocyBwZXUgZGUgcG9pbnRzIHNww6ljaWZpcXVlcyBhdSBTTkRTLgorICoodXRpbGUpKiBsZSAqKipHdWlkZSBkZSBib25uZXMgcHJhdGlxdWVzIFNBUyAtIHYyLjEucGRmKioqIHF1aSBzZSB0cm91dmUgc3VyIGxhIHBhZ2UgZCdhY2N1ZWlsIGR1IHBvcnRhaWwuClVuIGNlcnRhaW4gbm9tYnJlIGRlIHBvaW50cyBzdXIgbCdvcHRpbWlzYXRpb24gZGVzIGNvZGVzIHZhbGVudCBhdXNzaSBwb3VyIFIuCisgKih1dGlsZSkqIGxlICoqc3VwcG9ydCBkZSBmb3JtYXRpb24gRG9ubsOpZXMgaW5kaXZpZHVlbGxlcyBiw6luw6lmaWNpYWlyZXMqKiwgw6AgcmVsaXJlIGRldXggZm9pcyBwYXIgc2VtYWluZSwgZXQgcXVlIGwnb24gdHJvdXZlcmE8IS0tICsgc3VyIGxlIHLDqXNlYXUgbWluaXN0w6hyZSBbIkk6XFNORFNcRm9ybWF0aW9uIFNOSUlSLUFNXFN1cHBvcnRzIGRlIGZvcm1hdGlvblw4LUZvcm1hdGlvbiBEQ0lSUyAtIERDSVIgLSBqYW52IDIwMjEgLSBkaXN0YW5jaWVsLnBkZiJdKCJJOlxcU05EU1xcRm9ybWF0aW9uIFNOSUlSLUFNXFxTdXBwb3J0cyBkZSBmb3JtYXRpb25cXDgtRm9ybWF0aW9uIERDSVJTIC0gRENJUiAtIGphbnYgMjAyMSAtIGRpc3RhbmNpZWwucGRmIikgLS0+CnN1ciBsZSBbcG9ydGFpbF0oaHR0cHM6Ly9wb3J0YWlsLnNuaWlyYW0uYW1lbGkuZnIvKSAoIipUYWJsZWF1eCBldCByZXF1w6p0ZXMqIiA+IFNBUCBCdXNpbmVzT2JqZWN0cyA+IERvY3VtZW50cyA+IERvc3NpZXJzID4gU05JSVJBTSA+IF9Eb2N1bWVudGF0aW9uIFNOSUlSQU0tU05EUyA+IEZvcm1hdGlvbiA+IHN1cHBvcnQgZGVzIGZvcm1hdGlvbnMgU05JSVJBTSA+IDctRElCKS4KCiMgUGFyYW3DqXRyYWdlIGRlIFJTdHVkaW8KCkxhIENuYW0gYSByYXBwZWzDqSBwYXIgbWFpbCBxdWVscXVlcyBwYXJhbcOodHJlcyBpbXBvcnRhbnRzIHBvdXIgUlN0dWRpbywgcXVpIHZhbGVudCBkJ2FpbGxldXJzIGF1LWRlbMOgIGR1IHBvcnRhaWwgZGUgbGEgQ25hbS4KSWxzIMOpdml0ZW50IGRlIHJlY2hhcmdlciBsZXMgb2JqZXRzIGF1IGTDqW1hcnJhZ2UgZGUgbGEgc2Vzc2lvbiwgY2UgcXVpIHBldXQgw6p0cmUgdHLDqHMgbG9uZy4KCkRhbnMgbGUgbWVudSAqVG9vbHMqID4gKkdsb2JhbCBPcHRpb25zKiA6CgorIETDqXNhY3RpdmVyIMKrIFJlc3RvcmUgbW9zdCByZWNlbnRseSBvcGVuIHByb2plY3QgYXQgc3RhcnR1cCDCuworIETDqXNhY3RpdmVyIMKrIFJlc3RvcmUgLlJEYXRhIGludG8gd29ya3NwYWNlIGF0IHN0YXJ0dXAgwrsKKyBEw6lzYWN0aXZlciDCqyBTYXZlIHdvcmtzcGFjZSB0byAuUkRhdGEgb24gZXhpdCDCuwoKCiMgQ29ubmV4aW9uIMOgIGxhIGJhc2UgZGUgZG9ubsOpZXMKCmBgYHtyIGluY2x1ZGU9RkFMU0V9CiMgUXVlbCBlc3QgbW9uIGlkZW50aWZpYW50ID8KaWQgPC0gZ3N1YigiLipcXC8oMzAuKj8pXFwvLioiLCAiXFwxIiwgZ2V0d2QoKSkKCiMgRm9uY3Rpb24gcXVpIHJlbXBsYWNlIHNvbiBpZGVudGlmaWFudCBwYXIgdW4gaWRlbnRpZmlhbnQgZmljdGlmIGRhbnMgdW4gdmVjdGV1ciwgcG91ciDDqXZpdGVyIHNhIGRpZmZ1c2lvbgpuZXR0b3kgPC0gZnVuY3Rpb24odmVjdCkgewogIHJlbXAgPC0gIjAxYTAxMjM0NTY3ODkwMSIKICB2ZWN0IDwtIGdzdWIodG9sb3dlcihpZCksIHJlbXAsIHZlY3QsIGZpeGVkID0gVCkKICB2ZWN0IDwtIGdzdWIodG91cHBlcihpZCksIHJlbXAsIHZlY3QsIGZpeGVkID0gVCkKICB2ZWN0Cn0KYGBgCgpPbiBhcHByZW5kIGRhbnMgKioqUiAtIEd1aWRlX3V0aWxpc2F0ZXVyX1JTdHVkaW9fIFYxLjIucGRmKioqIHF1ZSBjZSBibG9jIGVzdCBpbmRpc3BlbnNhYmxlIDoKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoUk9yYWNsZSkgIyBjaGFyZ2VyIGxlIHBhY2thZ2UgUm9yYWNsZQpkcnYgPC0gZGJEcml2ZXIoIk9yYWNsZSIpICMgb2J0ZW5pciBsZSBwaWxvdGUgcG91ciBzZSBjb25uZWN0ZXIgw6AgdW5lIGJhc2Ugb3JhY2xlCmNvbm4gPC0gZGJDb25uZWN0KGRydiwgZGJuYW1lID0gIklQSUFNUFIyLldPUkxEIikgIyBzZSBjb25uZWN0ZXIgw6AgbGEgYmFzZSBJUElBTVBSMi5XT1JMRApTeXMuc2V0ZW52KFRaID0gIkV1cm9wZS9QYXJpcyIpICMgZnVzZWF1eCBob3JhaXJlcwpTeXMuc2V0ZW52KE9SQV9TRFRaID0gIkV1cm9wZS9QYXJpcyIpCmBgYAoKSWwgcGVybWV0IGRlIHNlIGNvbm5lY3RlciDDoCBsYSBiYXNlIGRlIGRvbm7DqWVzIE9yYWNsZSBkZSBsYSBDbmFtLiBWb3VzIHBvdXJyaWV6IMOqdHJlIHRlbnTDqSBwYXIgdW5lIGNvbm5leGlvbiB2aWEgbGUgcGFubmVsICpDb25uZWN0aW9ucyogZGUgUlN0dWRpbyBtYWlzIGNlbGEgbidlc3QgcGFzIHBvc3NpYmxlLiBQb3VyIGwnZXhwbGljYXRpb24gdGVjaG5pcXVlLCBSU3R1ZGlvIG5lIHN1cHBvcnRlIMOgIGNlIGpvdXIgcXVlIGxlcyBjb25uZXhpb25zICoqT0RCQyoqIGV0ICoqU3BhcmsqKiB0YW5kaXMgcXVlIFJPcmFjbGUgdXRpbGlzZSB1bmUgY29ubmV4aW9uICoqREJJKiouCgpPbiBjcsOpZSB1bmUgZmF1c3NlIHRhYmxlIHBvdXIgbGUgdHV0b3JpZWwuCgpgYGB7ciB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZBTFNFLCByZXN1bHRzPSJoaWRlIn0KbGlicmFyeShkYXRhLnRhYmxlKSAjIHBhcXVldCB0csOocyBwdWlzc2FudCwgcXVpIHBvdXJyYSB0b3Vqb3VycyBzZXJ2aXIKbGlicmFyeShkcGx5cikKbGlicmFyeShkYnBseXIpCmBgYAoKYGBge3IsIHJlc3VsdHM9ImhpZGUifQojIENyw6llciB1biBqZXUgZXggbmloaWxvIApmYXV4MCA8LSBkYXRhLmZyYW1lKHggPSAxOjQsIHkgPSBjKDEsIE5BKSwgeiA9IGMocmVwKCJBIiwzKSwgIkIiKSkKdHJ5KGRiUmVtb3ZlVGFibGUoY29ubiwgIlBST1YiKSwgc2lsZW50ID0gVCkgIyBzdXBwcmltZXIgbGEgdGFibGUgc2kgZWxsZSBleGlzdGUgZMOpasOgLCBzaW5vbiBlcnJldXIKZGJXcml0ZVRhYmxlKGNvbm4sICJQUk9WIiwgZmF1eDApICMgw6ljcmlyZSBsYSB0YWJsZQpmYXV4IDwtIHRibChjb25uLCAiUFJPViIpICMgY2hhcmdlciBsYSB0YWJsZQpgYGAKCgojIExlcyBkZXV4IG1hbmnDqHJlcyA6IFNRTCBvdSBkcGx5cgoKTGEgZ3VpZGUgZGUgbGEgQ25hbSBwcm9wb3NlIGRlIHRyYXZhaWxsZXIgc29pdCBlbiBTUUwgT3JhY2xlIHNvaXQgZW4gc3ludGF4ZSBkcGx5ci4gSWxsdXN0cmF0aW9uIGV0IGNvbXBhcmFpc29uIDoKCiMjIEV4ZW1wbGUgU1FMIE9yYWNsZQoKPGZvbnQgY29sb3I9IiM3MzczNzMiPkV4ZW1wbGUgOiBvbiByw6ljdXDDqHJlIGxlIHLDqWbDqXJlbnRpZWwgZGUgYmlvbG9naWUuPC9mb250PgoKYGBge3J9CmRiR2V0UXVlcnkoY29ubiwgInNlbGVjdCAqIGZyb20gaXJfYmlvX3IiKVsxOjUsXQpgYGAKCjxzcGFuIHN0eWxlPSJmb250LXNpemU6MjAwJTtkaXNwbGF5OmlubGluZS1ibG9jazsgdmVydGljYWwtYWxpZ246bWlkZGxlOyBtYXJnaW4tYm90dG9tOjBweDsiPuKaoDwvc3Bhbj4gbmUgcGFzIHV0aWxpc2VyIGBmZXRjaCAKZmlyc3QgMTAwIHJvd3Mgb25seWAgcXVpIG4nZXN0IHBhcyBvcHRpbWlzw6kgZXQgbidhYm91dGl0IHBhcyAoW2V4cGxpY2F0aW9uc10oaHR0cHM6Ly9ibG9ncy5vcmFjbGUuY29tL29wdGltaXplci9wb3N0L2ZldGNoLWZpcnN0LXJvd3MtanVzdC1nb3QtZmFzdGVyKSkgY29tbWUgZGFucyBsYSBjb21tYW5kZSBzdWl2YW50ZSA6CgpgYGB7cn0KIyBORSBQQVMgRVhFQ1VURVIsIE4nQUJPVVRJVCBQQVMKIyBkYkdldFF1ZXJ5KGNvbm4sICJzZWxlY3QgKiBmcm9tIEVSX1BSU19GIGZldGNoIGZpcnN0IDEwMCByb3dzIG9ubHkiKQpgYGAKCkFsb3JzIHF1ZSBjZWxsZS1jaSBlc3QgaW1tw6lkaWF0ZSAoY29tbWVudMOpZSBjYXIgaWwgcydhZ2l0IGRlIHLDqXN1bHRhdHMgaW5kaXZpZHVlbHMgbm9uIHB1YmxpYWJsZXMpIDoKCmBgYHtyfQojIGRiR2V0UXVlcnkoY29ubiwgIlNFTEVDVCAqIEZST00gRVJfUFJTX0YgV0hFUkUgUk9XTlVNIDw9IDEwMC4wIikKYGBgCgoKIyMgRXhlbXBsZSBEcGx5cgo8Zm9udCBjb2xvcj0iIzczNzM3MyI+RXhlbXBsZSA6IG9uIHLDqWN1cMOocmUgbGUgcsOpZsOpcmVudGllbCBkZSBiaW9sb2dpZS48L2ZvbnQ+CgpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShkcGx5cikgIyBsZSBwYXF1ZXQgZHBseXIgZXN0IG7DqWNlc3NhaXJlIHBvdXIgY2V0dGUgbcOpdGhvZGUKbGlicmFyeShkYnBseXIpICMgamUgcmVjb21tYW5kZSBkZSBjaGFyZ2VyIMOpZ2FsZW1lbnQgZGJwbHlyIHBvdXIgcXVlbHF1ZXMgZm9uY3Rpb25zIHN1cHDDqW1lbnRhaXJlcwoKaXJiaW9yIDwtIHRibChjb25uLCAiSVJfQklPX1IiKQppcmJpb3IgJT4lIGNvbGxlY3QgJT4lIGhlYWQoNSkKYGBgCgojIyBRdWVsbGUgbWFuacOocmUgY2hvaXNpciA/CgpMZXMgaGFiaXR1w6lzIGRlIFNBUyBwcsOpZsOpcmVyb250IHBldXQtw6p0cmUgdHJhdmFpbGxlciBkaXJlY3RlbWVudCBlbiBTUUwgZW4gZW52b3lhbnQgbGVzIHJlcXXDqnRlcyB2aWEgYGRiR2V0UXVlcnkoKWAuClBlcnNvbm5lbGxlbWVudCwgZXQgbcOqbWUgc2kgamUgbidhaW1lIHBhcyBgZHBseXJgIGVuIHRlbXBzIG5vcm1hbCwgamUgdHJvdXZlIGxhIG1hbmnDqHJlIGBkcGx5cmAgYmVhdWNvdXAgcGx1cyBzb3VwbGUuCkVuIHBhcnRpY3VsaWVyIGVsbGUgcGVybWV0IGRlIGZhaXJlIGxlcyBjaG9zZXMgcHJvZ3Jlc3NpdmVtZW50IGdyw6JjZSDDoCBsJ8OpdmFsdWF0aW9uIHBhcmVzc2V1c2UgKCpsYXp5KikuCkxlIGxvZ2ljaWVsIG4nZXjDqWN1dGUgcGFzIGRpcmVjdGVtZW50IGNoYXF1ZSDDqXRhcGUgbWFpcyBsZXMgZW5yZWdpc3RyZSBqdXNxdSdhdSBtb21lbnQgb8O5IGlsIG4nYSBwbHVzIGxlIGNob2l4LCBwYXIgZXhlbXBsZSBsb3JzcXUnb24gbHVpIGRlbWFuZGUgdW4gY29tcHRhZ2UuCgo8Zm9udCBjb2xvcj0iIzczNzM3MyI+RXhlbXBsZSA6IG9uIHBldXQgY3LDqWVyIHVuIG9iamV0IGBlcnByc2ZgIGF2ZWMgZGVzIGZpbHRyZXMgdXN1ZWxzIHN1ciBgRVNORF9FUl9QUlNfRmAgKMOpY2hhbnRpbGxvbiBTTkRTLCAyJSBkZXMgYsOpbsOpZmljaWFpcmVzKS4gU2kgbCdvbiBuZSBjb2xsZWN0ZSBwYXMgbGUgcsOpc3VsdGF0LCBsJ29ww6lyYXRpb24gZXN0IGltbcOpZGlhdGUgcHVpc3F1J29uIG5lIHLDqWFsaXNlIHBhcyByw6llbGxlbWVudCBsZSBmaWx0cmUuCkVuc3VpdGUsIG9uIGEgdG91dCBsZSBsb2lzaXIgZGUgcmVwYXJ0aXIgZGUgY2V0IG9iamV0IHBvdXIgZmlsdHJlciBzdXIgdGVsbGUgZXQgdGVsbGUgcHJlc3RhdGlvbi4KVW4gYGRiR2V0UXVlcnlgIHN1ciBgRVJFX1BSU19GYCBhdmVjIGRlcyBmaWx0cmVzIHVzdWVscyBzZXJhaXQgaW1wb3NzaWJsZSBjYXIgZGVtYW5kZXJhaXQgdHJvcCBkZSByZXNzb3VyY2VzIGVuIG3DqW1vaXJlIHZpdmUuCkRvbmMgaWwgZmF1ZHJhaXQsIHBvdXIgY2hhcXVlIGNpYmxhZ2UgZGUgcHJlc3RhdGlvbiwgcmVtZXR0cmUgdG91cyBsZXMgZmlsdHJlcyBkZXB1aXMgYEVSRV9QUlNfRmAgc2kgbCdvbiBwYXNzYWl0IHBhciBTUUwgT3JhY2xlLiBUcmFkdWN0aW9uIGVuIGNvZGUgOjwvZm9udD4KCmBgYHtyLCB0aWR5PUZBTFNFfQojIEltbcOpZGlhdCAow6l2YWx1YXRpb24gcGFyZXNzZXVzZSkKZXJwcnNmIDwtIHRibChjb25uLCBpbl9zY2hlbWEoIk1FUFNHUF8xNTYiLCAiRVNORF9FUl9QUlNfRiIpKSAlPiUKICAgIGZpbHRlcigKICAgICAgICBGTFhfRElTX0RURCAtIEVYRV9TT0lfRFREIDwgMTgzICYgIyBvbiBlbmzDqHZlCiAgICAgICAgIyBsZXMgc29pbnMgcmVtb250w6lzIDYgbW9pcyBhcHLDqHMgbGEgZGF0ZSBkZSBkw6lidXQgcG91ciByYWlzb25uZXIgw6AgZHVyw6llIGRlIGZsdXggY29udGFudGUKICAgICAgICBEUE5fUUxGICE9IDcxICYKICAgICAgICBQUlNfRFBOX1FMUCAhPSA3MSAmCiAgICAgICAgQ1BMX01BSl9UT1AgPCAyICYKICAgICAgICBDUExfQUZGX0NPRCAhPSAxNiAmCiAgICAgICAgKCFCRU5fU0VYX0NPRCAlaW4lIGMoIjAiKSkgJgogICAgICAgIEJFTl9OQUlfQU5OID4gMTgwMCAmCiAgICAgICAgQkVOX05BSV9BTk4gPD0gMjAxNgogICAgKSAlPiUKICBzZWxlY3QoQkVOX05JUl9QU0EsIEJFTl9STkdfR0VNLCBCRU5fU0VYX0NPRCwgQkVOX0FNQV9DT0QsIEJFTl9SRVNfRFBULCBCRU5fUkVTX0NPTSwgRkxYX0RJU19EVEQsIEVYRV9TT0lfQU1ELCBFWEVfU09JX0RURCwgUFJTX05BVF9SRUYsIFBTRV9BQ1RfTkFULCBQU0VfU1BFX0NPRCwgUFJTX0FDVF9RVEUpCmBgYAoKRmlsdHJlciBsZXMgdmFjY2lucyBjb250cmUgbGEgKipncmlwcGUqKiAoY29kZXMgcHJlc3RhdGlvbnMgMzMzMSkgZW4gcmVwYXJ0YW50IGRlIGwnb2JqZXQgYGVycHJzZmAgOgoKYGBge3J9CmVycHJzZiAlPiUgZmlsdGVyKFBSU19OQVRfUkVGID09ICIzMzMxIikgJT4lIHRhbGx5CmBgYAoKRmlsdHJlciBsZXMgc29pbnMgZGVzICoqbcOpZGVjaW5zICoqZ8OpbsOpcmFsaXN0ZXMgKGNvZGVzIHNww6ljaWFsaXTDqSAxLCAyMiBvdSAyMykgZW4gcmVwYXJ0YW50IGRlIGwnb2JqZXQgYGVycHJzZmAgOgoKYGBge3J9CmVycHJzZiAlPiUgZmlsdGVyKFBTRV9TUEVfQ09EICVpbiUgYygxLCAyMiwgMjMpKSAlPiUgdGFsbHkKYGBgCgoKTGEgcGx1cGFydCBkZXMgY29tbWFuZGVzIHByw6lzZW50w6llcyBlbnN1aXRlIGNvbmNlcm5lbnQgbGEgbWFuacOocmUgZHBseXIuCgojIyBSRUdMRSBEJ09SCgpJbCBmYXV0IGJpZW4gY2hvaXNpciBsZSBtb21lbnQgZHUgcmFwYXRyaWVtZW50IGVuIFIgKGBjb2xsZWN0YCBvdSBgZGJHZXRRdWVyeWApLiA8Zm9udCBjb2xvcj0iI2NmMDUwNSI+KipHbG9iYWxlbWVudCwgcGx1cyBjZSBtb21lbnQgdmllbnQgdGFyZCwgbWlldXggYydlc3QuKio8L2ZvbnQ+IEF1dHJlbWVudCBkaXQgcGx1cyBvbiBmYWl0IHRyYXZhaWxsZXIgT3JhY2xlIGF1IGxpZXUgZGUgUiwgbWlldXggYydlc3QuIEMnZXN0IGxlIG3Dqm1lIHByaW5jaXBlIHNpIG9uIHRyYXZhaWxsYWl0IHN1ciB1biBhdXRyZSBtb3RldXIgZGUgYmFzZSBkZSBkb25uw6llcyBvdSBzdXIgU3BhcmsuIE5lIGphbWFpcyByYXBhdHJpZXIgZGVzIGRvbm7DqWVzIGRlIHByZXN0YXRpb25zIChwYXIgZXhlbXBsZSB1biBib3V0IGRlIGBFUl9QUlNfRmApLCBjZWxhIHZhIMOqdHJlIHRyw6hzIGxvbmcgdG91dCBlbiByaXNxdWFudCBkZSBzdXJjaGFyZ2VyIGxhIG3DqW1vaXJlIHZpdmUgZHUgc2VydmV1ciwgY29uZHVpc2FudCDDoCB1biBtYWlsIGRlIGxhIENuYW0gZXQgbCdhcnLDqnQgZGUgbGEgc2Vzc2lvbi4KT24gcGV1dCDDqXZlbnR1ZWxsZW1lbnQgcmFwYXRyaWVyIHVuZSB0YWJsZSBhdmVjIGF1dGFudCBkZSBsaWduZXMgcXVlIGQnaW5kaXZpZHVzLCDDoCBjb25kaXRpb24gZGUgc8OpbGVjdGlvbm5lciBwZXUgZGUgY29sb25uZXMuCkVuc3VpdGUgZmluaXIgZW4gUi4KCiMjIFRyYWR1aXJlIHVuZSBjb21tYW5kZSBkcGx5ciBlbiBTUUwgT3JhY2xlCgpEYW5zIGxlcyBjb3VsaXNzZXMsIGxlIHBhcXVldCBkKipiKipwbHlyIHRyYWR1aXQgbGUgY29kZSB0eXBlIGRwbHlyIGVuIFNRTCBPcmFjbGUuClBvdXIgcydlbiBjb252YWluY3JlLCBvbiBwZXV0IG1vbnRyZXIgbGEgcmVxdcOqdGUgZWZmZWN0aXZlbWVudCBleMOpY3V0w6llIGF2ZWMgYHNob3dfcXVlcnkoKWAgb3UgYHJlbW90ZV9xdWVyeSgpYApgYGB7cn0KdGJsKGNvbm4sICJJUl9CSU9fUiIpICU+JSBzaG93X3F1ZXJ5ICMgZHUgcGFxdWV0IGRwbHlyCnRibChjb25uLCAiSVJfQklPX1IiKSAlPiUgcmVtb3RlX3F1ZXJ5ICMgZHUgcGFxdWV0IGRicGx5cgpgYGAKCmBzaG93X3F1ZXJ5YCBhZmZpY2hlIGxhIGNvbW1hbmRlIHNvdXMgbGEgZm9ybWUgZCd1biBtZXNzYWdlIHRhbmRpcyBxdWUgYHJlbW90ZV9xdWVyeWAgcmVudm9pZSBsYSBjb21tYW5kZSBzb3VzIGxhIGZvcm1lIGQndW4gcsOpc3VsdGF0LCBjZSBxdWkgcGV1dCBwZXJtZXR0cmUgZCdlbnJlZ2lzdHJlciBsZSBjb2RlIGRlIGxhIHJlcXXDqnRlIGNvbW1lIG9uIGxlIHZlcnJhIHBsdXMgYmFzLgoKYHNob3dfcXVlcnkoKWAgZXQgYHJlbW90ZV9xdWVyeSgpYCBwZXV2ZW50IMOqdHJlIHByYXRpcXVlcyBzaSBsJ29uIHZldXQgdHJhdmFpbGxlciBkaXJlY3RlbWVudCBhdmVjIGxlIFNRTCBPcmFjbGUgKHZpYSBgZGJHZXRRdWVyeSgpYCkgbWFpcyBxdWUgbCdvbiBlc3Qgcm91aWxsw6kuIENlcyBjb21tYW5kZXMgbm91cyBkb25uZW50IGxhIHRyYWR1Y3Rpb24gU1FMIG91Ymxpw6llIGQndW5lIGNvbW1hbmRlIGRwbHlyIHF1ZSBsJ29uIGNvbm5haXQuCgpBc3R1Y2UgOiBvbiBwZXV0IG1vbnRyZXIgbGEgdHJhZHVjdGlvbiBlbiBtw6ptZSB0ZW1wcyBxdSdvbiBkZW1hbmRlIGwnZXjDqWN1dGlvbiBkZSBsYSByZXF1w6p0ZSBlbiBlbmNoYWluYW50IHBhciBkZSBub3V2ZWxsZXMgY29tbWFuZGVzIGFwcsOocyBgc2hvd19xdWVyeWAgOgoKYGBge3J9CnRibChjb25uLCAiSVJfQklPX1IiKSAlPiUKICBzaG93X3F1ZXJ5ICAlPiUKICBoZWFkKDEpICU+JQogIGNvbGxlY3QKYGBgCgoKIyMgQ29tbWFuZGVzIGRwbHlyIG5vbiBzdXBwb3J0w6llcwpUb3V0ZXMgbGVzIGNvbW1hbmRlcyBkcGx5ciBuZSBmb25jdGlvbm5lcm9udCBwYXMgY2FyIGxlIHBhcXVldCBkKipiKipwbHlyIG5lIGxlcyB0cmFkdWl0IHBhcyB0b3V0ZXMuCgpQYXIgZXhlbXBsZSwgbGEgY29tbWFuZGUgYHNsaWNlYCBxdWkgZG9pdCBwZXJtZXR0cmUgZGUgc8OpbGVjdGlvbm5lciB1bmUgbGlnbmUgcGFyIHNvbiBudW3DqXJvLCBuZSBwZXV0IHBhcyDDqnRyZSB0cmFkdWl0ZSA6CgpgYGB7cn0KIyBOZSBmb25jdGlvbm5lIFBBUyBzdXIgdW5lIGNvbm5leGlvbiDDoCB1bmUgYmFzZSBPcmFjbGUgOgojIHRibChjb25uLCAiSVJfQklPX1IiKSAlPiUgc2xpY2UoMTAwKQoKIyBBbG9ycyBxdSdlbGxlIGZvbmN0aW9ubmUgc3VyIHVuIHRhYmxlYXUgZMOpasOgIHJhcHBhdHJpw6kgZW4gUiA6CnRibChjb25uLCAiSVJfQklPX1IiKSAlPiUgY29sbGVjdCAlPiUgc2xpY2UoMTAwKQpgYGAKCgojIyBGb25jdGlvbnMgY291cmFudGVzCgpQcmluY2lwYWxlbWVudCBkZXMgZm9uY3Rpb25zIGRicGx5ci4KCiMjIyBDcsOpZXIgdW5lIGNvbG9ubmUgOiBgbXV0YXRlYAoKT24gcGV1dCBtb2RpZmllciB1bmUgY29sb25uZSBvdSBsYSBjcsOpZXIgc2kgZWxsZSBuJ2V4aXN0ZSBwYXMgZW5jb3JlLgoKPGZvbnQgY29sb3I9IiM3MzczNzMiPkV4ZW1wbGUgc3VyIGxlIGZhdXggamV1IDogcGFzc2VyIGxhIGNvbG9ubmUgYHhgIGF1IGNhcnLDqS48L2ZvbnQ+CmBgYHtyfQpmYXV4ICU+JSBtdXRhdGUoeCA9IHheMikKYGBgCgo8Zm9udCBjb2xvcj0iIzczNzM3MyI+Q3LDqWVyIHVuZSBub3V2ZWxsZSBjb2xvbm5lIGF2ZWMgbGUgY2FycsOpIGRlIGxhIGNvbG9ubmUgYHhgLjwvZm9udD4KYGBge3J9CmZhdXggJT4lIG11dGF0ZSh4MiA9IHheMikKYGBgCgpQb3VyIMOpdml0ZXIgbGVzIHByb2Jsw6htZXMsICoqcmVzcGVjdGVyIGxhIGNhc3NlKiogOyBzaSBvbiBjcsOpZSB1bmUgY29sb25uZSBzb3VzIGxlIG5vbSBkZSAiTm9NIiwgaWwgdmF1dCBtaWV1eCBsJ2FwcGVsZXIgcGFyICJOb00iLiAibm9tIiBtYXJjaGVyYSBkYW5zIGNlcnRhaW5zIGNhcyAoZW4gU1FMKSBldCBwYXMgZGFucyBkJ2F1dHJlcy4KCjxmb250IGNvbG9yPSIjY2YwNTA1Ij48c3BhbiBzdHlsZT0iZm9udC1zaXplOjIwMCU7ZGlzcGxheTppbmxpbmUtYmxvY2s7IHZlcnRpY2FsLWFsaWduOm1pZGRsZTsgbWFyZ2luLWJvdHRvbTowcHg7Ij7imqA8L3NwYW4+IHF1ZWxxdWVzIHJhY2NvdXJjaXMgaGFiaXR1ZWxzIGVuIFIgbmUgZm9uY3Rpb25uZW50IHBsdXM8L2ZvbnQ+Cgo8Zm9udCBjb2xvcj0iIzczNzM3MyI+UGFyIGV4ZW1wbGUsIHN1ciB1biBvYmpldCBSIChpY2kgcmFwcGF0cmnDqSB2aWEgYGNvbGxlY3RgKSwgb24gcGV1dCBjcsOpZXIgdW5lIGNvbG9ubmUgYXZlYyB1bmUgY29uZGl0aW9uLjwvZm9udD4KYGBge3J9CmZhdXggJT4lIGNvbGxlY3QgJT4lIG11dGF0ZSh4MiA9IHggPiAyKQpgYGAKCjxmb250IGNvbG9yPSIjNzM3MzczIj5TdXIgdW5lIGJhc2UgT3JhY2xlIChzYW5zIGxlIGBjb2xsZWN0YCksIGNlbGEgZ8OpbsOpcmVyYSB1bmUgZXJyZXVyIDo8L2ZvbnQ+CmBgYHtyLCBlcnJvcj1UUlVFfQpmYXV4ICU+JSBtdXRhdGUoeDIgPSB4ID4gMikgJT4lIHNob3dfcXVlcnkoKSAlPiUgY29sbGVjdApgYGAKCkV0IGlsIGZhdWRyYSBleHBsaWNpdGVyIGF2ZWMgYGlmZWxzZWAsIHF1aSBzZXJhIHRyYWR1aXQgcGFyIGBDQVNFIFdIRU5gIGVuIFNRTCA6CgpgYGB7cn0KZmF1eCAlPiUgbXV0YXRlKHgyID0gaWZlbHNlKHggPiAyLCAxLCAwKSkgJT4lIHNob3dfcXVlcnkoKSAlPiUgY29sbGVjdCAjIGZvbmN0aW9ubmUKYGBgCgoKIyMjIyBBc3R1Y2UgOiBtYW5pcHVsZXIgdW5lIGNvbG9ubmUganVzdGUgYXByw6hzIHNhIGNyw6lhdGlvbgoKT24gcGV1dCB1dGlsaXNlciBkYW5zIGxlIG3Dqm1lIGBtdXRhdGVgIHVuZSBjb2xvbm5lIHF1aSB2aWVudCBkJ8OqdHJlIGNyw6nDqWUuCgo8Zm9udCBjb2xvcj0iIzczNzM3MyI+IE9uIGNyw6llIHVuZSB2YXJpYWJsZSBgeDNgIHF1aSBkw6lwZW5kIGQndW5lIHZhcmlhYmxlIGB4MmAgY3LDqcOpZSBwYXIgbGEgbcOqbWUgb2NjYXNpb24uPC9mb250PgoKYGBge3J9CmZhdXggJT4lIG11dGF0ZSh4MiA9IHheMiwKICAgICAgICAgICAgICAgIHgzID0geDIgKyAxMCkKYGBgCgojIyMgT3JkcmUgOiBgYXJyYW5nZWAKCk9uIHBldXQgb3Jkb25uZXIgYXZlYyBgYXJyYW5nZWAgcXVpIHZhIGNyw6llciB1biBgT1JERVIgQllgIGVuIFNRTCA6CgpgYGB7cn0KZmF1eCAlPiUgYXJyYW5nZSgteCkgJT4lIHNob3dfcXVlcnkoKSAlPiUgY29sbGVjdApgYGAKYC14YCBwZXJtZXQgZCdvYnRlbmlyIHVuIG9yZHJlIGTDqWNyb2lzc2FudCB0b3V0IGNvbW1lIGBkZXNjKHgpYC4KCgojIyMgRMOpZG91Ymxvbm5hZ2UgOiBgZGlzdGluY3RgCgpMYSBmb25jdGlvbiBkYnBseXIgYGRpc3RpbmN0YCBjb3JyZXNwb25kIMOgIGxhIGZvbmN0aW9uIFIgYmFzZSBgdW5pcXVlYCwgZWxsZSDDqWxpbWluZSBsZXMgbGlnbmVzIGVuIGRvdWJsb25zLgoKPGZvbnQgY29sb3I9IiM3MzczNzMiPkV4ZW1wbGUgYXZlYyBub3RyZSBmYXV4IGpldSBkZSBkb25uw6llcy48L2ZvbnQ+CgpgYGB7cn0KZmF1eCAlPiUgc2VsZWN0KHkpICU+JSBkaXN0aW5jdApgYGAKCiMjIyBDb21wdGVzIDogYHRhbGx5YAoKYHRhbGx5YCBwZXJtZXQgZGUgY29tcHRlciBsZSBub21icmUgZGUgbGlnbmVzIGV0IHZhIMOqdHJlIHRyYWR1aXQgcGFyIHVuIGBDT1VOVCgqKWAgZW4gU1FMIDoKCgpgYGB7cn0KbWNvYyA8LSB0YmwoY29ubiwgaW5fc2NoZW1hKCJNRVBTR1BfMTU2IiwgIkVTTkRfVF9NQ08xOUMiKSkKbWNvYyAlPiUKICBzZWxlY3QoIk5JUl9BTk9fMTciKSAlPiUKICBkaXN0aW5jdCAlPiUKICB0YWxseSAlPiUKICBzaG93X3F1ZXJ5ICU+JQogIGNvbGxlY3QKYGBgCgojIyMgR3JvdXBhZ2VzIDogYGdyb3VwX2J5YApgdGFsbHlgIGZvbmN0aW9ubmUgYXVzc2kgYXZlYyBgZ3JvdXBfYnlgIHNpIGwnb24gc291aGFpdGUgZ3JvdXBlciBwYXIgdW5lIHZhcmlhYmxlLgoKPGZvbnQgY29sb3I9IiM3MzczNzMiPkV4ZW1wbGUgb24gdmV1dCBjb21wdGVyIGxlIG5vbWJyZSBkZSBzw6lqb3VycyBmaW5pcyBjaGFxdWUgbW9pcyBkZSBsJ2FubsOpZSAyMDE5LjwvZm9udD4KCmBgYHtyfQptY29jICU+JQogIGdyb3VwX2J5KEVYRV9TT0lfQU1GKSAlPiUKICB0YWxseSAlPiUKICBzaG93X3F1ZXJ5ICU+JQogIGNvbGxlY3QKYGBgCgpgY291bnRgIGVzdCB1biByYWNjb3VyY2kgcXVpIGZhaXQgbGEgbcOqbWUgY2hvc2UuIE5vdGV6IHF1ZSBjJ2VzdCBleGFjdGVtZW50IGxhIG3Dqm1lIHRyYWR1Y3Rpb24gU1FMIDoKCmBgYHtyfQptY29jICU+JQogIGNvdW50KEVYRV9TT0lfQU1GKSAlPiUKICBzaG93X3F1ZXJ5ICU+JQogIGNvbGxlY3QKYGBgCgojIyMgQWdyw6lnYXRpb25zCgpgdGFsbHlgIGVzdCB1biByYWNjb3VyY2kgcG91ciBgc3VtbWFyaXNlKG49bigpKWAKYGBge3IsIHdhcm5pbmc9RkFMU0V9CmZhdXggJT4lCiAgZ3JvdXBfYnkoeSkgJT4lCiAgc3VtbWFyaXNlKG4gPSBuKCkpICU+JSAjIGF1IGxpZXUgZGUgdGFsbHkgCiAgc2hvd19xdWVyeSAlPiUKICBjb2xsZWN0CmBgYApPbiBwZXV0IGNob2lzaXIgZCdhZ3LDqWdlciBkJ3VuZSBhdXRyZSBtYW5pw6hyZSwgZW4gcHJlbmFudCB1biBjb21wdGUgdW5pcXVlLCBsYSBtb3llbm5lLCBsYSBzb21tZSwgbGUgbWF4IDogCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KZmF1eCAlPiUgc3VtbWFyaXNlKG4gPSBuKCksCiAgICAgICAgICAgICAgICAgICBuX3VuaXF1ZSA9IG5fZGlzdGluY3QoeSksCiAgICAgICAgICAgICAgICAgICBtb3kgPSBtZWFuKHgpLAogICAgICAgICAgICAgICAgICAgdG90ID0gc3VtKHgpLAogICAgICAgICAgICAgICAgICAgbWF4ID0gbWF4KHgpKQpgYGAKClBhcmZvaXMgaWwgZXN0IHByYXRpcXVlIGQnb3Jkb25uZXIgZXQgZGUgcHJlbmRyZSBsYSBwcmVtacOocmUgKGBmaXJzdCgpYCkgb3UgbGEgZGVybmnDqHJlIHZhbGV1ciAoYGxhc3QoKWApLgpNYWxoZXVyZXVzZW1lbnQsIGNlcyBkZXV4IGNvbW1hbmRlcyBuZSBmb25jdGlvbm5lbnQgcXUnZW4gdGFudCBxdWUgZm9uY3Rpb24gZGUgZmVuw6p0cmFnZSAoYXZlYyBgbXV0YXRlKClgKSBldCBub24gZW4gZm9uY3Rpb24gZCdhZ3LDqWdhdGlvbiAoYXZlYyBgc3VtbWFyaXplYCkuCkFpbnNpIGNldHRlIGNvbW1hbmRlIMOpY2hvdWUgOgoKYGBge3IsIGVycm9yPVRSVUV9CmZhdXggJT4lIHN1bW1hcmlzZShwcmVtaWVyZSA9IGZpcnN0KHgpKQpgYGAKClVuZSBhc3R1Y2UgY29uc2lzdGUgw6AgY291cGxlciBgbXV0YXRlKClgIGF2ZWMgYHNlbGVjdCgpYCBldCBgZGlzdGluY3QoKWAgOgoKYGBge3IsIGVycm9yPVRSVUV9CmZhdXggJT4lCiAgZ3JvdXBfYnkoeikgJT4lCiAgbXV0YXRlKHByZW1pZXJlID0gZmlyc3QoeCkpICU+JQogIHNlbGVjdCh6LCBwcmVtaWVyZSkgJT4lCiAgZGlzdGluY3QKYGBgCk1haXMgaWwgZmF1dCB2ZWlsbGVyIMOgIGNob2lzaXIgbGVzIGJvbm5lcyBjb2xvbm5lcyBkYW5zIGxlIGBzZWxlY3RgIHBvdXIgYXJyaXZlciBhdSByw6lzdWx0YXQgZXNjb21wdMOpLgoKIyMjIEZvbmN0aW9ucyBkZSBmZW7DqnRyYWdlCgoKIyMjIyBQcmVtacOocmUgb2JzZXJ2YXRpb24KCk9uIHBldXQgdXRpbGlzZXIgYGdyb3VwX2J5YCBwdWlzIGBhcnJhbmdlYCAob3UgZGFucyBsJ2F1dHJlIHNlbnMpIHBvdXIgZW5zdWl0ZSBjcsOpZXIgdW5lIHZhcmlhYmxlIGRlIHJhbmcgb3UgZmlsdHJlciBkaXJlY3RlbWVudCBzdXIgbGUgcmFuZyAxLgoKYGBge3J9CmZhdXggJT4lCiAgYXJyYW5nZSgteCkgJT4lCiAgZ3JvdXBfYnkoeSkgJT4lCiAgZmlsdGVyKHJvd19udW1iZXIoKSA9PSAxKQpgYGAKT24gcmXDp29pdCB1biBhdmVydGlzc2VtZW50IGBPUkRFUiBCWSBpcyBpZ25vcmVkIGluIHN1YnF1ZXJpZXMgd2l0aG91dCBMSU1JVGAgbWFpcyBjZWxhIHNlbWJsZSBwb3VydGFudCBiaWVuIGZvbmN0aW9ubmVyLCBjb21tZSBvbiBwZXV0IGxlIHZvaXIgc2kgb24gcmVtcGxhY2UgYC14YCBwYXIgYHhgIHB1aXNxdWUgbGUgcsOpc3VsdGF0IGVzdCBjb2jDqXJlbnQuIMOAIG1hbmlwdWxlciBhdmVjIHByw6ljYXV0aW9uLgoKYHJvd19udW1iZXJgIGFjY2VwdGUgdW4gYXJndW1lbnQgZCdvcmRyZSwgY2UgcXVpIHBlcm1ldCBkZSBnYWduZXIgZW4gY29uY2lzaW9uIDoKCmBgYHtyfQpmYXV4ICU+JQogIGdyb3VwX2J5KHkpICU+JQogIGZpbHRlcihyb3dfbnVtYmVyKC14KSA9PSAxKQpgYGAKQsOpbW9sIDogYHJvd19udW1iZXJgIG4nYWNjZXB0ZSBxdSd1bmUgdmFyaWFibGUgZCdvcmRyZSwgw6AgbGEgZGlmZsOpcmVuY2UgZGUgYGFycmFuZ2VgLgoKTGEgZm9uY3Rpb24gYHdpbmRvd19vcmRlcigpYCBwb3VycmFpdCDDqnRyZSBsYSBzb2x1dGlvbiA6CgpgYGB7cn0KZmF1eCAlPiUKICBncm91cF9ieSh5KSAlPiUKICB3aW5kb3dfb3JkZXIoLXgsIHopICU+JQogIGZpbHRlcihyb3dfbnVtYmVyKCkgPT0gMSkKYGBgCkVuIHBsdXMgbGUgdGliYmxlIGdhcmRlIGxlcyB2YXJpYWJsZXMgZGUgZ3JvdXBhZ2UgZXQgZCdvcmRyZSBlbiBhdHRyaWJ1dHMgZXQgbGVzIGFmZmljaGUsIGNlIHF1aSBlc3QgcHJhdGlxdWUuCgojIyMjIEFncsOpZ2F0aW9uIHBhciByZWN5Y2xhZ2UKCkwnYWdyw6lnYXRpb24gcGFyIHJlY3ljbGFnZSBlc3QgdW4gdHlwZSBwYXJ0aWN1bGllciBkZSBmb25jdGlvbiBkZSBmZW7DqnRyYWdlLgpBIGxhIGRpZmbDqXJlbmNlIGRlcyBmb25jdGlvbnMgZCdhZ3LDqWdhdGlvbiwgb24gZ2FyZGUgbGUgbcOqbWUgbm9tYnJlIGRlIGxpZ25lcyBtYWlzIG9uIHLDqXBsaXF1ZSBsJ2luZm9ybWF0aW9uIGF1IHNlaW4gZGUgY2hhcXVlIGdyb3VwZS4KSWwgc3VmZml0IGRlIHJlbXBsYWNlciBgc3VtbWFyaXNlKClgIHBhciBgbXV0YXRlKClgLgoKRXhlbXBsZSA6CgpgYGB7cn0KZmF1eCAlPiUKICBncm91cF9ieSh6KSAlPiUKICBtdXRhdGUoc29tID0gbl9kaXN0aW5jdCh4KSkKYGBgCgoKIyMjIyBQb3VyIGxlcyBhbWF0ZXVycwoKUG91ciBkZXMgZm9uY3Rpb25zIGRlIGZlbsOqdHJhZ2UgcGx1cyBjb21wbGV4ZXMsIG9uIGVzdCBvYmxpZ8OpcyBkZSBwYXNzZXIgcGFyIGRlcyBtw6l0aG9kZXMgcGx1cyBwcm9jaGVzIGRlIFNRTC4KVW4gb3B0aW9uIGVzdCBsYSBmb25jdGlvbiBgd2luX292ZXJgIHF1aSBhaWRlIMOgIGNvbnN0cnVpcmUgc29uIGZlbsOqdHJhZ2UuCgpgYGB7cn0KZmF1eCAlPiUKICBtdXRhdGUocmFuZyA9IGRicGx5cjo6d2luX292ZXIoY29uID0gY29ubiwgZXhwciA9IHNxbCgiUkFOSygpIiksIHBhcnRpdGlvbiA9ICJ5Iiwgb3JkZXIgPSAieCIpKQpgYGAKTWFpcyBpbCBuJ2VzdCBwYXMgcG9zc2libGUgZCd1dGlsaXNlciB1biBvcmRyZSBkw6ljcm9pc3NhbnQgw6AgbWEgY29ubmFpc3NhbmNlLgoKUmllbiBuZSB2YXV0IGxlIDEwMCUgU1FMIGRhbnMgY2UgY2FzIDoKCmBgYHtyfQpmYXV4ICU+JQogIG11dGF0ZShyYW5nID0gc3FsKCdSQU5LKCkgT1ZFUiAoUEFSVElUSU9OIEJZICJ5IiBPUkRFUiBCWSAieCIgREVTQyknKSkKYGBgCgoKIyMjIFZhbGV1cnMgbWFucXVhbnRlcyA6IGBOVkxgIHsjTlZMfQpPbiBwZXV0IGZhY2lsZW1lbnQgcmVtcGxhY2VyIGxlcyB2YWxldXJzIG1hbnF1YW50ZXMgZGFucyBPcmFjbGUgYXZlYyBsYSBmb25jdGlvbiBgTlZMYCAoKm5vIHZhbHVlKikuCgo8Zm9udCBjb2xvcj0iIzczNzM3MyI+RXhlbXBsZSBhdmVjIG5vdHJlIGZhdXggamV1IGRlIGRvbm7DqWVzLjwvZm9udD4KCmBgYHtyfQpmYXV4ICU+JQogIG11dGF0ZSh5MiA9IE5WTCh5LCBOQSksICMgbmUgY2hhbmdlIHJpZW4KICAgICAgICAgeTMgPSBOVkwoeSwgMCkpICMgcmVtcGxhY2UgcGFyIDAKCiMgTlZMKHksICJBIikgbmUgZm9uY3Rpb25uZSBwYXMgY2FyIHkgZXN0IGRlIHR5cGUgbnVtZXJpYwoKIyBFeGVtcGxlIHLDqWVsIChjb21tZW50w6kgY2FyIHLDqXN1bHRhdHMgaW5kaXZpZHVlbHMpCiMgaXJiZW5yIDwtIHRibChjb25uLCAiSVJfQkVOX1IiKQojIGlyYmVuciAlPiUKIyAgIHNlbGVjdChJTkRfUk5NX0JFTikgJT4lCiMgICBtdXRhdGUoSU5EX1JOTV9CRU4yID0gTlZMKElORF9STk1fQkVOLCAiTkEiKSkgIyBPbiBwZXV0IG1ldHRyZSBjZSBxdWUgbCdvbiB2ZXV0IMOgIGxhIHBsYWNlIGRlICJOQSIKCmBgYAoKCiMjIyBFbXBpbGVyIGRlcyB0YWJsZXMgOiBgdW5pb25fYWxsYAoKRXF1aXZhbGVudCBkZSBsYSBmb25jdGlvbiBSIGByYmluZGAgOgoKYGBge3J9CnVuaW9uX2FsbChmYXV4LCBmYXV4KSAlPiUgc2hvd19xdWVyeSgpICU+JSBjb2xsZWN0CmBgYAoKIyMjIEpvaW50dXJlcwoKRCdhcHLDqHMgbGUgW21hbnVlbCBkZSBgZGJwbHlyYF0oaHR0cHM6Ly9kYnBseXIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2Uvam9pbi50Ymxfc3FsLmh0bWwpLCB0b3V0ZXMgbGVzIGpvaW50dXJlcyBwb3NzaWJsZXMgZXQgbGV1ciDDqXF1aXZhbGVudCBlbiBTUUwgOgoKKyBgaW5uZXJfam9pbih4LCB5KWAgOiBgU0VMRUNUICogRlJPTSB4IEpPSU4geSBPTiB4LmEgPSB5LmFgCisgYGxlZnRfam9pbih4LCB5KWAgOiBgU0VMRUNUICogRlJPTSB4IExFRlQgSk9JTiB5IE9OIHguYSA9IHkuYWAKKyBgcmlnaHRfam9pbih4LCB5KWAgOiBgU0VMRUNUICogRlJPTSB4IFJJR0hUIEpPSU4geSBPTiB4LmEgPSB5LmFgCisgYGZ1bGxfam9pbih4LCB5KWAgOiBgU0VMRUNUICogRlJPTSB4IEZVTEwgSk9JTiB5IE9OIHguYSA9IHkuYWAKKyBgc2VtaV9qb2luKHgsIHkpYCA6IGBTRUxFQ1QgKiBGUk9NIHggV0hFUkUgRVhJU1RTIChTRUxFQ1QgMSBGUk9NIHkgV0hFUkUgeC5hID0geS5hKWAKKyBgYW50aV9qb2luKHgsIHkpYCA6IGBTRUxFQ1QgKiBGUk9NIHggV0hFUkUgTk9UIEVYSVNUUyAoU0VMRUNUIDEgRlJPTSB5IFdIRVJFIHguYSA9IHkuYSlgCgoKCjxmb250IGNvbG9yPSIjY2YwNTA1Ij4qKlBvdXIgY2hlcmNoZXIgcGx1cyBkZSBmb25jdGlvbnMsIGxlIG1pZXV4IGVzdCBlbmNvcmUgZGUgY29uc3VsdGVyIGxlIFttYW51ZWwgZGUgZGJwbHlyXShodHRwczovL2RicGx5ci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9pbmRleC5odG1sKS4qKjwvZm9udD4KCgoKCiMjIFV0aWxpc2VyIFNRTCBkYW5zIGRwbHlyCgpJbCBlc3QgcG9zc2libGUgZCd1dGlsaXNlciBkZXMgY29tbWFuZGVzIFNRTCBkaXJlY3RlbWVudCBkYW5zIGxhIHN5bnRheGUgZHBseXIgbG9yc3F1J3VuIGJlc29pbiBuJ2VzdCBwYXMgY291dmVydCBwYXIgdW5lIGNvbW1hbmRlIGRwbHlyLgoKSWwgeSBhIHRyb2lzIG1hbmnDqHJlcyBkZSBmYWlyZSA6IGF2ZWMgZGVzIGZvbmN0aW9ucyBTUUwsIGF2ZWMgZGVzIG9ww6lyYXRldXJzIFNRTCwgYXZlYyBkZXMgYm91dHMgZGUgY29kZSBTUUwuCgojIyMgRm9uY3Rpb25zCgpTaSB1bmUgZm9uY3Rpb24gbidlc3QgcGFzIHJlY29ubnVlIHBhciBSLCBlbGxlIHZhIMOqdHJlIGxhaXNzw6llIHRlbGxlIHF1ZWxsZSBwb3VyIE9yYWNsZSwgY2UgcXVpIGVzdCB0csOocyBwcmF0aXF1ZS4KQSB2cmFpIGRpcmUsIGMnw6l0YWl0IGxlIGNhcyBkZSBsYSBmb25jdGlvbiBgTlZMYCBwcsOpc2VudMOpZSBbY2ktZGVzc3VzXSgjTlZMKS4KCjxmb250IGNvbG9yPSIjNzM3MzczIj5FeGVtcGxlIDogbGEgZm9uY3Rpb24gYENPTkNBVGAgcG91ciBjb25jYXTDqW5lciBuJ2V4aXN0ZSBwYXMgZW4gUiBtYWlzIGVuIE9yYWNsZSA6PC9mb250PgoKYGBge3J9CnRibChjb25uLCAiVF9NQ08xNUMiKSAlPiUgbXV0YXRlKGFub19yZXRvdXIgPSBDT05DQVQoTklSX1JFVCwgTkFJX1JFVCkpICU+JSBzaG93X3F1ZXJ5CmBgYAoKTCdpbmNvbnbDqW5pZW50IGRlIGNlcyBmb25jdGlvbnMgZXN0IHF1J2VsbGVzIG5lIHMnYXV0by1jb21wbMOodGVudCBwYXMgZW4gYXBwdXlhbnQgc3VyIFRBQiBldCBxdSdvbiBuZSBkaXNwb3NlIHBhcyBkJ3VuZSBkb2N1bWVudGF0aW9uIGludMOpZ3LDqWUuCklsIGZhdXQgdXRpbGlzZXIgc29uIG5hdmlnYXRldXIgcG91ciBsZXMgZMOpY291dnJpciBldCBhcHByZW5kcmUgw6AgbGVzIHV0aWxpc2VyLiBbRXhlbXBsZSBkZSBkb2N1bWVudGF0aW9uIHBvdXIgYENPTkNBVGBdKGh0dHBzOi8vZG9jcy5vcmFjbGUuY29tL2NkL0IxOTMwNl8wMS9zZXJ2ZXIuMTAyL2IxNDIwMC9mdW5jdGlvbnMwMjYuaHRtKS4KCiMjIyBPcMOpcmF0ZXVycwoKU2kgdW4gb3DDqXJhdGV1ciBlbnRyZSBgJWAgbidlc3QgcGFzIHJlY29ubnUgcGFyIFIsIGlsIHZhIMOqdHJlIGxhaXNzw6kgdGVsIHF1ZWwgZXQgT3JhY2xlIHBvdXJyYSBsZSBjb21wcmVuZHJlLgoKPGZvbnQgY29sb3I9IiM3MzczNzMiPkV4ZW1wbGUgc3VyIHVuIGZpbHRyZSBjbGFzc2lxdWUgcG91ciByZXRpcmVyIGxlcyBzw6lqb3VycyBkZSBNQ08gYXZlYyB1bmUgZXJyZXVyIHN1ciBsZSBOSVIsIG9uIHV0aWxpc2UgYCV8fCVgIHF1aSB2YSBkZXZlbmlyIHNpbXBsZW1lbnQgYHx8YCBldCBxdWkgcGVybWV0IGxhIGNvbmNhdMOpbmF0aW9uIGRlIHBsdXNpZXVycyB2YXJpYWJsZXMgOiA8L2ZvbnQ+CgpgYGB7cn0KbWNvYyAlPiUKICBtdXRhdGUoYW5vX3JldG91ciA9IE5JUl9SRVQgJXx8JSBOQUlfUkVUICV8fCUgU0VYX1JFVCAlfHwlCiAgU0VKX1JFVCAlfHwlIEZIT19SRVQgJXx8JSBQTVNfUkVUICV8fCUgREFUX1JFVCAlfHwlIENPSF9OQUlfUkVUICV8fCUgQ09IX1NFWF9SRVQpICU+JQogIGZpbHRlcihhbm9fcmV0b3VyID09ICIwMDAwMDAwMDAiKSAlPiUKICB0YWxseSAlPiUKICBzaG93X3F1ZXJ5ICU+JQogIGNvbGxlY3QKYGBgCgpMJ2V4ZW1wbGUgbGUgcGx1cyBwcmF0aXF1ZSBlc3QgbCdvcMOpcmF0ZXVyIGBMSUtFYCBxdWkgcGVybWV0IGQndXRpbGlzZXIgbGVzIFtleHByZXNzaW9ucyByw6lndWxpw6hyZXNdKGh0dHBzOi8vZnIud2lraXBlZGlhLm9yZy93aWtpL0V4cHJlc3Npb25fciVDMyVBOWd1bGklQzMlQThyZSkgKHJlZ2V4KSBPcmFjbGUgcG91ciBkw6ljcmlyZSBkZXMgY2hhw65uZXMgZGUgY2FyYWN0w6hyZXMgY29tcGxleGVzLgpTaSBvbiB2ZXV0IHF1ZSBgUFJTX05BVF9SRUZgIGNvbW1lbmNlIHBhciAzMSA6CgpgYGB7cn0KZXJwcnNmICU+JSBmaWx0ZXIoUFJTX05BVF9SRUYgJUxJS0UlICIzMSUiKSAlPiUgc2hvd19xdWVyeQpgYGAKCjxzcGFuIHN0eWxlPSJmb250LXNpemU6MjAwJTtkaXNwbGF5OmlubGluZS1ibG9jazsgdmVydGljYWwtYWxpZ246bWlkZGxlOyBtYXJnaW4tYm90dG9tOjBweDsiPuKaoDwvc3Bhbj4gbGVzIHJlZ2V4IE9yYWNsZSBuZSBmb25jdGlvbm5lbnQgcGFzIGRlIGxhIG3Dqm1lIG1hbmnDqHJlIHF1ZSBsZXMgcmVnZXggY291cmFudGVzIHN1ciBMaW51eC4KCiMjIyBCb3V0cyBkZSBjb2RlIFNRTHsjc3FsfQpPbiBwZXV0IHRvdWpvdXJzIGdsaXNzZXIgZGVzIGJvdXRzIGRlIGNvZGUgU1FMIHMnaWwgbid5IGEgcXVlbHF1ZSBjaG9zZSBxdWUgbCdvbiBuZSBzYWl0IHBhcyBmYWlyZSBlbiBzeW50YXhlIGRwbHlyLgpQb3VyIMOnYSBvbiB1dGlsaXNlcmEgbGEgY29tbWFuZGUgYHNxbGAuCgoKYGBge3J9CmVycHJzZiAlPiUgZmlsdGVyKFBTRV9TUEVfQ09EID09IHNxbCgiQU5ZICgxLDIsMykiKSkgJT4lIHNob3dfcXVlcnkoKQpgYGAKCjxmb250IGNvbG9yPSIjNzM3MzczIj5EYW5zIGNldCBleGVtcGxlLCBvbiBhdXJhaXQgZmFjaWxlbWVudCBwdSByZW1wbGFjZXIgbGEgY29uZGl0aW9uIHBhciBgUFNFX1NQRV9DT0QgJWluJSAoMSwyLDMpYCwgbWFpcyBkYW5zIHF1ZWxxdWVzIGNhcyBsZSBTUUwgZXN0IGxhIHNldWxlIHNvbHV0aW9uLCBlbiBwYXJ0aWN1bGllciBwb3VyIGxlcyBmb25jdGlvbnMgZGUgZmVuw6p0cmFnZS4KCkF1dHJlIGNhcyBkJ3VzYWdlIDogcG91ciDDqXZpdGVyIGRlcyAqaWZlbHNlKiBpbWJyaXF1w6lzLCBvbiB1dGlsaXNlcmEgZGlyZWN0ZW1lbnQgbGEgZm9uY3Rpb24gYFNRTCBDQVNFIFdIRU5gLgpQYXIgZXhlbXBsZSBzaSBvbiB2ZXV0IHJlbXBsaXIgYEJFTl9SRVNfRFBUMjAxNl9DT01QYCBhdmVjIGxlIGTDqXBhcnRlbWVudCBhdSBkw6lidXQgMjAxNiwgYEJFTl9SRVNfRFBUMjAxNmAsIHByw6lhbGFibGVtZW50IGV4dHJhaXQuCkxvcnNxdSdpbCBlc3QgYWJzZW50IG9uIHZhIGNoZXJjaGVyIGRhbnMgMjAxNyAoYEJFTl9SRVNfRFBUMjAxN2ApLCAyMDE1LCAyMDE4LCAyMDE0LCAyMDE5LCAyMDEzLCAyMDIwLgpTaSBvbiBhIHJpZW4gZGFucyB0b3V0ZXMgY2VzIGFubsOpZXMgKHBvdXIgdW4gbm9uIGNvbnNvbW1hbnQpLCBvbiBwcmVuZCBjZXMgaW5mb3MgZGFucyBgSVJfQkVOX1JgIChgQkVOX1JFU19EUFRgKS48L2ZvbnQ+CgpgYGB7cn0KIyBDZSBjb2RlIHZpZW50IGR1IHByb2pldCBiZW5lZmljaWFpcmVzU05EUyAoMDJfcHJvZHVjdGlvbi5SKSwgb8O5IGlsIGZvbmN0aW9ubmUKIyBpcmJlbnI0ICU+JQojICAgbXV0YXRlKEJFTl9SRVNfRFBUMjAxNl9DT01QID0gc3FsKCdDQVNFCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV0hFTiBOT1QoKCgiQkVOX1JFU19EUFQyMDE2IikgSVMgTlVMTCkpIFRIRU4gKCJCRU5fUkVTX0RQVDIwMTYiKQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdIRU4gTk9UKCgoIkJFTl9SRVNfRFBUMjAxNyIpIElTIE5VTEwpKSBUSEVOICgiQkVOX1JFU19EUFQyMDE3IikKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXSEVOIE5PVCgoKCJCRU5fUkVTX0RQVDIwMTUiKSBJUyBOVUxMKSkgVEhFTiAoIkJFTl9SRVNfRFBUMjAxNSIpCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV0hFTiBOT1QoKCgiQkVOX1JFU19EUFQyMDE4IikgSVMgTlVMTCkpIFRIRU4gKCJCRU5fUkVTX0RQVDIwMTgiKQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdIRU4gTk9UKCgoIkJFTl9SRVNfRFBUMjAxOSIpIElTIE5VTEwpKSBUSEVOICgiQkVOX1JFU19EUFQyMDE5IikKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXSEVOIE5PVCgoKCJCRU5fUkVTX0RQVDIwMTMiKSBJUyBOVUxMKSkgVEhFTiAoIkJFTl9SRVNfRFBUMjAxMyIpCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV0hFTiBOT1QoKCgiQkVOX1JFU19EUFQyMDIwIikgSVMgTlVMTCkpIFRIRU4gKCJCRU5fUkVTX0RQVDIwMjAiKQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVMU0UgIkJFTl9SRVNfRFBUIgojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVORCcpCmBgYAoKIyMgTGVzIGRhdGVzCgpQYXMgdnJhaW1lbnQgZmFjaWxlIMOgIGfDqXJlci4KTGUgZ3VpZGUgQ05BTSBkb25uZSBsJ2luZGljYXRpb24gcG91ciB1biBmaWx0cmUgZW4gdXRpbGlzYW50IGxhIGZvbmN0aW9uIE9yYWNsZSBgVE9fREFURWAgOgoKYGBge3J9CmlyYmlvciAlPiUKZmlsdGVyKEJJT19DUkVfREFUID49IFRPX0RBVEUoJzIwMTctMDEtMDEnLCd5eXl5LU1NLWRkJykpICU+JQogIGhlYWQoNSkgJT4lCiAgY29sbGVjdApgYGAKCjxzcGFuIHN0eWxlPSJmb250LXNpemU6MjAwJTtkaXNwbGF5OmlubGluZS1ibG9jazsgdmVydGljYWwtYWxpZ246bWlkZGxlOyBtYXJnaW4tYm90dG9tOjBweDsiPuKaoDwvc3Bhbj4gaWwgcydhZ2l0IGRlIGRhdGVIZXVyZS4KRGV1eCBkYXRlcyBwZXV2ZW50IG5lIGRpZmbDqXJlciBxdWUgZGUgcXVlbHF1ZXMgaGV1cmVzLgpMZSByw6lmw6lyZW50aWVsIGRlcyBiw6luw6lmaWNpYWlyZXMgZXN0IHVuIGJvbiBleGVtcGxlIHF1YW5kIG9uIGNvbXBhcmUgbGEgZGF0ZSBkJ2luc2VydGlvbiBkYW5zIGxlIHLDqWbDqXJlbnRpZWwgKGBCRU5fRFRFX0lOU2ApIGV0IGxhIGRhdGUgZGUgbWlzZSDDoCBqb3VyIChgQkVOX0lEVF9NQUpgKSA6CgpgYGB7cn0KaXJiZW5yIDwtIHRibChjb25uLCAiSVJfQkVOX1IiKQoKaXJiZW5yICU+JQogIHNlbGVjdChCRU5fRFRFX0lOUywgQkVOX0lEVF9NQUopICU+JQogIGZpbHRlcihCRU5fRFRFX0lOUyA9PSBUT19EQVRFKCcyMDIwLTAzLTExJywneXl5eS1NTS1kZCcpCiAgICAgICAgJiBCRU5fSURUX01BSiA8IFRPX0RBVEUoJzIwMjAtMDMtMTInLCd5eXl5LU1NLWRkJykKICAgICAgICAgKSAlPiUKICBtdXRhdGUoaWRlbnRpcXVlcyA9IGlmZWxzZShCRU5fRFRFX0lOUyA9PSBCRU5fSURUX01BSiwgMSwgMCkpICU+JQogIGhlYWQoNSkgJT4lCiAgY29sbGVjdApgYGAKKlJlbWFycXVlIDEgOiogaWwgeSBhIHVuIHByb2Jsw6htZSBkZSBmdXNlYXUgaG9yYWlyZSDDoCBsJ2FmZmljaGFnZSBkZXMgcsOpc3VsdGF0cyAoMjAyMC0wMy0xMCAyMzowMDowMCBldCBub24gMjAyMC0wMy0xMSAwMDowMDowMCkuIENlIHByb2Jsw6htZSBzZSB0cm91dmUgZHUgY8O0dMOpIGRlIGwnYWZmaWNoYWdlIGVuIFIgZXQgbm9uIGR1IGPDtHTDqSBkJ09yYWNsZSBjYXIgbGUgZmlsdHJlIGEgYmllbiBwb3J0w6kgc3VyIGxlIDExIG1hcnMgZXQgbm9uIGxlIDEwLgoKKlJlbWFycXVlIDIgOiogcG91ciBjZXMgbGlnbmVzIGwnaW5zZXJ0aW9uIGV0IGxhIG1pc2Ugw6Agam91ciBvbnQgw6l0w6kgZmFpdGVzIGxlIG3Dqm1lIGpvdXIgbWFpcyDDoCBkZXMgaGV1cmVzIGRpZmbDqXJlbnRlcywgZCdvw7kgbGEgY29sb25uZSBgaWRlbnRpcXVlc2Agw6AgYDBgLgpJbCBmYXV0IGRvbmMgw6p0cmUgdmlnaWxhbnQgcXVhbmQgb24gY29tcGFyZSBkZXMgZGF0ZXMgc2kgbCdvbiBuZSBzb3VoYWl0ZSB1bmUgcHLDqWNpc2lvbiBxdSdhdSBqb3VyIHByw6hzLgoKIyMjIERlIGRhdGVIZXVyZSDDoCBkYXRlCgpQb3VyIHBlcmRyZSBsZSBkw6l0YWlsIGRlIGwnaGV1cmUsIG9uIHBldXQgdXRpbGlzZXIgbGEgZm9uY3Rpb24gU1FMIGBUUlVOQ2AgOgoKYGBge3J9CmlyYmVuciAlPiUKICBmaWx0ZXIoQkVOX0RURV9JTlMgPT0gVE9fREFURSgnMjAyMC0wMy0xMScsJ3l5eXktTU0tZGQnKQogICAgICAgICYgQkVOX0lEVF9NQUogPCBUT19EQVRFKCcyMDIwLTAzLTEyJywneXl5eS1NTS1kZCcpCiAgICAgICAgICkgJT4lCiAgc2VsZWN0KEJFTl9JRFRfTUFKKSAlPiUKICBtdXRhdGUoQkVOX0lEVF9NQUpfdHJ1bmMgPSBUUlVOQyhCRU5fSURUX01BSikpICU+JSAjIMOpcXVpdmFsZW50IDoKICAjIG11dGF0ZShCRU5fSURUX01BSl90cnVuYyA9IHNxbCgiVFJVTkMoQkVOX0lEVF9NQUopIikpICU+JQogIGhlYWQoNSkgJT4lCiAgY29sbGVjdApgYGAKUG91ciB1bmUgY29tcGFyYWlzb24gZGUgZGV1eCBkYXRlcywgdW5lIGF1dHJlIHNvbHV0aW9uIGF1cmFpdCDDqXTDqSBkZSBzJ2Fzc3VyZXIgcXVlIGxhIHZhbGV1ciBhYnNvbHVlIGRlIGxhIGRpZmbDqXJlbmNlIGVzdCBpbmbDqXJpZXVyZSDDoCAxIChgYWJzKEJFTl9EVEVfSU5TIC0gQkVOX0lEVF9NQUopPDFgKSBtYWlzIGNlbGEgbmUgcmV2aWVudCBwYXMgZXhhY3RlbWVudCBhdSBtw6ptZSBjYXIgb24gdsOpcmlmaWUgYWxvcnMgcXVlIG1vaW5zIGRlIDI0aCBzZSBzb250IMOpY291bMOpZXMgZW50cmUgbGVzIGRldXggZGF0ZXMgZXQgbm9uIHF1J2VsbGVzIHRvbWJlbnQgc3VyIGxlIG3Dqm1lIGpvdXIuCgojIyMgRXh0cmFpcmUgbGUgam91ciwgbGUgbW9pcywgbCdhbm7DqWUKCklsIGZhdXQgZGUgbm91dmVhdSByZWNvdXJpciDDoCBsYSBmb25jdGlvbiBPcmFjbGUgZMOpZGnDqWUsIGBFWFRSQUNUYCA6CgpgYGB7cn0KcmVzIDwtIGlyYmVuciAlPiUKICBzZWxlY3QoQkVOX0RURV9JTlMpICU+JQogIGZpbHRlcihCRU5fRFRFX0lOUyA9PSBUT19EQVRFKCcyMDIwLTAzLTExJywneXl5eS1NTS1kZCcpKSAlPiUKICBoZWFkKDEpICU+JQogIG11dGF0ZShBTk5FRSA9IHNxbCgiRVhUUkFDVChZRUFSIEZST00gQkVOX0RURV9JTlMpIiksIE1PSVMgPSBzcWwoIkVYVFJBQ1QoTU9OVEggRlJPTSBCRU5fRFRFX0lOUykiKSkgJT4lCiAgY29sbGVjdCgpCnJlcwpgYGAKT24gcGV1dCBleHRyYWlyZSBsZXMgw6lsw6ltZW50cyBzdWl2YW50cyA6CgorICpZRUFSKgorICpNT05USCoKKyAqREFZKgorICpIT1VSKgorICpNSU5VVEUqCisgKlNFQ09ORCoKKyAqVElNRVpPTkVfSE9VUioKKyAqVElNRVpPTkVfTUlOVVRFKgorICpUSU1FWk9ORV9SRUdJT04qCisgKlRJTUVaT05FX0FCQlIqCgoKIyMgQW5hbG9naWUgZW50cmUgZGJwbHlyIGV0IFNwYXJrUgoKSWwgeSBhIHVuZSBjbGFpcmUgYW5hbG9naWUgZW50cmUgU3BhcmtSIGV0IFJPcmFjbGUuIERhbnMgbGVzIGRldXggY2FzIFIgZW52b2llIGRlcyByZXF1w6p0ZXMgw6AgdW4gbG9naWNpZWwgZXh0ZXJuZS4KTGVzIHBhcXVldHMgU3BhcmsqKlIqKiBldCAqKlIqKk9yYWNsZSBmb250IGxhIHRyYWR1Y3Rpb24sIFNwYXJrIG91IE9yYWNsZSBmb250IGxlIHRyYXZhaWwuCkVuc3VpdGUgbGUgcsOpc3VsdGF0IGVzdCByYXBhdHJpw6kgZW4gUi4KCkwnYW5hbG9naWUgdmEgcGx1cyBsb2luIHB1aXNxdWUgU3BhcmtSIGEgcmVwcmlzIGxlIHZvY2FidWxhaXJlIGRlIGRwbHlyIChgc2VsZWN0YCwgYGZpbHRlcmAuLi4pLCBjZSBxdWkgZXN0IGdsb2JhbGVtZW50IGFzc2V6IHByYXRpcXVlLgpJbCB5IGEgcXVlbHF1ZXMgc3VidGlsaXTDqXMgcXUnb24gZMOpY291dnJlIHBhciBsYSBwcmF0aXF1ZS4KUGFyIGV4ZW1wbGUgbGUgYGhlYWRgIGRlIGRicGx5ciBuZSByYXBhdHJpZSBwYXMgZW4gUiDDoCBsYSBkaWZmw6lyZW5jZSBkZSBTcGFya1IgcXVpIGludMOoZ3JlIHVuIGBjb2xsZWN0YCBkYW5zIHNvbiBgaGVhZGAuCkF1dHJlIHN1YnRpbGl0w6ksIGxlcyBwcmVtacOocmVzIGxpZ25lcyBzb250IHRvdWpvdXJzIGxlcyBtw6ptZXMgYXZlYyBPcmFjbGUsIGMnZXN0LcOgLWRpcmUgcXVlIGRldXggYGhlYWRgIGRvbm5lbnQgbGUgbcOqbWUgcsOpc3VsdGF0LgpFbiBTcGFyayBsZXMgbGlnbmVzIG5lIHNvbnQgcGFzIHZyYWltZW50IG9yZG9ubsOpZXMgZXQgZGV1eCBgaGVhZGAgZG9ubmVudCBkZXMgcsOpc3VsdGF0cyBkaWZmw6lyZW50cy4KCkVuIFNwYXJrUiwgaWwgZmF1dCBub21tZXIgbGVzIGNvbG9ubmVzIHZpYSBgZG9ubmVlcyRjb2xvbm5lYCBhbG9ycyBxdSdvbiBwZXV0IHV0aWxpc2VyIGBjb2xvbm5lYCBlbiBkYnBseXIuCgpFbiBTcGFya1IsIG9uIHBldXQgdXRpbGlzZXIgbGVzIGZvbmN0aW9ucyB0ZW1wb3JlbGxlcyBwcmF0aXF1ZXMgYHllYXJgIG91IGBtb250aGAgcXVlIGwnb24gbmUgdHJvdXZlIHBhcyBlbiBST3JhY2xlIG/DuSBpbCBmYXVkcmEgcGFyIGV4ZW1wbGUgcGFzc2VyIHBhciBgc3FsKCJFWFRSQUNUKFlFQVIgRlJPTSBCRU5fRENEX0RURSkiKWAuCgojIyBVbiBub20gZGUgdmFyaWFibGUgdmFyaWFibGUKCkxvcnNxdWUgbCdvbiBzb3VoYWl0ZSB1dGlsaXNlciB1bmUgdmFyaWFibGUgYXUgbGlldSBkdSBub20gZW4gZHVyIGQndW5lIHZhcmlhYmxlLCBvbiBwZXV0IHV0aWxpc2VyIGxlcyBvcMOpcmF0ZXVycyBkcGx5ciBgISFgICgqYmFuZy1iYW5nIG9wZXJhdG9yKikgZXQgYCEhIWAgKCpiaWcgYmFuZyBvcGVyYXRvciopLCBxdWkgc2VydmVudCDDoCAqKnByw6ktw6l2YWx1ZXIqKiB1biBvYmpldC4KCjxmb250IGNvbG9yPSIjNzM3MzczIj5BdSBsaWV1IGRlIDwvZm9udD4KCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQpmYXV4ICU+JSBzdW1tYXJpemUobW95ID0gbWVhbih4KSkKYGBgCkplIHBldXggdXRpbGlzZXIgOgpgYGB7ciwgd2FybmluZz1GQUxTRX0Kbm9tVmFyaWFibGUgPC0gIngiCmZhdXggJT4lIHN1bW1hcml6ZShtb3kgPSBtZWFuKCEhc3ltKG5vbVZhcmlhYmxlKSkpCmBgYApDZSBxdWkgcGV1dCDDqnRyZSBwcmF0aXF1ZSBwb3VyIGJvdWNsZXIgc3VyIGRlcyBjb2xvbm5lcy4KClVuZSB2YXJpYW50ZSBwb3VyIGNlcnRhaW5zIGNhcyBzaW1wbGVzIGNvbnNpc3RlIMOgIHV0aWxpc2VyIGxhIGZvbmN0aW9uIGBzcWwoKWAgcXVpIGluamVjdGUgZHUgY29kZSBTUUwgKHByw6lzZW50w6llIFtpY2ldKCNzcWwpKToKCmBgYHtyfQpub21WYXJpYWJsZSA8LSAiWiIKZmF1eCAlPiUKICBtdXRhdGUoWiA9IHopICU+JSAjIGNvbnRyYWludGUgc3VwcGzDqW1lbnRhaXJlIDoKICAjIHNxbCB2YSBwYXNzZXIgbGUgY29kZSBlbiBtYWp1c2N1bGVzLCBpbCBmYXV0IGRvbmMgZGVzIG5vbXMKICAjIGRlIGNvbG9ubmUgZW4gbWFqdXNjdWxlcyBwb3VyIHF1ZSDDp2EgZm9uY3Rpb25uZQogIGdyb3VwX2J5KGdycCA9IHNxbChub21WYXJpYWJsZSkpICU+JSB0YWxseQpgYGAKCgpTaSB2b3VzIMOqdGVzIGhhYml0dcOpIMOgIHV0aWxpc2VyIGBnZXQoKWAgcG91ciBjZSBnZW5yZSBkJ3VzYWdlcywgY2VsYSBuZSBmb25jdGlvbm5lcmEgcGFzIGljaSBjYXIgbGUgYGdldGAgc2VyYSB0cmFuc21pcyDDoCBPcmFjbGUgOgpgYGB7ciwgZXJyb3I9VFJVRX0KZmF1eCAlPiUgc3VtbWFyaXplKG1veSA9IG1lYW4oZ2V0KG5vbVZhcmlhYmxlKSkpICU+JSBzaG93X3F1ZXJ5ICU+JSBjb2xsZWN0CmBgYAoKPGZvbnQgY29sb3I9IiM3MzczNzMiPkV4ZW1wbGUgcsOpZWwgcG91ciBleHBvcnRlciBsZXMgZWZmZWN0aWZzIGRlIGNoYXF1ZSBwYXRob2xvZ2llIGRlIGxhIGNhcnRvZ3JhcGhpZSBkZXMgcGF0aG9sb2dpZXMgZW4gMjAxOCAoMzBzKS48L2ZvbnQ+CgpgYGB7ciwgZXZhbD1GQUxTRX0KY3RpbmQgPC0gdGJsKGNvbm4sIGluX3NjaGVtYSgiTUVQU0dQXzE1NiIsICJFU05EX0NUX0lORF9HOF8yMDE2IikpCgojIExlcyB2YXJpYWJsZXMgcXVpIG5vdXMgaW50w6lyZXNzZW50IGNvbW1lbmNlbnQgcGFyIGxlIG1vdCBUT1Agb3UgU1VQCnRvcHMgPC0gY29sbmFtZXMoY3RpbmQpW2NvbG5hbWVzKGN0aW5kKSAlbGlrZSUgIl4oVE9QfFNVUCkiXQoKIyBPbiB2YSBlbnJlZ2lzdHJlciBsZXMgcsOpc3VsdGF0cyBkYW5zIHVuZSBsaXN0ZSBxdSdvbiBpbml0aWUKbGlzdGUgPC0gbGlzdCgpCgpmb3IgKHRvcCBpbiB0b3BzKXsKICBwcmludCh0b3ApCiAgcHJvdiA8LSBjdGluZCAlPiUKICAgIGZpbHRlcighIXN5bSh0b3ApID09IDEpICU+JSAjIHRlY2huaXF1ZSBwb3VyIGF2b2lyIHVuIG5vbSBkZSBjb2xvbm5lIHZhcmlhYmxlCiAgICB0YWxseSAlPiUKICAgIG11dGF0ZSh0b3AgPSB0b3ApICU+JSAjIHJlbm9tbWVyIGxhIGNvbG9ubmUgYXZlYyBsZSBub20gZGUgbGEgcGF0aG9sb2dpZQogICAgY29sbGVjdCAlPiUKICAgIHNldERUCiAgbGlzdGUgPC0gYXBwZW5kKGxpc3RlLCBsaXN0KHByb3YpKSAjIGFqb3V0ZXIgw6AgbGEgbGlzdGUKfQp0YWIgPC0gcmJpbmRsaXN0KGxpc3RlKSAjIGVtcGlsZXIgbGEgbGlzdGUKc2V0Y29sb3JkZXIodGFiLCAidG9wIikgIyByw6lvcmRvbm5lciBsZXMgY29sb25uZXMgYXZlYyB0b3AgZW4gcHJlbWllcgoKIyBFY3JpdHVyZSBwb3VyIGV4cG9ydAojIGZ3cml0ZSh0YWIsICJ+L0VYUE9SVC9lZmZlY3RpZnNfY2FydG9QYXRob3MuY3N2IikKdGFiWzE6MTAsXQpgYGAKClBvdXIgZMOpcGxveWVyICoqcGx1c2lldXJzKiogdmFyaWFibGVzIGRhbnMgdW5lIGZvbmN0aW9uLCBpbCBmYXVkcmEgc2UgdG91cm5lciB2ZXJzIGxlICpiaWctYmFuZyBvcGVyYXRvciosIGAhISFgICA6CmBgYHtyfQp2YXJpYWJsZXMgPC0gYygieCIsICJ6IikKZmF1eCAlPiUgc2VsZWN0KCEhIXZhcmlhYmxlcykKYGBgCgojIyBGb3JjZXIgbCfDqXZhbHVhdGlvbiBlbiBSCgpMYSBsaWduZSBkZSBwYXJ0YWdlIGVudHJlIGNlIHF1aSBlc3Qgw6l2YWx1w6kgZW4gUiBldCBjZSBxdWkgZXN0IMOpdmFsdcOpIGVuIFNRTCBlc3QgdMOpbnVlLiBMYSBwcmV1dmUgcGFyIGwnZXhlbXBsZSA6IGRhbnMgbGEgY29tbWFuZGUgc3VpdmFudGUgaWQgZXN0IGJpZW4gcmVtcGxhY8OpIHBhciBzYSB2YWxldXIsIHRvdXQgZm9uY3Rpb25uZSA6IAoKYGBge3J9CmlkIDwtIDMKZmF1eCAlPiUgZmlsdGVyKHggPT0gaWQpCmBgYApBbG9ycyBxdWUgc2kgb24gY2hlcmNoZSDDoCBwcmVuZHJlIHVuIMOpbMOpbWVudCBkYW5zIHVuIHZlY3RldXIsIGlsIGZhaXQgdW5lIHRyYWR1Y3Rpb24gU1FMIMOpdHJhbmdlIGF2ZWMgdW4gYENBU0UgV0hFTmAgcXVpIGfDqW7DqHJlIHVuZSBlcnJldXIgOgoKYGBge3IsIGVycm9yPVRSVUV9CmlkcyA8LSAzOjQKZmF1eCAlPiUgZmlsdGVyKHggPT0gaWRzWzFdKSAlPiUgc2hvd19xdWVyeSAlPiUgY29sbGVjdApgYGAKCkxhIHByw6ktw6l2YWx1YXRpb24gcGFyIGwnb3DDqXJhdGV1ciAqYmFuZy1iYW5nKiBgISFgIG5vdXMgc29ydGlyYSBkZSBjZSBtYXV2YWlzIHBhcyA6CgpgYGB7cn0KaWRzIDwtIDM6NApmYXV4ICU+JSBmaWx0ZXIoeCA9PSAhIWlkc1sxXSkgJT4lIHNob3dfcXVlcnkgJT4lIGNvbGxlY3QKYGBgCgpDZWxhIHZhdXQgYXVzc2kgcG91ciBkZXMgZm9uY3Rpb25zIHF1aSBnw6luw6lyZXJhaXQgZHUgY29kZSBTUUwgOgoKYGBge3IsIGVycm9yID0gVFJVRX0KY29udGVudVNRTCA8LSBmdW5jdGlvbigpIHNxbCgnUE9XRVIoIngiLCAyLjApJykKZmF1eCAlPiUgbXV0YXRlKHgyID0gY29udGVudVNRTCgpKSAjIEVSUkVVUgpmYXV4ICU+JSBtdXRhdGUoeDIgPSAhIWNvbnRlbnVTUUwoKSkgIyBGT05DVElPTk5FCmBgYAoKCiMgVGFibGVzIGV0IHZ1ZXMKCklsIG4neSBhIHBhcyBkZSBtYW5pw6hyZSBncmFwaGlxdWUgcG91ciBuYXZpZ3VlciBkYW5zIGxlcyB0YWJsZXMgZGlzcG9uaWJsZXMgY29tbWUgc3VyIFNBUy4KU2kgb24gY29ubmFpdCBsZSBub20gZGUgbGEgdGFibGUsIG9uIGwnYXBwZWxsZSBkaXJlY3RlbWVudC4KU2lub24sIHBvdXIgcmV0cm91dmVyIHVuZSB0YWJsZSwgb24gcGV1dCB0b3Vqb3VycyBhbGxlciBjaGVyY2hlciBzb24gbm9tIGRhbnMgU0FTLgpTaSBsJ29uIG5lIHNvdWhhaXRlIHBhcyBsYW5jZXIgU0FTLCBvbiBwZXV0IHRvdWpvdXJzIHV0aWxpc2VyIGxlcyBjb21tYW5kZXMgY2ktZGVzc291cy4KCk4uQi4gOiBsYSBwbHVwYXJ0IGRlcyBub21zIGRlIHRhYmxlcyBxdWUgbCdvbiBjb25uYcOudCBuZSByZW52b2llbnQgcGFzIHZyYWltZW50IMOgIGRlcyAqKnRhYmxlcyoqIG1haXMgw6AgZGVzICoqdnVlcyoqLCBjJ2VzdC3DoC1kaXJlIGRlcyBlbnJlZ2lzdHJlbWVudHMgZGUgcmVxdcOqdGVzLCBvdSDDoCBkZXMgKipzeW5vbnltZXMqKiwgYydlc3Qtw6AtZGlyZSBkZXMgc3Vybm9tcy4KCiMjIExpc3RlciBsZXMgdGFibGVzIHB1YmxpcXVlcwoKRGFucyB1bmUgYmFzZSBPcmFjbGUsIGlsIGV4aXN0ZSBkZXMgdGFibGVzIHNww6ljaWFsZXMgcXVpIGxpc3RlbnQgbGVzIG9iamV0cyA6CgorIGBhbGxfdGFibGVzYCBsaXN0ZSBsZXMgKip0YWJsZXMqKiA7CisgYGFsbF92aWV3c2AgbGlzdGUgbGVzICoqdnVlcyoqIDsKKyBgYWxsX3N5bm9ueW1zYCBsZXMgKipzeW5vbnltZXMqKiA7CisgYGFsbF9vYmplY3RzYCB0b3VzIGxlcyAqKm9iamV0cyoqICh0YWJsZXMsIHZ1ZXMsIHN5bm9ueW1lcywgaW5kZXgsIHPDqXF1ZW5jZXMsIMOpZGl0aW9ucywgcGFxdWV0cy4uLikuCgpKZSBkw6ljb25zZWlsbGUgbGUgY2hhcmdlbWVudCBlbiBtw6ltb2lyZSBkJ3VuZSBkZSBzZXMgdGFibGVzLCBhc3NleiBsb25nLgpQYXIgZXhlbXBsZSwgbGEgdGFibGUgYGFsbF90YWJsZXNgIHNlIGNoYXJnZSBlbiAxNSBtaW51dGVzIGFsb3JzIHF1ZSBsYSBwbHVwYXJ0IGRlcyBvYmpldHMgcXVpIG5vdXMgaW50w6lyZXNzZW50IChkZXMgdnVlcyBldCBkZXMgc3lub255bWVzKSBlbiBzb250IGFic2VudHMuCgpNaWV1eCB2YXV0IGZhaXJlIGxhIHJlY2hlcmNoZSBkaXJlY3RlbWVudCBlbiBPcmFjbGUgc3VyIGxhIHRhYmxlIGBhbGxfb2JqZWN0c2AuCkltYWdpbm9ucyBxdWUgbCdvbiBjaGVyY2hlIHRvdXMgbGVzIG9iamV0cyBxdWkgY29udGllbm5lbnQgYENUX0lORGAgcG91ciBsYSB0YWJsZSBjZW50cmFsZSBkZSBsYSBjYXJ0b2dyYXBoaWUgZGVzIHBhdGhvbG9naWVzLgoKYGBge3J9CnN5c3RlbS50aW1lKHsKICB0YWJsZXMgPC0gZGJHZXRRdWVyeShjb25uLAogICJTRUxFQ1Qgb3duZXIsIG9iamVjdF9uYW1lLCBvYmplY3RfaWQsIG9iamVjdF90eXBlLCBjcmVhdGVkIEZST00gYWxsX29iamVjdHMgV0hFUkUgb2JqZWN0X25hbWUgTElLRSAnJUNUX0lORCUnIikKICAjICclQ1RfSU5EJScgZXN0IHVuZSBleHByZXNzaW9uIHLDqWd1bGnDqHJlIE9yYWNsZSBvw7kgJSBlc3QgcmVtcGxhY8OpIHBhciBhdXRhbnQgZGUgY2FyYWN0w6hyZXMgcXVlIG7DqWNlc3NhaXJlCiAgfSkgIyA4IHNlY29uZGVzCnRhYmxlcyRPV05FUiA8LSBuZXR0b3kodGFibGVzJE9XTkVSKSAjIHJldHJhaXQgZGUgbCdpZGVudGlmaWFudCB1dGlsaXNhdGV1cgp0YWJsZXNbLCBjKCJPV05FUiIsICJPQkpFQ1RfTkFNRSIsICJPQkpFQ1RfVFlQRSIpXQpgYGAKCkNlcnRlcyBhdmVjIFIsIG5vdXMgbidhdm9ucyBwYXMgbCdhcmJvcmVzY2VuY2UgY29tbWUgZGFucyBTQVMgcG91ciBjaGVyY2hlciBsZXMgdGFibGVzIG1haXMgY2VsYSBlc3QgYXVzc2kgdW4gKiphdG91dCoqLgpQYXIgZXhlbXBsZSBpY2kgaidhaSBwdSBsaXN0ZXIgdG91dGVzIGxlcyB0YWJsZXMgcmVsYXRpdmVzIMOgIGxhIGNhcnRvZ3JhcGhpZSBhbG9ycyBxdWUgbGVzIGFuY2llbm5lcyB2ZXJzaW9ucyBzZSB0cm91dmVudCBkYW5zIE9SQVZVRSBldCBsZXMgbm91dmVsbGVzIGRhbnMgT1JBTUVQUy4gU3VyIFNBUywgaWwgZmF1dCBzYXZvaXIgb8O5IGNoZXJjaGVyIHBvdXIgZMOpcm91bGVyIGxhIGJvbm5lIGJpYmxpb3Row6hxdWUuCgpPbiBkw6ljb3V2cmUgYXVzc2kgbGUgdHlwZSBkZSBsJ29iamV0IGV0IG9uIHBldXQgYWpvdXRlciBzYSBkYXRlIGRlIGNyw6lhdGlvbiAoYGNyZWF0ZWRgKS4KCiMjIExpc3RlciBsZXMgdGFibGVzIHByaXbDqWVzClBvdXIgbGlzdGVyIGxlcyB0YWJsZXMgZGUgbCd1dGlsaXNhdGV1ciwgaWwgc3VmZml0IGQndXRpbGlzZXIgYHVzZXJfdGFibGVzYCBhdSBsaWV1IGRlIGBhbGxfdGFibGVzYC4KRW4gbCdvY2N1cnJlbmNlLCBpbCBzJ2FnaXJhIG5vcm1hbGVtZW50IGRlICoqdGFibGVzKiogZXQgbm9uIGRlIHZ1ZXMgZXQgc3lub255bWVzIGRvbmMgaWwgbidlc3QgcGFzIG7DqWNlc3NhaXJlIGQndXRpbGlzZXIgYHVzZXJfb2JqZWN0c2AuCgpgYGB7cn0Kc3lzdGVtLnRpbWUoe3VzZXJfdGFibGVzIDwtIGRiR2V0UXVlcnkoY29ubiwgIlNFTEVDVCBUQUJMRV9OQU1FIEZST00gdXNlcl90YWJsZXMiKX0pICMgMgppZiAobnJvdyh1c2VyX3RhYmxlcykgPT0gMSkgewogIHVzZXJfdGFibGVzCn0gZWxzZSB7CiAgd2FybmluZygiSWwgeSBhIGQnYXV0cmVzIHRhYmxlcyBxdWUgbGUgZmF1eCBqZXUgZGUgZG9ubsOpZXMsIG9uIGFmZmljaGUgcmllbi4iKQp9CmBgYAoKTGVzIHRhYmxlcyBsaXN0w6llcyBpY2kgc2UgdHJvdXZlbnQgZGFucyBsYSBiaWJsaW90aMOocXVlIE9SQVVTRVIgc3VyIFNBUy4gT24gcmV0cm91dmUgYmllbiBsYSB0YWJsZSBgUFJPVmAgcXVpIGVzdCBub3RyZSBmYXV4IGpldSBkZSBkb25uw6llcy4KCiMjIENoYXJnZXIgdW5lIHRhYmxlIGQndW4gYXV0cmUgc2Now6ltYQoKRW4gT3JhY2xlLCB1biBzY2jDqW1hIGVzdCB1biBlbnNlbWJsZSBkJ29iamV0cyBkJ3VuZSBiYXNlIGRlIGRvbm7DqWVzLgpVbiBzY2jDqW1hIGNvcnJlc3BvbmQgw6AgdW4gcHJvcHJpw6l0YWlyZSAoKm93bmVyKikgZXQgYSBsZSBtw6ptZSBub20gcXVlIGx1aS4KRXN0LWNlIHV0aWxlIGRlIHNhdm9pciBjZWxhID8gT3VpIGNhciBzaSB2b3VzIHZvdWxleiB0cmF2YWlsbGVyIHN1ciBsJ0VTTkQsIHZvdXMgYWxsZXogYXZvaXIgdW4gcHJvYmzDqG1lIDoKCmBgYHtyLCBlcnJvcj1UUlVFfQp0YmwoY29ubiwgIkVTTkRfRVJfUFJTX0YiKQpgYGAKTGEgdnVlIG4nZXN0IHBhcyB0cm91dsOpZS4gT24gdsOpcmlmaWUgcXUnZWxsZSBleGlzdGUgYmllbiA6CgpgYGB7cn0KdGFibGVzIDwtIGRiR2V0UXVlcnkoY29ubiwgIlNFTEVDVCBvd25lciwgb2JqZWN0X25hbWUsIG9iamVjdF90eXBlIEZST00gYWxsX29iamVjdHMgV0hFUkUgb2JqZWN0X25hbWUgTElLRSAnJUVTTkRfRVJfUFJTJSciKQp0YWJsZXMkT1dORVIgPC0gbmV0dG95KHRhYmxlcyRPV05FUikgIyByZXRyYWl0IGRlIGwnaWRlbnRpZmlhbnQgdXRpbGlzYXRldXIKdGFibGVzCmBgYApFbGxlIGV4aXN0ZSBtYWlzIGVsbGUgYXBwYXJ0aWVudCBhdSBzY2jDqW1hL3Byb3ByacOpdGFpcmUgYE1FUFNHUF8xNTZgIChkYW5zIGxlIGNhcyBkdSBwcm9maWwgMTU2KSwgcXVpIGRpZmbDqHJlIGR1IHNjaMOpbWEgYEFER1BQYCBkZSBgRVJfUFJTX0ZgLgpMZSBzY2jDqW1hIGRlIGBFUl9QUlNfRmBkb2l0IMOqdHJlIGNlbHVpIHBhciBkw6lmYXV0IGRlIHRlbGxlIHNvcnRlIHF1ZSBgdGJsKGNvbm4sICJFUl9QUlNfRiIpYCBmb25jdGlvbm5lIGFsb3JzIHF1ZSBgdGJsKGNvbm4sICJFU05EX0VSX1BSU19GIilgIG5lIGZvbmN0aW9ubmUgcGFzLgpPbiBwZXV0IHByw6ljaXNlciB1biBzY2jDqW1hIGRpZmbDqXJlbnQgZHUgc2Now6ltYSBwYXIgZMOpZmF1dCBncsOiY2Ugw6AgYGluX3NjaGVtYSgpYCA6CgpgYGB7cn0KIyBlcnByc2YgPC0gdGJsKGNvbm4sIGluX3NjaGVtYSgiUFJFR1BfMTU2IiwgIkVTTkRfRVJfUFJTX0YiKSkgIyBuZSBmb25jdGlvbm5lIHBhcwplcnByc2YgPC0gdGJsKGNvbm4sIGluX3NjaGVtYSgiTUVQU0dQXzE1NiIsICJFU05EX0VSX1BSU19GIikpICMgZm9uY3Rpb25uZQpgYGAKCiMjIExlcyBiaWJsaW90aMOocXVlcyBTQVMKTGVzIHRhYmxlcyBTQVMgbmUgc29udCBwYXMgYWNjZXNzaWJsZXMuIFN1ciBTQVMgZWxsZXMgcG9ydGVudCB1biBwaWN0b2dyYW1tZSBkZSBsb3VwZS4KClBvdXIgbGVzIHJhbWVuZXIgZGFucyBPcmFjbGUgKGRhbnMgT3JhdXNlcikgZXQgYWluc2kgcG91dm9pciBsZXMgbWFuaXB1bGVyIGF2ZWMgUk9yYWNsZSwgaWwgZmF1dCBleMOpY3V0ZXIgdW5lIHJlcXXDqnRlIHN1ciBTQVMuCgo8Zm9udCBjb2xvcj0iIzczNzM3MyI+UGFyIGV4ZW1wbGUgc2kgbCdvbiB2ZXV0IHLDqWN1cMOpcmVyIGxhIHRhYmxlIEVYVFJBQ1RJT05fUEFUSUVOVFMyMDE5VFIgZGUgY29uc29wYXQgOjwvZm9udD4KCgpgYGB7U0FTLCBldmFsPUZBTFNFfQojIENvZGUgb3B0aW1pc8OpIHF1aSB2aWVudCBkdSBHVUlERSBERVMgQk9OTkVTIFBSQVRJUVVFUyBTQVMgLSB2Mi4wLnBkZgpwcm9jIHNxbDsKCWRyb3AgdGFibGUgb3JhdXNlci50YWJsZW9yYWNsZTsKCWNyZWF0ZSB0YWJsZSBvcmF1c2VyLnRhYmxlb3JhY2xlIChCVUxLTE9BRD15ZXMgQkxfREFUQUZJTEU9IiVzeXNmdW5jKHBhdGhuYW1lKHdvcmspKS90dHQueHl6IiBCTF9ERUxFVEVfREFUQUZJTEU9eWVzKSBhcyBzZWxlY3QgKiBmcm9tIENPTlNPUEFULkVYVFJBQ1RJT05fUEFUSUVOVFMyMDE5VFI7CnF1aXQ7CmBgYAoKPGZvbnQgY29sb3I9IiNjZjA1MDUiPioqSWwgeSBhIHVuIGZvcnQgaW50w6lyw6p0IMOgIHPDqWxlY3Rpb25uZXIgcXVlbHF1ZXMgdmFyaWFibGVzIGF1IGxpZXUgZGUgYHNlbGVjdCAqYCBwb3VyIGFjY8OpbGVyZXIgbCdleMOpY3V0aW9uIGV0IHLDqWR1aXJlIGxhIHBsYWNlIHByaXNlIHN1ciBPUkFVU0VSLioqPC9mb250PgoKVW5lIGF1dHJlIHNvbHV0aW9uIGNvbnNpc3RlIMOgIGNoYXJnZXIgbGVzIHRhYmxlcyBTQVMgYXZlYyBsZSBwYXF1ZXQgaGF2ZW4uCkNlbGEgZm9uY3Rpb25uZSBhdmVjIGxlcyB2cmFpZXMgdGFibGVzIDoKCmBgYHtyfQpoYXZlbjo6cmVhZF9zYXMoIn4vY29uc29wYXQvZXNfbXRzX3Yuc2FzN2JkYXQiKVsxOjQsXQpgYGAKCk1haXMgcGFzIGF2ZWMgbGVzIHZ1ZXMgU0FTICgqLnNhczdidmV3KikgOgoKYGBge3IgZXJyb3I9VFJVRX0KaGF2ZW46OnJlYWRfc2FzKCJ+L2NvbnNvcGF0L2V4dHJhY3Rpb25fcGF0aWVudHMyMDIxdHIuc2FzN2J2ZXciKQpgYGAKClBhcyBkZSBzb2x1dGlvbiBhcHBhcmVudGUgKFt2b2lyIGNldHRlIGRpc2N1c3Npb24gc3VyIFN0YWNrb3ZlcmZsb3ddKGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzUxMjgzMzU2L2hvdy1jYW4taS1yZWFkLXNhczdidmV3LWZpbGVzLWluLXIpKSwgaWwgZmF1dCByZWNvdXJpciBhdSByYXBhdHJpZW1lbnQgZW4gU0FTIHN1ciBPcmF1c2VyLgoKCiMjIFNhdXZlZ2FyZGVyIHVuZSB0YWJsZSBpbnRlcm3DqWRpYWlyZQoKSWwgZmF1dCBiaWVuIGRpc3Rpbmd1ZXIgbGVzIHRhYmxlYXV4IFIgZGVzIHBvaW50ZXVycyB2ZXJzIGxlcyB0YWJsZXMgT3JhY2xlIDoKCmBgYHtyfQphIDwtIHRibChjb25uLCAiSVJfQklPX1IiKSAjIGEgZXN0IHVuIHBvaW50ZXVyIHZlcnMgbGEgdGFibGUgT3JhY2xlCmEgPC0gdGJsKGNvbm4sICJJUl9CSU9fUiIpICU+JSBjb2xsZWN0ICMgYSBlc3QgdW5lIHRhYmxlIFIKYGBgCgpTaSBvbiBwb3Nzw6hkZSB1bmUgdGFibGUgUiwgb24gcGV1dCBzdWl2cmUgbGUgZ3VpZGUgZGUgbGEgQ25hbSBwb3VyIGwnZW5yZWdpc3RyZXIgc291cyBPcmFjbGUgYXZlYyA6CgpgYGB7ciwgZXZhbD1GQUxTRX0KIyBPbiBzdXBwcmltZSBsYSB0YWJsZSBzaSBlbGxlIGV4aXN0ZSBkw6lqw6AsIHNpbm9uIGVycmV1cgp0cnkoZGJSZW1vdmVUYWJsZShjb25uLCAiUFJPViIpLCBzaWxlbnQgPSBUKSAjIGxlIG5vbSBkb2l0IMOqdHJlIGVuIG1hanVzY3VsZQoKIyBFY3JpdHVyZSDDoCBwcm9wcmVtZW50IHBhcmxlcgpkYldyaXRlVGFibGUoY29ubiwgIlBST1YiLCBmYXV4MCkgIyBmYXV4MCBlc3QgdW4gZGF0YS5mcmFtZQpgYGAKCk1haXMgY2VsYSBpbXBsaXF1ZSB1biBjb2xsZWN0IGF1cGFyYXZhbnQuIExlIGNpcmN1aXQgZXN0IGFsb3JzIAoqT3JhY2xlID4gUiA+IE9yYWNsZSogY2UgcXVpIGVzdCBzb3VzLW9wdGltYWwgcHVpc3F1ZSBsZSBgY29sbGVjdGAgZXN0IHVuZSBvcMOpcmF0aW9uIGNvw7t0ZXVzZS4KVW5lIG1laWxsZXVyZSBzb2x1dGlvbiBlc3QgZGUgbmUgcGFzIHJhcGF0cmllciBsJ29iamV0IGVuIFIsIGRlIHRvdXQgZmFpcmUgZW4gT3JhY2xlLgoKVGjDqW9yaXF1ZW1lbnQgbGEgZm9uY3Rpb24gZGJwbHlyIGBjb21wdXRlYCByw6lhbGlzZSBjZWxhIGV0IGRvaXQgw6p0cmUgdHLDqHMgcHJhdGlxdWUgOgpgYGB7ciwgZXZhbD1GQUxTRX0KIyBFUlJFVVIgQSBDRSBKT1VSCiMgYSA8LSB0YmwoY29ubiwgIklSX1BIQV9SIikgJT4lCiMgICBmaWx0ZXIoUEhBX0FUQ19DMDMgPT0gIkExMCIpICU+JSAjIE3DqWRpY2FtZW50cyBkdSBkaWFiw6h0ZQojICAgY29tcHV0ZQpgYGAKRWxsZSBkb2l0IGVucmVnaXN0cmVyIGxlIHLDqXN1bHRhdCBkdSBjYWxjdWwgZW4gdGFudCBxdWUgdGFibGUgT3JhY2xlIGV0IGxhIHJlbGlyZSBwb3VyIGZhaXJlIGRlIGBhYCB1biBwb2ludGV1ci4KQ2VsYSBwZXJtZXR0cmFpdCBkb25jIGRlIGZhaXJlIHVuIHBvaW50IGQnYXJyw6p0IHF1aSDDqXZpdGUgZGUgcmVwcmVuZHJlIHRvdXMgbGVzIGNhbGN1bHMgZGVwdWlzIGxlIGTDqWJ1dC4KCkjDqWxhcyBsYSBmb25jdGlvbiBgY29tcHV0ZWAgbmUgbWFyY2hlIHBhcyBhdmVjIHVuZSBiYXNlIE9yYWNsZSBwb3VyIGwnaW5zdGFudCBjYXIgbGEgdHJhZHVjdGlvbiBvdWJsaWUgbGUgbW90IGNsw6kgR0xPQkFMIGRhbnMgbGEgcmVxdcOqdGUuCkxlIGJ1ZyBjb25jZXJuZSBhdXNzaSBsYSBmb25jdGlvbiBgY29weV90b2AsIHF1aSBmYWl0IGxlIGNvbnRyYWlyZSBkdSBgY29sbGVjdGAsIGMnZXN0LcOgLWRpcmUgZW52b3llciDDoCBPcmFjbGUgdW4gb2JqZXQgUi4KSidhaSBlbnJlZ2lzdHLDqSBbdW4gdGlja2V0XShodHRwczovL2dpdGh1Yi5jb20vdGlkeXZlcnNlL2RicGx5ci9pc3N1ZXMvNjIxKSBzdXIgbGUgR2l0aHViIGR1IHByb2pldCBkYnBseXIsIGxlIGJ1ZyBhIMOpdMOpIHRyYWl0w6kgZXQgbGUgYGNvbXB1dGVgIGRldnJhaXQgYmllbnTDtHQgZGUgbm91dmVhdSBmb25jdGlvbm5lci4KCkVuIGF0dGVuZGFudCBqZSBwcm9wb3NlIGNldHRlIGZvbmN0aW9uIGFydGlzYW5hbGUgcXVpIGZhaXQgbGEgbcOqbWUgY2hvc2UgZXQgamUgbGEgY291cGxlIGF2ZWMgdW5lIGZvbmN0aW9uIGRlIG5ldHRveWFnZSBwb3VyIHN1cHByaW1lciBsZXMgdGFibGVzIHByb3Zpc29pcmVzIGRlIHRlbXBzIGVuIHRlbXBzIDoKCmBgYHtyfQojIFN1cHByaW1lciBkZXMgdGFibGVzIDoKIyAtIGxlc3F1ZWxsZXMgPSAicHJvdiIgOiBsZXMgdGFibGVzIHByb3Zpc29pcmVzCiMgLSBsZXNxdWVsbGVzID0gInByb3ZSZWNlbnRlcyIgOiBsZXMgdGFibGVzIHByb3Zpc29pcmVzIGF2YW50IGF1am91cmQnaHVpCiMgLSBsZXNxdWVsbGVzID0gInRvdXRlcyIgOiB0b3V0ZXMgbGVzIHRhYmxlcyBkZSBsJ3V0aWxpc2F0ZXVyCnN1cHByVGFibGVzIDwtIGZ1bmN0aW9uKGxlc3F1ZWxsZXMgPSAicHJvdlJlY2VudGVzIil7CiAgdmVjdDAgPC0gZGJHZXRRdWVyeShjb25uLCAiU0VMRUNUIFRBQkxFX05BTUUgRlJPTSB1c2VyX3RhYmxlcyIpJFRBQkxFX05BTUUKICAKICAjIE9uIGZpbHRyZSBsZXMgdGFibGVzIHByb3Zpc29pcmVzIAogIHZlY3QxIDwtIHZlY3QwW2dyZXBsKCJQUk9WWzAtOV17MTR9IiwgdmVjdDAsIHBlcmwgPSBUKV0KICAKICB2ZWN0MiA8LSBnc3ViKCJQUk9WKFswLTldezE0fSkiLCAiXFwxIiwgdmVjdDEsIHBlcmwgPSBUKQogIHZlY3QyIDwtIGFzLkRhdGUodmVjdDIsICIlWSVtJWQiKQogIAogIGlmIChsZXNxdWVsbGVzID09ICJ0b3V0ZXMiKSB7CiAgICB2ZWN0QXN1cHAgPC0gdmVjdDAKICB9IGVsc2UgaWYgKGxlc3F1ZWxsZXMgPT0gInByb3YiKXsKICAgIHZlY3RBc3VwcCA8LSB2ZWN0MQogIH0gZWxzZSB7CiAgICB2ZWN0QXN1cHAgPC0gdmVjdDFbdmVjdDIgPCBTeXMuRGF0ZSgpXQogIH0KICAKICBpZiAobGVuZ3RoKHZlY3RBc3VwcCkpIHNwcmludGYoIlRhYmxlcyBzdXBwcmltw6llcyA6ICVzIiwgcGFzdGUodmVjdEFzdXBwLCBjb2xsYXBzZSA9ICIsICIpKQogIAogIGZvciAobm9tVGFibGUgaW4gdmVjdEFzdXBwKSB7CiAgICB0cnkoZGJSZW1vdmVUYWJsZShjb25uLCBub21UYWJsZSksIHNpbGVudCA9IFQpCiAgfQp9CgojIEVxdWl2YWxlbnQgZm9uY3Rpb25uZWwgZGUgbGEgZm9uY3Rpb24gY29tcHV0ZSgpCmNvbXB1dGUyIDwtIGZ1bmN0aW9uKHRibF9vcmFjbGUpIHsKICAjIE9uIGNvbW1lbmNlIHBhciB1biBuZXR0b3lhZ2UgZGVzIGFuY2llbm5lcyB0YWJsZXMgcHJvdmlzb2lyZXMKICAjIHNpIG9uIGEgb3VibGnDqSBkZSBsZSBmYWlyZQogIHN1cHByVGFibGVzKGxlc3F1ZWxsZXMgPSAicHJvdlJlY2VudGVzIikKICAKICAjIE9uIGNob2lzaXIgdW4gbm9tIGRhdMOpIHBvdXIgZW5yZWdpc3RyZXIgbGEgdGFibGUKICBub21UYWJsZSA8LSAgcGFzdGUwKCJQUk9WIiwgZm9ybWF0KFN5cy50aW1lKCksICIlWSVtJWQlSCVNJVMiKSkKICAKICAjIHJlbnZvaWUgdW5lIGVycmV1ciBzaSBsYSB0YWJsZSBuJ2V4aXN0ZSBwYXMKICBzeXN0ZW0udGltZSh7CiAgIwogIGRiRXhlY3V0ZShjb25uLCBwYXN0ZTAoIkNSRUFURSBUQUJMRSAiLCBub21UYWJsZSwiIEFTICgiLCByZW1vdGVfcXVlcnkodGJsX29yYWNsZSksICIpIikpCiAgfSkKICB0YmwoY29ubiwgbm9tVGFibGUpCn0KYGBgCjxmb250IGNvbG9yPSIjNzM3MzczIj5FeGVtcGxlIGQndXRpbGlzYXRpb248L2ZvbnQ+CgpgYGB7cn0KIyBMb25ncyBjYWxjdWxzIChpY2kgY291cnRzIHBvdXIgbCdleGVtcGxlKQphIDwtIHRibChjb25uLCAiSVJfUEhBX1IiKSAlPiUKICBmaWx0ZXIoUEhBX0FUQ19DMDMgPT0gIkExMCIpCgojIE9uIMOpY3JpdCB1bmUgdGFibGUgaW50ZXJtw6lkaWFpcmUKYSA8LSBjb21wdXRlMihhKQpgYGAKCjxmb250IGNvbG9yPSIjNzM3MzczIj5QcmV1dmUgcXVlIGBhYCBlc3QgdW4gcG9pbnRldXIgdmVycyB1bmUgdGFibGUgaW50ZXJtw6lkaWFpcmUgZXQgbm9uIHVuZSBzdWl0ZSBkJ2luc3RydWN0aW9ucyA6PC9mb250PgoKYGBge3J9CmEgJT4lIHNob3dfcXVlcnkKYGBgCgpQb3VyIG5ldHRveWVyIGxlcyB0YWJsZXMgaW50ZXJtw6lkaWFpcmVzIMOpY3JpdGVzLCBwb3J0YW50IGxlIG5vbSBkZSAiUFJPViIsIHZvdXMgcG91dmV6IGV4w6ljdXRlciBkZSB0ZW1wcyBlbiB0ZW1wcyBsYSBmb25jdGlvbiBgc3VwcHJUYWJsZXNgIDoKYGBge3J9CnN1cHByVGFibGVzKGxlc3F1ZWxsZXMgPSAicHJvdiIpCmBgYAoKQXByw6hzIG5ldHRveWFnZSwgbGUgcG9pbnRldXIgYGFgIGVzdCBjYXNzw6kgOgoKYGBge3IsIGVycm9yPVRSVUV9CmEgIyAiT1JBLTAwOTQyOiBUYWJsZSBvdSB2dWUgaW5leGlzdGFudGUiCmBgYAoKIyMgT3JhdXNlcgoKUXVlIGNlIHNvaXQgdmlhIGBkYldyaXRlVGFibGUoY29ubiwgIm5vbVRBQkxFIiwgdW5EYXRhRnJhbWVSKWAgb3UgbGEgZm9uY3Rpb24gY2ktZGVzc3VzIGBjb21wdXRlMmAsIG9uIMOpY3JpdCBzdXIgbCdlc3BhY2UgT3JhdXNlciwgcXVpIGVzdCB1biBlc3BhY2UgcGFydGFnw6kuCkNvbnRyYWlyZW1lbnQgw6AgKipTQVNEQVRBMSoqLCBpbCBuJ3kgYSBwYXMgZGUgcXVvdGFzIGluZGl2aWR1ZWxzLgpMJ2VzcGFjZSBlc3QgcXVhbmQgbcOqbWUgY29udHJhaW50IGV0IGxhIENuYW0gZW52b2llIHLDqWd1bGnDqHJlbWVudCBkZXMgbWFpbHMgcG91ciBmYWlyZSBzdXBwcmltZXIgZGVzIHRhYmxlcyBldCDDqXZpdGVyIGxhIHNhdHVyYXRpb24gZGUgbCdlc3BhY2UgcGFydGFnw6kuCkxhIGJvbm5lIHByYXRpcXVlIGVzdCBkZSBzdXBwcmltZXIgc2VzIHRhYmxlcyBkw6hzIHF1ZSBwb3NzaWJsZS4KTGUgbmV0dG95YWdlIHBldXQgc2UgZmFpcmUgZGUgZmHDp29uIHZpc3VlbGxlIHN1ciBTQVMgb3UgYXZlYyBsYSBmb25jdGlvbiBgc3VwcHJUYWJsZXNgIGTDqWZpbmllIGNpLWRlc3N1cy4KCgoKIyBHw6lyZXIgc2Egc2Vzc2lvbiBSU3R1ZGlvCiMjIFF1ZSBmYWlyZSBlbiBjYXMgZGUgcGVydGUgZGUgY29udHLDtGxlID8KCkFycsOqdGVyIHVuZSByZXF1w6p0ZSBlbiBjb3VycyBkJ2V4w6ljdXRpb24gbidlc3QgcGFzIHVuZSBjaG9zZSBzaW1wbGUuClNvdXZlbnQgbGUgYm91dG9uIHJvdWdlICpTdG9wKiBuZSBmb25jdGlvbm5lIHBhcyBjYXIgY2VsYSBlbnZvaWUgdW4gc2lnbmFsIGQnYXJyw6p0IMOgIGxhIGNvbW1hbmRlIFIgZXQgbm9uIMOgIGxhIHJlcXXDqnRlIE9yYWNsZS4KCk9uIHBldXQgdG91am91cnMgZXNzYXllciBtYWlzIHNpIMOnYSBuZSBmb25jdGlvbm5lIHBhcywgbGEgbWVpbGxldXJlIHNvbHV0aW9uIGVzdCBkZSBmb3JjZXIgbCdhcnLDqnQgZGUgbGEgc2Vzc2lvbiA6CgoxLiBzw6lsZWN0aW9ubmVyIGxlIGNvZGUgcydpbCBuJ2VzdCBwYXMgZW5yZWdpc3Ryw6ksIGxlIGNvcGllci1jb2xsZXIgcXVlbHF1ZSBwYXJ0LCBlbiBzw6ljdXJpdMOpIDsKMi4gY2xpcXVlciBzdXIgbGUgZ3JvcyBSIGJsZXUgZW4gaGF1dCDDoCBnYXVjaGUgcG91ciByZWpvaW5kcmUgbGUgbWVudSA7CjMuIGNvY2hlciBsYSBzZXNzaW9uIGVuIHF1ZXN0aW9uIDsKNC4gc8OpbGVjdGlvbm5lciAiKmZvcmNlIHF1aXQqIiA7CjUuIHLDqW91dnJpciB1bmUgc2Vzc2lvbiAvIGxlIHByb2pldCA7CjYuIHJlcHJlbmRyZSBzb24gY29kZSBub24tZW5yZWdpc3Ryw6kuCgpTaSBsJ8OpdGFwZSAyIGVzdCBpbXBvc3NpYmxlIGNhciBsZSBncm9zIFIgYmxldSBuJ2VzdCBwYXMgdmlzaWJsZSwgdm91cyBwb3V2ZXogZGlyZWN0ZW1lbnQgY29waWVyLWNvbGxlciBsJ1VSTCBbaHR0cHM6Ly9yc3R1ZGlvLXByb2QubG9jYWwvUi9dKGh0dHBzOi8vcnN0dWRpby1wcm9kLmxvY2FsL1IvKSBkYW5zIGxlIG5hdmlnYXRldXIgY2hyb21pdW0gKGNlbHVpIG91dmVydCBwb3VyIFIpLgoKIyMgQ291cHVyZXMKCkxvcnMgZGVzIHBhdXNlcywgQ2l0cml4IHBldXQgc2UgZMOpY29ubmVjdGVyIGNlIHF1aSBmZXJtZSBSU3R1ZGlvLgpOw6lhbm1vaW5zIGxlcyBjYWxjdWxzIGVuIGNvdXJzIG5lIHNvbnQgcGFzIGFycsOqdMOpcywgbcOqbWUgZW4gcGxlaW5lIG51aXQsIMOgIGxhIGRpZmbDqXJlbmNlIGRlIFNBUyBxdWkgZXN0IHJlZMOpbWFycsOpIMOgIDJoLgpMb3JzcXUnb24gbGFuY2Ugw6Agbm91dmVhdSAiUlN0dWRpbyBXb3JrYmVuY2giIGRlcHVpcyBsZSBwb3J0YWlsLCBsYSBzZXNzaW9uIHMnb3V2cmUgZGFucyBsZSBtw6ptZSDDqXRhdCBxdSdvbiBsJ2EgbGFpc3PDqWUsIGF2ZWMgdG91cyBsZXMgb2JqZXRzIGNoYXJnw6lzLgpBdHRlbnRpb24sIHNpIGxhIHNlc3Npb24gY29tcHRlIGRlIGdyb3Mgb2JqZXRzLCBsJ291dmVydHVyZSBkZSBsYSBzZXNzaW9uIHBldXQgw6p0cmUgbG9uZy4KSWwgZXN0IGNvbnNlaWxsw6kgYXV0YW50IHF1ZSBwb3NzaWJsZSBkZSBmZXJtZXIgc2Egc2Vzc2lvbiBsb3JzcXUnb24gYSBmaW5pIGRlIHRyYXZhaWxsZXIgZXQgZCdlbiBvdXZyaXIgdW5lIG5vdXZlbGxlLgpDZWxhIGxpYsOocmUgZGUgbGEgbcOpbW9pcmUgdG91dCBlbiBnYXJhbnRpc3NhbnQgbGEgcsOpcGxpY2FiaWxpdMOpIGRlIHNlcyByw6lzdWx0YXRzLgoKPGZvbnQgY29sb3I9IiNjZjA1MDUiPkxvcnNxdSd1bmUgc2Vzc2lvbiBlc3QgaW5hY3RpdmUgZGVwdWlzIHRyb3AgbG9uZ3RlbXBzLCBlbGxlIHBldXQgw6p0cmUgc3VwcHJpbcOpZSBhdXRvbWF0aXF1ZW1lbnQuCklsIGVzdCBwbHVzIHBydWRlbnQgZGUgbmUgamFtYWlzIGNvbXB0ZXIgc3VyIGxlcyBvYmpldHMgUiBkZSBsYSBzZXNzaW9uLjwvZm9udD4KCiMjIENvcGllci1jb2xsZXIKCkRlcHVpcyBsYSBtaXNlIGVuIHByb2R1Y3Rpb24gZGUgbCdpbnRlcmZhY2UgUlN0dWRpbywgbGUgY29sbGFnZSBkZXB1aXMgbGUgcHJlc3NlLXBhcGllciDDqWNob3VlIHBhcmZvaXMuCkxvcnNxdSdvbiBjb3BpZSBkZXB1aXMgUlN0dWRpbyBldCBxdSdvbiBjb2xsZSBkYW5zIFJTdHVkaW8sIGF1Y3VuIHByb2Jsw6htZS4KTG9yc3F1J29uIGNvcGllIHVuIGJvdXQgZGUgY29kZSBob3JzIGRlIFJTdHVkaW8gZXQgcXUnb24gbGUgY29sbGUgZGFucyBSU3R1ZGlvLCBjZWxhIMOpY2hvdWUgc2kgbCdvbiBhdmFpdCBkw6lqw6Agc8OpbGVjdGlvbm7DqSB1biBib3V0IGRlIHRleHRlIHF1ZSBsJ29uIGNvbXB0YWl0IHJlbXBsYWNlci4KSWwgZmF1dCBkb25jIHN1cHByaW1lciBsZSB0ZXh0ZSBhdmFudCwgcHVpcyBjb3BpZXIgbGUgY29udGVudSBldCBsZSBjb2xsZXIgZW5zdWl0ZSBzYW5zIGF2b2lyIHPDqWxlY3Rpb25uw6kgZGUgdGV4dGUuCkF0dGVudGlvbiBjZWxhIMOpY2hvdWUgc2kgb24gaW52ZXJzZSBsJ29yZHJlIGRlcyBvcMOpcmF0aW9ucyAocGFyIGV4ZW1wbGUgOiBjb3BpZXItc3VwcHJpbWVyIGxlIHRleHRlLWNvbGxlcikuCgojIEdpdAoKTG9naXF1ZW1lbnQsIGlsIG4nZXN0IHBhcyBwb3NzaWJsZSBkZSBjb25uZWN0ZXIgc29uIHByb2pldCDDoCB1biBkw6lww7R0IEdpdCBzdXIgR2l0aHViIG91IEdpdGxhYiBwdWlzcXUnaWwgcydhZ2l0IGQndW5lIGJ1bGxlIHPDqWN1cmlzw6llLgpDZWxhIG4nZW1ww6pjaGUgcGFzIGwndXRpbGlzYXRpb24gZGUgR2l0IGVuIGxvY2FsLgrDh2EgbmUgcGVybWV0IHBhcyBkZSB0cmF2YWlsbGVyIMOgIHBsdXNpZXVycyBzdXIgbGUgbcOqbWUgY29kZSAocXVvaSBxdWUgw6dhIHMnZW52aXNhZ2UgYXZlYyB1biBlc3BhY2UgcHJvamV0KSBtYWlzIGNlbGEgcGVybWV0IHRvdXQgZGUgbcOqbWUgZGUgZmFpcmUgbGUgdmVyc2lvbm5hZ2UgZGUgc29uIGNvZGUgdG91dCBlbiBsZSBzw6ljdXJpc2FudC4KClBvdXIgaW5pdGlhbGlzZXIgdW4gZG9zc2llciBHaXQgOiAqRmlsZSogPiAqTmV3IHByb2plY3QqID4gKk5ldyBEaXJlY3RvcnkqID4gKk5ldyBQcm9qZWN0KiBwdWlzIGNob2lzaXIgdW4gbm9tIGRlIHByb2pldCwgdW4gZW1wbGFjZW1lbnQgKipldCBjb2NoZXIqKiAqQ3JlYXRlIGEgZ2l0IHJlcG9zaXRvcnkqLgpFbmZpbiB2YWxpZGVyIGF2ZWMgKkNyZWF0ZSBwcm9qZWN0IGFzIHN1YmRpcmVjdG9yeSouCgpFbnN1aXRlIG9uIHBldXQgdXRpbGlzZXIgR2l0IG5vcm1hbGVtZW50IMOgIHBhcnRpciBkdSBwYW5uZWF1IGVuIGhhdXQgw6AgZHJvaXRlLgpTaSBsJ29uIHZldXQgZmFpcmUgY2VydGFpbmVzIG9ww6lyYXRpb25zIG5vbiBpbmNsdXNlcyBkYW5zIGxlIHBhbm5lYXUsIG9uIHBldXQgdG91am91cnMgdXRpbGlzZXIgbGEgY29tbWFuZGUgYHN5c3RlbWAgcG91ciBsYW5jZXIgdW5lIGNvbW1hbmRlIHN5c3TDqG1lLgoKPGZvbnQgY29sb3I9IiM3MzczNzMiPlF1ZWxxdWVzIGV4ZW1wbGVzIDo8L2ZvbnQ+CgpgYGB7cn0Kc3lzdGVtKAogICMgRMOpY29tbWVudGVyIGxhIGxpZ25lIGludMOpcmVzc2FudGUKICAKICAjIENvbmZpZ3VyZXIgc29uIG5vbQogICMgImdpdCBjb25maWcgLS1nbG9iYWwgdXNlci5uYW1lICdTYW11ZWwgQWxsYWluJyIKICAjIFbDqXJpZmllcgogICMgImdpdCBjb25maWcgLS1nbG9iYWwgdXNlci5uYW1lIgogICAKICAjIENvbmZpZ3VyZXIgc29uIGFkcmVzc2UgbWFpbAogICMgImdpdCBjb25maWcgLS1nbG9iYWwgdXNlci5lbWFpbCAnc2FtdWVsLmFsbGFpbkBzYW50ZS5nb3V2LmZyJyIKICAjIFbDqXJpZmllcgogICAjICJnaXQgY29uZmlnIC0tZ2xvYmFsIHVzZXIuZW1haWwiCiAgIAogICMgRXRhdCBkdSBkw6lww7R0CiAgIyAiZ2l0IHN0YXR1cyIKICAKICAjIEFqb3V0ZXIgdW5lIMOpdGlxdWV0dGUKICAjICJnaXQgdGFnIHYwLjIiCiAgCiAgIyBTdXBwcmltZXIgdW5lIMOpdGlxdWV0dGUKICAjICJnaXQgdGFnIC1kIHYwLjIiCiAgCiAgIyBWZXJzaW9uIGRlIEdpdAogICJnaXQgLS12ZXJzaW9uIgopCmBgYAoKPGZvbnQgY29sb3I9IiNjZjA1MDUiPkLDqW1vbCA6IGxvcnMgZGUgbGEgcHNldWRvbnltaXNhdGlvbiwgaWwgZXN0IGRpZmZpY2lsZSBkZSBnw6lyZXIgbCdoaXN0b3JpcXVlIEdpdCwgY29uc3RpdHXDqSBkJ3VuZSBmb3VsZSBkZSBmaWNoaWVycyBkYW5zIGxlIGRvc3NpZXIgLmdpdC4gRW4gZWZmZXQgbCd1dGlsaXRhaXJlIGZvdXJuaSBwYXIgbGEgQ25hbSwgQlJVUywgZGVtYW5kZSBkZSB2YWxpZGVyIGNoYXF1ZSBmaWNoaWVyLCB1biDDoCB1bi48L2ZvbnQ+CgoKIyBDb25maWd1cmF0aW9uIFJTdHVkaW8KCiMjIERpY3Rpb25uYWlyZSBmcmFuw6dhaXMgcG91ciBsYSBjb3JyZWN0aW9uIG9ydGhvZ3JhcGhpcXVlCkxlIGRpY3Rpb25uYWlyZSBmcmFuw6dhaXMgbidlc3QgcGFzIHByw6lzZW50IHBhciBkw6lmYXV0LgpJbCBuJ2VzdCBwYXMgcG9zc2libGUgZGUgbGUgdMOpbMOpY2hhcmdlciBkaXJlY3RlbWVudCBlbiB1dGlsaXNhbnQgKlRvb2xzID4gR2xvYmFsIE9wdGlvbnMgPiBTcGVsbGluZyA+IE1haW4gZGljdGlvbmFyeSBsYW5ndWFnZSA+IEluc3RhbGwgbW9yZSBsYW5ndWFnZSogY2FyIFJTdHVkaW8gZXN0IGNvdXDDqSBkdSByZXN0ZSBkJ2ludGVybmV0LgpPbiBwZXV0IGVuIHJldmFuY2hlIHLDqWN1cMOpcmVyIGwnVVJMIGluZGlxdcOpZSBwYXIgbCdlcnJldXIgOgoKPiBbaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3JzdHVkaW8tYnVpbGR0b29scy9kaWN0aW9uYXJpZXMvYWxsLWRpY3Rpb25hcmllcy56aXBdKGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9yc3R1ZGlvLWJ1aWxkdG9vbHMvZGljdGlvbmFyaWVzL2FsbC1kaWN0aW9uYXJpZXMuemlwKQoKUHVpcyB0w6lsw6ljaGFyZ2VyIGxlIHppcCwgbGUgZMOpY29tcHJlc3NlciwgZXQgaW1wb3J0ZXIgbGVzIGZpY2hpZXJzICpmcl9GUi5kaWMqIChkaWN0aW9ubmFpcmUpIGV0ICpmcl9GUi5hZmYqIChhZmZpeGVzIHBvdXIgbGVzIGFjY29yZHMgZXRjLikgZGFucyBzb24gSG9tZS4gVW5lIGZvaXMgY2VsYSBmYWl0LCBpbCBmYXV0IGTDqXBsYWNlciBsZXMgZmljaGllcnMgYXUgYm9uIGVuZHJvaXQsIGNlIHF1ZSBkZXZyYWllbnQgZmFpcmUgY2VzIGNvbW1hbmRlcyA6CgpgYGB7ciwgZXZhbD1GQUxTRX0KIyBPbiBjcsOpZSBsZSBkb3NzaWVyIHBvdXIgYWNjdWVpbGxpciBsZXMgZmljaGllcnMgZGUgZGljdGlvbm5haXJlcwpzeXN0ZW0oIm1rZGlyIC1wIH4vLmNvbmZpZy9yc3R1ZGlvL2RpY3Rpb25hcmllcy9sYW5ndWFnZXMtc3lzdGVtIikKCiMgQ29waWVyIGxlcyBmaWNoaWVycyBkYW5zIGNlIG5vdXZlYXUgZG9zc2llcgpzeXN0ZW0oImNwIH4vZnJfRlIuZGljIH4vLmNvbmZpZy9yc3R1ZGlvL2RpY3Rpb25hcmllcy9sYW5ndWFnZXMtc3lzdGVtIikKc3lzdGVtKCJjcCB+L2ZyX0ZSLmFmZiB+Ly5jb25maWcvcnN0dWRpby9kaWN0aW9uYXJpZXMvbGFuZ3VhZ2VzLXN5c3RlbSIpCgpgYGAKCkVuc3VpdGUgcmVkw6ltYXJyZXIgUlN0dWRpbyAoKlNlc3Npb24gPiBUZXJtaW5hdGUgUiopIHBvdXIgcHJlbmRyZSBlbiBjb21wdGUgbGVzIGNoYW5nZW1lbnRzLgpWw6lyaWZpZXIgZGFucyAqVG9vbHMgPiBHbG9iYWwgT3B0aW9ucyA+IFNwZWxsaW5nKiBxdWUgbGEgbWFuaXB1bGF0aW9uIGEgZm9uY3Rpb25uw6kgKCJGcmVuY2giKS4KCgoK