In diesem zweiten Kapitel steht nun die Analyse vor Wörtern und Texten im Mittelpunkt. Auf den ersten Blick erscheinen die Metriken, die hier vorgestellt werden, möglicherweise nicht als besonders relevant für sozialwissenschaftliche Fragestellung. Das liegt zum einen daran, das wir uns an dieser Stelle noch nicht mit abstrakten Konzepten wie Themen oder Sentiment beschäftigen, die in den folgenden Kapitel im Mittelpunkt stehen werden, sondern mit Aspekten wie der Frequenz von Begriffen und der Ähnlichkeit von Texten, die augenscheinlich vielleicht der Linguistik näher sind. Wort- und Textmetriken sind aber aus zwei Gründen von Bedeutung: erstens bilden sie die Grundlage der höherstufigen Verfahren, egal ob Lexikon-, Themen- oder Sentimentanalyse, und zu anderen lassen sich auch schon mit ihnen interessante sozialwissenschaftliche Fragestellungen bearbeiten.

Einige Beispiele:

Diese und ähnliche Fragen werden in den folgenden Kapiteln aufgegriffen – zunächst werden aber die Funktionen vorgestellt, welche die Arbeit mit Wörtern und Texten in Quanteda ermöglichen. Dazu ziehen wir neben den bereits bewährten Sherlock Holmes-Daten auch zwei weitere Korpora heran.

Installation und Laden der benötigten R-Bibliotheken, Laden des Korpus, Berechnen einer DFM

Zunächst werden wieder die notwendigen Bibliotheken geladen. Das Paket scales kommt hier neu dazu, um unkompliziert Variablen reskalieren zu können.

if(!require("quanteda")) {install.packages("quanteda"); library("quanteda")}
if(!require("tidyverse")) {install.packages("tidyverse"); library("tidyverse")}
if(!require("scales")) {install.packages("scales"); library("scales")}
if(!require("ggdendro")) {install.packages("ggdendro"); library("ggdendro")}
theme_set(theme_minimal())

Nun wird in einem zweiten Schritt das Sherlock-Korpus geladen, welches wir bereits im ersten Kapitel mittels readtext aus dem Rohtext erstellt und als RData-Datei gespeichert haben. In der RData-Datei ist nebem dem Korpus selbst auch eine mit summary erstellter Data Frame enthalten, welcher die Korpus-Metadaten enthält. Zum einen sparen wir uns so das Anlegen eines Korpus, zum anderen ist das RData-Format komprimiert, was sich bei Textdaten durchaus bei der Dateigröße bemerkbar macht.

Als nächstes berechnen wir dann auf Grundlage des Quanteda-Korpus-Objekts wieder eine DFM (vgl. Kapitel 1), da wir diese später noch benötigen.

load("daten/sherlock/sherlock.korpus.RData")
meine.dfm <- dfm(korpus, remove_numbers = TRUE, remove_punct = TRUE, remove_symbols = TRUE, remove = stopwords("english"))

Korkordanzen erstellen

Zu den einfachsten Funktionen von Quanteda gehört die Möglichkeit, Konkordanzen (auch KWIC genannt) zu erstellen, also die Textstelle eines Suchterms sowie dessen umgebenden Satzekontext zu extrahieren. Strenggenommen gehört diese Funktion nicht in den Bereich der Wort- und Textmetriken, wir behandeln sich aber hier, und nicht im ohnehin sehr umfangreichen ersten Kapitel, da wie auch bei den Wortmetriken bei Konkordanzen der Umgang mit Wörtern und ihr Kontext im Mittelpunkt steht.

Konkordanzen lassen sich in Quanteda für einzelen Wörter, aber auch für ganze Phrasen erzeugen. Oftmals ist der Export einer Konkodanz (etwa als CSV-Datei, die mit Excel geöffnet werden kann) neben der Darstellung innerhalb von R besonders nützlich. Dies geschieht hier mit der Funktion write_delim().

Anmerkung: Die Konkordanz kann mit dem kleinen Pfeil rechts oben gescrollt werden.

konkordanz <- kwic(korpus, "happy")
konkordanz

Konkordanzen bestehen aus den Metadaten (Textname und Position), dem linken Kontext, dem Suchterm, sowie dem rechten Kontext. Die erste Konkordanz enthält alle Vorkommnisse des Begriffs ‘data’ im Korpus.

konkordanz <- kwic(korpus, phrase("John|Mary [A-Z]+"), valuetype = "regex", case_insensitive = FALSE)
konkordanz
konkordanz <- kwic(korpus, c("log*", "emot*"), window = 10, case_insensitive = FALSE)
konkordanz
write_delim(konkordanz, path = "konkordanz.csv", delim = ";") # Datei ist Excel-kompatibel

Die zweite Konkordanz enthält Vorkommnisse der Namen ‘John’ und ‘Mary’ gefolgt von einem weiteren Wort in Großschreibung (i.d.R. der Nachname). Die dritte Konkordanz enthält schließlich die Wortfragmente ‘log’ und ‘emot’, also Wörter wie ‘logical’ und ‘emotional’, aber auch die Pluralform ‘emotions’. Strenggenommen handelt es sich hierbei nicht um Wortstämme, weil die Flexionsform bei unregelmäßigen Wörtern ganz vom Lemma abweicht (vgl. ‘go’ und ‘went’). In den meisten sozialwissenschaftlichen Anwendungsszenarien ist es aber bereits ausreichend, durch die Verwendung von Platzhaltern (*) verschieden Wortvarianten zu identifizieren. Hier bringt Quanteda eine Reihe nützlicher Eigenschaften mit, die in der Dokumentation von kwic() genau beschrieben werden.

Als nächstes berechnen wir die Häufigkeit und Dispersion von Tokens pro Erzählung, welche die Begriffe ‘dark’ und ‘light’ enthalten.

term1 <- kwic(korpus, "dark", valuetype = "regex", case_insensitive = FALSE) %>% 
  as.data.frame() %>% 
  count(docname, name = "Treffer") %>% 
  mutate(Prozentanteil = Treffer/(korpus.stats$Tokens/100), Suchterm = "dark") %>% 
  arrange(desc(Prozentanteil))
term2 <- kwic(korpus, "light", valuetype = "regex", case_insensitive = FALSE) %>% 
  as.data.frame() %>% 
  count(docname, name = "Treffer") %>% 
  mutate(Prozentanteil = Treffer/(korpus.stats$Tokens/100), Suchterm = "light") %>% 
  arrange(desc(Prozentanteil))
term1
term2

Wieder wenden wir zunächst die Funktion kwic() an, allerdings hier in Kombination mit mehreren Funktionen aus dem Paket dplyr (tidyverse). Diese Funktionen haben nichts mit Quanteda zu tun, sondern sind für die Umformung jeglicher Daten in R nützlich (wer mehr wissen möchte, sollte sich dieses Buch anschauen). Während zuvor einfach die resultierende Konkordanz ausgegeben wurde, wird das Ergebnis jetzt mit Hilfe der Funktionen group_by(), summarise(), mutate() und arrange() weiter verarbeitet. Dabei machen wir uns die Tatsache zunutze, dass in einem KWIC-Ergebnis bereits alle Informationen vorliegen, um die absolute und relative Frequenz eines Begriffs (hier ‘light’ und ‘dark’) in einer Reihe von Dokumenten zu berechnen. Den Prozentanteil haben wir dabei einfach mittels Dreisatz abgeleitet (mit Treffer/(korpus.stats$Tokens/100)).

Wortfrequenzen lassen sich allerdings wesentlich einfacher durch die Quanteda-eigenen Funktion textstat_frequency umsetzen, die wir folgend auch konsequent nutzen werden – auch dazu gleich noch etwas mehr.

Zunächst plotten wie die absolute und relativen Häufigkeit der beiden Begriffe.

terme.kombiniert <- bind_rows(term1, term2) %>% 
  mutate(docname = factor(docname, levels = levels(korpus.stats$Text)))
ggplot(terme.kombiniert, aes(docname, Treffer, group = Suchterm, col = Suchterm)) + 
  geom_line(size = 1) + 
  scale_colour_brewer(palette = "Set1") + 
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) + 
  ggtitle("Häufigkeit der Suchbegriffe \"dark\" und \"light\" pro Roman (absolut)") + 
  xlab("Roman") + ylab("Wörter (gesamt)")

ggplot(terme.kombiniert, aes(docname, Prozentanteil, group = Suchterm, col = Suchterm)) + 
  geom_line(size = 1) + 
  scale_colour_brewer(palette = "Set1") + 
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) + 
  ggtitle("Häufigkeit der Suchbegriffe \"dark\" und \"light\" pro Roman (relativ)") + 
  xlab("Roman") + ylab("Wörter (%)")

Wir sehen zwei unterschiedliche Berechnungen: das erste Plot zeigt die absolute Häufigkeit der beiden Begriffe, das zweite hingegen den relativen Prozenzanteil des Begriffs an der Gesamtwortzahl des jeweiligen Romans. Wieso sind die beiden Plots nahezu identisch? Dies hat mit der im Vergleich relativ ähnlichen Wortanzahl der Romane untereinander zu tun (zwischen 8,500 und 12,000 Tokens). Sind zwei Teilkorpora von sehr unterschiedlicher Größe, ist eine Normalisierung der Wortfrequenzen extrem wichtig, da sonst die Ergebnisse massiv verzerrt werden. Auch so ergeben sich durchaus Unterschiede, wenn man etwa den Anteil von ‘The Adventure of the Speckled Band’ und ‘The Adventure of the Copper Beeches’ vergleicht. Während die Anszahl der absoluten Treffer auf ‘light’ in beiden Romanen identisch ist, fällt der relative Anteil bei ‘Speckled Band’ im Vergleich ab.

Was tun, wenn man sich weniger für die Häufigkeit als für die Position der Suchterme interessiert? Dazu kann das Plotten der Begiffsdispersion als ‘xray-plot’ nützlich sein, wozu die Funktion textplot_xray existiert. Die X-Achse stellt hierbei die Position innerhalb des Textes dar, an dem der Suchbegriff vorkommt.

textplot_xray(kwic(korpus, "dark", valuetype = "regex", case_insensitive = FALSE)) + 
  ggtitle("Lexikalische Dispersion von \"dark\" in Sherlock Holmes")

textplot_xray(kwic(korpus, "light", valuetype = "regex", case_insensitive = FALSE)) + 
  ggtitle("Lexikalische Dispersion von \"light\" in Sherlock Holmes")

Wortfrequenzen

Im erste Kapitel wurde bereits kurz erläutert, wie mittels topfeatures Wortfrequenzen in einem Korpus oder einer DFM ermittelt werden können, und vorherigen Abschnitt haben wir dies durch den etwas hemdsärmerligen Einsatz von kwic realisiert. Die Funktion textstat_frequency liefert im Vergleich wesentlich präzisere und detailliertere Ergebisse als diese beiden Optionen. Zusätzlich zur absoluten Wortfrequenz erhält man nächlich noch den Rang (rank), die Anzahl der Dokumente, in denen das Feature vorkommt (docfreq) sowie Metadaten, nach denen bei der Zählung gefiltert wurde (group). Grundsätzlich ist textstat_frequency gegenüber topfeatures zu bevorzugen.

worthaeufigkeiten <- textstat_frequency(meine.dfm)
head(worthaeufigkeiten)

Ebenso lassen sich mit textstat_frequency auch gruppierte Wortfrequenzen nach Gruppen bilden.

textstat_frequency(meine.dfm, n = 10, groups = "Textnummer")

Um zu demonstrieren, wo die Stärken von textstat_frequency liegen, wenden wir uns erstmalig vom erprobten Sherlock Holmes-Korpus ab und laden einen neuen Datensatz, nämlich das schon ein wenig angejährte Poliblogs-Korpus. Dabei handelt es sich um eine Sammlung politischer Blogeinträge aus dem U.S.-Wahlkampf 2008, in dem John McCain gegen Barack Obama unterlag. Obwohl sich politisch seitdem vieles verändert und Blogs gegenüber Social Media-Plattformen wie Facebook und Twitter klar an Stellenwert verloren haben, ist das Beispiel immer noch interessant.

Zunächst werfen wir einen ersten Blick auf das Korpus und die beigefügten Metadaten.

load("daten/blogs/poliblogs2008.RData")
as.data.frame(poliblogs.stats)

Nachdem wir das bereits vorbereitete Korpus inspiziert haben, konstruieren wir zunächst eine DFM unter Entfernung von für uns wenig relevanten Features und kalkulieren dann mittels textstat_frequency eine Frequenztabelle. Dabei nutzen wir das ‘groups’-Argument von textstat_frequency aus, um gezielt getrennte Frequenzen für linke und rechte Blogs, statt für alle Dokumente im Korpus zu berechnen.

poliblogs.dfm <- dfm(poliblogs.korpus, 
                     remove_numbers = TRUE, 
                     remove_punct = TRUE, 
                     remove_symbols = TRUE, 
                     remove = c(stopwords("english"), 
                                "can", "may", "said", "one", "just", "now", "new", "even", "like", "get",
                                "also", "first", "last", "much", "well"))
poliblogs.freqs <- textstat_frequency(poliblogs.dfm, groups = "rating") %>% arrange(rank, group)
head(poliblogs.freqs, 100)

Nun können wir die frequente Begriffe in den rund 13.000 konservativen und linken U.S.-Blogeinträgen plotten. Dabei zeigt das erste Plot die populärsten Begriffe in konservativen Blogs und das zweite Plot die populärsten Begriffe in linken Blogs, wobei die Frequenz bei jedem Begriff für die jeweils andere ‘Seite’ auch gleich mitangezeigt wird.

freqs.con <- filter(poliblogs.freqs, group == "Conservative") %>% as.data.frame() %>% select(feature, frequency)
freqs.lib <- filter(poliblogs.freqs, group == "Liberal") %>% as.data.frame() %>% select(feature, frequency)
freqs <- left_join(freqs.con, freqs.lib, by = "feature") %>% head(25) %>% arrange(frequency.x) %>% mutate(feature = factor(feature, feature))
ggplot(freqs) +
  geom_segment(aes(x=feature, xend=feature, y=frequency.x, yend=frequency.y), color="grey") +
  geom_point(aes(x=feature, y=frequency.x), color = "red", size = 3 ) +
  geom_point(aes(x=feature, y=frequency.y), color = "lightblue", size = 3 ) +
  ggtitle("Häufige Begriffe in konservativen und linken U.S.-Blogs im Wahlkampf 2008") + 
  xlab("") + ylab("Wortfrequenz") + 
  coord_flip()

freqs <- left_join(freqs.con, freqs.lib, by = "feature") %>% head(25) %>% arrange(frequency.y) %>% mutate(feature = factor(feature, feature))
ggplot(freqs) +
  geom_segment(aes(x=feature, xend=feature, y=frequency.y, yend=frequency.x), color="grey") +
  geom_point(aes(x=feature, y=frequency.y), color = "blue", size = 3 ) +
  geom_point(aes(x=feature, y=frequency.x), color = "lightcoral", size = 3 ) +
  ggtitle("Häufige Begriffe in linken und konservativen U.S.-Blogs im Wahlkampf 2008") + 
  xlab("") + ylab("Wortfrequenz") + 
  coord_flip()

Wie sich schnell erkennen lässt, kann dieses Ergebnis durch eine systematischere Stopwortentfernung sowie die Gewichtung der DFM noch weiter optimiert werden. Wir kommen auf diese Möglichkeiten später noch zurück, wenden uns aber nun einem anderen Vefahren zu – der Ermittlung von Kollokationen.

Kollokationen

Wir gehen nun zu den sogenannten Textstatistiken über. Dabei handelt es sich um Funktionen anhand derer sich Wörter und Texte mit Blick auf ihre Ähnlichkeit mit oder Distanz zu anderen Wörtern oder Texte analysieren lassen. Eine wichtige Funktion in diesem Zusammenhang ist die Extraktion von Kollokationen. Die Kollokate eines Begriffs sind solche Begriffe, die häufig gemeinsam mit dem Term vorkommen. Der Prozess ist dabei induktiv.

Folgend wenden wir die Funktion textstat_collocations an, die häufige Kollokate im Sherlock Holmes-Korpus ermittelt. Anhand von write_delim wird das Ergebnis dann noch als Excel-kompatible CSV-Datei gespeichert.

kollokationen <- textstat_collocations(korpus, min_count = 10)
arrange(kollokationen, desc(count))
arrange(kollokationen, desc(lambda))
write_delim(kollokationen, path = "kollokationen.csv", delim = ";") # Datei ist Excel-kompatibel

In den beiden Tabellen zeigt uns collocation das Kollokat und count dessen absolute Häufigkeit an. Die Assoziationsstärker der Kollokation wird mit lambda und z gemessen (genauer ist z ein z-standardisiertes lambda). Lambda beschreibt dabei die Wahrscheinlichkeit, dass genau diese zwei Begriffe auf einander folgen, was insofern von der absoluten Häufigkeit zu differenzieren ist, als das diese nicht das auftreten eines Teilbegriffs mit allen anderen Wörtern im Korpus berücksichtigt.

Die beiden Tabellen illustrieren diesen Unterschied. Während die erste nach der absoluten Häufigkeit sortiert ist, so dass gängige Kollokate wie ‘of the’ oder ‘it is’ ganz vorne liegen, ist die zweite absteigend nach Lambda sortiert, so dass eine Reihe von Eigennamen wie ‘hosmer angel’ oder ‘briony lodge’ die Liste anführen. Praktisch betrachtet ist es meist sinnvoller, Eigennamen als Phrasen zu betrachten, statt als einzelne Begriffe, deren gemeinsames Auftreten wirklich etwas über den Text verrät. Echte Kollokate sind hingegen ‘no doubt’ oder ‘young lady’.

Welche Ergebnisse erhalten wir, wenn wir textstat_collocations auf das Poliblogs-Korpus anwenden? Statt die Funktion auf das gesamt Korpus anzuwenden, filtern wie zunächst nach der konservativen Blogs und ziehen dann ein Zufallsample von 1.000 Beiträgen. Dies beschleunigt die Berechnung der Kollokationen sehr stark, die sonst bei diesem relativ großen Datensatz mitunter sehr lange dauern kann.

kollokationen <- corpus_subset(poliblogs.korpus, rating == "Conservative") %>% corpus_sample(size = 1000) %>% textstat_collocations(min_count = 15)
arrange(kollokationen, desc(lambda))

Wie man gleich erkennt, enthält die Liste auch hier zahlreiche Personen-, Organisations- und Ortsnamen, die sehr gut erkannt werden, aber auch Ausdrücke wie ‘hell yeah’ oder ‘hat tip’.

Wortähnlichkeit und -Distanz

Wie sich im ersten Kapitel bereits angedeutet hat, lassen sich auf Grundlage einer DFM zahlreiche Metriken berechnen, welche die Nähe und Distanz von Wörtern und Dokumenten zu einander relektieren. Dies geschieht mit textstat_simil. Zunächst konstruieren wir dazu eine DFM, in der jeder Satz einem Dokument entspricht. Dies wird deshalb notwendig, weil sich Wortähnlichkeiten bei einer geringen Dokumentanzahl nicht besonders zuverlässig berechnen lassen, da Ähnlichkeit als Kookurenz innerhalb des gleichen Dokuments operationalisiert wird. Dann Berechnen wir die Wortähnlichkeit zum Begriff ‘love’ mittels Kosinusdistanz. Andere verfügbare Metriken sind ‘correlation’, ‘jaccard’, ‘eJaccard’, ‘dice’, ‘eDice’, ‘simple matching’, ‘hamann’ und ‘faith’, welche Wortähnlichkeit jeweils unterschiedlich operationalisieren.

korpus.saetze <- corpus_reshape(korpus, to = "sentences")
meine.dfm.saetze <- dfm(korpus.saetze, remove_numbers = TRUE, remove_punct = TRUE, remove_symbols = TRUE, remove = stopwords("english"))
meine.dfm.saetze <- dfm_trim(meine.dfm.saetze, min_docfreq = 5)
aehnlichkeit.woerter <- textstat_simil(meine.dfm.saetze, meine.dfm.saetze[,"love"], margin = "features", method = "cosine")
head(aehnlichkeit.woerter[order(aehnlichkeit.woerter[,1], decreasing = T),], 10)
     love     loved     lover   working    ceased      wife   majesty   account 
1.0000000 0.2108185 0.1781742 0.1781742 0.1666667 0.1295048 0.1178511 0.1081476 
  emotion     share 
0.1054093 0.1054093 

Analog zur Ähnlichkeit funktionert auch die Berechnung von Wortdistanzen mit der Funktion textstat_dist(). Auch hier haben wir wieder eine große Anzahl von Distanzmaßen zur Auswahl (‘euclidean’, ‘chisquared’, ‘chisquared2’, ‘hamming’, ‘kullback’. ‘manhattan’, ‘maximum’, ‘canberra’, ‘minkowski’).

distanz.woerter <- textstat_dist(meine.dfm.saetze, meine.dfm.saetze[,"love"], margin = "features", method = "euclidean")
head(distanz.woerter[order(distanz.woerter[,1], decreasing = T),], 10)
    upon     said   holmes      one      man       mr   little      now      see 
23.60085 22.60531 21.47091 20.97618 18.22087 17.63519 17.34935 16.12452 15.71623 
     may 
15.19868 

Was sagt das Ergebnis aus? Vor allem das (wenig überraschend) Wörter wie ‘upon’ und ‘said’ sehr weit von ‘love’ entfernt sind – allerdings nicht in dem Sinne, dass sie das logische Gegenteil von ‘love’ darstellen würden (in der Linguistik spricht man von Antonymie). Das liegt daran, dass diese Begriffe im Korpus nahezu gleich verteilt sind, also überall vorkommen. Mit dem Verfahren der Wortvektoren (welches wir hier nicht behandeln) und sehr großen Datenbeständen lassen sich allerdings auch solche und andere semantisch Beziehungen indentifizieren. Die Filterung, die wir zuvor an der DFM vorgenommen haben, schließt Begriffe aus, die vielleicht nie gemeinsam mit ‘love’ vorkommen, und insofern noch distanzierter wären, allerdings gibt es derer auch sehr viele. Zusammenfassend kann man sagen, dass textstatistische Nähe- und Distanzmaße immer ausreichend viele Daten benötigen, um ein zuverlässiges Resultat liefern zu können, und dass der Suchterm selbst ausreichend oft vorkommen muss.

Wieder wenden wir uns dem Poliblogs-Korpus für ein etwas lebensnaheres Beispiel zu und vergleichen die Ähnlichkeit anderer Begriffe zum Zielterm ‘taxes’ in konservativen und linken Blogs.

aehnlichkeit.con <- poliblogs.korpus %>% 
  corpus_subset(rating == "Conservative") %>% 
  dfm(remove_numbers = TRUE, remove_punct = TRUE, remove_symbols = TRUE, remove = stopwords("english")) %>% 
  dfm_trim(min_termfreq = 10) %>% 
  textstat_simil(y = .[,"taxes"], margin = "features", method = "cosine")
aehnlichkeit.lib <- poliblogs.korpus %>% 
  corpus_subset(rating == "Liberal") %>% 
  dfm(remove_numbers = TRUE, remove_punct = TRUE, remove_symbols = TRUE, remove = stopwords("english")) %>% 
  dfm_trim(min_termfreq = 10) %>% 
  textstat_simil(y = .[,"taxes"], margin = "features", method = "cosine")
aehnlichkeit.con.df <- data.frame(Begriff = aehnlichkeit.con@Dimnames[[1]], Kosinus = aehnlichkeit.con@x, stringsAsFactors = F) %>% 
  filter(!Begriff %in% c("tax", "taxes")) %>% 
  arrange(desc(Kosinus)) %>% 
  mutate(Rang = row_number()) %>% 
  filter(Rang <= 15)
aehnlichkeit.lib.df <- data.frame(Begriff = aehnlichkeit.lib@Dimnames[[1]], Kosinus = aehnlichkeit.lib@x, stringsAsFactors = F) %>% 
  filter(!Begriff %in% c("tax", "taxes")) %>% 
  arrange(desc(Kosinus)) %>% 
  mutate(Rang = row_number()) %>% 
  filter(Rang <= 15)
ggplot(aehnlichkeit.con.df, aes(reorder(Begriff, Rang), Kosinus)) + 
  geom_bar(stat = "identity") + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  xlab("") + ylab("") + 
  ggtitle("Begriffe mit hoher Kosinusnähe zum Zielterm \'taxes\' in konservativen U.S.-Blogs")

ggplot(aehnlichkeit.lib.df, aes(reorder(Begriff, Rang), Kosinus)) + 
  geom_bar(stat = "identity") + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  xlab("") + ylab("") + 
  ggtitle("Begriffe mit hoher Kosinusnähe zum Zielterm \'taxes\' in linken U.S.-Blogs")

Textähnlichkeit und -Distanz

Wer die Dokumentation von textstat_simil und textstat_dist anschaut, wird feststellen, dass es dort den etwas kryptischen Hinweis auf das Argument ‘margin’ gibt. Dieses hat zwei mögliche erinstellungen: ‘documents’ oder ‘features’. Stellt man hier ‘documents’ ein, werden die besprochenen Metriken nicht auf Wörter, sondern auf Texte angewandt. Folgend plotten wir die Textnähe via Kosinusähnlichkeit (hier ausgehend vom ersten Roman, ‘A Case of Identity’).

aehnlichkeit.texte <- data.frame(Text = factor(korpus.stats$Text, levels = rev(korpus.stats$Text)), as.matrix(textstat_simil(meine.dfm, meine.dfm["A Case of Identity",], margin = "documents", method = "cosine")))
ggplot(aehnlichkeit.texte, aes(A.Case.of.Identity, Text)) + 
  geom_point(size = 2.5) + 
  ggtitle("Text-Kosinusähnlichkeit (hier für 'A Case of Identity')") + 
  xlab("Kosinunsähnlichkeit") + ylab("")

Wie wir sehen, ist die Ähnlichkeit der Romane ‘The Red-headed League’ und ‘The Adventure of the Copper Beeches’ etwas größer, als dies bei den anderen Erzählungen der Fall ist.

Während diese sehr schlichte Art der Darstellung sinnvoll ist, um gezielt Unterschiede eines ganz bestimmten Dokuments mit anderen Dokumenten zu untersuchen, gibt es Situationen, in denen wir Ähnlichkeiten und Unterschiede zwischen Dokumenten innerhalb eines Korpuses ganz grundsätzlich in den Blick nehmen wollen. Dazu dient das folgende Plot. Nachdem wir die Distanzmatrix in einen Data Frame umgewandelt haben, den wir mit ggplot darstellen können, führen wir eine neuen Variable Ähnlichkeit ein, die wir zugleich re-skalieren, um den Kontrast zwischen Texten möchlichst deutlich zu machen. Das Ergebnis lässt sich dann in einer sog. Heatmap darstellen, in der Ähnlichkeit rot und Unterschiede blau dargestellt werden.

aehnlichkeit.texte <- textstat_simil(meine.dfm, margin = "documents", method = "cosine") %>%
  as.data.frame() %>% 
  mutate(Ähnlichkeit = rescale(cosine, to = c(-1,1)))
aehnlichkeit.texte$document2 <- factor(aehnlichkeit.texte$document2, levels=rev(levels(aehnlichkeit.texte$document2)))
ggplot(aehnlichkeit.texte, aes(document1, document2)) + 
  geom_tile(aes(fill = Ähnlichkeit)) + 
  scale_fill_gradient2(low = "blue", high = "red", mid = "white", midpoint = 0) + 
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) +
  ggtitle("Text-Kosinusähnlichkeit (skaliert) in zwölf Romanen") + 
  xlab("") + ylab("")

So sehen wir etwa neben dem Befund zu ‘A Case of Identity’ auch, dass sich the ‘The Adventure of the Noble Bachelor’ und ‘The Five Orange Pips’ besonders unähnlich sind, jedenfalls innerhalb des (vermutlich recht heterogenen) Sherlock Holmes-Korpus. Überhaupt scheint ‘The Adventure of the Noble Bachelor’ vergleichsweise stark aus dem Rahmen zu fallen. Man beachte, dass die Ähnlichkeit mit sich selbst hier bewusst ausgeklammert wurde – bei der Umwandunglich der Distanzmatrix mit as.data.frame kann diese aber auch beibehalten werden.

Wie geeignet ist die Textähnlichkeit, um etwa ideologische Unterschiede vorherzusagen? Wieder nehmen wir das Poliblogs-Korpus in den Blick und plotten auch hier die Textähnlichkeit. Dabei verwenden wir nicht die Variable ‘rating’, die ja die Einordnung in ‘Conservative’ und ‘Liberal’ enthält, sondern stattdessen die Variable ‘blog’ die das jeweilige Blog durch ein Kürzel identifiziert. Auch verwenden wir eine andere Clustering-Technik (das sog. hierarchische Clustering) und plotten das Ergebnis als Dendrogram. Hier verzichten wir darauf, das Ähnlichkeitskosinus zu reskalieren, weshalb sich die Blogs recht ähnlich sehen.

aehnlichkeit.poliblogs <- dfm(poliblogs.korpus, remove_numbers = TRUE, remove_punct = TRUE, remove_symbols = TRUE, remove = stopwords("english"), groups = "blog") %>% 
  dfm_trim(min_docfreq = 6) %>% 
  textstat_simil(margin = "documents", method = "cosine") %>%
  as.dist() %>% hclust(method = "ward.D2")
ggdendrogram(aehnlichkeit.poliblogs, rotate = TRUE) +
  theme(axis.text.x = element_text(size = 12)) + 
  theme(axis.text.y = element_text(colour = c("blue", "blue", "red", "red", "red", "blue"), size = 12)) + 
  ggtitle("Text-Kosinusähnlichkeit in sechs U.S.-Blogs")
Vectorized input to `element_text()` is not officially supported.
Results may be unexpected or may change in future versions of ggplot2.

Es lässt sich erkennen, dass die ideologische Richtung (rot = konservativ, blau = links) kein klares Muster beim Wortgebrauch produziert, wobei hier eine Reihe von Faktoren berücksichtigt werden müsste, bevor man von einer stichhaltigen Analyse sprechen könnte. Dazu gehört die Verwendung von N-Grammen genauso wie eine andere Filterung der DFM, die relevante Features zurückbehält und gleichzeitig nicht einer Überanpassung zum Opfer fällt.

Keyness

Bei der Keyness handelt es sich um ein Maß für die Distinktivität von Begriffen für einen bestimmten Text, also wie stark einzelne Begriffe im jeweiligen Text im Vergleich zum gesamten Korpus über- (positive Werte) oder unterrepräsentiert sind (negative Werte). Während wir zuvor die Distanz von Wörtern und Texten zu einander untersucht haben, macht sich die Keyness die Verteilungshäufigkeit von Wörtern auf Texte zunutze, ohne deren Position zu berücksichtigen und verlangt dementsprechend eine DFM als Argument. In diesem Fall verwenden wir das Chi-Quadrat-Assoziationsmaß, es stehen aber auch weitere Metriken zur Auswahl, um die Wahrscheinlichkeit einer nicht-zufälligen Verteilung zu bemessen. Keyness funktioniert auch mit längeren Texten gut, solange diese sich ausreichend markant unterscheiden. Folgend berechnen wir zunächst die Keyness für vier Texte mit textstat_keyness und plotten wir diese Keyness-Statistiken dann für vier Erzählungen mit Hilfe der zugehörigen Funktion textplot_keyness.

keyness <- textstat_keyness(meine.dfm, target = "A Scandal in Bohemia")
textplot_keyness(keyness)

keyness <- textstat_keyness(meine.dfm, target = "A Case of Identity")
textplot_keyness(keyness)

keyness <- textstat_keyness(meine.dfm, target = "The Five Orange Pips")
textplot_keyness(keyness)

keyness <- textstat_keyness(meine.dfm, target = "The Adventure of the Noble Bachelor")
textplot_keyness(keyness)

Schaut man sich die vier Beispieltexte einmal genauer an, so wird schnell klar, dass die Begriffe mit einem hohen Keyness-Wert tatsächlich sehr distinktiv für den jeweiligen Text sind, also Begriffe wie ‘majesty’ und ‘photograph’ tatsächlich nur in ‘A Scandal in Bohemia’ eine Rolle spielen. Wenig distinktive Begriffe sind hingegen solche, die zwar in anderen Texten, nicht aber dem Zieltext vorkommen.

Nützlich wird diese Funktion vor allem dann, wenn man Texte nach einen Kriterium wie Medium, Sprecher, Partei, Zeitpunkt oder manuell zugeordnete Inhaltskategorie gruppiert. Dies lässt sich ebenfalls wieder gut anhand des Poliblogs-Korpus illustrieren. Die ersten drei Plots beziehen sich hierbei auf konservative Blogs, die Plots 4-6 hingegen auf linke Blogs.

aehnlichkeit.poliblogs <- dfm(poliblogs.korpus, remove_numbers = TRUE, remove_punct = TRUE, remove_symbols = TRUE, remove = stopwords("english"), groups = "blog") %>% 
  dfm_select(min_nchar = 3) %>% 
  dfm_trim(min_docfreq = 6)
keyness <- textstat_keyness(aehnlichkeit.poliblogs, target = "at")
textplot_keyness(keyness)

keyness <- textstat_keyness(aehnlichkeit.poliblogs, target = "ha")
textplot_keyness(keyness)

keyness <- textstat_keyness(aehnlichkeit.poliblogs, target = "mm")
textplot_keyness(keyness)

keyness <- textstat_keyness(aehnlichkeit.poliblogs, target = "db")
textplot_keyness(keyness)

keyness <- textstat_keyness(aehnlichkeit.poliblogs, target = "tp")
textplot_keyness(keyness)

keyness <- textstat_keyness(aehnlichkeit.poliblogs, target = "tpm")
textplot_keyness(keyness)

Hier kann die vergleichende Dimension der Keyness-Metrik gut erkennen, die die Unterschiede zwischen den Blogs gut zur Geltung bringt.

Entropie

Was tun, wenn man etwas über den Informationsgehalt eines Textes relativ zum Gesamtkorpus wissen möchte (und davon ausgeht, dass neue Wörter gleichbedeutend mit neuen Informationen zu interpretieren sind)? Hier hilft uns die Shannon-Entropie weiter, benannt nach Claude E. Shannon, dem Gründer der Informationstheorie. In Quanteda ist diese Metrik durch die Funktion textstat_entropy integriert, die wir folgend auf das Sherlock-Holmes-Satzkorpus anwenden.

entropie <- textstat_entropy(meine.dfm.saetze, margin = "documents") %>% 
  arrange(desc(entropy)) %>% 
  head(3)
texts(korpus.saetze[entropie$document])
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                The Adventure of the Speckled Band.1 
"\"When you combine the ideas of whistles at night, the pres- ence of a band of gypsies who are on intimate terms with this old doctor, the fact that we have every reason to believe that the doctor has an interest in preventing his stepdaughter's marriage, the dying allusion to a band, and, finally, the fact that Miss Helen Stoner heard a metallic clang, which might have been caused by one of those metal bars that secured the shutters falling back into its place, I think that there is good ground to think that the mystery may be cleared along those lines.\"" 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                The Adventure of the Speckled Band.2 
                                                                                      "The Adventure of the Speckled Band    On glancing over my notes of the seventy odd cases in which I have during the last eight years studied the methods of my friend Sherlock Holmes, I find many tragic, some comic, a large number merely strange, but none commonplace; for, working as he did rather for the love of his art than for the acquirement of wealth, he refused to associate himself with any investigation which did not tend towards the unusual, and even the fantastic." 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                A Case of Identity.1 
                                                                                                                   "he continued, flushing up at the sight of the bitter sneer upon the man's face, \"it is not part of my duties to my client, but here's a hunting crop handy, and I think I shall just treat myself to --\" He took two swift steps to the whip, but before he could grasp it there was a wild clatter of steps upon the stairs, the heavy hall door banged, and from the window we could see Mr. James Windibank running at the top of his speed down the road." 

Die drei Beispiele verdeutlichen gut, was Entropie letztendlich misst. Alle drei Sätze sind lexikalisch sehr aussergewöhnlich, was sich zum Teil durch ihre Länge erklärt, aber auch etwas mit dem hohen Anteil ungewöhnlicher Begriffe zu tun hat, die sonst im Korpus kaum vorkommen.

Indikatoren lexikalischer Vielfalt

Unter Maßen lexikalischer Vielfalt versteht man Metriken, welche die Diversität eines Textes mit Blick auf den Wortgebrauch wiedergeben. Ein Beispiel ist die bereits in Kapitel 1 berechtete Typ-Token-Relation. Diese beschreibt die Wortvielfalt und geben so auch Aufschluss über die Komplexität eines Textes. Wir berechnen zunächst eine ganze Reihe in Quanteda implementierter Metriken für die lexikalische Diversität der zwölf Sherlock Holmes-Romane mit der Funktion textstat_lexdiv.

lexdiversitaet <- textstat_lexdiv(meine.dfm, measure = "all")
lexdiversitaet

Bei einem oberflächliche Vergleich der Metriken fällt auf, dass sich die Texte nicht sehr stark unterscheiden, was ihre jeweilige lexikalische Vielfalt betrifft, ganz unabhängig davon, welche Metrik verwendet wird. Dies ist nicht unbedingt verwunderlich, da es sich um Texte des selben Genres und Autors handelt. Interessanter werden solche Metriken dann, wenn wir sehr unterschiedliche Genres oder Autoren vergleichen wollen, etwa die Programmen von Parteien, Texte aus unterschiedlichen Medien, oder Tweets von unterschiedlichen Nutzern.

Wieder vergleichen wir daher die Ergebnisse, die eine Anwendung dieser Verfahren auf das Poliblogs-Korpus produziert. Wir verwenden dabei eine einzelne Metrik aus dem Repertoire von textstat_lexdiv und benutzen diese, um einen Vergleich der lexikalicshen Komplexität konservativer und linker Blogs im Zeitverlauf durchzuführen.

lexdiversitaet <- textstat_lexdiv(poliblogs.dfm, measure = "U")
poliblogs.U <- left_join(lexdiversitaet, poliblogs.stats, by = c("document" = "Text")) %>% 
  group_by(day, rating) %>% 
  summarise(meanU = mean(U))
`summarise()` regrouping output by 'day' (override with `.groups` argument)
ggplot(poliblogs.U, aes(day, meanU, color = rating)) +
  geom_line() +
  geom_smooth(method = "loess", formula = "y ~ x", na.rm = TRUE) + 
  scale_colour_brewer(name = "Typ", palette = "Set1") + 
  ggtitle("Lexikalische Vielfalt in konservativen und linken U.S.-Blogs über die Zeit") + 
  xlab("Tag") + ylab("U-Mittelwert")

Das Ergebnis zeigt, dass die konservativen Blogs im Mittel lexikalisch viefältiger sind, als dies bei den linken Blogs aus dem Korpus der Fall ist. Um diesen Befund einordnen zu können, muss man sich vor Augen führen, dass beispielsweise die Verwendung von Eigennamen oder Jargon ebenso wie die Textlänge einen positven Einfluss auf diese Metriken haben, ohne dass man damit unbedingt das eingefangen hat, was man sich unter Vielfalt oder Komplexität vorstellt.

Lesbarkeitsindizes

Eine weitere Klasse von Text-Metriken, die sich für ein Dokument aufgrund seiner Wortzusammensetzung berechnen lassen, sind die sog. Lesbarkeitsindizes. Darunter versteht man Metriken, die anhand von textlichen Eigenschaften einen Zahlenwert berechnen, der die Leseschwierigkeit eines Dokumentes möglichst akkurat wiedergeben soll. Anwendung finden solche Indizes etwa im Bildungsbereich, wenn es um die Frage geht, welches Schwierigkeitsniveau eines Textes für Schüler angemessen ist, aber auch in der öffentlichen Verwaltung, wenn möglichst klare und zugängliche Sprache bspw. auf einer berhördlichen Website verwendet werden soll.

Die Kalkulation zahlreicher Lesbarkeitsindizes erfolgt in Quanteda mit textstat_readability. Auch hier wenden wir die Funktion zunächst auf das Sherlock Holmes-Korpus an, um einen Eindruck davon bekommen, wie sich die Leseschwierigkeit von einem Roman zum nächsten unterscheidet.

lesbarkeit <- textstat_readability(korpus, measure = "all")
lesbarkeit

Wie verhält es sich mit der Lesbarkeit der Blogsbeiträge aus dem Poliblogs-Korpus? Wieder vergleichen wir einerseits die sechs Blogs untereinander und andererseits die beiden ideologischen Richtungen (eher konservativ v. eher links). Im Gegensatz zu der Berechnung von Mittelwerten, die wir bei den Statistiken zur lexikalischen Vielfalt herangezogen haben, vergleichen wir hier direkt die Gesamtanzahl der Beiträge, um einen Eindruck der Werteverteilung zu erhalten. Zunächst berechnen wir dazu die Lesbarkeit nach der Flesch-Kincaid-Metrik und verbinden die Ergebnisse dann mit den zu dem Korpus gehörenden Metadaten aus dem Stats-Data-Frame.

lesbarkeit <- textstat_readability(poliblogs.korpus, measure = "Flesch.Kincaid")
poliblogs.FK <- left_join(lesbarkeit, poliblogs.stats, by = c("document" = "Text"))

Dann plotten wir anschließend das Ergebnis als kombiniertes Box- und Scatterplot.

ggplot(poliblogs.FK, aes(blog, Flesch.Kincaid)) + 
  geom_boxplot(outlier.shape = NA) + 
  scale_colour_brewer(name = "Typ", palette = "Set1") + 
  scale_y_continuous(limits = quantile(poliblogs.FK$Flesch.Kincaid, c(0.01, 0.99))) + 
  geom_jitter(aes(blog, Flesch.Kincaid, colour = rating), position = position_jitter(width = 0.4, height = 0), alpha = 0.2, size = 0.3, show.legend = F) + 
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) + 
  xlab("") + ylab("Flesch-Kincaid-Index") + 
  ggtitle("Leseleichtigkeit von Beiträgen in konservativen und linken U.S.-Blogs")

Zunächst einmal lässt sich unschwer erkennen, dass politische Blogbeiträge im Sinne der Flesch-Kincaid-Metrik anspruchsvolle Texte sind, die sich auf der schwierigsten Lesestufe bewegen. Allerdings sind die Texte in den konservativen Blogs – von der Quelle ‘mm’ einmal abgesehen – etwas einfacher gehalten, als dies in den linken Blogs der Fall ist.

Ein weiteres schönes Beispiel für den Nutzen solcher Metriken findet sich in der Dokumentation der Funktion textstat_readability. Hatte die Antrittsrede von George Washington im Jahr 1789 noch einen Flesh-Kincaid-Index von 28, so betrug der Wert bei der Antrittsrede von Donald Trump in 2017 nur noch 9 (was allerdings dem Trend seit Mitte des 20. Jhd. entspricht).

LS0tCnRpdGxlOiAiQXV0b21hdGlzaWVydGUgSW5oYWx0c2FuYWx5c2UgbWl0IFIiCmF1dGhvcjogIkNvcm5lbGl1cyBQdXNjaG1hbm4iCnN1YnRpdGxlOiBXb3J0LSB1bmQgVGV4dG1ldHJpa2VuCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCjwhLS0tClRvZG9zCiogWmVpdC1TTUw6IFRpdGVsIGR1cmNoIFZvbGx0ZXh0ZSBlcnNldHplbj8KKiBNZEItU01MOiBTcGxpdCBpbiBUcmFpbmdzLSB1bmQgVGVzdHNldD8KKiAKLS0+CgpJbiBkaWVzZW0gendlaXRlbiBLYXBpdGVsIHN0ZWh0IG51biBkaWUgQW5hbHlzZSB2b3IgV8O2cnRlcm4gdW5kIFRleHRlbiBpbSBNaXR0ZWxwdW5rdC4gQXVmIGRlbiBlcnN0ZW4gQmxpY2sgZXJzY2hlaW5lbiBkaWUgTWV0cmlrZW4sIGRpZSBoaWVyIHZvcmdlc3RlbGx0IHdlcmRlbiwgbcO2Z2xpY2hlcndlaXNlIG5pY2h0IGFscyBiZXNvbmRlcnMgcmVsZXZhbnQgZsO8ciBzb3ppYWx3aXNzZW5zY2hhZnRsaWNoZSBGcmFnZXN0ZWxsdW5nLiBEYXMgbGllZ3QgenVtIGVpbmVuIGRhcmFuLCBkYXMgd2lyIHVucyBhbiBkaWVzZXIgU3RlbGxlIG5vY2ggbmljaHQgbWl0IGFic3RyYWt0ZW4gS29uemVwdGVuIHdpZSBUaGVtZW4gb2RlciBTZW50aW1lbnQgYmVzY2jDpGZ0aWdlbiwgZGllIGluIGRlbiBmb2xnZW5kZW4gS2FwaXRlbCBpbSBNaXR0ZWxwdW5rdCBzdGVoZW4gd2VyZGVuLCBzb25kZXJuIG1pdCBBc3Bla3RlbiB3aWUgZGVyIEZyZXF1ZW56IHZvbiBCZWdyaWZmZW4gdW5kIGRlciDDhGhubGljaGtlaXQgdm9uIFRleHRlbiwgZGllIGF1Z2Vuc2NoZWlubGljaCB2aWVsbGVpY2h0IGRlciBMaW5ndWlzdGlrIG7DpGhlciBzaW5kLiBXb3J0LSB1bmQgVGV4dG1ldHJpa2VuIHNpbmQgYWJlciBhdXMgendlaSBHcsO8bmRlbiB2b24gQmVkZXV0dW5nOiBlcnN0ZW5zIGJpbGRlbiBzaWUgZGllIEdydW5kbGFnZSBkZXIgaMO2aGVyc3R1ZmlnZW4gVmVyZmFocmVuLCBlZ2FsIG9iIExleGlrb24tLCBUaGVtZW4tIG9kZXIgU2VudGltZW50YW5hbHlzZSwgdW5kIHp1IGFuZGVyZW4gbGFzc2VuIHNpY2ggYXVjaCBzY2hvbiBtaXQgaWhuZW4gaW50ZXJlc3NhbnRlIHNvemlhbHdpc3NlbnNjaGFmdGxpY2hlIEZyYWdlc3RlbGx1bmdlbiBiZWFyYmVpdGVuLgoKRWluaWdlIEJlaXNwaWVsZToKCiogV2VsY2hlIEJlZ3JpZmZlIHNpbmQgYmVzb25kZXJzIGRpc3Rpbmt0aXYgZsO8ciBlaW5lbiBwb2xpdGlzY2hlIFBhcnRlaT8KKiBXaWUgc3ByYWNobGljaCBrb21wbGV4IHNpbmQgTmFjaHJpY2h0ZW5iZWl0csOkZ2UgaW4gdW50ZXJzY2hpZWRsaWNoZW4gTWVkaWVuPwoqIFdlbGNoZSBhbmRlcmVuIEJlZ3JpZmZlIHNpbmQgbWl0IGVpbmVtIGdlc2VsbHNjaGFmdGxpY2hlbiBTY2hsw7xzc2VsYmVncmlmZiAoJ0tsaW1hJywgJ01pZ3JhdGlvbicsICdHZXJlY2h0aWdrZWl0JywgJ0RpZ2l0YWxpc2llcnVuZycpIHZlcmtuw7xwZnQgdW5kIHdpZSB2ZXLDpG5kZXJuIHNpY2ggZGllc2Ugw7xiZXIgZGllIFplaXQ/CiogV2llIMOkaG5saWNoIHNpbmQgc2ljaCBPbmxpbmUtS29tbWVudGFyZSB6dSB1bnRlcnNjaGllZGxpY2hlbiBUaGVtZW4/CgpEaWVzZSB1bmQgw6RobmxpY2hlIEZyYWdlbiB3ZXJkZW4gaW4gZGVuIGZvbGdlbmRlbiBLYXBpdGVsbiBhdWZnZWdyaWZmZW4gLS0genVuw6RjaHN0IHdlcmRlbiBhYmVyIGRpZSBGdW5rdGlvbmVuIHZvcmdlc3RlbGx0LCB3ZWxjaGUgZGllIEFyYmVpdCBtaXQgV8O2cnRlcm4gdW5kIFRleHRlbiBpbiBRdWFudGVkYSBlcm3DtmdsaWNoZW4uIERhenUgemllaGVuIHdpciBuZWJlbiBkZW4gYmVyZWl0cyBiZXfDpGhydGVuIFNoZXJsb2NrIEhvbG1lcy1EYXRlbiBhdWNoIHp3ZWkgd2VpdGVyZSBLb3Jwb3JhIGhlcmFuLgoKIyMjIyBJbnN0YWxsYXRpb24gdW5kIExhZGVuIGRlciBiZW7DtnRpZ3RlbiBSLUJpYmxpb3RoZWtlbiwgTGFkZW4gZGVzIEtvcnB1cywgQmVyZWNobmVuIGVpbmVyIERGTQoKWnVuw6RjaHN0IHdlcmRlbiB3aWVkZXIgZGllIG5vdHdlbmRpZ2VuIEJpYmxpb3RoZWtlbiBnZWxhZGVuLiBEYXMgUGFrZXQgW3NjYWxlc10oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvcGFja2FnZT1zY2FsZXMpIGtvbW10IGhpZXIgbmV1IGRhenUsIHVtIHVua29tcGxpemllcnQgVmFyaWFibGVuIHJlc2thbGllcmVuIHp1IGvDtm5uZW4uCgpgYGB7ciBJbnN0YWxsYXRpb24gdW5kIExhZGVuIGRlciBiZW7DtnRpZ3RlbiBSLUJpYmxpb3RoZWtlbiwgbWVzc2FnZSA9IEZBTFNFfQppZighcmVxdWlyZSgicXVhbnRlZGEiKSkge2luc3RhbGwucGFja2FnZXMoInF1YW50ZWRhIik7IGxpYnJhcnkoInF1YW50ZWRhIil9CmlmKCFyZXF1aXJlKCJ0aWR5dmVyc2UiKSkge2luc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpOyBsaWJyYXJ5KCJ0aWR5dmVyc2UiKX0KaWYoIXJlcXVpcmUoInNjYWxlcyIpKSB7aW5zdGFsbC5wYWNrYWdlcygic2NhbGVzIik7IGxpYnJhcnkoInNjYWxlcyIpfQppZighcmVxdWlyZSgiZ2dkZW5kcm8iKSkge2luc3RhbGwucGFja2FnZXMoImdnZGVuZHJvIik7IGxpYnJhcnkoImdnZGVuZHJvIil9CnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkpCmBgYAoKTnVuIHdpcmQgaW4gZWluZW0gendlaXRlbiBTY2hyaXR0IGRhcyBTaGVybG9jay1Lb3JwdXMgZ2VsYWRlbiwgd2VsY2hlcyB3aXIgYmVyZWl0cyBpbSBlcnN0ZW4gS2FwaXRlbCBtaXR0ZWxzIFtyZWFkdGV4dF0oaHR0cHM6Ly9yZWFkdGV4dC5xdWFudGVkYS5pby9yZWZlcmVuY2UvcmVhZHRleHQuaHRtbCkgYXVzIGRlbSBSb2h0ZXh0IGVyc3RlbGx0IHVuZCBhbHMgUkRhdGEtRGF0ZWkgZ2VzcGVpY2hlcnQgaGFiZW4uIEluIGRlciBSRGF0YS1EYXRlaSBpc3QgbmViZW0gZGVtIEtvcnB1cyBzZWxic3QgYXVjaCBlaW5lIG1pdCBbc3VtbWFyeV0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3F1YW50ZWRhL3ZlcnNpb25zLzEuNS4wL3RvcGljcy9zdW1tYXJ5LmNvcnB1cykgZXJzdGVsbHRlciBEYXRhIEZyYW1lIGVudGhhbHRlbiwgd2VsY2hlciBkaWUgS29ycHVzLU1ldGFkYXRlbiBlbnRow6RsdC4gWnVtIGVpbmVuIHNwYXJlbiB3aXIgdW5zIHNvIGRhcyBBbmxlZ2VuIGVpbmVzIEtvcnB1cywgenVtIGFuZGVyZW4gaXN0IGRhcyBSRGF0YS1Gb3JtYXQga29tcHJpbWllcnQsIHdhcyBzaWNoIGJlaSBUZXh0ZGF0ZW4gZHVyY2hhdXMgYmVpIGRlciBEYXRlaWdyw7bDn2UgYmVtZXJrYmFyIG1hY2h0LgoKQWxzIG7DpGNoc3RlcyBiZXJlY2huZW4gd2lyIGRhbm4gYXVmIEdydW5kbGFnZSBkZXMgUXVhbnRlZGEtS29ycHVzLU9iamVrdHMgd2llZGVyIGVpbmUgREZNICh2Z2wuIFtLYXBpdGVsIDFdKDFfZ3J1bmRsYWdlbi5odG1sKSksIGRhIHdpciBkaWVzZSBzcMOkdGVyIG5vY2ggYmVuw7Z0aWdlbi4gCgpgYGB7ciBMYWRlcyBkZXMgU2hlcmxvY2sgSG9sbWVzLUtvcnB1cyB1bmQgQmVyZWNobmVuIGVpbmVyIERGTX0KbG9hZCgiZGF0ZW4vc2hlcmxvY2svc2hlcmxvY2sua29ycHVzLlJEYXRhIikKbWVpbmUuZGZtIDwtIGRmbShrb3JwdXMsIHJlbW92ZV9udW1iZXJzID0gVFJVRSwgcmVtb3ZlX3B1bmN0ID0gVFJVRSwgcmVtb3ZlX3N5bWJvbHMgPSBUUlVFLCByZW1vdmUgPSBzdG9wd29yZHMoImVuZ2xpc2giKSkKYGBgCgoKIyMjIyBLb3Jrb3JkYW56ZW4gZXJzdGVsbGVuCgpadSBkZW4gZWluZmFjaHN0ZW4gRnVua3Rpb25lbiB2b24gUXVhbnRlZGEgZ2Vow7ZydCBkaWUgTcO2Z2xpY2hrZWl0LCBbS29ua29yZGFuemVuXShodHRwczovL2RlLndpa2lwZWRpYS5vcmcvd2lraS9Lb25rb3JkYW56XyhUZXh0d2lzc2Vuc2NoYWZ0KSkgKGF1Y2ggS1dJQyBnZW5hbm50KSB6dSBlcnN0ZWxsZW4sIGFsc28gZGllIFRleHRzdGVsbGUgZWluZXMgU3VjaHRlcm1zIHNvd2llIGRlc3NlbiB1bWdlYmVuZGVuIFNhdHpla29udGV4dCB6dSBleHRyYWhpZXJlbi4gU3RyZW5nZ2Vub21tZW4gZ2Vow7ZydCBkaWVzZSBGdW5rdGlvbiBuaWNodCBpbiBkZW4gQmVyZWljaCBkZXIgV29ydC0gdW5kIFRleHRtZXRyaWtlbiwgd2lyIGJlaGFuZGVsbiBzaWNoIGFiZXIgaGllciwgdW5kIG5pY2h0IGltIG9obmVoaW4gc2VociB1bWZhbmdyZWljaGVuIGVyc3RlbiBLYXBpdGVsLCBkYSB3aWUgYXVjaCBiZWkgZGVuIFdvcnRtZXRyaWtlbiBiZWkgS29ua29yZGFuemVuIGRlciBVbWdhbmcgbWl0IFfDtnJ0ZXJuIHVuZCBpaHIgS29udGV4dCBpbSBNaXR0ZWxwdW5rdCBzdGVodC4KCktvbmtvcmRhbnplbiBsYXNzZW4gc2ljaCBpbiBRdWFudGVkYSBmw7xyIGVpbnplbGVuIFfDtnJ0ZXIsIGFiZXIgYXVjaCBmw7xyIGdhbnplIFBocmFzZW4gZXJ6ZXVnZW4uIE9mdG1hbHMgaXN0IGRlciBFeHBvcnQgZWluZXIgS29ua29kYW56IChldHdhIGFscyBDU1YtRGF0ZWksIGRpZSBtaXQgRXhjZWwgZ2XDtmZmbmV0IHdlcmRlbiBrYW5uKSBuZWJlbiBkZXIgRGFyc3RlbGx1bmcgaW5uZXJoYWxiIHZvbiBSIGJlc29uZGVycyBuw7x0emxpY2guIERpZXMgZ2VzY2hpZWh0IGhpZXIgbWl0IGRlciBGdW5rdGlvbiBbd3JpdGVfZGVsaW0oKV0oaHR0cHM6Ly9yZWFkci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS93cml0ZV9kZWxpbS5odG1sKS4KCipBbm1lcmt1bmc6IERpZSBLb25rb3JkYW56IGthbm4gbWl0IGRlbSBrbGVpbmVuIFBmZWlsIHJlY2h0cyBvYmVuIGdlc2Nyb2xsdCB3ZXJkZW4uKgoKYGBge3IgRWluZmFjaGUgS29ua29yZGFueiBlcnN0ZWxsZW59CmtvbmtvcmRhbnogPC0ga3dpYyhrb3JwdXMsICJoYXBweSIpCmtvbmtvcmRhbnoKYGBgCgpLb25rb3JkYW56ZW4gYmVzdGVoZW4gYXVzIGRlbiBNZXRhZGF0ZW4gKFRleHRuYW1lIHVuZCBQb3NpdGlvbiksIGRlbSBsaW5rZW4gS29udGV4dCwgZGVtIFN1Y2h0ZXJtLCBzb3dpZSBkZW0gcmVjaHRlbiBLb250ZXh0LiBEaWUgZXJzdGUgS29ua29yZGFueiBlbnRow6RsdCBhbGxlIFZvcmtvbW1uaXNzZSBkZXMgQmVncmlmZnMgJ2RhdGEnIGltIEtvcnB1cy4gCgpgYGB7ciBLb21wbGV4ZXJlIEtvbmtvcmRhbnplbiBlcnN0ZWxsZW59CmtvbmtvcmRhbnogPC0ga3dpYyhrb3JwdXMsIHBocmFzZSgiSm9obnxNYXJ5IFtBLVpdKyIpLCB2YWx1ZXR5cGUgPSAicmVnZXgiLCBjYXNlX2luc2Vuc2l0aXZlID0gRkFMU0UpCmtvbmtvcmRhbnoKa29ua29yZGFueiA8LSBrd2ljKGtvcnB1cywgYygibG9nKiIsICJlbW90KiIpLCB3aW5kb3cgPSAxMCwgY2FzZV9pbnNlbnNpdGl2ZSA9IEZBTFNFKQprb25rb3JkYW56CndyaXRlX2RlbGltKGtvbmtvcmRhbnosIHBhdGggPSAia29ua29yZGFuei5jc3YiLCBkZWxpbSA9ICI7IikgIyBEYXRlaSBpc3QgRXhjZWwta29tcGF0aWJlbApgYGAKCkRpZSB6d2VpdGUgS29ua29yZGFueiBlbnRow6RsdCBWb3Jrb21tbmlzc2UgZGVyIE5hbWVuICdKb2huJyB1bmQgJ01hcnknIGdlZm9sZ3Qgdm9uIGVpbmVtIHdlaXRlcmVuIFdvcnQgaW4gR3Jvw59zY2hyZWlidW5nIChpLmQuUi4gZGVyIE5hY2huYW1lKS4gRGllIGRyaXR0ZSBLb25rb3JkYW56IGVudGjDpGx0IHNjaGxpZcOfbGljaCBkaWUgV29ydGZyYWdtZW50ZSAnbG9nJyB1bmQgJ2Vtb3QnLCBhbHNvIFfDtnJ0ZXIgd2llICdsb2dpY2FsJyB1bmQgJ2Vtb3Rpb25hbCcsIGFiZXIgYXVjaCBkaWUgUGx1cmFsZm9ybSAnZW1vdGlvbnMnLiBTdHJlbmdnZW5vbW1lbiBoYW5kZWx0IGVzIHNpY2ggaGllcmJlaSBuaWNodCB1bSBXb3J0c3TDpG1tZSwgd2VpbCBkaWUgRmxleGlvbnNmb3JtIGJlaSB1bnJlZ2VsbcOkw59pZ2VuIFfDtnJ0ZXJuIGdhbnogdm9tIExlbW1hIGFid2VpY2h0ICh2Z2wuICdnbycgdW5kICd3ZW50JykuIEluIGRlbiBtZWlzdGVuIHNvemlhbHdpc3NlbnNjaGFmdGxpY2hlbiBBbndlbmR1bmdzc3plbmFyaWVuIGlzdCBlcyBhYmVyIGJlcmVpdHMgYXVzcmVpY2hlbmQsIGR1cmNoIGRpZSBWZXJ3ZW5kdW5nIHZvbiBQbGF0emhhbHRlcm4gKCopIHZlcnNjaGllZGVuIFdvcnR2YXJpYW50ZW4genUgaWRlbnRpZml6aWVyZW4uIEhpZXIgYnJpbmd0IFF1YW50ZWRhIGVpbmUgUmVpaGUgbsO8dHpsaWNoZXIgRWlnZW5zY2hhZnRlbiBtaXQsIGRpZSBpbiBkZXIgRG9rdW1lbnRhdGlvbiB2b24gW2t3aWMoKV0oaHR0cDovL2RvY3MucXVhbnRlZGEuaW8vcmVmZXJlbmNlL2t3aWMuaHRtbCkgZ2VuYXUgYmVzY2hyaWViZW4gd2VyZGVuLiAKCkFscyBuw6RjaHN0ZXMgYmVyZWNobmVuIHdpciBkaWUgSMOkdWZpZ2tlaXQgdW5kIERpc3BlcnNpb24gdm9uIFRva2VucyBwcm8gRXJ6w6RobHVuZywgd2VsY2hlIGRpZSBCZWdyaWZmZSAnZGFyaycgdW5kICdsaWdodCcgZW50aGFsdGVuLiAKCmBgYHtyIEjDpHVmaWdrZWl0IHZvbiBUb2tlbnMgYmVzdGltbWVuIGFuaGFuZCB2b24gS1dJQ30KdGVybTEgPC0ga3dpYyhrb3JwdXMsICJkYXJrIiwgdmFsdWV0eXBlID0gInJlZ2V4IiwgY2FzZV9pbnNlbnNpdGl2ZSA9IEZBTFNFKSAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICBjb3VudChkb2NuYW1lLCBuYW1lID0gIlRyZWZmZXIiKSAlPiUgCiAgbXV0YXRlKFByb3plbnRhbnRlaWwgPSBUcmVmZmVyLyhrb3JwdXMuc3RhdHMkVG9rZW5zLzEwMCksIFN1Y2h0ZXJtID0gImRhcmsiKSAlPiUgCiAgYXJyYW5nZShkZXNjKFByb3plbnRhbnRlaWwpKQp0ZXJtMiA8LSBrd2ljKGtvcnB1cywgImxpZ2h0IiwgdmFsdWV0eXBlID0gInJlZ2V4IiwgY2FzZV9pbnNlbnNpdGl2ZSA9IEZBTFNFKSAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICBjb3VudChkb2NuYW1lLCBuYW1lID0gIlRyZWZmZXIiKSAlPiUgCiAgbXV0YXRlKFByb3plbnRhbnRlaWwgPSBUcmVmZmVyLyhrb3JwdXMuc3RhdHMkVG9rZW5zLzEwMCksIFN1Y2h0ZXJtID0gImxpZ2h0IikgJT4lIAogIGFycmFuZ2UoZGVzYyhQcm96ZW50YW50ZWlsKSkKdGVybTEKdGVybTIKYGBgCgpXaWVkZXIgd2VuZGVuIHdpciB6dW7DpGNoc3QgZGllIEZ1bmt0aW9uIGt3aWMoKSBhbiwgYWxsZXJkaW5ncyBoaWVyIGluIEtvbWJpbmF0aW9uIG1pdCBtZWhyZXJlbiBGdW5rdGlvbmVuIGF1cyBkZW0gUGFrZXQgZHBseXIgKHRpZHl2ZXJzZSkuIERpZXNlIEZ1bmt0aW9uZW4gaGFiZW4gbmljaHRzIG1pdCBRdWFudGVkYSB6dSB0dW4sIHNvbmRlcm4gc2luZCBmw7xyIGRpZSBVbWZvcm11bmcgamVnbGljaGVyIERhdGVuIGluIFIgbsO8dHpsaWNoICh3ZXIgbWVociB3aXNzZW4gbcO2Y2h0ZSwgc29sbHRlIHNpY2ggW2RpZXNlcyBCdWNoXShodHRwOi8vcjRkcy5oYWQuY28ubnovKSBhbnNjaGF1ZW4pLiBXw6RocmVuZCB6dXZvciBlaW5mYWNoIGRpZSByZXN1bHRpZXJlbmRlIEtvbmtvcmRhbnogYXVzZ2VnZWJlbiB3dXJkZSwgd2lyZCBkYXMgRXJnZWJuaXMgamV0enQgbWl0IEhpbGZlIGRlciBGdW5rdGlvbmVuIFtncm91cF9ieSgpXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvZHBseXIvdmVyc2lvbnMvMC43LjYvdG9waWNzL2dyb3VwX2J5KSwgW3N1bW1hcmlzZSgpXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvZHBseXIvdmVyc2lvbnMvMC43LjYvdG9waWNzL3N1bW1hcmlzZSksIFttdXRhdGUoKV0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL2RwbHlyL3ZlcnNpb25zLzAuNy42L3RvcGljcy9tdXRhdGUpIHVuZCBbYXJyYW5nZSgpXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvZHBseXIvdmVyc2lvbnMvMC43LjYvdG9waWNzL2FycmFuZ2UpIHdlaXRlciB2ZXJhcmJlaXRldC4gRGFiZWkgbWFjaGVuIHdpciB1bnMgZGllIFRhdHNhY2hlIHp1bnV0emUsIGRhc3MgaW4gZWluZW0gS1dJQy1FcmdlYm5pcyBiZXJlaXRzIGFsbGUgSW5mb3JtYXRpb25lbiB2b3JsaWVnZW4sIHVtIGRpZSBhYnNvbHV0ZSB1bmQgcmVsYXRpdmUgRnJlcXVlbnogZWluZXMgQmVncmlmZnMgKGhpZXIgJ2xpZ2h0JyB1bmQgJ2RhcmsnKSBpbiBlaW5lciBSZWloZSB2b24gRG9rdW1lbnRlbiB6dSBiZXJlY2huZW4uIERlbiBQcm96ZW50YW50ZWlsIGhhYmVuIHdpciBkYWJlaSBlaW5mYWNoIG1pdHRlbHMgRHJlaXNhdHogYWJnZWxlaXRldCAobWl0ICpUcmVmZmVyLyhrb3JwdXMuc3RhdHMkVG9rZW5zLzEwMCkqKS4KCldvcnRmcmVxdWVuemVuIGxhc3NlbiBzaWNoIGFsbGVyZGluZ3Mgd2VzZW50bGljaCBlaW5mYWNoZXIgZHVyY2ggZGllIFF1YW50ZWRhLWVpZ2VuZW4gRnVua3Rpb24gW3RleHRzdGF0X2ZyZXF1ZW5jeV0oaHR0cHM6Ly9xdWFudGVkYS5pby9yZWZlcmVuY2UvdGV4dHN0YXRfZnJlcXVlbmN5Lmh0bWwpIHVtc2V0emVuLCBkaWUgd2lyIGZvbGdlbmQgYXVjaCBrb25zZXF1ZW50IG51dHplbiB3ZXJkZW4gLS0gYXVjaCBkYXp1IGdsZWljaCBub2NoIGV0d2FzIG1laHIuIAoKWnVuw6RjaHN0IHBsb3R0ZW4gd2llIGRpZSBhYnNvbHV0ZSB1bmQgcmVsYXRpdmVuIEjDpHVmaWdrZWl0IGRlciBiZWlkZW4gQmVncmlmZmUuCgpgYGB7ciBBYnNvbHV0ZSB1bmQgcmVsYXRpdmUgSMOkdWZpZ2tlaXQgcGxvdHRlbn0KdGVybWUua29tYmluaWVydCA8LSBiaW5kX3Jvd3ModGVybTEsIHRlcm0yKSAlPiUgCiAgbXV0YXRlKGRvY25hbWUgPSBmYWN0b3IoZG9jbmFtZSwgbGV2ZWxzID0gbGV2ZWxzKGtvcnB1cy5zdGF0cyRUZXh0KSkpCmdncGxvdCh0ZXJtZS5rb21iaW5pZXJ0LCBhZXMoZG9jbmFtZSwgVHJlZmZlciwgZ3JvdXAgPSBTdWNodGVybSwgY29sID0gU3VjaHRlcm0pKSArIAogIGdlb21fbGluZShzaXplID0gMSkgKyAKICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgdmp1c3QgPSAxLCBoanVzdCA9IDEpKSArIAogIGdndGl0bGUoIkjDpHVmaWdrZWl0IGRlciBTdWNoYmVncmlmZmUgXCJkYXJrXCIgdW5kIFwibGlnaHRcIiBwcm8gUm9tYW4gKGFic29sdXQpIikgKyAKICB4bGFiKCJSb21hbiIpICsgeWxhYigiV8O2cnRlciAoZ2VzYW10KSIpCmdncGxvdCh0ZXJtZS5rb21iaW5pZXJ0LCBhZXMoZG9jbmFtZSwgUHJvemVudGFudGVpbCwgZ3JvdXAgPSBTdWNodGVybSwgY29sID0gU3VjaHRlcm0pKSArIAogIGdlb21fbGluZShzaXplID0gMSkgKyAKICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgdmp1c3QgPSAxLCBoanVzdCA9IDEpKSArIAogIGdndGl0bGUoIkjDpHVmaWdrZWl0IGRlciBTdWNoYmVncmlmZmUgXCJkYXJrXCIgdW5kIFwibGlnaHRcIiBwcm8gUm9tYW4gKHJlbGF0aXYpIikgKyAKICB4bGFiKCJSb21hbiIpICsgeWxhYigiV8O2cnRlciAoJSkiKQpgYGAKCldpciBzZWhlbiB6d2VpIHVudGVyc2NoaWVkbGljaGUgQmVyZWNobnVuZ2VuOiBkYXMgZXJzdGUgUGxvdCB6ZWlndCBkaWUgYWJzb2x1dGUgSMOkdWZpZ2tlaXQgZGVyIGJlaWRlbiBCZWdyaWZmZSwgZGFzIHp3ZWl0ZSBoaW5nZWdlbiBkZW4gcmVsYXRpdmVuIFByb3plbnphbnRlaWwgZGVzIEJlZ3JpZmZzIGFuIGRlciBHZXNhbXR3b3J0emFobCBkZXMgamV3ZWlsaWdlbiBSb21hbnMuIFdpZXNvIHNpbmQgZGllIGJlaWRlbiBQbG90cyBuYWhlenUgaWRlbnRpc2NoPyBEaWVzIGhhdCBtaXQgZGVyIGltIFZlcmdsZWljaCByZWxhdGl2IMOkaG5saWNoZW4gV29ydGFuemFobCBkZXIgUm9tYW5lIHVudGVyZWluYW5kZXIgenUgdHVuICh6d2lzY2hlbiA4LDUwMCB1bmQgMTIsMDAwIFRva2VucykuIFNpbmQgendlaSBUZWlsa29ycG9yYSB2b24gc2VociB1bnRlcnNjaGllZGxpY2hlciBHcsO2w59lLCBpc3QgZWluZSBOb3JtYWxpc2llcnVuZyBkZXIgV29ydGZyZXF1ZW56ZW4gZXh0cmVtIHdpY2h0aWcsIGRhIHNvbnN0IGRpZSBFcmdlYm5pc3NlIG1hc3NpdiB2ZXJ6ZXJydCB3ZXJkZW4uIEF1Y2ggc28gZXJnZWJlbiBzaWNoIGR1cmNoYXVzIFVudGVyc2NoaWVkZSwgd2VubiBtYW4gZXR3YSBkZW4gQW50ZWlsIHZvbiAnVGhlIEFkdmVudHVyZSBvZiB0aGUgU3BlY2tsZWQgQmFuZCcgdW5kICdUaGUgQWR2ZW50dXJlIG9mIHRoZSBDb3BwZXIgQmVlY2hlcycgdmVyZ2xlaWNodC4gV8OkaHJlbmQgZGllIEFuc3phaGwgZGVyIGFic29sdXRlbiBUcmVmZmVyIGF1ZiAnbGlnaHQnIGluIGJlaWRlbiBSb21hbmVuIGlkZW50aXNjaCBpc3QsIGbDpGxsdCBkZXIgcmVsYXRpdmUgQW50ZWlsIGJlaSAnU3BlY2tsZWQgQmFuZCcgaW0gVmVyZ2xlaWNoIGFiLiAKCldhcyB0dW4sIHdlbm4gbWFuIHNpY2ggd2VuaWdlciBmw7xyIGRpZSBIw6R1Zmlna2VpdCBhbHMgZsO8ciBkaWUgUG9zaXRpb24gZGVyIFN1Y2h0ZXJtZSBpbnRlcmVzc2llcnQ/IERhenUga2FubiBkYXMgUGxvdHRlbiBkZXIgQmVnaWZmc2Rpc3BlcnNpb24gYWxzICd4cmF5LXBsb3QnIG7DvHR6bGljaCBzZWluLCB3b3p1IGRpZSBGdW5rdGlvbiBbdGV4dHBsb3RfeHJheV0oaHR0cHM6Ly9kb2NzLnF1YW50ZWRhLmlvL3JlZmVyZW5jZS90ZXh0cGxvdF94cmF5Lmh0bWwpIGV4aXN0aWVydC4gRGllIFgtQWNoc2Ugc3RlbGx0IGhpZXJiZWkgZGllIFBvc2l0aW9uIGlubmVyaGFsYiBkZXMgVGV4dGVzIGRhciwgYW4gZGVtIGRlciBTdWNoYmVncmlmZiB2b3Jrb21tdC4gCgpgYGB7ciBMZXhpa2FsaXNjaGUgRGlzcGVyc2lvbiBwbG90dGVufQp0ZXh0cGxvdF94cmF5KGt3aWMoa29ycHVzLCAiZGFyayIsIHZhbHVldHlwZSA9ICJyZWdleCIsIGNhc2VfaW5zZW5zaXRpdmUgPSBGQUxTRSkpICsgCiAgZ2d0aXRsZSgiTGV4aWthbGlzY2hlIERpc3BlcnNpb24gdm9uIFwiZGFya1wiIGluIFNoZXJsb2NrIEhvbG1lcyIpCnRleHRwbG90X3hyYXkoa3dpYyhrb3JwdXMsICJsaWdodCIsIHZhbHVldHlwZSA9ICJyZWdleCIsIGNhc2VfaW5zZW5zaXRpdmUgPSBGQUxTRSkpICsgCiAgZ2d0aXRsZSgiTGV4aWthbGlzY2hlIERpc3BlcnNpb24gdm9uIFwibGlnaHRcIiBpbiBTaGVybG9jayBIb2xtZXMiKQpgYGAKCgojIyMjIFdvcnRmcmVxdWVuemVuCgpJbSBlcnN0ZSBLYXBpdGVsIHd1cmRlIGJlcmVpdHMga3VyeiBlcmzDpHV0ZXJ0LCB3aWUgbWl0dGVscyBbdG9wZmVhdHVyZXNdKGh0dHBzOi8vcXVhbnRlZGEuaW8vcmVmZXJlbmNlL3RvcGZlYXR1cmVzLmh0bWwpIFdvcnRmcmVxdWVuemVuIGluIGVpbmVtIEtvcnB1cyBvZGVyIGVpbmVyIERGTSBlcm1pdHRlbHQgd2VyZGVuIGvDtm5uZW4sIHVuZCB2b3JoZXJpZ2VuIEFic2Nobml0dCBoYWJlbiB3aXIgZGllcyBkdXJjaCBkZW4gZXR3YXMgaGVtZHPDpHJtZXJsaWdlbiBFaW5zYXR6IHZvbiBba3dpY10oaHR0cHM6Ly9xdWFudGVkYS5pby9yZWZlcmVuY2Uva3dpYy5odG1sKSByZWFsaXNpZXJ0LiBEaWUgRnVua3Rpb24gW3RleHRzdGF0X2ZyZXF1ZW5jeV0oaHR0cHM6Ly9xdWFudGVkYS5pby9yZWZlcmVuY2UvdGV4dHN0YXRfZnJlcXVlbmN5Lmh0bWwpIGxpZWZlcnQgaW0gVmVyZ2xlaWNoIHdlc2VudGxpY2ggcHLDpHppc2VyZSB1bmQgZGV0YWlsbGllcnRlcmUgRXJnZWJpc3NlIGFscyBkaWVzZSBiZWlkZW4gT3B0aW9uZW4uIFp1c8OkdHpsaWNoIHp1ciBhYnNvbHV0ZW4gV29ydGZyZXF1ZW56IGVyaMOkbHQgbWFuIG7DpGNobGljaCBub2NoIGRlbiBSYW5nIChyYW5rKSwgZGllIEFuemFobCBkZXIgRG9rdW1lbnRlLCBpbiBkZW5lbiBkYXMgRmVhdHVyZSB2b3Jrb21tdCAoZG9jZnJlcSkgc293aWUgTWV0YWRhdGVuLCBuYWNoIGRlbmVuIGJlaSBkZXIgWsOkaGx1bmcgZ2VmaWx0ZXJ0IHd1cmRlIChncm91cCkuIEdydW5kc8OkdHpsaWNoIGlzdCBbdGV4dHN0YXRfZnJlcXVlbmN5XShodHRwczovL3F1YW50ZWRhLmlvL3JlZmVyZW5jZS90ZXh0c3RhdF9mcmVxdWVuY3kuaHRtbCkgZ2VnZW7DvGJlciBbdG9wZmVhdHVyZXNdKGh0dHBzOi8vcXVhbnRlZGEuaW8vcmVmZXJlbmNlL3RvcGZlYXR1cmVzLmh0bWwpIHp1IGJldm9yenVnZW4uCgpgYGB7ciBEZXRhaWxsaWVydGUgV29ydGjDpHVmaWdrZWl0ZW4gYmVyZWNobmVufQp3b3J0aGFldWZpZ2tlaXRlbiA8LSB0ZXh0c3RhdF9mcmVxdWVuY3kobWVpbmUuZGZtKQpoZWFkKHdvcnRoYWV1Zmlna2VpdGVuKQpgYGAKRWJlbnNvIGxhc3NlbiBzaWNoIG1pdCBbdGV4dHN0YXRfZnJlcXVlbmN5XShodHRwczovL3F1YW50ZWRhLmlvL3JlZmVyZW5jZS90ZXh0c3RhdF9mcmVxdWVuY3kuaHRtbCkgYXVjaCBncnVwcGllcnRlIFdvcnRmcmVxdWVuemVuIG5hY2ggR3J1cHBlbiBiaWxkZW4uCgpgYGB7ciBXb3J0aMOkdWZpZ2tlaXRlbiBuYWNoIFJvbWFuIGJlcmVjaG5lbn0KdGV4dHN0YXRfZnJlcXVlbmN5KG1laW5lLmRmbSwgbiA9IDEwLCBncm91cHMgPSAiVGV4dG51bW1lciIpCmBgYAoKVW0genUgZGVtb25zdHJpZXJlbiwgd28gZGllIFN0w6Rya2VuIHZvbiBbdGV4dHN0YXRfZnJlcXVlbmN5XShodHRwczovL3F1YW50ZWRhLmlvL3JlZmVyZW5jZS90ZXh0c3RhdF9mcmVxdWVuY3kuaHRtbCkgbGllZ2VuLCB3ZW5kZW4gd2lyIHVucyBlcnN0bWFsaWcgdm9tIGVycHJvYnRlbiBTaGVybG9jayBIb2xtZXMtS29ycHVzIGFiIHVuZCBsYWRlbiBlaW5lbiBuZXVlbiBEYXRlbnNhdHosIG7DpG1saWNoIGRhcyBzY2hvbiBlaW4gd2VuaWcgYW5nZWrDpGhydGUgUG9saWJsb2dzLUtvcnB1cy4gRGFiZWkgaGFuZGVsdCBlcyBzaWNoIHVtIGVpbmUgU2FtbWx1bmcgcG9saXRpc2NoZXIgQmxvZ2VpbnRyw6RnZSBhdXMgZGVtIFtVLlMuLVdhaGxrYW1wZiAyMDA4XShodHRwczovL2RlLndpa2lwZWRpYS5vcmcvd2lraS9QciVDMyVBNHNpZGVudHNjaGFmdHN3YWhsX2luX2Rlbl9WZXJlaW5pZ3Rlbl9TdGFhdGVuXzIwMDgpLCBpbiBkZW0gSm9obiBNY0NhaW4gZ2VnZW4gQmFyYWNrIE9iYW1hIHVudGVybGFnLiBPYndvaGwgc2ljaCBwb2xpdGlzY2ggc2VpdGRlbSB2aWVsZXMgdmVyw6RuZGVydCB1bmQgQmxvZ3MgZ2VnZW7DvGJlciBTb2NpYWwgTWVkaWEtUGxhdHRmb3JtZW4gd2llIEZhY2Vib29rIHVuZCBUd2l0dGVyIGtsYXIgYW4gU3RlbGxlbndlcnQgdmVybG9yZW4gaGFiZW4sIGlzdCBkYXMgQmVpc3BpZWwgaW1tZXIgbm9jaCBpbnRlcmVzc2FudC4KClp1bsOkY2hzdCB3ZXJmZW4gd2lyIGVpbmVuIGVyc3RlbiBCbGljayBhdWYgZGFzIEtvcnB1cyB1bmQgZGllIGJlaWdlZsO8Z3RlbiBNZXRhZGF0ZW4uIAoKYGBge3IgTGFkZW4gZGVzIFBvbGlibG9ncy1Lb3JwdXN9CmxvYWQoImRhdGVuL2Jsb2dzL3BvbGlibG9nczIwMDguUkRhdGEiKQphcy5kYXRhLmZyYW1lKHBvbGlibG9ncy5zdGF0cykKYGBgCgpOYWNoZGVtIHdpciBkYXMgYmVyZWl0cyB2b3JiZXJlaXRldGUgS29ycHVzIGluc3BpemllcnQgaGFiZW4sIGtvbnN0cnVpZXJlbiB3aXIgenVuw6RjaHN0IGVpbmUgREZNIHVudGVyIEVudGZlcm51bmcgdm9uIGbDvHIgdW5zIHdlbmlnIHJlbGV2YW50ZW4gRmVhdHVyZXMgdW5kIGthbGt1bGllcmVuIGRhbm4gbWl0dGVscyAgW3RleHRzdGF0X2ZyZXF1ZW5jeV0oaHR0cHM6Ly9xdWFudGVkYS5pby9yZWZlcmVuY2UvdGV4dHN0YXRfZnJlcXVlbmN5Lmh0bWwpIGVpbmUgRnJlcXVlbnp0YWJlbGxlLiBEYWJlaSBudXR6ZW4gd2lyIGRhcyAnZ3JvdXBzJy1Bcmd1bWVudCB2b24gIFt0ZXh0c3RhdF9mcmVxdWVuY3ldKGh0dHBzOi8vcXVhbnRlZGEuaW8vcmVmZXJlbmNlL3RleHRzdGF0X2ZyZXF1ZW5jeS5odG1sKSBhdXMsIHVtIGdlemllbHQgZ2V0cmVubnRlIEZyZXF1ZW56ZW4gZsO8ciBsaW5rZSB1bmQgcmVjaHRlIEJsb2dzLCBzdGF0dCBmw7xyIGFsbGUgRG9rdW1lbnRlIGltIEtvcnB1cyB6dSBiZXJlY2huZW4uIAoKYGBge3IgQmVyZWNobmVuIGVpbmVyIERGTSBmw7xyIGRhcyBQb2xpYmxvZ3MtS29ycHVzfQpwb2xpYmxvZ3MuZGZtIDwtIGRmbShwb2xpYmxvZ3Mua29ycHVzLCAKICAgICAgICAgICAgICAgICAgICAgcmVtb3ZlX251bWJlcnMgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgcmVtb3ZlX3B1bmN0ID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgICAgIHJlbW92ZV9zeW1ib2xzID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgICAgIHJlbW92ZSA9IGMoc3RvcHdvcmRzKCJlbmdsaXNoIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjYW4iLCAibWF5IiwgInNhaWQiLCAib25lIiwgImp1c3QiLCAibm93IiwgIm5ldyIsICJldmVuIiwgImxpa2UiLCAiZ2V0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYWxzbyIsICJmaXJzdCIsICJsYXN0IiwgIm11Y2giLCAid2VsbCIpKQpwb2xpYmxvZ3MuZnJlcXMgPC0gdGV4dHN0YXRfZnJlcXVlbmN5KHBvbGlibG9ncy5kZm0sIGdyb3VwcyA9ICJyYXRpbmciKSAlPiUgYXJyYW5nZShyYW5rLCBncm91cCkKaGVhZChwb2xpYmxvZ3MuZnJlcXMsIDEwMCkKYGBgCgpOdW4ga8O2bm5lbiB3aXIgZGllIGZyZXF1ZW50ZSBCZWdyaWZmZSBpbiBkZW4gcnVuZCAxMy4wMDAga29uc2VydmF0aXZlbiB1bmQgbGlua2VuIFUuUy4tQmxvZ2VpbnRyw6RnZW4gcGxvdHRlbi4gRGFiZWkgemVpZ3QgZGFzIGVyc3RlIFBsb3QgZGllIHBvcHVsw6Ryc3RlbiBCZWdyaWZmZSBpbiBrb25zZXJ2YXRpdmVuIEJsb2dzIHVuZCBkYXMgendlaXRlIFBsb3QgZGllIHBvcHVsw6Ryc3RlbiBCZWdyaWZmZSBpbiBsaW5rZW4gQmxvZ3MsIHdvYmVpIGRpZSBGcmVxdWVueiBiZWkgamVkZW0gQmVncmlmZiBmw7xyIGRpZSBqZXdlaWxzIGFuZGVyZSAnU2VpdGUnIGF1Y2ggZ2xlaWNoIG1pdGFuZ2V6ZWlndCB3aXJkLiAKCmBgYHtyIEZyZXF1ZW50ZSBCZWdyaWZmZSBpbiBrb25zZXJ2YXRpdmVuIHVuZCBsaW5rZW4gVS5TLi1CbG9ncyBwbG90dGVufQpmcmVxcy5jb24gPC0gZmlsdGVyKHBvbGlibG9ncy5mcmVxcywgZ3JvdXAgPT0gIkNvbnNlcnZhdGl2ZSIpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHNlbGVjdChmZWF0dXJlLCBmcmVxdWVuY3kpCmZyZXFzLmxpYiA8LSBmaWx0ZXIocG9saWJsb2dzLmZyZXFzLCBncm91cCA9PSAiTGliZXJhbCIpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHNlbGVjdChmZWF0dXJlLCBmcmVxdWVuY3kpCmZyZXFzIDwtIGxlZnRfam9pbihmcmVxcy5jb24sIGZyZXFzLmxpYiwgYnkgPSAiZmVhdHVyZSIpICU+JSBoZWFkKDI1KSAlPiUgYXJyYW5nZShmcmVxdWVuY3kueCkgJT4lIG11dGF0ZShmZWF0dXJlID0gZmFjdG9yKGZlYXR1cmUsIGZlYXR1cmUpKQpnZ3Bsb3QoZnJlcXMpICsKICBnZW9tX3NlZ21lbnQoYWVzKHg9ZmVhdHVyZSwgeGVuZD1mZWF0dXJlLCB5PWZyZXF1ZW5jeS54LCB5ZW5kPWZyZXF1ZW5jeS55KSwgY29sb3I9ImdyZXkiKSArCiAgZ2VvbV9wb2ludChhZXMoeD1mZWF0dXJlLCB5PWZyZXF1ZW5jeS54KSwgY29sb3IgPSAicmVkIiwgc2l6ZSA9IDMgKSArCiAgZ2VvbV9wb2ludChhZXMoeD1mZWF0dXJlLCB5PWZyZXF1ZW5jeS55KSwgY29sb3IgPSAibGlnaHRibHVlIiwgc2l6ZSA9IDMgKSArCiAgZ2d0aXRsZSgiSMOkdWZpZ2UgQmVncmlmZmUgaW4ga29uc2VydmF0aXZlbiB1bmQgbGlua2VuIFUuUy4tQmxvZ3MgaW0gV2FobGthbXBmIDIwMDgiKSArIAogIHhsYWIoIiIpICsgeWxhYigiV29ydGZyZXF1ZW56IikgKyAKICBjb29yZF9mbGlwKCkKZnJlcXMgPC0gbGVmdF9qb2luKGZyZXFzLmNvbiwgZnJlcXMubGliLCBieSA9ICJmZWF0dXJlIikgJT4lIGhlYWQoMjUpICU+JSBhcnJhbmdlKGZyZXF1ZW5jeS55KSAlPiUgbXV0YXRlKGZlYXR1cmUgPSBmYWN0b3IoZmVhdHVyZSwgZmVhdHVyZSkpCmdncGxvdChmcmVxcykgKwogIGdlb21fc2VnbWVudChhZXMoeD1mZWF0dXJlLCB4ZW5kPWZlYXR1cmUsIHk9ZnJlcXVlbmN5LnksIHllbmQ9ZnJlcXVlbmN5LngpLCBjb2xvcj0iZ3JleSIpICsKICBnZW9tX3BvaW50KGFlcyh4PWZlYXR1cmUsIHk9ZnJlcXVlbmN5LnkpLCBjb2xvciA9ICJibHVlIiwgc2l6ZSA9IDMgKSArCiAgZ2VvbV9wb2ludChhZXMoeD1mZWF0dXJlLCB5PWZyZXF1ZW5jeS54KSwgY29sb3IgPSAibGlnaHRjb3JhbCIsIHNpemUgPSAzICkgKwogIGdndGl0bGUoIkjDpHVmaWdlIEJlZ3JpZmZlIGluIGxpbmtlbiB1bmQga29uc2VydmF0aXZlbiBVLlMuLUJsb2dzIGltIFdhaGxrYW1wZiAyMDA4IikgKyAKICB4bGFiKCIiKSArIHlsYWIoIldvcnRmcmVxdWVueiIpICsgCiAgY29vcmRfZmxpcCgpCmBgYAoKV2llIHNpY2ggc2NobmVsbCBlcmtlbm5lbiBsw6Rzc3QsIGthbm4gZGllc2VzIEVyZ2VibmlzIGR1cmNoIGVpbmUgc3lzdGVtYXRpc2NoZXJlIFN0b3B3b3J0ZW50ZmVybnVuZyBzb3dpZSBkaWUgR2V3aWNodHVuZyBkZXIgREZNIG5vY2ggd2VpdGVyIG9wdGltaWVydCB3ZXJkZW4uIFdpciBrb21tZW4gYXVmIGRpZXNlIE3DtmdsaWNoa2VpdGVuIHNww6R0ZXIgbm9jaCB6dXLDvGNrLCB3ZW5kZW4gdW5zIGFiZXIgbnVuIGVpbmVtIGFuZGVyZW4gVmVmYWhyZW4genUgLS0gZGVyIEVybWl0dGx1bmcgdm9uIEtvbGxva2F0aW9uZW4uIAoKIyMjIyBLb2xsb2thdGlvbmVuIAoKV2lyIGdlaGVuIG51biB6dSBkZW4gc29nZW5hbm50ZW4gVGV4dHN0YXRpc3Rpa2VuIMO8YmVyLiBEYWJlaSBoYW5kZWx0IGVzIHNpY2ggdW0gRnVua3Rpb25lbiBhbmhhbmQgZGVyZXIgc2ljaCBXw7ZydGVyIHVuZCBUZXh0ZSBtaXQgQmxpY2sgYXVmIGlocmUgw4RobmxpY2hrZWl0IG1pdCBvZGVyIERpc3RhbnogenUgYW5kZXJlbiBXw7ZydGVybiBvZGVyIFRleHRlIGFuYWx5c2llcmVuIGxhc3Nlbi4gRWluZSB3aWNodGlnZSBGdW5rdGlvbiBpbiBkaWVzZW0gWnVzYW1tZW5oYW5nIGlzdCBkaWUgRXh0cmFrdGlvbiB2b24gW0tvbGxva2F0aW9uZW5dKGh0dHBzOi8vZGUud2lraXBlZGlhLm9yZy93aWtpL0tvbGxva2F0aW9uKS4gRGllIEtvbGxva2F0ZSBlaW5lcyBCZWdyaWZmcyBzaW5kIHNvbGNoZSBCZWdyaWZmZSwgZGllIGjDpHVmaWcgZ2VtZWluc2FtIG1pdCBkZW0gVGVybSB2b3Jrb21tZW4uIERlciBQcm96ZXNzIGlzdCBkYWJlaSBpbmR1a3Rpdi4KCkZvbGdlbmQgd2VuZGVuIHdpciBkaWUgRnVua3Rpb24gW3RleHRzdGF0X2NvbGxvY2F0aW9uc10oaHR0cHM6Ly9xdWFudGVkYS5pby9yZWZlcmVuY2UvdGV4dHN0YXRfY29sbG9jYXRpb25zLmh0bWwpIGFuLCBkaWUgaMOkdWZpZ2UgS29sbG9rYXRlIGltIFNoZXJsb2NrIEhvbG1lcy1Lb3JwdXMgZXJtaXR0ZWx0LiBBbmhhbmQgdm9uIHdyaXRlX2RlbGltIHdpcmQgZGFzIEVyZ2VibmlzIGRhbm4gbm9jaCBhbHMgRXhjZWwta29tcGF0aWJsZSBDU1YtRGF0ZWkgZ2VzcGVpY2hlcnQuCgpgYGB7ciBLb2xsb2thdGlvbmVuIGltIFNoZXJsb2NrIEhvbG1lcy1Lb3JwdXMgZXh0cmFoaWVyZW59CmtvbGxva2F0aW9uZW4gPC0gdGV4dHN0YXRfY29sbG9jYXRpb25zKGtvcnB1cywgbWluX2NvdW50ID0gMTApCmFycmFuZ2Uoa29sbG9rYXRpb25lbiwgZGVzYyhjb3VudCkpCmFycmFuZ2Uoa29sbG9rYXRpb25lbiwgZGVzYyhsYW1iZGEpKQp3cml0ZV9kZWxpbShrb2xsb2thdGlvbmVuLCBwYXRoID0gImtvbGxva2F0aW9uZW4uY3N2IiwgZGVsaW0gPSAiOyIpICMgRGF0ZWkgaXN0IEV4Y2VsLWtvbXBhdGliZWwKYGBgCgpJbiBkZW4gYmVpZGVuIFRhYmVsbGVuIHplaWd0IHVucyAqY29sbG9jYXRpb24qIGRhcyBLb2xsb2thdCB1bmQgKmNvdW50KiBkZXNzZW4gYWJzb2x1dGUgSMOkdWZpZ2tlaXQgYW4uIERpZSBBc3NvemlhdGlvbnNzdMOkcmtlciBkZXIgS29sbG9rYXRpb24gd2lyZCBtaXQgKmxhbWJkYSogdW5kICp6KiBnZW1lc3NlbiAoZ2VuYXVlciBpc3QgeiBlaW4gW3otc3RhbmRhcmRpc2llcnRlcyBsYW1iZGFdKGh0dHBzOi8vZGUud2lraXBlZGlhLm9yZy93aWtpL1N0YW5kYXJkaXNpZXJ1bmdfKFN0YXRpc3RpaykpKS4gTGFtYmRhIGJlc2NocmVpYnQgZGFiZWkgZGllIFdhaHJzY2hlaW5saWNoa2VpdCwgZGFzcyBnZW5hdSBkaWVzZSB6d2VpIEJlZ3JpZmZlIGF1ZiBlaW5hbmRlciBmb2xnZW4sIHdhcyBpbnNvZmVybiB2b24gZGVyIGFic29sdXRlbiBIw6R1Zmlna2VpdCB6dSBkaWZmZXJlbnppZXJlbiBpc3QsIGFscyBkYXMgZGllc2UgbmljaHQgZGFzIGF1ZnRyZXRlbiBlaW5lcyBUZWlsYmVncmlmZnMgbWl0IGFsbGVuIGFuZGVyZW4gV8O2cnRlcm4gaW0gS29ycHVzIGJlcsO8Y2tzaWNodGlndC4KCkRpZSBiZWlkZW4gVGFiZWxsZW4gaWxsdXN0cmllcmVuIGRpZXNlbiBVbnRlcnNjaGllZC4gV8OkaHJlbmQgZGllIGVyc3RlIG5hY2ggZGVyIGFic29sdXRlbiBIw6R1Zmlna2VpdCBzb3J0aWVydCBpc3QsIHNvIGRhc3MgZ8OkbmdpZ2UgS29sbG9rYXRlIHdpZSAnb2YgdGhlJyBvZGVyICdpdCBpcycgZ2FueiB2b3JuZSBsaWVnZW4sIGlzdCBkaWUgendlaXRlIGFic3RlaWdlbmQgbmFjaCBMYW1iZGEgc29ydGllcnQsIHNvIGRhc3MgZWluZSBSZWloZSB2b24gRWlnZW5uYW1lbiB3aWUgJ2hvc21lciBhbmdlbCcgb2RlciAnYnJpb255IGxvZGdlJyBkaWUgTGlzdGUgYW5mw7xocmVuLiBQcmFrdGlzY2ggYmV0cmFjaHRldCBpc3QgZXMgbWVpc3Qgc2lubnZvbGxlciwgRWlnZW5uYW1lbiBhbHMgUGhyYXNlbiB6dSBiZXRyYWNodGVuLCBzdGF0dCBhbHMgZWluemVsbmUgQmVncmlmZmUsIGRlcmVuIGdlbWVpbnNhbWVzIEF1ZnRyZXRlbiB3aXJrbGljaCBldHdhcyDDvGJlciBkZW4gVGV4dCB2ZXJyw6R0LiBFY2h0ZSBLb2xsb2thdGUgc2luZCBoaW5nZWdlbiAnbm8gZG91YnQnIG9kZXIgJ3lvdW5nIGxhZHknLiAKCldlbGNoZSBFcmdlYm5pc3NlIGVyaGFsdGVuIHdpciwgd2VubiB3aXIgW3RleHRzdGF0X2NvbGxvY2F0aW9uc10oaHR0cHM6Ly9xdWFudGVkYS5pby9yZWZlcmVuY2UvdGV4dHN0YXRfY29sbG9jYXRpb25zLmh0bWwpIGF1ZiBkYXMgUG9saWJsb2dzLUtvcnB1cyBhbndlbmRlbj8gU3RhdHQgZGllIEZ1bmt0aW9uIGF1ZiBkYXMgZ2VzYW10IEtvcnB1cyBhbnp1d2VuZGVuLCBmaWx0ZXJuIHdpZSB6dW7DpGNoc3QgbmFjaCBkZXIga29uc2VydmF0aXZlbiBCbG9ncyB1bmQgemllaGVuIGRhbm4gZWluIFp1ZmFsbHNhbXBsZSB2b24gMS4wMDAgQmVpdHLDpGdlbi4gRGllcyBiZXNjaGxldW5pZ3QgZGllIEJlcmVjaG51bmcgZGVyIEtvbGxva2F0aW9uZW4gc2VociBzdGFyaywgZGllIHNvbnN0IGJlaSBkaWVzZW0gcmVsYXRpdiBncm/Dn2VuIERhdGVuc2F0eiBtaXR1bnRlciBzZWhyIGxhbmdlIGRhdWVybiBrYW5uLiAKCmBgYHtyIEtvbGxva2F0aW9uZW4gaW0gUG9saWJsb2dzLUtvcnB1cyBleHRyYWhpZXJlbn0Ka29sbG9rYXRpb25lbiA8LSBjb3JwdXNfc3Vic2V0KHBvbGlibG9ncy5rb3JwdXMsIHJhdGluZyA9PSAiQ29uc2VydmF0aXZlIikgJT4lIGNvcnB1c19zYW1wbGUoc2l6ZSA9IDEwMDApICU+JSB0ZXh0c3RhdF9jb2xsb2NhdGlvbnMobWluX2NvdW50ID0gMTUpCmFycmFuZ2Uoa29sbG9rYXRpb25lbiwgZGVzYyhsYW1iZGEpKQpgYGAKCldpZSBtYW4gZ2xlaWNoIGVya2VubnQsIGVudGjDpGx0IGRpZSBMaXN0ZSBhdWNoIGhpZXIgemFobHJlaWNoZSBQZXJzb25lbi0sIE9yZ2FuaXNhdGlvbnMtIHVuZCBPcnRzbmFtZW4sIGRpZSBzZWhyIGd1dCBlcmthbm50IHdlcmRlbiwgYWJlciBhdWNoIEF1c2Ryw7xja2Ugd2llICdoZWxsIHllYWgnIG9kZXIgJ2hhdCB0aXAnLiAKCiMjIyMgV29ydMOkaG5saWNoa2VpdCB1bmQgLURpc3RhbnoKCldpZSBzaWNoIGltIGVyc3RlbiBLYXBpdGVsIGJlcmVpdHMgYW5nZWRldXRldCBoYXQsIGxhc3NlbiBzaWNoIGF1ZiBHcnVuZGxhZ2UgZWluZXIgREZNIHphaGxyZWljaGUgTWV0cmlrZW4gYmVyZWNobmVuLCB3ZWxjaGUgZGllIE7DpGhlIHVuZCBEaXN0YW56IHZvbiBXw7ZydGVybiB1bmQgRG9rdW1lbnRlbiB6dSBlaW5hbmRlciByZWxla3RpZXJlbi4gRGllcyBnZXNjaGllaHQgbWl0IFt0ZXh0c3RhdF9zaW1pbF0oaHR0cHM6Ly9kb2NzLnF1YW50ZWRhLmlvL3JlZmVyZW5jZS90ZXh0c3RhdF9mcmVxdWVuY3kuaHRtbCkuIFp1bsOkY2hzdCBrb25zdHJ1aWVyZW4gd2lyIGRhenUgZWluZSBERk0sIGluIGRlciBqZWRlciBTYXR6IGVpbmVtIERva3VtZW50IGVudHNwcmljaHQuIERpZXMgd2lyZCBkZXNoYWxiIG5vdHdlbmRpZywgd2VpbCBzaWNoIFdvcnTDpGhubGljaGtlaXRlbiBiZWkgZWluZXIgZ2VyaW5nZW4gRG9rdW1lbnRhbnphaGwgbmljaHQgYmVzb25kZXJzIHp1dmVybMOkc3NpZyBiZXJlY2huZW4gbGFzc2VuLCBkYSDDhGhubGljaGtlaXQgYWxzIEtvb2t1cmVueiBpbm5lcmhhbGIgZGVzIGdsZWljaGVuIERva3VtZW50cyBvcGVyYXRpb25hbGlzaWVydCB3aXJkLiBEYW5uIEJlcmVjaG5lbiB3aXIgZGllIFdvcnTDpGhubGljaGtlaXQgenVtIEJlZ3JpZmYgJ2xvdmUnIG1pdHRlbHMgW0tvc2ludXNkaXN0YW56XShodHRwczovL2RlLndpa2lwZWRpYS5vcmcvd2lraS9Lb3NpbnVzLSVDMyU4NGhubGljaGtlaXQpLiBBbmRlcmUgdmVyZsO8Z2JhcmUgTWV0cmlrZW4gc2luZCAnY29ycmVsYXRpb24nLCAnamFjY2FyZCcsICdlSmFjY2FyZCcsICdkaWNlJywgJ2VEaWNlJywgJ3NpbXBsZSBtYXRjaGluZycsICdoYW1hbm4nIHVuZCAnZmFpdGgnLCB3ZWxjaGUgV29ydMOkaG5saWNoa2VpdCBqZXdlaWxzIHVudGVyc2NoaWVkbGljaCBvcGVyYXRpb25hbGlzaWVyZW4uIAoKYGBge3IgV29ydMOkaG5saWNoa2VpdGVuIGluIGVpbmVtIFNhdHprb3JwdXMgYmVyZWNobmVufQprb3JwdXMuc2FldHplIDwtIGNvcnB1c19yZXNoYXBlKGtvcnB1cywgdG8gPSAic2VudGVuY2VzIikKbWVpbmUuZGZtLnNhZXR6ZSA8LSBkZm0oa29ycHVzLnNhZXR6ZSwgcmVtb3ZlX251bWJlcnMgPSBUUlVFLCByZW1vdmVfcHVuY3QgPSBUUlVFLCByZW1vdmVfc3ltYm9scyA9IFRSVUUsIHJlbW92ZSA9IHN0b3B3b3JkcygiZW5nbGlzaCIpKQptZWluZS5kZm0uc2FldHplIDwtIGRmbV90cmltKG1laW5lLmRmbS5zYWV0emUsIG1pbl9kb2NmcmVxID0gNSkKYWVobmxpY2hrZWl0LndvZXJ0ZXIgPC0gdGV4dHN0YXRfc2ltaWwobWVpbmUuZGZtLnNhZXR6ZSwgbWVpbmUuZGZtLnNhZXR6ZVssImxvdmUiXSwgbWFyZ2luID0gImZlYXR1cmVzIiwgbWV0aG9kID0gImNvc2luZSIpCmhlYWQoYWVobmxpY2hrZWl0LndvZXJ0ZXJbb3JkZXIoYWVobmxpY2hrZWl0LndvZXJ0ZXJbLDFdLCBkZWNyZWFzaW5nID0gVCksXSwgMTApCmBgYAoKQW5hbG9nIHp1ciDDhGhubGljaGtlaXQgZnVua3Rpb25lcnQgYXVjaCBkaWUgQmVyZWNobnVuZyB2b24gV29ydGRpc3RhbnplbiBtaXQgZGVyIEZ1bmt0aW9uIFt0ZXh0c3RhdF9kaXN0KCldKGh0dHBzOi8vZG9jcy5xdWFudGVkYS5pby9yZWZlcmVuY2UvdGV4dHN0YXRfZnJlcXVlbmN5Lmh0bWwpLiBBdWNoIGhpZXIgaGFiZW4gd2lyIHdpZWRlciBlaW5lIGdyb8OfZSBBbnphaGwgdm9uIERpc3RhbnptYcOfZW4genVyIEF1c3dhaGwgKCdldWNsaWRlYW4nLCAnY2hpc3F1YXJlZCcsICdjaGlzcXVhcmVkMicsICdoYW1taW5nJywgJ2t1bGxiYWNrJy4gJ21hbmhhdHRhbicsICdtYXhpbXVtJywgJ2NhbmJlcnJhJywgJ21pbmtvd3NraScpLgoKYGBge3IgV29ydGRpc3RhbnplbiBpbiBlaW5lbSBTYXR6a29ycHVzIGJlcmVjaG5lbn0KZGlzdGFuei53b2VydGVyIDwtIHRleHRzdGF0X2Rpc3QobWVpbmUuZGZtLnNhZXR6ZSwgbWVpbmUuZGZtLnNhZXR6ZVssImxvdmUiXSwgbWFyZ2luID0gImZlYXR1cmVzIiwgbWV0aG9kID0gImV1Y2xpZGVhbiIpCmhlYWQoZGlzdGFuei53b2VydGVyW29yZGVyKGRpc3Rhbnoud29lcnRlclssMV0sIGRlY3JlYXNpbmcgPSBUKSxdLCAxMCkKYGBgCgpXYXMgc2FndCBkYXMgRXJnZWJuaXMgYXVzPyBWb3IgYWxsZW0gZGFzICh3ZW5pZyDDvGJlcnJhc2NoZW5kKSBXw7ZydGVyIHdpZSAndXBvbicgdW5kICdzYWlkJyBzZWhyIHdlaXQgdm9uICdsb3ZlJyBlbnRmZXJudCBzaW5kIC0tIGFsbGVyZGluZ3MgbmljaHQgaW4gZGVtIFNpbm5lLCBkYXNzIHNpZSBkYXMgbG9naXNjaGUgR2VnZW50ZWlsIHZvbiAnbG92ZScgZGFyc3RlbGxlbiB3w7xyZGVuIChpbiBkZXIgTGluZ3Vpc3RpayBzcHJpY2h0IG1hbiB2b24gW0FudG9ueW1pZV0oaHR0cHM6Ly9kZS53aWtpcGVkaWEub3JnL3dpa2kvQW50b255bSkpLiBEYXMgbGllZ3QgZGFyYW4sIGRhc3MgZGllc2UgQmVncmlmZmUgaW0gS29ycHVzIG5haGV6dSBnbGVpY2ggdmVydGVpbHQgc2luZCwgYWxzbyDDvGJlcmFsbCB2b3Jrb21tZW4uIE1pdCBkZW0gVmVyZmFocmVuIGRlciBbV29ydHZla3RvcmVuXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Xb3JkX2VtYmVkZGluZykgKHdlbGNoZXMgd2lyIGhpZXIgbmljaHQgYmVoYW5kZWxuKSB1bmQgc2VociBncm/Dn2VuIERhdGVuYmVzdMOkbmRlbiBsYXNzZW4gc2ljaCBhbGxlcmRpbmdzIGF1Y2ggc29sY2hlIHVuZCBhbmRlcmUgc2VtYW50aXNjaCBCZXppZWh1bmdlbiBpbmRlbnRpZml6aWVyZW4uIERpZSBGaWx0ZXJ1bmcsIGRpZSB3aXIgenV2b3IgYW4gZGVyIERGTSB2b3JnZW5vbW1lbiBoYWJlbiwgc2NobGllw590IEJlZ3JpZmZlIGF1cywgZGllIHZpZWxsZWljaHQgbmllIGdlbWVpbnNhbSBtaXQgJ2xvdmUnIHZvcmtvbW1lbiwgdW5kIGluc29mZXJuIG5vY2ggZGlzdGFuemllcnRlciB3w6RyZW4sIGFsbGVyZGluZ3MgZ2lidCBlcyBkZXJlciBhdWNoIHNlaHIgdmllbGUuIFp1c2FtbWVuZmFzc2VuZCBrYW5uIG1hbiBzYWdlbiwgZGFzcyB0ZXh0c3RhdGlzdGlzY2hlIE7DpGhlLSB1bmQgRGlzdGFuem1hw59lIGltbWVyIGF1c3JlaWNoZW5kIHZpZWxlIERhdGVuIGJlbsO2dGlnZW4sIHVtIGVpbiB6dXZlcmzDpHNzaWdlcyBSZXN1bHRhdCBsaWVmZXJuIHp1IGvDtm5uZW4sIHVuZCBkYXNzIGRlciBTdWNodGVybSBzZWxic3QgYXVzcmVpY2hlbmQgb2Z0IHZvcmtvbW1lbiBtdXNzLgoKV2llZGVyIHdlbmRlbiB3aXIgdW5zIGRlbSBQb2xpYmxvZ3MtS29ycHVzIGbDvHIgZWluIGV0d2FzIGxlYmVuc25haGVyZXMgQmVpc3BpZWwgenUgdW5kIHZlcmdsZWljaGVuIGRpZSDDhGhubGljaGtlaXQgYW5kZXJlciBCZWdyaWZmZSB6dW0gWmllbHRlcm0gJ3RheGVzJyBpbiBrb25zZXJ2YXRpdmVuIHVuZCBsaW5rZW4gQmxvZ3MuCgpgYGB7ciBXb3J0w6RobmxpY2hrZWl0ZW4gaW4ga29uc2VydmF0aXZlbiB1bmQgbGlua2VuIFUuUy4tQmxvZ3MgYmVyZWNobmVuIHVuZCBwbG90dGVufQphZWhubGljaGtlaXQuY29uIDwtIHBvbGlibG9ncy5rb3JwdXMgJT4lIAogIGNvcnB1c19zdWJzZXQocmF0aW5nID09ICJDb25zZXJ2YXRpdmUiKSAlPiUgCiAgZGZtKHJlbW92ZV9udW1iZXJzID0gVFJVRSwgcmVtb3ZlX3B1bmN0ID0gVFJVRSwgcmVtb3ZlX3N5bWJvbHMgPSBUUlVFLCByZW1vdmUgPSBzdG9wd29yZHMoImVuZ2xpc2giKSkgJT4lIAogIGRmbV90cmltKG1pbl90ZXJtZnJlcSA9IDEwKSAlPiUgCiAgdGV4dHN0YXRfc2ltaWwoeSA9IC5bLCJ0YXhlcyJdLCBtYXJnaW4gPSAiZmVhdHVyZXMiLCBtZXRob2QgPSAiY29zaW5lIikKYWVobmxpY2hrZWl0LmxpYiA8LSBwb2xpYmxvZ3Mua29ycHVzICU+JSAKICBjb3JwdXNfc3Vic2V0KHJhdGluZyA9PSAiTGliZXJhbCIpICU+JSAKICBkZm0ocmVtb3ZlX251bWJlcnMgPSBUUlVFLCByZW1vdmVfcHVuY3QgPSBUUlVFLCByZW1vdmVfc3ltYm9scyA9IFRSVUUsIHJlbW92ZSA9IHN0b3B3b3JkcygiZW5nbGlzaCIpKSAlPiUgCiAgZGZtX3RyaW0obWluX3Rlcm1mcmVxID0gMTApICU+JSAKICB0ZXh0c3RhdF9zaW1pbCh5ID0gLlssInRheGVzIl0sIG1hcmdpbiA9ICJmZWF0dXJlcyIsIG1ldGhvZCA9ICJjb3NpbmUiKQphZWhubGljaGtlaXQuY29uLmRmIDwtIGRhdGEuZnJhbWUoQmVncmlmZiA9IGFlaG5saWNoa2VpdC5jb25ARGltbmFtZXNbWzFdXSwgS29zaW51cyA9IGFlaG5saWNoa2VpdC5jb25AeCwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpICU+JSAKICBmaWx0ZXIoIUJlZ3JpZmYgJWluJSBjKCJ0YXgiLCAidGF4ZXMiKSkgJT4lIAogIGFycmFuZ2UoZGVzYyhLb3NpbnVzKSkgJT4lIAogIG11dGF0ZShSYW5nID0gcm93X251bWJlcigpKSAlPiUgCiAgZmlsdGVyKFJhbmcgPD0gMTUpCmFlaG5saWNoa2VpdC5saWIuZGYgPC0gZGF0YS5mcmFtZShCZWdyaWZmID0gYWVobmxpY2hrZWl0LmxpYkBEaW1uYW1lc1tbMV1dLCBLb3NpbnVzID0gYWVobmxpY2hrZWl0LmxpYkB4LCBzdHJpbmdzQXNGYWN0b3JzID0gRikgJT4lIAogIGZpbHRlcighQmVncmlmZiAlaW4lIGMoInRheCIsICJ0YXhlcyIpKSAlPiUgCiAgYXJyYW5nZShkZXNjKEtvc2ludXMpKSAlPiUgCiAgbXV0YXRlKFJhbmcgPSByb3dfbnVtYmVyKCkpICU+JSAKICBmaWx0ZXIoUmFuZyA8PSAxNSkKZ2dwbG90KGFlaG5saWNoa2VpdC5jb24uZGYsIGFlcyhyZW9yZGVyKEJlZ3JpZmYsIFJhbmcpLCBLb3NpbnVzKSkgKyAKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArIAogIHhsYWIoIiIpICsgeWxhYigiIikgKyAKICBnZ3RpdGxlKCJCZWdyaWZmZSBtaXQgaG9oZXIgS29zaW51c27DpGhlIHp1bSBaaWVsdGVybSBcJ3RheGVzXCcgaW4ga29uc2VydmF0aXZlbiBVLlMuLUJsb2dzIikKZ2dwbG90KGFlaG5saWNoa2VpdC5saWIuZGYsIGFlcyhyZW9yZGVyKEJlZ3JpZmYsIFJhbmcpLCBLb3NpbnVzKSkgKyAKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArIAogIHhsYWIoIiIpICsgeWxhYigiIikgKyAKICBnZ3RpdGxlKCJCZWdyaWZmZSBtaXQgaG9oZXIgS29zaW51c27DpGhlIHp1bSBaaWVsdGVybSBcJ3RheGVzXCcgaW4gbGlua2VuIFUuUy4tQmxvZ3MiKQpgYGAKCiMjIyMgVGV4dMOkaG5saWNoa2VpdCB1bmQgLURpc3RhbnoKCldlciBkaWUgRG9rdW1lbnRhdGlvbiB2b24gW3RleHRzdGF0X3NpbWlsXShodHRwczovL3F1YW50ZWRhLmlvL3JlZmVyZW5jZS90ZXh0c3RhdF9zaW1pbC5odG1sKSB1bmQgW3RleHRzdGF0X2Rpc3RdKGh0dHBzOi8vcXVhbnRlZGEuaW8vcmVmZXJlbmNlL3RleHRzdGF0X2Rpc3QuaHRtbCkgYW5zY2hhdXQsIHdpcmQgZmVzdHN0ZWxsZW4sIGRhc3MgZXMgZG9ydCBkZW4gZXR3YXMga3J5cHRpc2NoZW4gSGlud2VpcyBhdWYgZGFzIEFyZ3VtZW50ICdtYXJnaW4nIGdpYnQuIERpZXNlcyBoYXQgendlaSBtw7ZnbGljaGUgZXJpbnN0ZWxsdW5nZW46ICdkb2N1bWVudHMnIG9kZXIgJ2ZlYXR1cmVzJy4gU3RlbGx0IG1hbiBoaWVyICdkb2N1bWVudHMnIGVpbiwgd2VyZGVuIGRpZSBiZXNwcm9jaGVuZW4gTWV0cmlrZW4gbmljaHQgYXVmIFfDtnJ0ZXIsIHNvbmRlcm4gYXVmIFRleHRlIGFuZ2V3YW5kdC4gRm9sZ2VuZCBwbG90dGVuIHdpciBkaWUgVGV4dG7DpGhlIHZpYSBLb3NpbnVzw6RobmxpY2hrZWl0IChoaWVyIGF1c2dlaGVuZCB2b20gZXJzdGVuIFJvbWFuLCAnQSBDYXNlIG9mIElkZW50aXR5JykuCgpgYGB7ciDDhGhubGljaGtlaXQgZWluZXMgYmVzdGltbXRlbiBUZXh0ZXMgenUgYW5kZXJlbiBUZXh0ZW4gcGxvdHRlbn0KYWVobmxpY2hrZWl0LnRleHRlIDwtIGRhdGEuZnJhbWUoVGV4dCA9IGZhY3Rvcihrb3JwdXMuc3RhdHMkVGV4dCwgbGV2ZWxzID0gcmV2KGtvcnB1cy5zdGF0cyRUZXh0KSksIGFzLm1hdHJpeCh0ZXh0c3RhdF9zaW1pbChtZWluZS5kZm0sIG1laW5lLmRmbVsiQSBDYXNlIG9mIElkZW50aXR5IixdLCBtYXJnaW4gPSAiZG9jdW1lbnRzIiwgbWV0aG9kID0gImNvc2luZSIpKSkKZ2dwbG90KGFlaG5saWNoa2VpdC50ZXh0ZSwgYWVzKEEuQ2FzZS5vZi5JZGVudGl0eSwgVGV4dCkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMi41KSArIAogIGdndGl0bGUoIlRleHQtS29zaW51c8OkaG5saWNoa2VpdCAoaGllciBmw7xyICdBIENhc2Ugb2YgSWRlbnRpdHknKSIpICsgCiAgeGxhYigiS29zaW51bnPDpGhubGljaGtlaXQiKSArIHlsYWIoIiIpCmBgYAoKV2llIHdpciBzZWhlbiwgaXN0IGRpZSDDhGhubGljaGtlaXQgZGVyIFJvbWFuZSAnVGhlIFJlZC1oZWFkZWQgTGVhZ3VlJyB1bmQgJ1RoZSBBZHZlbnR1cmUgb2YgdGhlIENvcHBlciBCZWVjaGVzJyBldHdhcyBncsO2w59lciwgYWxzIGRpZXMgYmVpIGRlbiBhbmRlcmVuIEVyesOkaGx1bmdlbiBkZXIgRmFsbCBpc3QuCgpXw6RocmVuZCBkaWVzZSBzZWhyIHNjaGxpY2h0ZSBBcnQgZGVyIERhcnN0ZWxsdW5nIHNpbm52b2xsIGlzdCwgdW0gZ2V6aWVsdCBVbnRlcnNjaGllZGUgZWluZXMgZ2FueiBiZXN0aW1tdGVuIERva3VtZW50cyBtaXQgYW5kZXJlbiBEb2t1bWVudGVuIHp1IHVudGVyc3VjaGVuLCBnaWJ0IGVzIFNpdHVhdGlvbmVuLCBpbiBkZW5lbiB3aXIgw4RobmxpY2hrZWl0ZW4gdW5kIFVudGVyc2NoaWVkZSB6d2lzY2hlbiBEb2t1bWVudGVuIGlubmVyaGFsYiBlaW5lcyBLb3JwdXNlcyBnYW56IGdydW5kc8OkdHpsaWNoIGluIGRlbiBCbGljayBuZWhtZW4gd29sbGVuLiBEYXp1IGRpZW50IGRhcyBmb2xnZW5kZSBQbG90LiBOYWNoZGVtIHdpciBkaWUgRGlzdGFuem1hdHJpeCBpbiBlaW5lbiBEYXRhIEZyYW1lIHVtZ2V3YW5kZWx0IGhhYmVuLCBkZW4gd2lyIG1pdCBnZ3Bsb3QgZGFyc3RlbGxlbiBrw7ZubmVuLCBmw7xocmVuIHdpciBlaW5lIG5ldWVuIFZhcmlhYmxlICrDhGhubGljaGtlaXQqIGVpbiwgZGllIHdpciB6dWdsZWljaCByZS1za2FsaWVyZW4sIHVtIGRlbiBLb250cmFzdCB6d2lzY2hlbiBUZXh0ZW4gbcO2Y2hsaWNoc3QgZGV1dGxpY2ggenUgbWFjaGVuLiBEYXMgRXJnZWJuaXMgbMOkc3N0IHNpY2ggZGFubiBpbiBlaW5lciBzb2cuIEhlYXRtYXAgZGFyc3RlbGxlbiwgaW4gZGVyIMOEaG5saWNoa2VpdCByb3QgdW5kIFVudGVyc2NoaWVkZSBibGF1IGRhcmdlc3RlbGx0IHdlcmRlbi4gCgpgYGB7ciDDhGhubGljaGtlaXQgYWxsZXIgVGV4dGUgenUgZWluYW5kZXIgcGxvdHRlbiB9CmFlaG5saWNoa2VpdC50ZXh0ZSA8LSB0ZXh0c3RhdF9zaW1pbChtZWluZS5kZm0sIG1hcmdpbiA9ICJkb2N1bWVudHMiLCBtZXRob2QgPSAiY29zaW5lIikgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICBtdXRhdGUow4RobmxpY2hrZWl0ID0gcmVzY2FsZShjb3NpbmUsIHRvID0gYygtMSwxKSkpCmFlaG5saWNoa2VpdC50ZXh0ZSRkb2N1bWVudDIgPC0gZmFjdG9yKGFlaG5saWNoa2VpdC50ZXh0ZSRkb2N1bWVudDIsIGxldmVscz1yZXYobGV2ZWxzKGFlaG5saWNoa2VpdC50ZXh0ZSRkb2N1bWVudDIpKSkKZ2dwbG90KGFlaG5saWNoa2VpdC50ZXh0ZSwgYWVzKGRvY3VtZW50MSwgZG9jdW1lbnQyKSkgKyAKICBnZW9tX3RpbGUoYWVzKGZpbGwgPSDDhGhubGljaGtlaXQpKSArIAogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdyA9ICJibHVlIiwgaGlnaCA9ICJyZWQiLCBtaWQgPSAid2hpdGUiLCBtaWRwb2ludCA9IDApICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgdmp1c3QgPSAxLCBoanVzdCA9IDEpKSArCiAgZ2d0aXRsZSgiVGV4dC1Lb3NpbnVzw6RobmxpY2hrZWl0IChza2FsaWVydCkgaW4genfDtmxmIFJvbWFuZW4iKSArIAogIHhsYWIoIiIpICsgeWxhYigiIikKYGBgCgpTbyBzZWhlbiB3aXIgZXR3YSBuZWJlbiBkZW0gQmVmdW5kIHp1ICdBIENhc2Ugb2YgSWRlbnRpdHknIGF1Y2gsIGRhc3Mgc2ljaCB0aGUgJ1RoZSBBZHZlbnR1cmUgb2YgdGhlIE5vYmxlIEJhY2hlbG9yJyB1bmQgJ1RoZSBGaXZlIE9yYW5nZSBQaXBzJyBiZXNvbmRlcnMgdW7DpGhubGljaCBzaW5kLCBqZWRlbmZhbGxzIGlubmVyaGFsYiBkZXMgKHZlcm11dGxpY2ggcmVjaHQgaGV0ZXJvZ2VuZW4pIFNoZXJsb2NrIEhvbG1lcy1Lb3JwdXMuIMOcYmVyaGF1cHQgc2NoZWludCAnVGhlIEFkdmVudHVyZSBvZiB0aGUgTm9ibGUgQmFjaGVsb3InIHZlcmdsZWljaHN3ZWlzZSBzdGFyayBhdXMgZGVtIFJhaG1lbiB6dSBmYWxsZW4uIE1hbiBiZWFjaHRlLCBkYXNzIGRpZSDDhGhubGljaGtlaXQgbWl0IHNpY2ggc2VsYnN0IGhpZXIgYmV3dXNzdCBhdXNnZWtsYW1tZXJ0IHd1cmRlIC0tIGJlaSBkZXIgVW13YW5kdW5nbGljaCBkZXIgRGlzdGFuem1hdHJpeCBtaXQgW2FzLmRhdGEuZnJhbWVdKGh0dHBzOi8vcXVhbnRlZGEuaW8vcmVmZXJlbmNlL3RleHRzdGF0X3NpbWlsLmh0bWwpIGthbm4gZGllc2UgYWJlciBhdWNoIGJlaWJlaGFsdGVuIHdlcmRlbi4KCldpZSBnZWVpZ25ldCBpc3QgZGllIFRleHTDpGhubGljaGtlaXQsIHVtIGV0d2EgaWRlb2xvZ2lzY2hlIFVudGVyc2NoaWVkZSB2b3JoZXJ6dXNhZ2VuPyBXaWVkZXIgbmVobWVuIHdpciBkYXMgUG9saWJsb2dzLUtvcnB1cyBpbiBkZW4gQmxpY2sgdW5kIHBsb3R0ZW4gYXVjaCBoaWVyIGRpZSBUZXh0w6RobmxpY2hrZWl0LiBEYWJlaSB2ZXJ3ZW5kZW4gd2lyIG5pY2h0IGRpZSBWYXJpYWJsZSAncmF0aW5nJywgZGllIGphIGRpZSBFaW5vcmRudW5nIGluICdDb25zZXJ2YXRpdmUnIHVuZCAnTGliZXJhbCcgZW50aMOkbHQsIHNvbmRlcm4gc3RhdHRkZXNzZW4gZGllIFZhcmlhYmxlICdibG9nJyBkaWUgZGFzIGpld2VpbGlnZSBCbG9nIGR1cmNoIGVpbiBLw7xyemVsIGlkZW50aWZpemllcnQuIEF1Y2ggdmVyd2VuZGVuIHdpciBlaW5lIGFuZGVyZSBDbHVzdGVyaW5nLVRlY2huaWsgKGRhcyBzb2cuIFtoaWVyYXJjaGlzY2hlIENsdXN0ZXJpbmddKGh0dHBzOi8vZGUud2lraXBlZGlhLm9yZy93aWtpL0hpZXJhcmNoaXNjaGVfQ2x1c3RlcmFuYWx5c2UpKSB1bmQgcGxvdHRlbiBkYXMgRXJnZWJuaXMgYWxzIERlbmRyb2dyYW0uIEhpZXIgdmVyemljaHRlbiB3aXIgZGFyYXVmLCBkYXMgw4RobmxpY2hrZWl0c2tvc2ludXMgenUgcmVza2FsaWVyZW4sIHdlc2hhbGIgc2ljaCBkaWUgQmxvZ3MgcmVjaHQgw6RobmxpY2ggc2VoZW4uIAoKYGBge3Igw4RobmxpY2hrZWl0IGVpbnplbG5lciBCbG9ncyB6dSBlaW5hbmRlciBwbG90dGVuIH0KYWVobmxpY2hrZWl0LnBvbGlibG9ncyA8LSBkZm0ocG9saWJsb2dzLmtvcnB1cywgcmVtb3ZlX251bWJlcnMgPSBUUlVFLCByZW1vdmVfcHVuY3QgPSBUUlVFLCByZW1vdmVfc3ltYm9scyA9IFRSVUUsIHJlbW92ZSA9IHN0b3B3b3JkcygiZW5nbGlzaCIpLCBncm91cHMgPSAiYmxvZyIpICU+JSAKICBkZm1fdHJpbShtaW5fZG9jZnJlcSA9IDYpICU+JSAKICB0ZXh0c3RhdF9zaW1pbChtYXJnaW4gPSAiZG9jdW1lbnRzIiwgbWV0aG9kID0gImNvc2luZSIpICU+JQogIGFzLmRpc3QoKSAlPiUgaGNsdXN0KG1ldGhvZCA9ICJ3YXJkLkQyIikKZ2dkZW5kcm9ncmFtKGFlaG5saWNoa2VpdC5wb2xpYmxvZ3MsIHJvdGF0ZSA9IFRSVUUpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKSArIAogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9IGMoImJsdWUiLCAiYmx1ZSIsICJyZWQiLCAicmVkIiwgInJlZCIsICJibHVlIiksIHNpemUgPSAxMikpICsgCiAgZ2d0aXRsZSgiVGV4dC1Lb3NpbnVzw6RobmxpY2hrZWl0IGluIHNlY2hzIFUuUy4tQmxvZ3MiKQpgYGAKCkVzIGzDpHNzdCBzaWNoIGVya2VubmVuLCBkYXNzIGRpZSBpZGVvbG9naXNjaGUgUmljaHR1bmcgKHJvdCA9IGtvbnNlcnZhdGl2LCBibGF1ID0gbGlua3MpIGtlaW4ga2xhcmVzIE11c3RlciBiZWltIFdvcnRnZWJyYXVjaCBwcm9kdXppZXJ0LCB3b2JlaSBoaWVyIGVpbmUgUmVpaGUgdm9uIEZha3RvcmVuIGJlcsO8Y2tzaWNodGlndCB3ZXJkZW4gbcO8c3N0ZSwgYmV2b3IgbWFuIHZvbiBlaW5lciBzdGljaGhhbHRpZ2VuIEFuYWx5c2Ugc3ByZWNoZW4ga8O2bm50ZS4gRGF6dSBnZWjDtnJ0IGRpZSBWZXJ3ZW5kdW5nIHZvbiBOLUdyYW1tZW4gZ2VuYXVzbyB3aWUgZWluZSBhbmRlcmUgRmlsdGVydW5nIGRlciBERk0sIGRpZSByZWxldmFudGUgRmVhdHVyZXMgenVyw7xja2JlaMOkbHQgdW5kIGdsZWljaHplaXRpZyBuaWNodCBlaW5lciBbw5xiZXJhbnBhc3N1bmddKGh0dHBzOi8vZGUud2lraXBlZGlhLm9yZy93aWtpLyVDMyU5Q2JlcmFucGFzc3VuZykgenVtIE9wZmVyIGbDpGxsdC4gCgojIyMjIEtleW5lc3MKCkJlaSBkZXIgKktleW5lc3MqIGhhbmRlbHQgZXMgc2ljaCB1bSBlaW4gTWHDnyBmw7xyIGRpZSBEaXN0aW5rdGl2aXTDpHQgdm9uIEJlZ3JpZmZlbiBmw7xyIGVpbmVuIGJlc3RpbW10ZW4gVGV4dCwgYWxzbyB3aWUgc3RhcmsgZWluemVsbmUgQmVncmlmZmUgaW0gamV3ZWlsaWdlbiBUZXh0IGltIFZlcmdsZWljaCB6dW0gZ2VzYW10ZW4gS29ycHVzIMO8YmVyLSAocG9zaXRpdmUgV2VydGUpIG9kZXIgdW50ZXJyZXByw6RzZW50aWVydCBzaW5kIChuZWdhdGl2ZSBXZXJ0ZSkuIFfDpGhyZW5kIHdpciB6dXZvciBkaWUgRGlzdGFueiB2b24gV8O2cnRlcm4gdW5kIFRleHRlbiB6dSBlaW5hbmRlciB1bnRlcnN1Y2h0IGhhYmVuLCBtYWNodCBzaWNoIGRpZSBLZXluZXNzIGRpZSBWZXJ0ZWlsdW5nc2jDpHVmaWdrZWl0IHZvbiBXw7ZydGVybiBhdWYgVGV4dGUgenVudXR6ZSwgb2huZSBkZXJlbiBQb3NpdGlvbiB6dSBiZXLDvGNrc2ljaHRpZ2VuIHVuZCB2ZXJsYW5ndCBkZW1lbnRzcHJlY2hlbmQgZWluZSBERk0gYWxzIEFyZ3VtZW50LiBJbiBkaWVzZW0gRmFsbCB2ZXJ3ZW5kZW4gd2lyIGRhcyBDaGktUXVhZHJhdC1Bc3NvemlhdGlvbnNtYcOfLCBlcyBzdGVoZW4gYWJlciBhdWNoIHdlaXRlcmUgTWV0cmlrZW4genVyIEF1c3dhaGwsIHVtIGRpZSBXYWhyc2NoZWlubGljaGtlaXQgZWluZXIgbmljaHQtenVmw6RsbGlnZW4gVmVydGVpbHVuZyB6dSBiZW1lc3Nlbi4gS2V5bmVzcyBmdW5rdGlvbmllcnQgYXVjaCBtaXQgbMOkbmdlcmVuIFRleHRlbiBndXQsIHNvbGFuZ2UgZGllc2Ugc2ljaCBhdXNyZWljaGVuZCBtYXJrYW50IHVudGVyc2NoZWlkZW4uIEZvbGdlbmQgYmVyZWNobmVuIHdpciB6dW7DpGNoc3QgZGllIEtleW5lc3MgZsO8ciB2aWVyIFRleHRlIG1pdCBbdGV4dHN0YXRfa2V5bmVzc10oaHR0cHM6Ly9kb2NzLnF1YW50ZWRhLmlvL3JlZmVyZW5jZS90ZXh0c3RhdF9rZXluZXNzLmh0bWwpIHVuZCBwbG90dGVuIHdpciBkaWVzZSBLZXluZXNzLVN0YXRpc3Rpa2VuIGRhbm4gZsO8ciB2aWVyIEVyesOkaGx1bmdlbiBtaXQgSGlsZmUgZGVyIHp1Z2Vow7ZyaWdlbiBGdW5rdGlvbiBbdGV4dHBsb3Rfa2V5bmVzc10oaHR0cHM6Ly9kb2NzLnF1YW50ZWRhLmlvL3JlZmVyZW5jZS90ZXh0cGxvdF9rZXluZXNzLmh0bWwpLgoKYGBge3IgS2V5bmVzcyB2b24gQmVncmlmZmVuIG1pdHRlbHMgQ2hpLVF1YWRyYXQtQXNzb3ppYXRpb24gcGxvdHRlbn0Ka2V5bmVzcyA8LSB0ZXh0c3RhdF9rZXluZXNzKG1laW5lLmRmbSwgdGFyZ2V0ID0gIkEgU2NhbmRhbCBpbiBCb2hlbWlhIikKdGV4dHBsb3Rfa2V5bmVzcyhrZXluZXNzKQprZXluZXNzIDwtIHRleHRzdGF0X2tleW5lc3MobWVpbmUuZGZtLCB0YXJnZXQgPSAiQSBDYXNlIG9mIElkZW50aXR5IikKdGV4dHBsb3Rfa2V5bmVzcyhrZXluZXNzKQprZXluZXNzIDwtIHRleHRzdGF0X2tleW5lc3MobWVpbmUuZGZtLCB0YXJnZXQgPSAiVGhlIEZpdmUgT3JhbmdlIFBpcHMiKQp0ZXh0cGxvdF9rZXluZXNzKGtleW5lc3MpCmtleW5lc3MgPC0gdGV4dHN0YXRfa2V5bmVzcyhtZWluZS5kZm0sIHRhcmdldCA9ICJUaGUgQWR2ZW50dXJlIG9mIHRoZSBOb2JsZSBCYWNoZWxvciIpCnRleHRwbG90X2tleW5lc3Moa2V5bmVzcykKYGBgCgpTY2hhdXQgbWFuIHNpY2ggZGllIHZpZXIgQmVpc3BpZWx0ZXh0ZSBlaW5tYWwgZ2VuYXVlciBhbiwgc28gd2lyZCBzY2huZWxsIGtsYXIsIGRhc3MgZGllIEJlZ3JpZmZlIG1pdCBlaW5lbSBob2hlbiBLZXluZXNzLVdlcnQgdGF0c8OkY2hsaWNoIHNlaHIgZGlzdGlua3RpdiBmw7xyIGRlbiBqZXdlaWxpZ2VuIFRleHQgc2luZCwgYWxzbyBCZWdyaWZmZSB3aWUgJ21hamVzdHknIHVuZCAncGhvdG9ncmFwaCcgdGF0c8OkY2hsaWNoIG51ciBpbiAnQSBTY2FuZGFsIGluIEJvaGVtaWEnIGVpbmUgUm9sbGUgc3BpZWxlbi4gV2VuaWcgZGlzdGlua3RpdmUgQmVncmlmZmUgc2luZCBoaW5nZWdlbiBzb2xjaGUsIGRpZSB6d2FyIGluIGFuZGVyZW4gVGV4dGVuLCBuaWNodCBhYmVyIGRlbSBaaWVsdGV4dCB2b3Jrb21tZW4uIAoKTsO8dHpsaWNoIHdpcmQgZGllc2UgRnVua3Rpb24gdm9yIGFsbGVtIGRhbm4sIHdlbm4gbWFuIFRleHRlIG5hY2ggZWluZW4gS3JpdGVyaXVtIHdpZSBNZWRpdW0sIFNwcmVjaGVyLCBQYXJ0ZWksIFplaXRwdW5rdCBvZGVyIG1hbnVlbGwgenVnZW9yZG5ldGUgSW5oYWx0c2thdGVnb3JpZSBncnVwcGllcnQuIERpZXMgbMOkc3N0IHNpY2ggZWJlbmZhbGxzIHdpZWRlciBndXQgYW5oYW5kIGRlcyBQb2xpYmxvZ3MtS29ycHVzIGlsbHVzdHJpZXJlbi4gRGllIGVyc3RlbiBkcmVpIFBsb3RzIGJlemllaGVuIHNpY2ggaGllcmJlaSBhdWYga29uc2VydmF0aXZlIEJsb2dzLCBkaWUgUGxvdHMgNC02IGhpbmdlZ2VuIGF1ZiBsaW5rZSBCbG9ncy4KCmBgYHtyIEtleW5lc3Mgdm9uIEJlZ3JpZmZlbiBmw7xyIGRhcyBQb2xpYmxvZ3MtS29ycHVzIHBsb3R0ZW59CmFlaG5saWNoa2VpdC5wb2xpYmxvZ3MgPC0gZGZtKHBvbGlibG9ncy5rb3JwdXMsIHJlbW92ZV9udW1iZXJzID0gVFJVRSwgcmVtb3ZlX3B1bmN0ID0gVFJVRSwgcmVtb3ZlX3N5bWJvbHMgPSBUUlVFLCByZW1vdmUgPSBzdG9wd29yZHMoImVuZ2xpc2giKSwgZ3JvdXBzID0gImJsb2ciKSAlPiUgCiAgZGZtX3NlbGVjdChtaW5fbmNoYXIgPSAzKSAlPiUgCiAgZGZtX3RyaW0obWluX2RvY2ZyZXEgPSA2KQprZXluZXNzIDwtIHRleHRzdGF0X2tleW5lc3MoYWVobmxpY2hrZWl0LnBvbGlibG9ncywgdGFyZ2V0ID0gImF0IikKdGV4dHBsb3Rfa2V5bmVzcyhrZXluZXNzKQprZXluZXNzIDwtIHRleHRzdGF0X2tleW5lc3MoYWVobmxpY2hrZWl0LnBvbGlibG9ncywgdGFyZ2V0ID0gImhhIikKdGV4dHBsb3Rfa2V5bmVzcyhrZXluZXNzKQprZXluZXNzIDwtIHRleHRzdGF0X2tleW5lc3MoYWVobmxpY2hrZWl0LnBvbGlibG9ncywgdGFyZ2V0ID0gIm1tIikKdGV4dHBsb3Rfa2V5bmVzcyhrZXluZXNzKQprZXluZXNzIDwtIHRleHRzdGF0X2tleW5lc3MoYWVobmxpY2hrZWl0LnBvbGlibG9ncywgdGFyZ2V0ID0gImRiIikKdGV4dHBsb3Rfa2V5bmVzcyhrZXluZXNzKQprZXluZXNzIDwtIHRleHRzdGF0X2tleW5lc3MoYWVobmxpY2hrZWl0LnBvbGlibG9ncywgdGFyZ2V0ID0gInRwIikKdGV4dHBsb3Rfa2V5bmVzcyhrZXluZXNzKQprZXluZXNzIDwtIHRleHRzdGF0X2tleW5lc3MoYWVobmxpY2hrZWl0LnBvbGlibG9ncywgdGFyZ2V0ID0gInRwbSIpCnRleHRwbG90X2tleW5lc3Moa2V5bmVzcykKYGBgCgpIaWVyIGthbm4gZGllIHZlcmdsZWljaGVuZGUgRGltZW5zaW9uIGRlciBLZXluZXNzLU1ldHJpayBndXQgZXJrZW5uZW4sIGRpZSBkaWUgVW50ZXJzY2hpZWRlIHp3aXNjaGVuIGRlbiBCbG9ncyBndXQgenVyIEdlbHR1bmcgYnJpbmd0LiAKCiMjIyMgRW50cm9waWUKCldhcyB0dW4sIHdlbm4gbWFuIGV0d2FzIMO8YmVyIGRlbiBJbmZvcm1hdGlvbnNnZWhhbHQgZWluZXMgVGV4dGVzIHJlbGF0aXYgenVtIEdlc2FtdGtvcnB1cyB3aXNzZW4gbcO2Y2h0ZSAodW5kIGRhdm9uIGF1c2dlaHQsIGRhc3MgbmV1ZSBXw7ZydGVyIGdsZWljaGJlZGV1dGVuZCBtaXQgbmV1ZW4gSW5mb3JtYXRpb25lbiB6dSBpbnRlcnByZXRpZXJlbiBzaW5kKT8gSGllciBoaWxmdCB1bnMgZGllIFtTaGFubm9uLUVudHJvcGllXShodHRwczovL2RlLndpa2lwZWRpYS5vcmcvd2lraS9FbnRyb3BpZV8oSW5mb3JtYXRpb25zdGhlb3JpZSkpIHdlaXRlciwgYmVuYW5udCBuYWNoIFtDbGF1ZGUgRS4gU2hhbm5vbl0oaHR0cHM6Ly9kZS53aWtpcGVkaWEub3JnL3dpa2kvQ2xhdWRlX1NoYW5ub24pLCBkZW0gR3LDvG5kZXIgZGVyIEluZm9ybWF0aW9uc3RoZW9yaWUuIEluIFF1YW50ZWRhIGlzdCBkaWVzZSBNZXRyaWsgZHVyY2ggZGllIEZ1bmt0aW9uIFt0ZXh0c3RhdF9lbnRyb3B5XShodHRwczovL3F1YW50ZWRhLmlvL3JlZmVyZW5jZS90ZXh0c3RhdF9lbnRyb3B5Lmh0bWwpIGludGVncmllcnQsIGRpZSB3aXIgZm9sZ2VuZCBhdWYgZGFzIFNoZXJsb2NrLUhvbG1lcy1TYXR6a29ycHVzIGFud2VuZGVuLiAKCmBgYHtyIEVudHJvcGllIHZvbiBEb2t1bWVudGVuIGltIFNoZXJsb2NrIEhvbG1lcy1TYXR6a29ycHVzfQplbnRyb3BpZSA8LSB0ZXh0c3RhdF9lbnRyb3B5KG1laW5lLmRmbS5zYWV0emUsIG1hcmdpbiA9ICJkb2N1bWVudHMiKSAlPiUgCiAgYXJyYW5nZShkZXNjKGVudHJvcHkpKSAlPiUgCiAgaGVhZCgzKQp0ZXh0cyhrb3JwdXMuc2FldHplW2VudHJvcGllJGRvY3VtZW50XSkKYGBgCgpEaWUgZHJlaSBCZWlzcGllbGUgdmVyZGV1dGxpY2hlbiBndXQsIHdhcyBFbnRyb3BpZSBsZXR6dGVuZGxpY2ggbWlzc3QuIEFsbGUgZHJlaSBTw6R0emUgc2luZCBsZXhpa2FsaXNjaCBzZWhyIGF1c3Nlcmdld8O2aG5saWNoLCB3YXMgc2ljaCB6dW0gVGVpbCBkdXJjaCBpaHJlIEzDpG5nZSBlcmtsw6RydCwgYWJlciBhdWNoIGV0d2FzIG1pdCBkZW0gaG9oZW4gQW50ZWlsIHVuZ2V3w7ZobmxpY2hlciBCZWdyaWZmZSB6dSB0dW4gaGF0LCBkaWUgc29uc3QgaW0gS29ycHVzIGthdW0gdm9ya29tbWVuLgoKIyMjIyBJbmRpa2F0b3JlbiBsZXhpa2FsaXNjaGVyIFZpZWxmYWx0CgpVbnRlciBNYcOfZW4gbGV4aWthbGlzY2hlciBWaWVsZmFsdCB2ZXJzdGVodCBtYW4gTWV0cmlrZW4sIHdlbGNoZSBkaWUgRGl2ZXJzaXTDpHQgZWluZXMgVGV4dGVzIG1pdCBCbGljayBhdWYgZGVuIFdvcnRnZWJyYXVjaCB3aWVkZXJnZWJlbi4gRWluIEJlaXNwaWVsIGlzdCBkaWUgYmVyZWl0cyBpbiBLYXBpdGVsIDEgYmVyZWNodGV0ZSBbVHlwLVRva2VuLVJlbGF0aW9uXShodHRwczovL2RlLndpa2lwZWRpYS5vcmcvd2lraS9UeXBlLVRva2VuLVJlbGF0aW9uKS4gRGllc2UgYmVzY2hyZWlidCBkaWUgV29ydHZpZWxmYWx0IHVuZCBnZWJlbiBzbyBhdWNoIEF1ZnNjaGx1c3Mgw7xiZXIgZGllIEtvbXBsZXhpdMOkdCBlaW5lcyBUZXh0ZXMuIFdpciBiZXJlY2huZW4genVuw6RjaHN0IGVpbmUgZ2FuemUgUmVpaGUgaW4gUXVhbnRlZGEgaW1wbGVtZW50aWVydGVyIE1ldHJpa2VuIGbDvHIgZGllIGxleGlrYWxpc2NoZSBEaXZlcnNpdMOkdCBkZXIgenfDtmxmIFNoZXJsb2NrIEhvbG1lcy1Sb21hbmUgbWl0IGRlciBGdW5rdGlvbiBbdGV4dHN0YXRfbGV4ZGl2XShodHRwczovL3F1YW50ZWRhLmlvL3JlZmVyZW5jZS90ZXh0c3RhdF9sZXhkaXYuaHRtbCkuCgpgYGB7ciBMZXhpa2FsaXNjaGUgVmllbGZhbHQgaW0gU2hlcmxvY2sgSG9sbWVzLUtvcnB1cyBiZXJlY2huZW59CmxleGRpdmVyc2l0YWV0IDwtIHRleHRzdGF0X2xleGRpdihtZWluZS5kZm0sIG1lYXN1cmUgPSAiYWxsIikKbGV4ZGl2ZXJzaXRhZXQKYGBgCgpCZWkgZWluZW0gb2JlcmZsw6RjaGxpY2hlIFZlcmdsZWljaCBkZXIgTWV0cmlrZW4gZsOkbGx0IGF1ZiwgZGFzcyBzaWNoIGRpZSBUZXh0ZSBuaWNodCBzZWhyIHN0YXJrIHVudGVyc2NoZWlkZW4sIHdhcyBpaHJlIGpld2VpbGlnZSBsZXhpa2FsaXNjaGUgVmllbGZhbHQgYmV0cmlmZnQsIGdhbnogdW5hYmjDpG5naWcgZGF2b24sIHdlbGNoZSBNZXRyaWsgdmVyd2VuZGV0IHdpcmQuIERpZXMgaXN0IG5pY2h0IHVuYmVkaW5ndCB2ZXJ3dW5kZXJsaWNoLCBkYSBlcyBzaWNoIHVtIFRleHRlIGRlcyBzZWxiZW4gR2VucmVzIHVuZCBBdXRvcnMgaGFuZGVsdC4gSW50ZXJlc3NhbnRlciB3ZXJkZW4gc29sY2hlIE1ldHJpa2VuIGRhbm4sIHdlbm4gd2lyIHNlaHIgdW50ZXJzY2hpZWRsaWNoZSBHZW5yZXMgb2RlciBBdXRvcmVuIHZlcmdsZWljaGVuIHdvbGxlbiwgZXR3YSBkaWUgUHJvZ3JhbW1lbiB2b24gUGFydGVpZW4sIFRleHRlIGF1cyB1bnRlcnNjaGllZGxpY2hlbiBNZWRpZW4sIG9kZXIgVHdlZXRzIHZvbiB1bnRlcnNjaGllZGxpY2hlbiBOdXR6ZXJuLiAKCldpZWRlciB2ZXJnbGVpY2hlbiB3aXIgZGFoZXIgZGllIEVyZ2Vibmlzc2UsIGRpZSBlaW5lIEFud2VuZHVuZyBkaWVzZXIgVmVyZmFocmVuIGF1ZiBkYXMgUG9saWJsb2dzLUtvcnB1cyBwcm9kdXppZXJ0LiBXaXIgdmVyd2VuZGVuIGRhYmVpIGVpbmUgZWluemVsbmUgTWV0cmlrIGF1cyBkZW0gUmVwZXJ0b2lyZSB2b24gIFt0ZXh0c3RhdF9sZXhkaXZdKGh0dHBzOi8vcXVhbnRlZGEuaW8vcmVmZXJlbmNlL3RleHRzdGF0X2xleGRpdi5odG1sKSB1bmQgYmVudXR6ZW4gZGllc2UsIHVtIGVpbmVuIFZlcmdsZWljaCBkZXIgbGV4aWthbGljc2hlbiBLb21wbGV4aXTDpHQga29uc2VydmF0aXZlciB1bmQgbGlua2VyIEJsb2dzIGltIFplaXR2ZXJsYXVmIGR1cmNoenVmw7xocmVuLgoKYGBge3IgTGV4aWthbGlzY2hlIFZpZWxmYWx0IGluIGtvbnNlcnZhdGl2ZW4gdW5kIGxpbmtlbiBVLlMuLUJsb2dzIMO8YmVyIGRpZSBaZWl0fQpsZXhkaXZlcnNpdGFldCA8LSB0ZXh0c3RhdF9sZXhkaXYocG9saWJsb2dzLmRmbSwgbWVhc3VyZSA9ICJVIikKcG9saWJsb2dzLlUgPC0gbGVmdF9qb2luKGxleGRpdmVyc2l0YWV0LCBwb2xpYmxvZ3Muc3RhdHMsIGJ5ID0gYygiZG9jdW1lbnQiID0gIlRleHQiKSkgJT4lIAogIGdyb3VwX2J5KGRheSwgcmF0aW5nKSAlPiUgCiAgc3VtbWFyaXNlKG1lYW5VID0gbWVhbihVKSkKZ2dwbG90KHBvbGlibG9ncy5VLCBhZXMoZGF5LCBtZWFuVSwgY29sb3IgPSByYXRpbmcpKSArCiAgZ2VvbV9saW5lKCkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsb2VzcyIsIGZvcm11bGEgPSAieSB+IHgiLCBuYS5ybSA9IFRSVUUpICsgCiAgc2NhbGVfY29sb3VyX2JyZXdlcihuYW1lID0gIlR5cCIsIHBhbGV0dGUgPSAiU2V0MSIpICsgCiAgZ2d0aXRsZSgiTGV4aWthbGlzY2hlIFZpZWxmYWx0IGluIGtvbnNlcnZhdGl2ZW4gdW5kIGxpbmtlbiBVLlMuLUJsb2dzIMO8YmVyIGRpZSBaZWl0IikgKyAKICB4bGFiKCJUYWciKSArIHlsYWIoIlUtTWl0dGVsd2VydCIpCmBgYAoKRGFzIEVyZ2VibmlzIHplaWd0LCBkYXNzIGRpZSBrb25zZXJ2YXRpdmVuIEJsb2dzIGltIE1pdHRlbCBsZXhpa2FsaXNjaCB2aWVmw6RsdGlnZXIgc2luZCwgYWxzIGRpZXMgYmVpIGRlbiBsaW5rZW4gQmxvZ3MgYXVzIGRlbSBLb3JwdXMgZGVyIEZhbGwgaXN0LiBVbSBkaWVzZW4gQmVmdW5kIGVpbm9yZG5lbiB6dSBrw7ZubmVuLCBtdXNzIG1hbiBzaWNoIHZvciBBdWdlbiBmw7xocmVuLCBkYXNzIGJlaXNwaWVsc3dlaXNlIGRpZSBWZXJ3ZW5kdW5nIHZvbiBFaWdlbm5hbWVuIG9kZXIgSmFyZ29uIGViZW5zbyB3aWUgZGllIFRleHRsw6RuZ2UgZWluZW4gcG9zaXR2ZW4gRWluZmx1c3MgYXVmIGRpZXNlIE1ldHJpa2VuIGhhYmVuLCBvaG5lIGRhc3MgbWFuIGRhbWl0IHVuYmVkaW5ndCBkYXMgZWluZ2VmYW5nZW4gaGF0LCB3YXMgbWFuIHNpY2ggdW50ZXIgVmllbGZhbHQgb2RlciBLb21wbGV4aXTDpHQgdm9yc3RlbGx0LgoKIyMjIyBMZXNiYXJrZWl0c2luZGl6ZXMKCkVpbmUgd2VpdGVyZSBLbGFzc2Ugdm9uIFRleHQtTWV0cmlrZW4sIGRpZSBzaWNoIGbDvHIgZWluIERva3VtZW50IGF1ZmdydW5kIHNlaW5lciBXb3J0enVzYW1tZW5zZXR6dW5nIGJlcmVjaG5lbiBsYXNzZW4sIHNpbmQgZGllIHNvZy4gW0xlc2JhcmtlaXRzaW5kaXplc10oaHR0cHM6Ly9kZS5tLndpa2lwZWRpYS5vcmcvd2lraS9MZXNiYXJrZWl0c2luZGV4KS4gRGFydW50ZXIgdmVyc3RlaHQgbWFuIE1ldHJpa2VuLCBkaWUgYW5oYW5kIHZvbiB0ZXh0bGljaGVuIEVpZ2Vuc2NoYWZ0ZW4gZWluZW4gWmFobGVud2VydCBiZXJlY2huZW4sIGRlciBkaWUgTGVzZXNjaHdpZXJpZ2tlaXQgZWluZXMgRG9rdW1lbnRlcyBtw7ZnbGljaHN0IGFra3VyYXQgd2llZGVyZ2ViZW4gc29sbC4gQW53ZW5kdW5nIGZpbmRlbiBzb2xjaGUgSW5kaXplcyBldHdhIGltIEJpbGR1bmdzYmVyZWljaCwgd2VubiBlcyB1bSBkaWUgRnJhZ2UgZ2VodCwgd2VsY2hlcyBTY2h3aWVyaWdrZWl0c25pdmVhdSBlaW5lcyBUZXh0ZXMgZsO8ciBTY2jDvGxlciBhbmdlbWVzc2VuIGlzdCwgYWJlciBhdWNoIGluIGRlciDDtmZmZW50bGljaGVuIFZlcndhbHR1bmcsIHdlbm4gbcO2Z2xpY2hzdCBrbGFyZSB1bmQgenVnw6RuZ2xpY2hlIFNwcmFjaGUgYnNwdy4gYXVmIGVpbmVyIGJlcmjDtnJkbGljaGVuIFdlYnNpdGUgdmVyd2VuZGV0IHdlcmRlbiBzb2xsLiAKCkRpZSBLYWxrdWxhdGlvbiB6YWhscmVpY2hlciBMZXNiYXJrZWl0c2luZGl6ZXMgZXJmb2xndCBpbiBRdWFudGVkYSBtaXQgW3RleHRzdGF0X3JlYWRhYmlsaXR5XShodHRwczovL3F1YW50ZWRhLmlvL3JlZmVyZW5jZS90ZXh0c3RhdF9yZWFkYWJpbGl0eS5odG1sKS4gQXVjaCBoaWVyIHdlbmRlbiB3aXIgZGllIEZ1bmt0aW9uIHp1bsOkY2hzdCBhdWYgZGFzIFNoZXJsb2NrIEhvbG1lcy1Lb3JwdXMgYW4sIHVtIGVpbmVuIEVpbmRydWNrIGRhdm9uIGJla29tbWVuLCB3aWUgc2ljaCBkaWUgTGVzZXNjaHdpZXJpZ2tlaXQgdm9uIGVpbmVtIFJvbWFuIHp1bSBuw6RjaHN0ZW4gdW50ZXJzY2hlaWRldC4KCmBgYHtyIExlc2JhcmtlaXRzaW5kaXplcyBpbSBTaGVybG9jayBIb2xtZXMtS29ycHVzIGJlcmVjaG5lbn0KbGVzYmFya2VpdCA8LSB0ZXh0c3RhdF9yZWFkYWJpbGl0eShrb3JwdXMsIG1lYXN1cmUgPSAiYWxsIikKbGVzYmFya2VpdApgYGAKCldpZSB2ZXJow6RsdCBlcyBzaWNoIG1pdCBkZXIgTGVzYmFya2VpdCBkZXIgQmxvZ3NiZWl0csOkZ2UgYXVzIGRlbSBQb2xpYmxvZ3MtS29ycHVzPyBXaWVkZXIgdmVyZ2xlaWNoZW4gd2lyIGVpbmVyc2VpdHMgZGllIHNlY2hzIEJsb2dzIHVudGVyZWluYW5kZXIgdW5kIGFuZGVyZXJzZWl0cyBkaWUgYmVpZGVuIGlkZW9sb2dpc2NoZW4gUmljaHR1bmdlbiAoZWhlciBrb25zZXJ2YXRpdiB2LiBlaGVyIGxpbmtzKS4gSW0gR2VnZW5zYXR6IHp1IGRlciBCZXJlY2hudW5nIHZvbiBNaXR0ZWx3ZXJ0ZW4sIGRpZSB3aXIgYmVpIGRlbiBTdGF0aXN0aWtlbiB6dXIgbGV4aWthbGlzY2hlbiBWaWVsZmFsdCBoZXJhbmdlem9nZW4gaGFiZW4sIHZlcmdsZWljaGVuIHdpciBoaWVyIGRpcmVrdCBkaWUgR2VzYW10YW56YWhsIGRlciBCZWl0csOkZ2UsIHVtIGVpbmVuIEVpbmRydWNrIGRlciBXZXJ0ZXZlcnRlaWx1bmcgenUgZXJoYWx0ZW4uIFp1bsOkY2hzdCBiZXJlY2huZW4gd2lyIGRhenUgZGllIExlc2JhcmtlaXQgbmFjaCBkZXIgW0ZsZXNjaC1LaW5jYWlkLU1ldHJpa10oaHR0cHM6Ly9kZS53aWtpcGVkaWEub3JnL3dpa2kvTGVzYmFya2VpdHNpbmRleCkgdW5kIHZlcmJpbmRlbiBkaWUgRXJnZWJuaXNzZSBkYW5uIG1pdCBkZW4genUgZGVtIEtvcnB1cyBnZWjDtnJlbmRlbiBNZXRhZGF0ZW4gYXVzIGRlbSBTdGF0cy1EYXRhLUZyYW1lLiAKCmBgYHtyIExlc2JhcmtlaXRzaW5kaXplcyBpbiBrb25zZXJ2YXRpdmVuIHVuZCBsaW5rZW4gVS5TLi1CbG9nIGJlcmVjaG5lbn0KbGVzYmFya2VpdCA8LSB0ZXh0c3RhdF9yZWFkYWJpbGl0eShwb2xpYmxvZ3Mua29ycHVzLCBtZWFzdXJlID0gIkZsZXNjaC5LaW5jYWlkIikKcG9saWJsb2dzLkZLIDwtIGxlZnRfam9pbihsZXNiYXJrZWl0LCBwb2xpYmxvZ3Muc3RhdHMsIGJ5ID0gYygiZG9jdW1lbnQiID0gIlRleHQiKSkKYGBgCgpEYW5uIHBsb3R0ZW4gd2lyIGFuc2NobGllw59lbmQgZGFzIEVyZ2VibmlzIGFscyBrb21iaW5pZXJ0ZXMgQm94LSB1bmQgU2NhdHRlcnBsb3QuIAoKYGBge3IgTGVzYmFya2VpdHNpbmRpemVzIGluIGtvbnNlcnZhdGl2ZW4gdW5kIGxpbmtlbiBVLlMuLUJsb2cgcGxvdHRlbn0KZ2dwbG90KHBvbGlibG9ncy5GSywgYWVzKGJsb2csIEZsZXNjaC5LaW5jYWlkKSkgKyAKICBnZW9tX2JveHBsb3Qob3V0bGllci5zaGFwZSA9IE5BKSArIAogIHNjYWxlX2NvbG91cl9icmV3ZXIobmFtZSA9ICJUeXAiLCBwYWxldHRlID0gIlNldDEiKSArIAogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBxdWFudGlsZShwb2xpYmxvZ3MuRkskRmxlc2NoLktpbmNhaWQsIGMoMC4wMSwgMC45OSkpKSArIAogIGdlb21faml0dGVyKGFlcyhibG9nLCBGbGVzY2guS2luY2FpZCwgY29sb3VyID0gcmF0aW5nKSwgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjQsIGhlaWdodCA9IDApLCBhbHBoYSA9IDAuMiwgc2l6ZSA9IDAuMywgc2hvdy5sZWdlbmQgPSBGKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgaGp1c3QgPSAxKSkgKyAKICB4bGFiKCIiKSArIHlsYWIoIkZsZXNjaC1LaW5jYWlkLUluZGV4IikgKyAKICBnZ3RpdGxlKCJMZXNlbGVpY2h0aWdrZWl0IHZvbiBCZWl0csOkZ2VuIGluIGtvbnNlcnZhdGl2ZW4gdW5kIGxpbmtlbiBVLlMuLUJsb2dzIikKYGBgCgpadW7DpGNoc3QgZWlubWFsIGzDpHNzdCBzaWNoIHVuc2Nod2VyIGVya2VubmVuLCBkYXNzIHBvbGl0aXNjaGUgQmxvZ2JlaXRyw6RnZSBpbSBTaW5uZSBkZXIgRmxlc2NoLUtpbmNhaWQtTWV0cmlrIGFuc3BydWNoc3ZvbGxlIFRleHRlIHNpbmQsIGRpZSBzaWNoIGF1ZiBkZXIgc2Nod2llcmlnc3RlbiBMZXNlc3R1ZmUgYmV3ZWdlbi4gQWxsZXJkaW5ncyBzaW5kIGRpZSBUZXh0ZSBpbiBkZW4ga29uc2VydmF0aXZlbiBCbG9ncyAtLSB2b24gZGVyIFF1ZWxsZSAnbW0nIGVpbm1hbCBhYmdlc2VoZW4gLS0gZXR3YXMgZWluZmFjaGVyIGdlaGFsdGVuLCBhbHMgZGllcyBpbiBkZW4gbGlua2VuIEJsb2dzIGRlciBGYWxsIGlzdC4gCgpFaW4gd2VpdGVyZXMgc2Now7ZuZXMgQmVpc3BpZWwgZsO8ciBkZW4gTnV0emVuIHNvbGNoZXIgTWV0cmlrZW4gZmluZGV0IHNpY2ggaW4gZGVyIERva3VtZW50YXRpb24gZGVyIEZ1bmt0aW9uIFt0ZXh0c3RhdF9yZWFkYWJpbGl0eV0oaHR0cHM6Ly9xdWFudGVkYS5pby9yZWZlcmVuY2UvdGV4dHN0YXRfcmVhZGFiaWxpdHkuaHRtbCkuIEhhdHRlIGRpZSBBbnRyaXR0c3JlZGUgdm9uIEdlb3JnZSBXYXNoaW5ndG9uIGltIEphaHIgMTc4OSBub2NoIGVpbmVuIEZsZXNoLUtpbmNhaWQtSW5kZXggdm9uIDI4LCBzbyBiZXRydWcgZGVyIFdlcnQgYmVpIGRlciBBbnRyaXR0c3JlZGUgdm9uIERvbmFsZCBUcnVtcCBpbiAyMDE3IG51ciBub2NoIDkgKHdhcyBhbGxlcmRpbmdzIGRlbSBUcmVuZCBzZWl0IE1pdHRlIGRlcyAyMC4gSmhkLiBlbnRzcHJpY2h0KS4gCgoK