Data Must Flow
  • About
  •      
  • Blog
  • R101
  • Photography
  • Generative art
  •      
  • CV
  • Contact
  • Show All Code
  • Hide All Code
  • Download Rmd

Introduction

Starting to work with a new tool may not be easy, especially when that “tool” means a new programming language. At the same time, there might be an opportunity to build upon something already known and so make the transition smoother and less painful.

In my case, it was the transition from R to Python. Unfortunately, my former colleagues, pythonians, did not share my genuine fondness of R. Also, whether R enthusiasts like it or not, Python is a widely used tool in data analysis/engineering/science and beyond. So, I concluded that learning at least some Python is a reasonable thing to do.

For me, the first steps were maybe the most difficult ones. Residing in the comfort of RStudio, IDEs like Pycharm or Atom did not feel familiar. This experience led to the decision to begin in the well-known environment and test its limits when it comes to working with Python.

To tell the truth, I did not end up using RStudio as the weapon of choice for using Python in a general setting. Hopefully, the following text will deliver the message why. However, I am convinced that for some use-cases, like integrating R and Python in an ad hoc analysis R Markdown way, RStudio still represents a viable way to go.

More importantly, it could be a convenient starting line for people with the primary background in R.

So, what did I find?

Analysis

Packages and environment

First of all, let us set the environment and load the required packages.

  • Global environment:
# Globally round numbers at decimals
  options(digits=2)
  
  # Force R to use regular numbers instead of using the e+10-like notation
  options(scipen = 999)
  • R libraries:
# Load the required packages. 
  # If these are not available, install them first.
  ipak <- function(pkg){
    new.pkg <- pkg[!(pkg %in% installed.packages()[, "Package"])]
    if (length(new.pkg)) 
      install.packages(new.pkg, 
                       dependencies = TRUE)
    sapply(pkg, 
           require, 
           character.only = TRUE)
  }
  
  packages <- c("tidyverse", # Data wrangling
                "gapminder", # Data source
                "knitr", # R Markdown styling
                "htmltools") # .html files manipulation
  
  ipak(packages)
tidyverse gapminder     knitr htmltools 
       TRUE      TRUE      TRUE      TRUE 

In this analysis, I will be working with the reticulate library, a package developed at RStudio. However, feel free to look for alternatives.

Also, I am going to import the reticulate package separately for the purposes of a clearer flow.

  • Python activation:
# The "weapon of choice"
  library(reticulate)
  
  # Specifying which version of python to use.
  use_python("/home/vg/anaconda3/bin/python3.7", 
             required = T) # Locate and run Python
  • Python libraries:
import pandas as pd # data wrangling
  import numpy as np # arrays, matrices, math
  import statistics as st # functions for calculating mathematical statistics
  import plotly.express as px # plotting package

One of the limits of working with Python in R (Studio) is that in some cases you do not receive the Python traceback by default. That is a problem because, if I oversimplify things, a traceback helps you to identify where is the problem. Meaning it helps you fix it. So, consider it as an error message (or the lack of it).

  • For example, when calling a library that you do not have installed, the Python chunk in R Markdown gives you green lights (so everything looks up and running), but this does not mean that the code ran the way you would expect (e.g. it imported a library).

To deal with that, I suggest making sure you have your libraries installed in Terminal. If they are not, you can always install them and import afterwards.

For example, I will first import the json package which is already installed on my machine. I will do so by using Terminal here in RStudio. In addition, let me try to import the TensorFlow package.

From the following picture, you can see that there is no package TensorFlow. So, let me switch back to bash and install the package:

The working directory was changed to /home/vg inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.


Then go to the directory where is the newly installed package TensorFlow installed, switch to Python and import the package once again:


All right. For more information, take a look at the Installing Python Modules page.

Data

As we now have R and Python set, let us import some data to play with. I will be working with a sample from the Gapminder, an intriguing project related to socio-demography of the world’s population. To be more specific, I will be working with the latest available data within the GapMinder library.

  • Data import in R:
# Let us begin with the most recent set of data
  gapminder_latest <- gapminder %>%
            filter(year == max(year))

So, now we have a data loaded in R. Unfortunately, it is not possible to access the R objects (e.g. vectors od tibbles) directly by Python. So, we need to convert the R object(s) first.

  • Data import in Python:
# Convert R Data Frame (tibble) into the pandas Data Frame
  gapminder_latest_py = r['gapminder_latest'] 

One important thing to realise when working with Python objects (e.g. arrays or pandas Data Frame) is that they are not explicitly stored in the environment as the R objects are.

  • In other words, if we want to know what is stored in the workspace, we must call functions like dir(), globals() or locals():
['R', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'gapminder_latest_py', 'np', 'pd', 'px', 'r', 'st', 'sys']

Great, among the present objects, we can clearly see the data (gapminder_latest_py) or libraries (e.g. px).

So, let us explore the data a bit!

Life Expectancy

For the demonstration purposes, I will focus on the life expectancy or the average number of years a person is expected to live.

Descriptive statistics

Let’s begin with calculating some descriptive statistics like mean, median or the number of rows in the data using Python:

# Descriptive statistics for the inline code in Python
  
  ## Data Frame Overview
  
  ### Number of rows
  gapminder_latest_shape = gapminder_latest_py.shape[0] 
  ### Number of distinct values within the life expectancy variable
  gapminder_latest_count_py = gapminder_latest_py['lifeExp'].nunique()
  
  ## Life Expectancy
  
  ### Median (Life Expectancy)
  gapminder_latest_median_lifeExp_py = st.median(gapminder_latest_py['lifeExp']) 
  ### Mean
  gapminder_latest_mean_lifeExp_py = st.mean(gapminder_latest_py['lifeExp'])
  ### Minimum
  gapminder_latest_min_lifeExp_py = min(gapminder_latest_py['lifeExp']) 
  ### Maximum
  gapminder_latest_max_lifeExp_py = max(gapminder_latest_py['lifeExp'])
  ### Standard deviation
  gapminder_latest_stdev_lifeExp_py = st.stdev(gapminder_latest_py['lifeExp'])

Nice. Unfortunately, we are not able to use the Python objects for inline coding, one of the key features of literate coding in R Markdown. So, if we want to use the results for inline codes, we need to transform the Python objects back to R:

# Descriptive statistics for the inline code in Python - transformed to R
  
  ## Data Frame Overview
  
  ## Number of rows
  gapminder_latest_nrow_r = py$gapminder_latest_shape
  ### Number of distinct values within the life expectancy variable
  gapminder_latest_count_r = py$gapminder_latest_count_py
  
  ## Life Expectancy
  
  ### Median (Life Expectancy)
  gapminder_latest_median_lifeExp_r = py$gapminder_latest_median_lifeExp_py
  ### Mean
  gapminder_latest_mean_lifeExp_r = py$gapminder_latest_mean_lifeExp_py
  ### Minimum
  gapminder_latest_min_lifeExp_r = py$gapminder_latest_min_lifeExp_py
  ### Maximum
  gapminder_latest_max_lifeExp_r = py$gapminder_latest_max_lifeExp_py
  ### Standard deviation
  gapminder_latest_stdev_lifeExp_r = py$gapminder_latest_stdev_lifeExp_py

So, what can we say about life expectancy in 2007?

First of all, there were 142 countries on the list. The minimum value of life expectancy was 39.61 years, the maximum 82.6 years.

The average value for life expectancy was 67.01 years and 50% or median hope to live 71.94 years or more. Lastly, the standard deviation was 12.07 years.

Graphs (using Plotly)

Okay, let’s move to something else, like graphs.

For example, we can take a look at how is the life expectancy distributed across the globe using Plotly:

fig = px.histogram(gapminder_latest_py, # package.function; Data Frame
                     x="lifeExp", # Variable on the X axis
                     range_x=(gapminder_latest_min_lifeExp_py, 
                              gapminder_latest_max_lifeExp_py), # Minimum and maximum values for the X axis
                     labels={'lifeExp':'Life expectancy - in years'}, # Naming of the interactive part
                     color_discrete_sequence=['#005C4E']) # Colour of fill 
  
  lifeExpHist = fig.update_layout(
    title="Figure 1. Life Expectancy in 2007 Across the Globe - in Years", # The name of the graph
    xaxis_title="Years", # X-axis title
    yaxis_title="Count", # Y-axis title
    font=dict( # "css"
      family="Roboto",
      size=12,
      color="#252A31"
    ))
  
  lifeExpHist.write_html("lifeExpHist.html") # Save the graph as a .html object

Unfortunately, it is not possible to print interactive Plotly graphs in R Markdown via Python. Or, to be more precise, you will receive a Figure object by printing (e.g. print(lifeExpHist)) it:

Figure({
      'data': [{'alignmentgroup': 'True',
                'bingroup': 'x',
                'hovertemplate': 'Life expectancy - in years=%{x}<br>count=%{y}<extra></extra>',
                'legendgroup': '',
                'marker': {'color': '#005C4E'},
                'name': '',
                'offsetgroup': '',
                'orientation': 'v',
                'showlegend': False,
                'type': 'histogram',
                'x': array([43.828, 76.423, 72.301, 42.731, 75.32 , 81.235, 79.829, 75.635, 64.062,
                            79.441, 56.728, 65.554, 74.852, 50.728, 72.39 , 73.005, 52.295, 49.58 ,
                            59.723, 50.43 , 80.653, 44.741, 50.651, 78.553, 72.961, 72.889, 65.152,
                            46.462, 55.322, 78.782, 48.328, 75.748, 78.273, 76.486, 78.332, 54.791,
                            72.235, 74.994, 71.338, 71.878, 51.579, 58.04 , 52.947, 79.313, 80.657,
                            56.735, 59.448, 79.406, 60.022, 79.483, 70.259, 56.007, 46.388, 60.916,
                            70.198, 82.208, 73.338, 81.757, 64.698, 70.65 , 70.964, 59.545, 78.885,
                            80.745, 80.546, 72.567, 82.603, 72.535, 54.11 , 67.297, 78.623, 77.588,
                            71.993, 42.592, 45.678, 73.952, 59.443, 48.303, 74.241, 54.467, 64.164,
                            72.801, 76.195, 66.803, 74.543, 71.164, 42.082, 62.069, 52.906, 63.785,
                            79.762, 80.204, 72.899, 56.867, 46.859, 80.196, 75.64 , 65.483, 75.537,
                            71.752, 71.421, 71.688, 75.563, 78.098, 78.746, 76.442, 72.476, 46.242,
                            65.528, 72.777, 63.062, 74.002, 42.568, 79.972, 74.663, 77.926, 48.159,
                            49.339, 80.941, 72.396, 58.556, 39.613, 80.884, 81.701, 74.143, 78.4  ,
                            52.517, 70.616, 58.42 , 69.819, 73.923, 71.777, 51.542, 79.425, 78.242,
                            76.384, 73.747, 74.249, 73.422, 62.698, 42.384, 43.487]),
                'xaxis': 'x',
                'yaxis': 'y'}],
      'layout': {'barmode': 'relative',
                 'font': {'color': '#252A31', 'family': 'Roboto', 'size': 12},
                 'legend': {'tracegroupgap': 0},
                 'margin': {'t': 60},
                 'template': '...',
                 'title': {'text': 'Figure 1. Life Expectancy in 2007 Across the Globe - in Years'},
                 'xaxis': {'anchor': 'y', 'domain': [0.0, 1.0], 'range': [39.613, 82.603], 'title': {'text': 'Years'}},
                 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'Count'}}}
  })

So, we import the previously created .html file instead (e.g. using the includeHTML function from the htmltools package):

htmltools::includeHTML("lifeExpHist.html") # Render the graph

So, here comes a basic, yet interactive histogram made in the Python version of Plotly.

However, producing this graph in RStudio required quite a workaround. At the same time, a size of the graphs produced this way could easily be tens of MBs.

  • Consequently, a .html report containing such graphs would require a lot of data to download for a reader and it would take more time to render the page.

Summary tables (using pandas)

One of the common use-cases for pandas is to provide a data description. The respective code runs just fine. However, the output cannot be styled as you are used from styling in pandas.

  • When speaking of pandas, we can easily create a summary table for the commonly used statistics like mean or standard deviation of life expectancy for individual continents:
# Create a pandas Data Frame object containing the relevant variable,
  # conduct formatting.
  
  gapminder_latest_py['continent'] = gapminder_latest_py['continent'].astype(str)
  variable = 'continent'
  variable_name = 'Continents'
  
  gapminder_latest_py['lifeExp'] = gapminder_latest_py['lifeExp'].astype(int)
  variable_grouping = 'lifeExp'
  
  contact_window_days = gapminder_latest_py.groupby([
                          pd.Grouper(key=variable)])\
                          [variable_grouping]\
                          .agg(['count',
                                'min',
                                'mean',
                                'median',
                                'std',
                                'max'])\
                          .reset_index()
  
  contact_window_days_style = contact_window_days\
                          .rename({'count': 'Count',
                                   'median': 'Median',
                                   'std': 'Standard Deviation',
                                   'min': 'Minimum', 
                                   'max': 'Maximum',
                                   'mean': 'Mean',}, axis='columns')
                                   
  continent  Count  Minimum       Mean  Median  Standard Deviation  Maximum
  0    Africa     52       39  54.326923    52.0            9.644100       76
  1  Americas     25       60  73.040000    72.0            4.495183       80
  2      Asia     33       43  70.151515    72.0            7.984834       82
  3    Europe     30       71  77.100000    78.0            2.916658       81
  4   Oceania      2       80  80.500000    80.5            0.707107       81

Also, note that the Python environment settings override those from R. For example, take a look at the number of digits for Mean or Standard Deviation. There are six digits instead of two set at the beginning.

Closing remarks

Okay, that’s enough for now. If you are hungry for more advanced things like unsupervised learning using scikit-learn package in R, take a look.

However, before closing this post, let me just say that if you think about switching to Python as such and using it often, consider IDE alternatives to RStudio.

Many analysts swear on Jupyter notebooks for the interactivity, integration of markdown or option to run code in various languages like R, Julia or JavaScript. JupyterHub is a platform based on Jupyter notebooks, adding version control. Usually, users run analyses in a containerised environment). Another take on interactivity and collaboration could be Colab, basically, Jupyter notebooks running on Google Cloud.

Last but not least, there is a great piece of software called Visual Studio Code. It not only allows you to create and run code in a plethora of languages or seamless flow between pure Python code and interactive Jupyter notebooks. And maybe even more importantly, it provides you with very efficient version control management (like Git integration and extensions). If you choose this IDE, you can set up VS Code for Python development like RStudio.

But no matter what path to Python you choose, don’t forget that it is a tool suitable for some situations and maybe not so suitable to others. Just like R. Try to leverage the best of it while being aware of the pros of different tools.

   

Go back to Blog

LS0tCnRpdGxlOiAiUHl0aG9uIGRvbmUgdGhlIFIgKE1hcmtkb3duKSB3YXkiCmF1dGhvcjogIlZpdCBHYWJyaGVsIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGFsbGlnbjogcmlnaHQKICAgIHRoZW1lOiBmbGF0bHkKICAgIGhpZ2hsaWdodDogbW9ub2Nocm9tZQogICAgY3NzOiB+L0Rlc2t0b3AvR2l0L1BlcnNvbmFsL2RhdGFtdXN0Zmxvdy9wdWJsaWMvY3NzL2NvZGVyLm1pbi5hNGYzMzIyMTNhMjFjZThlYjUyMTY3MGM2MTQ0NzBjNTg5MjNhYWFmMzg1ZTJhNzM5ODJjMzFkZDc2NDJkZWNiLmNzcwogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2Zsb2F0OiB5ZXMKICBwZGZfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAnMycKLS0tCgojIEludHJvZHVjdGlvbgoKU3RhcnRpbmcgdG8gd29yayB3aXRoIGEgbmV3IHRvb2wgbWF5IG5vdCBiZSBlYXN5LCBlc3BlY2lhbGx5IHdoZW4gdGhhdCAqInRvb2wiKiBtZWFucyBhIG5ldyBwcm9ncmFtbWluZyBsYW5ndWFnZS4gQXQgdGhlIHNhbWUgdGltZSwgdGhlcmUgbWlnaHQgYmUgYW4gb3Bwb3J0dW5pdHkgdG8gYnVpbGQgdXBvbiBzb21ldGhpbmcgYWxyZWFkeSBrbm93biBhbmQgc28gbWFrZSB0aGUgdHJhbnNpdGlvbiBzbW9vdGhlciBhbmQgbGVzcyBwYWluZnVsLgoKSW4gbXkgY2FzZSwgaXQgd2FzIHRoZSB0cmFuc2l0aW9uIGZyb20gKipSKiogdG8gKipQeXRob24qKi4gVW5mb3J0dW5hdGVseSwgbXkgZm9ybWVyIGNvbGxlYWd1ZXMsIHB5dGhvbmlhbnMsIGRpZCBub3Qgc2hhcmUgbXkgZ2VudWluZSBmb25kbmVzcyBvZiBSLiBBbHNvLCB3aGV0aGVyIFIgZW50aHVzaWFzdHMgbGlrZSBpdCBvciBub3QsICoqUHl0aG9uKiogaXMgYSB3aWRlbHkgdXNlZCB0b29sIGluIGRhdGEgYW5hbHlzaXMvZW5naW5lZXJpbmcvc2NpZW5jZSBhbmQgYmV5b25kLiBTbywgSSBjb25jbHVkZWQgdGhhdCBsZWFybmluZyBhdCBsZWFzdCBzb21lIFB5dGhvbiBpcyBhIHJlYXNvbmFibGUgdGhpbmcgdG8gZG8uCgpGb3IgbWUsIHRoZSBmaXJzdCBzdGVwcyB3ZXJlIG1heWJlIHRoZSBtb3N0IGRpZmZpY3VsdCBvbmVzLiBSZXNpZGluZyBpbiB0aGUgY29tZm9ydCBvZiAqUlN0dWRpbyosIElERXMgbGlrZSAqUHljaGFybSogb3IgKkF0b20qIGRpZCBub3QgZmVlbCBmYW1pbGlhci4gVGhpcyBleHBlcmllbmNlIGxlZCB0byB0aGUgZGVjaXNpb24gdG8gYmVnaW4gaW4gdGhlIHdlbGwta25vd24gZW52aXJvbm1lbnQgYW5kIHRlc3QgaXRzIGxpbWl0cyB3aGVuIGl0IGNvbWVzIHRvIHdvcmtpbmcgd2l0aCBQeXRob24uCgpUbyB0ZWxsIHRoZSB0cnV0aCwgSSBkaWQgbm90IGVuZCB1cCB1c2luZyAqUlN0dWRpbyogYXMgdGhlIHdlYXBvbiBvZiBjaG9pY2UgZm9yIHVzaW5nIFB5dGhvbiBpbiBhIGdlbmVyYWwgc2V0dGluZy4gSG9wZWZ1bGx5LCB0aGUgZm9sbG93aW5nIHRleHQgd2lsbCBkZWxpdmVyIHRoZSBtZXNzYWdlIHdoeS4gSG93ZXZlciwgSSBhbSBjb252aW5jZWQgdGhhdCBmb3Igc29tZSB1c2UtY2FzZXMsIGxpa2UgaW50ZWdyYXRpbmcgUiBhbmQgUHl0aG9uIGluIGFuIGFkIGhvYyBhbmFseXNpcyBSIE1hcmtkb3duIHdheSwgKlJTdHVkaW8qIHN0aWxsIHJlcHJlc2VudHMgYSB2aWFibGUgd2F5IHRvIGdvLgoKTW9yZSBpbXBvcnRhbnRseSwgaXQgY291bGQgYmUgYSBjb252ZW5pZW50ICoqc3RhcnRpbmcgbGluZSoqIGZvciBwZW9wbGUgd2l0aCB0aGUgKipwcmltYXJ5IGJhY2tncm91bmQgaW4gUioqLgoKU28sIHdoYXQgZGlkIEkgZmluZD8KCiMgQW5hbHlzaXMKCiMjIFBhY2thZ2VzIGFuZCBlbnZpcm9ubWVudAoKRmlyc3Qgb2YgYWxsLCBsZXQgdXMgc2V0IHRoZSBlbnZpcm9ubWVudCBhbmQgbG9hZCB0aGUgcmVxdWlyZWQgcGFja2FnZXMuCgoqICoqR2xvYmFsIGVudmlyb25tZW50OioqCmBgYHtyfQojIEdsb2JhbGx5IHJvdW5kIG51bWJlcnMgYXQgZGVjaW1hbHMKb3B0aW9ucyhkaWdpdHM9MikKCiMgRm9yY2UgUiB0byB1c2UgcmVndWxhciBudW1iZXJzIGluc3RlYWQgb2YgdXNpbmcgdGhlIGUrMTAtbGlrZSBub3RhdGlvbgpvcHRpb25zKHNjaXBlbiA9IDk5OSkKYGBgCgoqICoqUiBsaWJyYXJpZXM6KioKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CiMgTG9hZCB0aGUgcmVxdWlyZWQgcGFja2FnZXMuIAojIElmIHRoZXNlIGFyZSBub3QgYXZhaWxhYmxlLCBpbnN0YWxsIHRoZW0gZmlyc3QuCmlwYWsgPC0gZnVuY3Rpb24ocGtnKXsKICBuZXcucGtnIDwtIHBrZ1shKHBrZyAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpWywgIlBhY2thZ2UiXSldCiAgaWYgKGxlbmd0aChuZXcucGtnKSkgCiAgICBpbnN0YWxsLnBhY2thZ2VzKG5ldy5wa2csIAogICAgICAgICAgICAgICAgICAgICBkZXBlbmRlbmNpZXMgPSBUUlVFKQogIHNhcHBseShwa2csIAogICAgICAgICByZXF1aXJlLCAKICAgICAgICAgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQp9CgpwYWNrYWdlcyA8LSBjKCJ0aWR5dmVyc2UiLCAjIERhdGEgd3JhbmdsaW5nCiAgICAgICAgICAgICAgImdhcG1pbmRlciIsICMgRGF0YSBzb3VyY2UKICAgICAgICAgICAgICAia25pdHIiLCAjIFIgTWFya2Rvd24gc3R5bGluZwogICAgICAgICAgICAgICJodG1sdG9vbHMiKSAjIC5odG1sIGZpbGVzIG1hbmlwdWxhdGlvbgoKaXBhayhwYWNrYWdlcykKYGBgCgpJbiB0aGlzIGFuYWx5c2lzLCBJIHdpbGwgYmUgd29ya2luZyB3aXRoIHRoZSBgcmV0aWN1bGF0ZWAgW2xpYnJhcnldKGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vcmV0aWN1bGF0ZS8pLCBhIHBhY2thZ2UgZGV2ZWxvcGVkIGF0IFJTdHVkaW8uIEhvd2V2ZXIsIGZlZWwgZnJlZSB0byBsb29rIGZvciBbYWx0ZXJuYXRpdmVzXShodHRwczovL3d3dy5kYXRhY2FtcC5jb20vY29tbXVuaXR5L3R1dG9yaWFscy91c2luZy1ib3RoLXB5dGhvbi1yKS4KCkFsc28sIEkgYW0gZ29pbmcgdG8gaW1wb3J0IHRoZSBgcmV0aWN1bGF0ZWAgcGFja2FnZSBzZXBhcmF0ZWx5IGZvciB0aGUgcHVycG9zZXMgb2YgYSBjbGVhcmVyIGZsb3cuCgoqICoqUHl0aG9uIGFjdGl2YXRpb246KioKYGBge3IsIG1lc3NhZ2U9RkFMU0UsICB3YXJuaW5nPUZBTFNFfSAKIyBUaGUgIndlYXBvbiBvZiBjaG9pY2UiCmxpYnJhcnkocmV0aWN1bGF0ZSkKCiMgU3BlY2lmeWluZyB3aGljaCB2ZXJzaW9uIG9mIHB5dGhvbiB0byB1c2UuCnVzZV9weXRob24oIi9ob21lL3ZnL2FuYWNvbmRhMy9iaW4vcHl0aG9uMy43IiwgCiAgICAgICAgICAgcmVxdWlyZWQgPSBUKSAjIExvY2F0ZSBhbmQgcnVuIFB5dGhvbgpgYGAKCiogKipQeXRob24gbGlicmFyaWVzOioqCmBgYHtweXRob24sIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmltcG9ydCBwYW5kYXMgYXMgcGQgIyBkYXRhIHdyYW5nbGluZwppbXBvcnQgbnVtcHkgYXMgbnAgIyBhcnJheXMsIG1hdHJpY2VzLCBtYXRoCmltcG9ydCBzdGF0aXN0aWNzIGFzIHN0ICMgZnVuY3Rpb25zIGZvciBjYWxjdWxhdGluZyBtYXRoZW1hdGljYWwgc3RhdGlzdGljcwppbXBvcnQgcGxvdGx5LmV4cHJlc3MgYXMgcHggIyBwbG90dGluZyBwYWNrYWdlCmBgYAoKCk9uZSBvZiB0aGUgbGltaXRzIG9mIHdvcmtpbmcgd2l0aCAqKlB5dGhvbioqIGluICoqUioqICooU3R1ZGlvKSogaXMgdGhhdCBpbiBzb21lIGNhc2VzIHlvdSBkbyBub3QgcmVjZWl2ZSB0aGUgUHl0aG9uIHRyYWNlYmFjayBieSBkZWZhdWx0LiBUaGF0IGlzIGEgcHJvYmxlbSBiZWNhdXNlLCBpZiBJIG92ZXJzaW1wbGlmeSB0aGluZ3MsIGEgdHJhY2ViYWNrIGhlbHBzIHlvdSB0byBpZGVudGlmeSB3aGVyZSBpcyB0aGUgcHJvYmxlbS4gTWVhbmluZyBpdCBoZWxwcyB5b3UgZml4IGl0LiBTbywgY29uc2lkZXIgaXQgYXMgYW4gKiplcnJvciBtZXNzYWdlKiogKG9yIHRoZSBsYWNrIG9mIGl0KS4gCgoqIEZvciBleGFtcGxlLCB3aGVuIGNhbGxpbmcgYSBsaWJyYXJ5IHRoYXQgeW91IGRvIG5vdCBoYXZlIGluc3RhbGxlZCwgdGhlIFB5dGhvbiBjaHVuayBpbiBSIE1hcmtkb3duIGdpdmVzIHlvdSBncmVlbiBsaWdodHMgKHNvIGV2ZXJ5dGhpbmcgbG9va3MgdXAgYW5kIHJ1bm5pbmcpLCBidXQgdGhpcyBkb2VzIG5vdCBtZWFuIHRoYXQgdGhlIGNvZGUgcmFuIHRoZSB3YXkgeW91IHdvdWxkIGV4cGVjdCAoZS5nLiBpdCBpbXBvcnRlZCBhIGxpYnJhcnkpLgoKVG8gZGVhbCB3aXRoIHRoYXQsIEkgc3VnZ2VzdCBtYWtpbmcgc3VyZSB5b3UgaGF2ZSB5b3VyIGxpYnJhcmllcyBpbnN0YWxsZWQgaW4gYFRlcm1pbmFsYC4gSWYgdGhleSBhcmUgbm90LCB5b3UgY2FuIGFsd2F5cyBpbnN0YWxsIHRoZW0gYW5kIGltcG9ydCBhZnRlcndhcmRzLgoKRm9yIGV4YW1wbGUsIEkgd2lsbCBmaXJzdCBpbXBvcnQgdGhlIGBqc29uYCBwYWNrYWdlIHdoaWNoIGlzIGFscmVhZHkgaW5zdGFsbGVkIG9uIG15IG1hY2hpbmUuIEkgd2lsbCBkbyBzbyBieSB1c2luZyAqKlRlcm1pbmFsKiogaGVyZSBpbiAqUlN0dWRpbyouIEluIGFkZGl0aW9uLCBsZXQgbWUgdHJ5IHRvIGltcG9ydCB0aGUgYFRlbnNvckZsb3dgIFtwYWNrYWdlXShodHRwczovL3d3dy50ZW5zb3JmbG93Lm9yZy8pLgoKRnJvbSB0aGUgZm9sbG93aW5nIHBpY3R1cmUsIHlvdSBjYW4gc2VlIHRoYXQgdGhlcmUgaXMgbm8gcGFja2FnZSBgVGVuc29yRmxvd2AuIFNvLCBsZXQgbWUgc3dpdGNoIGJhY2sgdG8gKipiYXNoKiogYW5kIGluc3RhbGwgdGhlIHBhY2thZ2U6CgpgYGB7ciwgZWNobz1GQUxTRX0KaW5jbHVkZV9ncmFwaGljcygiL2hvbWUvdmcvRGVza3RvcC9HaXQvUGVyc29uYWwvZGF0YW11c3RmbG93L3B1YmxpYy9wb3N0cy9weXRob24taW4tci9pbnN0YWxsaW5nX3BhY2thZ2VzLnBuZyIpCmBgYAoKPGJyPgoKVGhlbiBnbyB0byB0aGUgZGlyZWN0b3J5IHdoZXJlIGlzIHRoZSBuZXdseSBpbnN0YWxsZWQgcGFja2FnZSBgVGVuc29yRmxvd2AgaW5zdGFsbGVkLCBzd2l0Y2ggdG8gKipQeXRob24qKiBhbmQgaW1wb3J0IHRoZSBwYWNrYWdlIG9uY2UgYWdhaW46CgpgYGB7ciwgZWNobz1GQUxTRX0KaW5jbHVkZV9ncmFwaGljcygiL2hvbWUvdmcvRGVza3RvcC9HaXQvUGVyc29uYWwvZGF0YW11c3RmbG93L3B1YmxpYy9wb3N0cy9weXRob24taW4tci9sb2FkaW5nX3RlbnNvcmZsb3cucG5nIikKYGBgCjxicj4KCkFsbCByaWdodC4gRm9yIG1vcmUgaW5mb3JtYXRpb24sIHRha2UgYSBsb29rIGF0IHRoZSBbSW5zdGFsbGluZyBQeXRob24gTW9kdWxlc10oaHR0cHM6Ly9kb2NzLnB5dGhvbi5vcmcvMy40L2luc3RhbGxpbmcvaW5kZXguaHRtbCkgcGFnZS4KCiMjIERhdGEKCkFzIHdlIG5vdyBoYXZlICoqUioqIGFuZCAqKlB5dGhvbioqIHNldCwgbGV0IHVzIGltcG9ydCBzb21lIGRhdGEgdG8gcGxheSB3aXRoLiBJIHdpbGwgYmUgd29ya2luZyB3aXRoIGEgc2FtcGxlIGZyb20gdGhlIFtHYXBtaW5kZXJdKGh0dHBzOi8vd3d3LmdhcG1pbmRlci5vcmcvKSwgYW4gaW50cmlndWluZyBwcm9qZWN0ICByZWxhdGVkIHRvIHNvY2lvLWRlbW9ncmFwaHkgb2YgdGhlIHdvcmxkJ3MgcG9wdWxhdGlvbi4gVG8gYmUgbW9yZSBzcGVjaWZpYywgSSB3aWxsIGJlIHdvcmtpbmcgd2l0aCB0aGUgbGF0ZXN0IGF2YWlsYWJsZSBkYXRhIHdpdGhpbiB0aGUgW0dhcE1pbmRlcl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dhcG1pbmRlci9SRUFETUUuaHRtbCkgbGlicmFyeS4KCiogKipEYXRhIGltcG9ydCBpbiBSOioqCmBgYHtyfQojIExldCB1cyBiZWdpbiB3aXRoIHRoZSBtb3N0IHJlY2VudCBzZXQgb2YgZGF0YQpnYXBtaW5kZXJfbGF0ZXN0IDwtIGdhcG1pbmRlciAlPiUKICAgICAgICAgIGZpbHRlcih5ZWFyID09IG1heCh5ZWFyKSkKYGBgCgpTbywgbm93IHdlIGhhdmUgYSBkYXRhIGxvYWRlZCBpbiAqKlIqKi4gVW5mb3J0dW5hdGVseSwgaXQgaXMgbm90IHBvc3NpYmxlIHRvIGFjY2VzcyB0aGUgKipSIG9iamVjdHMqKiAoZS5nLiAqdmVjdG9ycyogb2QgKnRpYmJsZXMqKSBkaXJlY3RseSBieSAqKlB5dGhvbioqLiBTbywgd2UgbmVlZCB0byAqKmNvbnZlcnQqKiB0aGUgUiBvYmplY3QocykgZmlyc3QuCgoqICoqRGF0YSBpbXBvcnQgaW4gUHl0aG9uOioqCgpgYGB7cHl0aG9ufSAKIyBDb252ZXJ0IFIgRGF0YSBGcmFtZSAodGliYmxlKSBpbnRvIHRoZSBwYW5kYXMgRGF0YSBGcmFtZQpnYXBtaW5kZXJfbGF0ZXN0X3B5ID0gclsnZ2FwbWluZGVyX2xhdGVzdCddIApgYGAKCk9uZSBpbXBvcnRhbnQgdGhpbmcgdG8gcmVhbGlzZSB3aGVuIHdvcmtpbmcgd2l0aCAqUHl0aG9uIG9iamVjdHMqIChlLmcuICphcnJheXMqIG9yIGBwYW5kYXNgICpEYXRhIEZyYW1lKikgaXMgdGhhdCB0aGV5IGFyZSAqKm5vdCoqIFtleHBsaWNpdGx5ICoqc3RvcmVkKiogaW4gdGhlIGVudmlyb25tZW50XShodHRwczovL2NvbW11bml0eS5yc3R1ZGlvLmNvbS90L3JzdHVkaW8taWRlLWdsb2JhbC1lbnZpcm9ubWVudC12YXJpYWJsZS13aGVuLXJ1bm5pbmctcHl0aG9uLzE3OTk5KSBhcyB0aGUgKlIgb2JqZWN0cyogYXJlLiAKCiogSW4gb3RoZXIgd29yZHMsIGlmIHdlIHdhbnQgdG8ga25vdyB3aGF0IGlzIHN0b3JlZCBpbiB0aGUgd29ya3NwYWNlLCB3ZSBtdXN0IGNhbGwgZnVuY3Rpb25zIGxpa2UgYGRpcigpYCwgYGdsb2JhbHMoKWAgb3IgYGxvY2FscygpYDogCgpgYGB7cHl0aG9ufQpkaXIoKQpgYGAKR3JlYXQsIGFtb25nIHRoZSBwcmVzZW50IG9iamVjdHMsIHdlIGNhbiBjbGVhcmx5IHNlZSB0aGUgZGF0YSAoYGdhcG1pbmRlcl9sYXRlc3RfcHlgKSBvciBsaWJyYXJpZXMgKGUuZy4gYHB4YCkuCgpTbywgbGV0IHVzIGV4cGxvcmUgdGhlIGRhdGEgYSBiaXQhCgojIyBMaWZlIEV4cGVjdGFuY3kKCkZvciB0aGUgZGVtb25zdHJhdGlvbiBwdXJwb3NlcywgSSB3aWxsIGZvY3VzIG9uIHRoZSBbbGlmZSBleHBlY3RhbmN5XShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MaWZlX2V4cGVjdGFuY3kpIG9yIHRoZSBhdmVyYWdlIG51bWJlciBvZiB5ZWFycyBhIHBlcnNvbiBpcyBleHBlY3RlZCB0byBsaXZlLgoKIyMjIERlc2NyaXB0aXZlIHN0YXRpc3RpY3MKCkxldCdzIGJlZ2luIHdpdGggY2FsY3VsYXRpbmcgc29tZSAqKmRlc2NyaXB0aXZlIHN0YXRpc3RpY3MqKiBsaWtlICptZWFuKiwgKm1lZGlhbiogb3IgdGhlICpudW1iZXIgb2Ygcm93cyogaW4gdGhlIGRhdGEgdXNpbmcgKipQeXRob24qKjoKCmBgYHtweXRob259IAojIERlc2NyaXB0aXZlIHN0YXRpc3RpY3MgZm9yIHRoZSBpbmxpbmUgY29kZSBpbiBQeXRob24KCiMjIERhdGEgRnJhbWUgT3ZlcnZpZXcKCiMjIyBOdW1iZXIgb2Ygcm93cwpnYXBtaW5kZXJfbGF0ZXN0X3NoYXBlID0gZ2FwbWluZGVyX2xhdGVzdF9weS5zaGFwZVswXSAKIyMjIE51bWJlciBvZiBkaXN0aW5jdCB2YWx1ZXMgd2l0aGluIHRoZSBsaWZlIGV4cGVjdGFuY3kgdmFyaWFibGUKZ2FwbWluZGVyX2xhdGVzdF9jb3VudF9weSA9IGdhcG1pbmRlcl9sYXRlc3RfcHlbJ2xpZmVFeHAnXS5udW5pcXVlKCkKCiMjIExpZmUgRXhwZWN0YW5jeQoKIyMjIE1lZGlhbiAoTGlmZSBFeHBlY3RhbmN5KQpnYXBtaW5kZXJfbGF0ZXN0X21lZGlhbl9saWZlRXhwX3B5ID0gc3QubWVkaWFuKGdhcG1pbmRlcl9sYXRlc3RfcHlbJ2xpZmVFeHAnXSkgCiMjIyBNZWFuCmdhcG1pbmRlcl9sYXRlc3RfbWVhbl9saWZlRXhwX3B5ID0gc3QubWVhbihnYXBtaW5kZXJfbGF0ZXN0X3B5WydsaWZlRXhwJ10pCiMjIyBNaW5pbXVtCmdhcG1pbmRlcl9sYXRlc3RfbWluX2xpZmVFeHBfcHkgPSBtaW4oZ2FwbWluZGVyX2xhdGVzdF9weVsnbGlmZUV4cCddKSAKIyMjIE1heGltdW0KZ2FwbWluZGVyX2xhdGVzdF9tYXhfbGlmZUV4cF9weSA9IG1heChnYXBtaW5kZXJfbGF0ZXN0X3B5WydsaWZlRXhwJ10pCiMjIyBTdGFuZGFyZCBkZXZpYXRpb24KZ2FwbWluZGVyX2xhdGVzdF9zdGRldl9saWZlRXhwX3B5ID0gc3Quc3RkZXYoZ2FwbWluZGVyX2xhdGVzdF9weVsnbGlmZUV4cCddKQpgYGAKCk5pY2UuIFVuZm9ydHVuYXRlbHksIHdlIGFyZSBub3QgYWJsZSB0byB1c2UgdGhlICoqUHl0aG9uIG9iamVjdHMqKiBmb3IgKippbmxpbmUgY29kaW5nKiosIG9uZSBvZiB0aGUga2V5IGZlYXR1cmVzIG9mIFsqKmxpdGVyYXRlIGNvZGluZyoqXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MaXRlcmF0ZV9wcm9ncmFtbWluZyM6fjp0ZXh0PUxpdGVyYXRlJTIwcHJvZ3JhbW1pbmclMjBpcyUyMGElMjBwcm9ncmFtbWluZyxzb3VyY2UlMjBjb2RlJTIwY2FuJTIwYmUlMjBnZW5lcmF0ZWQuKSBpbiBbKlIgTWFya2Rvd24qXShodHRwczovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbS9sZXNzb24tNC5odG1sKS4gU28sIGlmIHdlIHdhbnQgdG8gdXNlIHRoZSByZXN1bHRzIGZvciBpbmxpbmUgY29kZXMsIHdlIG5lZWQgdG8gdHJhbnNmb3JtIHRoZSAqKlB5dGhvbiBvYmplY3RzKiogYmFjayB0byAqKlIqKjogCgpgYGB7cn0KIyBEZXNjcmlwdGl2ZSBzdGF0aXN0aWNzIGZvciB0aGUgaW5saW5lIGNvZGUgaW4gUHl0aG9uIC0gdHJhbnNmb3JtZWQgdG8gUgoKIyMgRGF0YSBGcmFtZSBPdmVydmlldwoKIyMgTnVtYmVyIG9mIHJvd3MKZ2FwbWluZGVyX2xhdGVzdF9ucm93X3IgPSBweSRnYXBtaW5kZXJfbGF0ZXN0X3NoYXBlCiMjIyBOdW1iZXIgb2YgZGlzdGluY3QgdmFsdWVzIHdpdGhpbiB0aGUgbGlmZSBleHBlY3RhbmN5IHZhcmlhYmxlCmdhcG1pbmRlcl9sYXRlc3RfY291bnRfciA9IHB5JGdhcG1pbmRlcl9sYXRlc3RfY291bnRfcHkKCiMjIExpZmUgRXhwZWN0YW5jeQoKIyMjIE1lZGlhbiAoTGlmZSBFeHBlY3RhbmN5KQpnYXBtaW5kZXJfbGF0ZXN0X21lZGlhbl9saWZlRXhwX3IgPSBweSRnYXBtaW5kZXJfbGF0ZXN0X21lZGlhbl9saWZlRXhwX3B5CiMjIyBNZWFuCmdhcG1pbmRlcl9sYXRlc3RfbWVhbl9saWZlRXhwX3IgPSBweSRnYXBtaW5kZXJfbGF0ZXN0X21lYW5fbGlmZUV4cF9weQojIyMgTWluaW11bQpnYXBtaW5kZXJfbGF0ZXN0X21pbl9saWZlRXhwX3IgPSBweSRnYXBtaW5kZXJfbGF0ZXN0X21pbl9saWZlRXhwX3B5CiMjIyBNYXhpbXVtCmdhcG1pbmRlcl9sYXRlc3RfbWF4X2xpZmVFeHBfciA9IHB5JGdhcG1pbmRlcl9sYXRlc3RfbWF4X2xpZmVFeHBfcHkKIyMjIFN0YW5kYXJkIGRldmlhdGlvbgpnYXBtaW5kZXJfbGF0ZXN0X3N0ZGV2X2xpZmVFeHBfciA9IHB5JGdhcG1pbmRlcl9sYXRlc3Rfc3RkZXZfbGlmZUV4cF9weQpgYGAKClNvLCB3aGF0IGNhbiB3ZSBzYXkgYWJvdXQgKipsaWZlIGV4cGVjdGFuY3kqKiBpbiAqKmByIG1heChnYXBtaW5kZXJfbGF0ZXN0JHllYXIpYCoqPyAKCkZpcnN0IG9mIGFsbCwgdGhlcmUgd2VyZSAqKmByIGdhcG1pbmRlcl9sYXRlc3RfbnJvd19yYCBjb3VudHJpZXMqKiBvbiB0aGUgbGlzdC4gVGhlICoqbWluaW11bSoqIHZhbHVlIG9mIGxpZmUgZXhwZWN0YW5jeSB3YXMgKipgciBnYXBtaW5kZXJfbGF0ZXN0X21pbl9saWZlRXhwX3JgKiogeWVhcnMsIHRoZSBtYXhpbXVtICoqYHIgZ2FwbWluZGVyX2xhdGVzdF9tYXhfbGlmZUV4cF9yYCoqIHllYXJzLgoKVGhlICoqYXZlcmFnZSoqIHZhbHVlIGZvciBsaWZlIGV4cGVjdGFuY3kgd2FzICoqYHIgZ2FwbWluZGVyX2xhdGVzdF9tZWFuX2xpZmVFeHBfcmAqKiB5ZWFycyBhbmQgKio1MCUqKiBvciAqKm1lZGlhbioqIGhvcGUgdG8gbGl2ZSAqKmByIGdhcG1pbmRlcl9sYXRlc3RfbWVkaWFuX2xpZmVFeHBfcmAqKiB5ZWFycyBvciBtb3JlLiBMYXN0bHksIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gd2FzICoqYHIgZ2FwbWluZGVyX2xhdGVzdF9zdGRldl9saWZlRXhwX3JgKiogeWVhcnMuCgojIyMgR3JhcGhzICh1c2luZyBQbG90bHkpCgpPa2F5LCBsZXQncyBtb3ZlIHRvIHNvbWV0aGluZyBlbHNlLCBsaWtlIGdyYXBocy4gCgpGb3IgZXhhbXBsZSwgd2UgY2FuIHRha2UgYSBsb29rIGF0ICpob3cgaXMgdGhlIGxpZmUgZXhwZWN0YW5jeSBkaXN0cmlidXRlZCogYWNyb3NzIHRoZSBnbG9iZSB1c2luZyBgUGxvdGx5YDogCgpgYGB7cHl0aG9uLCBlY2hvID0gVH0KZmlnID0gcHguaGlzdG9ncmFtKGdhcG1pbmRlcl9sYXRlc3RfcHksICMgcGFja2FnZS5mdW5jdGlvbjsgRGF0YSBGcmFtZQogICAgICAgICAgICAgICAgICAgeD0ibGlmZUV4cCIsICMgVmFyaWFibGUgb24gdGhlIFggYXhpcwogICAgICAgICAgICAgICAgICAgcmFuZ2VfeD0oZ2FwbWluZGVyX2xhdGVzdF9taW5fbGlmZUV4cF9weSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBnYXBtaW5kZXJfbGF0ZXN0X21heF9saWZlRXhwX3B5KSwgIyBNaW5pbXVtIGFuZCBtYXhpbXVtIHZhbHVlcyBmb3IgdGhlIFggYXhpcwogICAgICAgICAgICAgICAgICAgbGFiZWxzPXsnbGlmZUV4cCc6J0xpZmUgZXhwZWN0YW5jeSAtIGluIHllYXJzJ30sICMgTmFtaW5nIG9mIHRoZSBpbnRlcmFjdGl2ZSBwYXJ0CiAgICAgICAgICAgICAgICAgICBjb2xvcl9kaXNjcmV0ZV9zZXF1ZW5jZT1bJyMwMDVDNEUnXSkgIyBDb2xvdXIgb2YgZmlsbCAKCmxpZmVFeHBIaXN0ID0gZmlnLnVwZGF0ZV9sYXlvdXQoCiAgdGl0bGU9IkZpZ3VyZSAxLiBMaWZlIEV4cGVjdGFuY3kgaW4gMjAwNyBBY3Jvc3MgdGhlIEdsb2JlIC0gaW4gWWVhcnMiLCAjIFRoZSBuYW1lIG9mIHRoZSBncmFwaAogIHhheGlzX3RpdGxlPSJZZWFycyIsICMgWC1heGlzIHRpdGxlCiAgeWF4aXNfdGl0bGU9IkNvdW50IiwgIyBZLWF4aXMgdGl0bGUKICBmb250PWRpY3QoICMgImNzcyIKICAgIGZhbWlseT0iUm9ib3RvIiwKICAgIHNpemU9MTIsCiAgICBjb2xvcj0iIzI1MkEzMSIKICApKQoKbGlmZUV4cEhpc3Qud3JpdGVfaHRtbCgibGlmZUV4cEhpc3QuaHRtbCIpICMgU2F2ZSB0aGUgZ3JhcGggYXMgYSAuaHRtbCBvYmplY3QKYGBgCgpVbmZvcnR1bmF0ZWx5LCBpdCBpcyBub3QgcG9zc2libGUgdG8gcHJpbnQgaW50ZXJhY3RpdmUgYFBsb3RseWAgZ3JhcGhzIGluICoqUiBNYXJrZG93bioqIHZpYSAqKlB5dGhvbioqLiBPciwgdG8gYmUgbW9yZSBwcmVjaXNlLCB5b3Ugd2lsbCByZWNlaXZlIGEgYEZpZ3VyZSBvYmplY3RgIGJ5IHByaW50aW5nIChlLmcuIGBwcmludChsaWZlRXhwSGlzdClgKSBpdDoKCmBgYHtweXRob24gZWNobz1UUlVFfQpwcmludChsaWZlRXhwSGlzdCkKYGBgClNvLCB3ZSAqKmltcG9ydCoqIHRoZSBwcmV2aW91c2x5IGNyZWF0ZWQgKi5odG1sKiBmaWxlIGluc3RlYWQgKGUuZy4gdXNpbmcgdGhlIGBpbmNsdWRlSFRNTGAgZnVuY3Rpb24gZnJvbSB0aGUgYGh0bWx0b29sc2AgcGFja2FnZSk6CgpgYGB7ciwgZWNobz1UUlVFLCByZXN1bHRzPSdoaWRlJywgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA4LCBmaWcuYWxpZ249J2NlbnRlcicsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGVycm9yPUZBTFNFfQpodG1sdG9vbHM6OmluY2x1ZGVIVE1MKCJsaWZlRXhwSGlzdC5odG1sIikgIyBSZW5kZXIgdGhlIGdyYXBoCmBgYAoKU28sIGhlcmUgY29tZXMgYSBiYXNpYywgeWV0IGludGVyYWN0aXZlIGhpc3RvZ3JhbSBtYWRlIGluIHRoZSBQeXRob24gdmVyc2lvbiBvZiBgUGxvdGx5YC4gCgpIb3dldmVyLCBwcm9kdWNpbmcgdGhpcyBncmFwaCBpbiAqKlJTdHVkaW8qKiByZXF1aXJlZCBxdWl0ZSBhIHdvcmthcm91bmQuIEF0IHRoZSBzYW1lIHRpbWUsIGEgc2l6ZSBvZiB0aGUgZ3JhcGhzIHByb2R1Y2VkIHRoaXMgd2F5IGNvdWxkIGVhc2lseSBiZSAqKnRlbnMgb2YgTUJzKiouIAoKKiBDb25zZXF1ZW50bHksIGEgLmh0bWwgcmVwb3J0IGNvbnRhaW5pbmcgc3VjaCBncmFwaHMgd291bGQgcmVxdWlyZSAqKmEgbG90IG9mIGRhdGEgdG8gZG93bmxvYWQqKiBmb3IgYSByZWFkZXIgYW5kIGl0IHdvdWxkIHRha2UgKiptb3JlIHRpbWUgdG8gcmVuZGVyIHRoZSBwYWdlKiouCgojIyMgU3VtbWFyeSB0YWJsZXMgKHVzaW5nIHBhbmRhcykKCk9uZSBvZiB0aGUgY29tbW9uIHVzZS1jYXNlcyBmb3IgYHBhbmRhc2AgaXMgdG8gcHJvdmlkZSBhIGRhdGEgZGVzY3JpcHRpb24uIFRoZSByZXNwZWN0aXZlIGNvZGUgcnVucyBqdXN0IGZpbmUuIEhvd2V2ZXIsIHRoZSBvdXRwdXQgY2Fubm90IGJlIHN0eWxlZCBhcyB5b3UgYXJlIHVzZWQgZnJvbSBzdHlsaW5nIGluIGBwYW5kYXNgLgoKKiBXaGVuIHNwZWFraW5nIG9mIGBwYW5kYXNgLCB3ZSBjYW4gZWFzaWx5IGNyZWF0ZSBhIHN1bW1hcnkgdGFibGUgZm9yIHRoZSBjb21tb25seSB1c2VkIHN0YXRpc3RpY3MgbGlrZSBtZWFuIG9yIHN0YW5kYXJkIGRldmlhdGlvbiBvZiAqKmxpZmUgZXhwZWN0YW5jeSoqIGZvciAqKmluZGl2aWR1YWwgY29udGluZW50cyoqOgoKYGBge3B5dGhvbiBlY2hvPVRSVUV9CiMgQ3JlYXRlIGEgcGFuZGFzIERhdGEgRnJhbWUgb2JqZWN0IGNvbnRhaW5pbmcgdGhlIHJlbGV2YW50IHZhcmlhYmxlLAojIGNvbmR1Y3QgZm9ybWF0dGluZy4KCmdhcG1pbmRlcl9sYXRlc3RfcHlbJ2NvbnRpbmVudCddID0gZ2FwbWluZGVyX2xhdGVzdF9weVsnY29udGluZW50J10uYXN0eXBlKHN0cikKdmFyaWFibGUgPSAnY29udGluZW50Jwp2YXJpYWJsZV9uYW1lID0gJ0NvbnRpbmVudHMnCgpnYXBtaW5kZXJfbGF0ZXN0X3B5WydsaWZlRXhwJ10gPSBnYXBtaW5kZXJfbGF0ZXN0X3B5WydsaWZlRXhwJ10uYXN0eXBlKGludCkKdmFyaWFibGVfZ3JvdXBpbmcgPSAnbGlmZUV4cCcKCmNvbnRhY3Rfd2luZG93X2RheXMgPSBnYXBtaW5kZXJfbGF0ZXN0X3B5Lmdyb3VwYnkoWwogICAgICAgICAgICAgICAgICAgICAgICBwZC5Hcm91cGVyKGtleT12YXJpYWJsZSldKVwKICAgICAgICAgICAgICAgICAgICAgICAgW3ZhcmlhYmxlX2dyb3VwaW5nXVwKICAgICAgICAgICAgICAgICAgICAgICAgLmFnZyhbJ2NvdW50JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ21pbicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdtZWFuJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ21lZGlhbicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdzdGQnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnbWF4J10pXAogICAgICAgICAgICAgICAgICAgICAgICAucmVzZXRfaW5kZXgoKQoKY29udGFjdF93aW5kb3dfZGF5c19zdHlsZSA9IGNvbnRhY3Rfd2luZG93X2RheXNcCiAgICAgICAgICAgICAgICAgICAgICAgIC5yZW5hbWUoeydjb3VudCc6ICdDb3VudCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdtZWRpYW4nOiAnTWVkaWFuJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3N0ZCc6ICdTdGFuZGFyZCBEZXZpYXRpb24nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnbWluJzogJ01pbmltdW0nLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ21heCc6ICdNYXhpbXVtJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ21lYW4nOiAnTWVhbicsfSwgYXhpcz0nY29sdW1ucycpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIApgYGAKCmBgYHtweXRob259CmNvbnRhY3Rfd2luZG93X2RheXNfc3R5bGUgIyBwcmludCB0aGUgdGFibGUKYGBgCkFsc28sIG5vdGUgdGhhdCB0aGUgKipQeXRob24gZW52aXJvbm1lbnQgc2V0dGluZ3Mgb3ZlcnJpZGUgdGhvc2UgZnJvbSBSKiouIEZvciBleGFtcGxlLCB0YWtlIGEgbG9vayBhdCB0aGUgbnVtYmVyIG9mIGRpZ2l0cyBmb3IgTWVhbiBvciBTdGFuZGFyZCBEZXZpYXRpb24uIFRoZXJlIGFyZSBzaXggZGlnaXRzIGluc3RlYWQgb2YgdHdvIHNldCBhdCB0aGUgYmVnaW5uaW5nLgoKIyBDbG9zaW5nIHJlbWFya3MKCk9rYXksIHRoYXQncyBlbm91Z2ggZm9yIG5vdy4gSWYgeW91IGFyZSBodW5ncnkgZm9yIG1vcmUgYWR2YW5jZWQgdGhpbmdzIGxpa2UgdW5zdXBlcnZpc2VkIGxlYXJuaW5nIHVzaW5nIGBzY2lraXQtbGVhcm5gIFtwYWNrYWdlXShodHRwczovL3NjaWtpdC1sZWFybi5vcmcvc3RhYmxlLykgaW4gKipSKiosIHRha2UgYSBbbG9va10oaHR0cHM6Ly93d3cuci1ibG9nZ2Vycy5jb20vci1hbmQtcHl0aG9uLXVzaW5nLXJldGljdWxhdGUtdG8tZ2V0LXRoZS1iZXN0LW9mLWJvdGgtd29ybGRzLykuCgpIb3dldmVyLCBiZWZvcmUgY2xvc2luZyB0aGlzIHBvc3QsIGxldCBtZSBqdXN0IHNheSB0aGF0IGlmIHlvdSB0aGluayBhYm91dCBzd2l0Y2hpbmcgdG8gKipQeXRob24qKiBhcyBzdWNoIGFuZCB1c2luZyBpdCBvZnRlbiwgY29uc2lkZXIgSURFIGFsdGVybmF0aXZlcyB0byAqUlN0dWRpbyouIAoKTWFueSBhbmFseXN0cyBzd2VhciBvbiBbKipKdXB5dGVyIG5vdGVib29rcyoqXShodHRwczovL2p1cHl0ZXIub3JnLykgZm9yIHRoZSBpbnRlcmFjdGl2aXR5LCBpbnRlZ3JhdGlvbiBvZiBgbWFya2Rvd25gIG9yIG9wdGlvbiB0byBydW4gY29kZSBpbiB2YXJpb3VzIGxhbmd1YWdlcyBsaWtlIGBSYCwgYEp1bGlhYCBvciBgSmF2YVNjcmlwdGAuIFsqKkp1cHl0ZXJIdWIqKl0oaHR0cHM6Ly9qdXB5dGVyLm9yZy9odWIpIGlzIGEgcGxhdGZvcm0gYmFzZWQgb24gSnVweXRlciBub3RlYm9va3MsIGFkZGluZyB2ZXJzaW9uIGNvbnRyb2wuIFVzdWFsbHksIHVzZXJzIHJ1biBhbmFseXNlcyBpbiBhIGNvbnRhaW5lcmlzZWQgZW52aXJvbm1lbnQpLiBBbm90aGVyIHRha2Ugb24gaW50ZXJhY3Rpdml0eSBhbmQgY29sbGFib3JhdGlvbiBjb3VsZCBiZSBbKipDb2xhYioqXShodHRwczovL2NvbGFiLnJlc2VhcmNoLmdvb2dsZS5jb20vbm90ZWJvb2tzL2ludHJvLmlweW5iI3JlY2VudD10cnVlKSwgYmFzaWNhbGx5LCBKdXB5dGVyIG5vdGVib29rcyBydW5uaW5nIG9uIEdvb2dsZSBDbG91ZC4KCkxhc3QgYnV0IG5vdCBsZWFzdCwgdGhlcmUgaXMgYSBncmVhdCBwaWVjZSBvZiBzb2Z0d2FyZSBjYWxsZWQgWyoqVmlzdWFsIFN0dWRpbyBDb2RlKipdKGh0dHBzOi8vY29kZS52aXN1YWxzdHVkaW8uY29tLykuIEl0IG5vdCBvbmx5IGFsbG93cyB5b3UgdG8gY3JlYXRlIGFuZCBydW4gY29kZSBpbiBhIHBsZXRob3JhIG9mIGxhbmd1YWdlcyBvciBzZWFtbGVzcyBmbG93IGJldHdlZW4gcHVyZSAqKlB5dGhvbioqIGNvZGUgYW5kIGludGVyYWN0aXZlICoqSnVweXRlciBub3RlYm9va3MqKi4gQW5kIG1heWJlIGV2ZW4gbW9yZSBpbXBvcnRhbnRseSwgaXQgcHJvdmlkZXMgeW91IHdpdGggdmVyeSBlZmZpY2llbnQgdmVyc2lvbiBjb250cm9sIG1hbmFnZW1lbnQgKGxpa2UgR2l0IGludGVncmF0aW9uIGFuZCBleHRlbnNpb25zKS4gSWYgeW91IGNob29zZSB0aGlzIElERSwgeW91IGNhbiBzZXQgdXAgW1ZTIENvZGUgZm9yIFB5dGhvbiBkZXZlbG9wbWVudCBsaWtlIFJTdHVkaW9dKGh0dHBzOi8vc3RldmVubW9ydGltZXIuY29tL3NldHRpbmctdXAtdnMtY29kZS1mb3ItcHl0aG9uLWRldmVsb3BtZW50LWxpa2UtcnN0dWRpby8jc2V0dGluZ3MtanNvbi1maWxlKS4gCgpCdXQgbm8gbWF0dGVyIHdoYXQgcGF0aCB0byBQeXRob24geW91IGNob29zZSwgZG9uJ3QgZm9yZ2V0IHRoYXQgaXQgaXMgYSB0b29sIHN1aXRhYmxlIGZvciBzb21lIHNpdHVhdGlvbnMgYW5kIG1heWJlIG5vdCBzbyBzdWl0YWJsZSB0byBvdGhlcnMuIEp1c3QgbGlrZSBSLiBUcnkgdG8gbGV2ZXJhZ2UgdGhlIGJlc3Qgb2YgaXQgd2hpbGUgYmVpbmcgYXdhcmUgb2YgdGhlIHByb3Mgb2YgZGlmZmVyZW50IHRvb2xzLiAKCiZuYnNwOwombmJzcDsKCjxwIHN0eWxlPSJ0ZXh0LWFsaWduOiBjZW50ZXI7Ij48Yj5HbyBiYWNrIHRvIDxhIGhyZWY9Ii9wb3N0Ij5CbG9nPC9iPjwvYT48L3A+Cg==