Data Import with readr

Getting Started

These functions are part of readr (which is loaded by tidyverse)
  • read_csv() reads comma separated values
  • read_csv2() semicolon separated
  • read_tsv() Tab delimited files
  • read_delim()(read any delimited files
  • read_fwf() reads fixed width files- use fwf_widths() or fwf_positions()
  • read_table() reas a common variation of fixed-width files where columns are separated by white space
  • read_log reads Apache style log files (also check out webreadr)
  • They all have similar syntax. The first argument is the path to the file to read.

    read_csv(file, col_names = TRUE, col_types = NULL, locale = default_locale(), na = c(“”, “NA”), quoted_na = TRUE, comment = “”, trim_ws = TRUE, skip = 0, n_max = Inf, guess_max = min(1000, n_max), progress = interactive())

    library(tidyverse)
    read_csv("g:\\Downloads\\Earthquake.csv")
    Parsed with column specification:
    cols(
      .default = col_double(),
      time = col_datetime(format = ""),
      magType = col_character(),
      nst = col_character(),
      gap = col_integer(),
      net = col_character(),
      id = col_character(),
      updated = col_datetime(format = ""),
      place = col_character(),
      type = col_character(),
      magNst = col_integer(),
      status = col_character(),
      locationSource = col_character(),
      magSource = col_character()
    )
    See spec(...) for full column specifications.
    4175 parsing failures.
     row col               expected actual
    1964 gap no trailing characters     .9
    1975 gap no trailing characters     .4
    1987 gap no trailing characters     .7
    1991 gap no trailing characters     .8
    2007 gap no trailing characters     .6
    .... ... ...................... ......
    See problems(...) for more details.

    You can use skip argument to skip the first few lines - typically used for skipping blank lines.

    library(tidyverse)
    read_csv("g:\\Downloads\\Earthquake.csv", skip = 2)
    Missing column names filled in: 'X8' [8]Duplicated column names deduplicated: 'us' => 'us_1' [22], 'us' => 'us_2' [23]Parsed with column specification:
    cols(
      .default = col_double(),
      `2017-04-10T10:38:53.620Z` = col_datetime(format = ""),
      mb = col_character(),
      X8 = col_character(),
      `45` = col_integer(),
      us = col_character(),
      us10008g7h = col_character(),
      `2017-04-10T12:42:59.585Z` = col_datetime(format = ""),
      `137km ESE of Pondaguitan, Philippines` = col_character(),
      earthquake = col_character(),
      `204` = col_integer(),
      reviewed = col_character(),
      us_1 = col_character(),
      us_2 = col_character()
    )
    See spec(...) for full column specifications.
    4175 parsing failures.
     row col               expected actual
    1962  45 no trailing characters     .9
    1973  45 no trailing characters     .4
    1985  45 no trailing characters     .7
    1989  45 no trailing characters     .8
    2005  45 no trailing characters     .6
    .... ... ...................... ......
    See problems(...) for more details.

    You can supply inline CSV (Each row must be in a new line.)

    read_csv("a,b,c
             1,2,3
             4,5,6")
    You can use colnames = FALSE to tell R that there are no headers. (If you get Unused argument error, make sure that you are using read_csv and not read.csv)

    library(tidyverse)
    read_csv("g:\\Downloads\\Earthquake.csv", col_names = FALSE)
    Parsed with column specification:
    cols(
      .default = col_character()
    )
    See spec(...) for full column specifications.

    You can also use header = FALSE when your csv data does not have headers in them.

    library(tidyverse)
    read.csv("g:\\Downloads\\Earthquake.csv", header = FALSE)

    Or you can pass character vectors for column names (doesnt work)

    library(tidyverse)
    read_csv(file = "g:\\Downloads\\query-noheader.csv", 
             col_names = c("x","y","z","wilson","jane","William"))
    Parsed with column specification:
    cols(
      x = col_datetime(format = ""),
      y = col_double(),
      z = col_double(),
      wilson = col_integer(),
      jane = col_double(),
      William = col_character()
    )
    70 parsing failures.
    row col  expected     actual
      1  -- 6 columns 22 columns
      2  -- 6 columns 22 columns
      3  -- 6 columns 22 columns
      4  -- 6 columns 22 columns
      5  -- 6 columns 22 columns
    ... ... ......... ..........
    See problems(...) for more details.

    How to treat NA

    To specify the values or values that are used to repesent missing values in your table.

    read_csv("a,b,c
             1,2,-", 
             na ="-")

    Compared to Base R

    There are two main alternatives to readr: base R and data.table’s fread(). The most important differences are discussed below.

    Base R

    Compared to the corresponding base functions, readr functions:

    Use a consistent naming scheme for the parameters (e.g. col_names and col_types not header and colClasses).

    Are much faster (up to 10x).

    Leave strings as is by default, and automatically parse common date/time formats.

    Have a helpful progress bar if loading is going to take a while.

    All functions work exactly the same way regardless of the current locale. To override the US-centric defaults, use locale().

    They produce tibbles. Base R function inherit some behavior from your OS and environment variables, so import code that works on one computer might not work on other computers.

    data.table and fread()

    data.table has a function similar to read_csv() called fread. Compared to fread, readr functions:

    Are slower (currently ~1.2-2x slower. If you want absolutely the best performance, use data.table::fread().

    Use a slightly more sophisticated parser, recognising both doubled (“”“”) and backslash escapes (“"”), and can produce factors and date/times directly.

    Forces you to supply all parameters, where fread() saves you work by automatically guessing the delimiter, whether or not the file has a header, and how many lines to skip.

    Are built on a different underlying infrastructure. Readr functions are designed to be quite general, which makes it easier to add support for new rectangular data formats. fread() is designed to be as fast as possible.

    Exercises

    Other arguments:
    read_csv(file, col_names = TRUE, col_types = NULL, locale = default_locale(), na = c(“”, “NA”), quoted_na = TRUE, comment = “”, trim_ws = TRUE, skip = 0, n_max = Inf, guess_max = min(1000, n_max), progress = interactive())

    Parsing a Vector

    The parse_* () functions take a character vector and return a more specialized vector like a logical, integer, or date.

    str(parse_logical(c("TRUE","FALSE","NA")))
     logi [1:3] TRUE FALSE NA
    str(parse_integer(c("1","2","3")))
     int [1:3] 1 2 3
    str(parse_date(c("2001-01-01","1979-10-14")))
     Date[1:2], format: "2001-01-01" "1979-10-14"

    The na argument specifies which strings should be treated as missing.

    parse_integer(c("1","2",".","4"), na=".")
    [1]  1  2 NA  4

    If parsing fails, you will get a warning. Plus, the failures will be missing in the output. If there are many failures, better use problems() to get the complete set.

    x <-  parse_integer(c("123","345","abc","123.45"))
    2 parsing failures.
    row col               expected actual
      3  -- an integer                abc
      4  -- no trailing characters    .45
    x
    [1] 123 345  NA  NA
    attr(,"problems")
    problems(x)

    Eight particualarly important parsers:

  • parse_logical() for true or false values
  • parse_integer() for whole numbers
  • parse_double() strict numeric parser
  • parse_number()
  • parse_character() be aware of character encodings
  • parse_factor() creates factors for categorical values
  • Parse_datetime()
  • parse_date()
  • parse_time
  • x <- parse_number("$10,000")
    y <- parse_number("10%")
    z <- parse_number("It cost $123.56")
    x
    [1] 10000
    y
    [1] 10
    z
    [1] 123.56

    Using locale argument to specify , as the decimal point

    parse_double("1.23")
    [1] 1.23
    parse_double("1,23", locale = locale(decimal_mark = ","))
    [1] 1.23

    Using grouping mark.

    # US
    parse_number("$123,456,789.00")
    [1] 123456789
    # Europe
    parse_number("$123.456.789,00",
                 locale=locale(grouping_mark = "."))
    [1] 123456789
    # Switzerland
    parse_number("$123'456'789.00",
                 locale=locale(grouping_mark = "'"))
    [1] 123456789

    Readr’s default locale is US centric.

    Strings

    # reader uses UTF-8 encoding 
    charToRaw("Wilson was here")
     [1] 57 69 6c 73 6f 6e 20 77 61 73 20 68 65 72 65
    x1 <-  "El Ni\xf1o was particularly bad this year"
    parse_character(x1, locale = locale(encoding = "Latin1"))
    [1] "El Niño was particularly bad this year"

    If you dont know the encoding method that was used, you can try guess_encoding() function

    guess_encoding(charToRaw(x1))

    Factors

    Parse_factor() to generate cateorical variables that have a known set of possible values.

    fruit <-  c("apple","banana")
    parse_factor(c("apple","banana", "bananana"), levels = fruit)
    1 parsing failure.
    row col           expected   actual
      3  -- value in level set bananana
    [1] apple  banana <NA>  
    attr(,"problems")
    Levels: apple banana

    Dates, Date-times and Times

    We can use 3 different parsers depending on whether we want date, time or date-time.

    Parse_date() expects an ISO8601 date-time.

    parse_datetime("2010-10-01T2010")
    [1] "2010-10-01 20:10:00 UTC"
    # if time is omitted, it will be set to midnight
    parse_datetime("2010-10-01")
    [1] "2010-10-01 UTC"

    Parse_date expects a four digit year, a - or /, the month, a - or / then the day.

    parse_date("2010-10-01")
    [1] "2010-10-01"
    parse_date("2010/10/01")
    [1] "2010-10-01"
    parse_date("2010-10/01")
    [1] "2010-10-01"
    parse_date("10-01-2010")
    1 parsing failure.
    row col   expected     actual
      1  -- date like  10-01-2010
    [1] NA

    Parse_time() expect the hour, : minutes, optionally : and seconds and an optional am/pm specifier

    library(hms)
    parse_time("1:10 am")
    01:10:00
    parse_time("02:10 am")
    02:10:00
    parse_time("20:10:01")
    20:10:01
    parse_time("8:10:01 pm")
    20:10:01

    You can supply your own data formats.

    Year: “%Y” (4 digits). “%y” (2 digits); 00-69 -> 2000-2069, 70-99 -> 1970-1999.

    Month: “%m” (2 digits), “%b” (abbreviated name in current locale), “%B” (full name in current locale).

    Day: “%d” (2 digits), “%e” (optional leading space)

    Hour: “%H”

    Minutes: “%M”

    Seconds: “%S” (integer seconds), “%OS” (partial seconds)

    Time zone: “%Z” (as name, e.g. “America/Chicago”), “%z” (as offset from UTC, e.g. “+0800”)

    AM/PM indicator: “%p”.

    Non-digits: “%.” skips one non-digit character, “%+” skips one or more non-digit characters, “%*" skips any number of non-digits characters.

    Automatic parsers: “%AD” parses with a flexible YMD parser, “%AT” parses with a flexible HMS parser.

    Shortcuts: “%D” = “%m/%d/%y”, “%F” = “%Y-%m-%d”, “%R” = “%H:%M”, “%T” = “%H:%M:%S”, “%x” = “%y/%m/%d”.

    parse_date("01/02/15", "%m/%d/%y")
    [1] "2015-01-02"
    parse_date("01/02/15", "%d/%m/%y")
    [1] "2015-02-01"
    parse_date("01/02/15", "%y/%m/%y")
    [1] "2015-02-01"
    parse_date("01/02/15", "%D")
    [1] "2015-01-02"
    parse_date("01/02/15", "%x")
    [1] "2001-02-15"
    parse_date("1 January 2015", "%d %B %Y")
    [1] "2015-01-01"
    parse_date("1 Jan 2015", "%d %b %Y")
    [1] "2015-01-01"

    Parsing a File

    readr uses a heuristic to figure out the type of each column: it reads the first 1000 rows and uses some moderately conservative heuristics to figure out the type of each column. The heuristic tries each of the following types, stopping when it finds a match. Logical, integer, double, number, time, date, date-time. Catch all is string.

    challenge <-  read_csv(readr_example("challenge.csv"))
    Parsed with column specification:
    cols(
      x = col_integer(),
      y = col_character()
    )
    1000 parsing failures.
     row col               expected             actual
    1001   x no trailing characters .23837975086644292
    1002   x no trailing characters .41167997173033655
    1003   x no trailing characters .7460716762579978 
    1004   x no trailing characters .723450553836301  
    1005   x no trailing characters .614524137461558  
    .... ... ...................... ..................
    See problems(...) for more details.
    problems(challenge)

    The error message shows that the integer column contains data with decimals in it. So we use parse_double.

    challenge <-  read_csv(readr_example("challenge.csv"),
    col_types = cols(
      x = col_double(),
      y = col_character()
    )
    )
    # look at the last few rows
    tail(challenge)

    Aha Y is a date field, not a character field.

    challenge <-  read_csv(readr_example("challenge.csv"),
    col_types = cols(
      x = col_double(),
      y = col_date()
    )
    )
    # look at the last few rows
    tail(challenge)

    It is best practice to use col_types to ensure consistent and reproducible data import script. To be really strict, we can also use stop_for_problems() which will throw an error and stop the script if there are any errors.

    Other Strategies

    Using guess_max argument

    challenge2 <-  read_csv(
      readr_example("challenge.csv"),
      guess_max = 1001
      )
    Parsed with column specification:
    cols(
      x = col_double(),
      y = col_date(format = "")
    )
    challenge2

    Sometimes it is easier to diagnose problems if you just read in all the columns as character vectors.

    challenge2 <-  read_csv(
      readr_example("challenge.csv"),
      col_types = cols(.default = col_character())
      )
    challenge2

    This is useful in conjunction with the type_convert() which applies the parsing heuristics to the character columns in a data frame.

    df <-  tribble(
      ~x, ~ y, 
      "1", "1.21",
      "2","2.32",
      "3", "4.56"
    )
    df
    # Note the column types after the convertion
    type_convert(df)
    Parsed with column specification:
    cols(
      x = col_integer(),
      y = col_double()
    )

    Note: for very large files, you might want to set n_max to a smallish number like 10,000 or 100,000.

    Writing to a File

    Two functions write_csv and write_tsv(). Always uses UTF-8 encoding Saving dates and date-times in ISO8601 format so they are esily parsed elsewhere.

    If you want to export a CSV file to Excel, use write_excel_csv() this writes a special character (a ’byte order mark“) at the start of the file, which tells Excel that you’re using the UTF-8 encoding.

    # to append to an existing file. 
    # Note: type information is lost when you save to csv format
    write_csv(challenge, "Challenge.csv")

    But saving in CSV format is a little unreliable for caching interim results. You need to re-create the coumn specification every time you load in. There are two alternatives: writerds() and read_rds() will store in R’s custom binary format called RDS.

    write_rds(challenge, "challenge.rds")
    read_rds("challenge.rds")

    You can also use the feather package to implement a fast binary file format tha tcan be shared across programming languages.

    library(feather)
    write_feather(challenge, "challenge.feather")
    read_feather("challenge.feather")

    Notes: Feather is faster than RDS and can be used outside of R but RDS supposrts list-columns which feather does not.

    Other Types of Data

    haven reads SPSS, Stata and SAS files
    readxl reads Excel files (both xls. and xlsx)
    DBI allows you to run SQL queries against a database and return a data frame.
    jsonlite for hierarchical data
    xml2 for XML data
    See also Jenny Bryan at https://jennybc.github.io/purrr-tutorial

    LS0tDQp0aXRsZTogIlIgZm9yIERhdGEgU2NpZW5jZSBDaGFwdGVyIDgiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQo8aDE+IERhdGEgSW1wb3J0IHdpdGggcmVhZHIgPGgxPg0KDQo8aDI+IEdldHRpbmcgU3RhcnRlZCA8L2gxPg0KVGhlc2UgZnVuY3Rpb25zIGFyZSBwYXJ0IG9mIHJlYWRyICh3aGljaCBpcyBsb2FkZWQgYnkgdGlkeXZlcnNlKTwvYnI+DQo8bGk+cmVhZF9jc3YoKSByZWFkcyBjb21tYSBzZXBhcmF0ZWQgdmFsdWVzPC9saT4NCjxsaT5yZWFkX2NzdjIoKSBzZW1pY29sb24gc2VwYXJhdGVkIDwvbGk+DQo8bGk+cmVhZF90c3YoKSBUYWIgZGVsaW1pdGVkIGZpbGVzPC9saT4NCjxsaT5yZWFkX2RlbGltKCkocmVhZCBhbnkgZGVsaW1pdGVkIGZpbGVzIDwvbGk+DQo8bGk+cmVhZF9md2YoKSByZWFkcyBmaXhlZCB3aWR0aCBmaWxlcy0gdXNlIGZ3Zl93aWR0aHMoKSBvciBmd2ZfcG9zaXRpb25zKCk8L2xpPg0KPGxpPnJlYWRfdGFibGUoKSByZWFzIGEgY29tbW9uIHZhcmlhdGlvbiBvZiBmaXhlZC13aWR0aCBmaWxlcyB3aGVyZSBjb2x1bW5zIGFyZSBzZXBhcmF0ZWQgYnkgd2hpdGUgc3BhY2UgPC9saT4NCjxsaT5yZWFkX2xvZyByZWFkcyBBcGFjaGUgc3R5bGUgbG9nIGZpbGVzIChhbHNvIGNoZWNrIG91dCB3ZWJyZWFkcikgPC9saT4NCjwvcD4NCg0KDQpUaGV5IGFsbCBoYXZlIHNpbWlsYXIgc3ludGF4LiBUaGUgZmlyc3QgYXJndW1lbnQgaXMgdGhlIHBhdGggdG8gdGhlIGZpbGUgdG8gcmVhZC4gPC9wPg0KDQpyZWFkX2NzdihmaWxlLCBjb2xfbmFtZXMgPSBUUlVFLCBjb2xfdHlwZXMgPSBOVUxMLA0KICBsb2NhbGUgPSBkZWZhdWx0X2xvY2FsZSgpLCBuYSA9IGMoIiIsICJOQSIpLCBxdW90ZWRfbmEgPSBUUlVFLA0KICBjb21tZW50ID0gIiIsIHRyaW1fd3MgPSBUUlVFLCBza2lwID0gMCwgbl9tYXggPSBJbmYsDQogIGd1ZXNzX21heCA9IG1pbigxMDAwLCBuX21heCksIHByb2dyZXNzID0gaW50ZXJhY3RpdmUoKSkNCg0KPC9wPg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KcmVhZF9jc3YoImc6XFxEb3dubG9hZHNcXEVhcnRocXVha2UuY3N2IikNCmBgYA0KDQpZb3UgY2FuIHVzZSBza2lwIGFyZ3VtZW50IHRvIHNraXAgdGhlIGZpcnN0IGZldyBsaW5lcyAtIHR5cGljYWxseSB1c2VkIGZvciBza2lwcGluZyBibGFuayBsaW5lcy4gDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpyZWFkX2NzdigiZzpcXERvd25sb2Fkc1xcRWFydGhxdWFrZS5jc3YiLCBza2lwID0gMikNCmBgYA0KDQpZb3UgY2FuIHN1cHBseSBpbmxpbmUgQ1NWDQooRWFjaCByb3cgbXVzdCBiZSBpbiBhIG5ldyBsaW5lLikNCmBgYHtyfQ0KcmVhZF9jc3YoImEsYixjDQogICAgICAgICAxLDIsMw0KICAgICAgICAgNCw1LDYiKQ0KYGBgDQoNCg0KWW91IGNhbiB1c2UgY29sbmFtZXMgPSBGQUxTRSB0byB0ZWxsIFIgdGhhdCB0aGVyZSBhcmUgbm8gaGVhZGVycy4NCihJZiB5b3UgZ2V0IFVudXNlZCBhcmd1bWVudCBlcnJvciwgbWFrZSBzdXJlIHRoYXQgeW91IGFyZSB1c2luZyByZWFkX2NzdiBhbmQgbm90IHJlYWQuY3N2KSA8L3A+DQoNCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCnJlYWRfY3N2KCJnOlxcRG93bmxvYWRzXFxFYXJ0aHF1YWtlLmNzdiIsIGNvbF9uYW1lcyA9IEZBTFNFKQ0KYGBgDQoNCllvdSBjYW4gYWxzbyB1c2UgaGVhZGVyID0gRkFMU0Ugd2hlbiB5b3VyIGNzdiBkYXRhIGRvZXMgbm90IGhhdmUgaGVhZGVycyBpbiB0aGVtLg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KcmVhZC5jc3YoImc6XFxEb3dubG9hZHNcXEVhcnRocXVha2UuY3N2IiwgaGVhZGVyID0gRkFMU0UpDQpgYGANCg0KT3IgeW91IGNhbiBwYXNzIGNoYXJhY3RlciB2ZWN0b3JzIGZvciBjb2x1bW4gbmFtZXMNCihkb2VzbnQgd29yaykNCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCiMgdXNlIGEgY29sbGVjdGlvbiBvZiBjaGFyYWN0ZXJzIGFzIHRoZSBoZWFkZXINCg0KcmVhZF9jc3YoZmlsZSA9ICJnOlxcRG93bmxvYWRzXFxxdWVyeS1ub2hlYWRlci5jc3YiLCANCiAgICAgICAgIGNvbF9uYW1lcyA9IGMoIngiLCJ5IiwieiIsIndpbHNvbiIsImphbmUiLCJXaWxsaWFtIikpDQpgYGANCg0KPGgzPiBIb3cgdG8gdHJlYXQgTkEgPC9oMz4NCg0KVG8gc3BlY2lmeSB0aGUgdmFsdWVzIG9yIHZhbHVlcyB0aGF0IGFyZSB1c2VkIHRvIHJlcGVzZW50IG1pc3NpbmcgdmFsdWVzIGluIHlvdXIgdGFibGUuIA0KDQpgYGB7cn0NCnJlYWRfY3N2KCJhLGIsYw0KICAgICAgICAgMSwyLC4iLCANCiAgICAgICAgIG5hID0iLiIpDQpgYGANCg0KPGgyPkNvbXBhcmVkIHRvIEJhc2UgUjwvaDI+DQoNClRoZXJlIGFyZSB0d28gbWFpbiBhbHRlcm5hdGl2ZXMgdG8gcmVhZHI6IGJhc2UgUiBhbmQgZGF0YS50YWJsZSdzIGZyZWFkKCkuIFRoZSBtb3N0IGltcG9ydGFudCBkaWZmZXJlbmNlcyBhcmUgZGlzY3Vzc2VkIGJlbG93LjwvcD4NCg0KPGgzPkJhc2UgUjwvaDM+DQoNCkNvbXBhcmVkIHRvIHRoZSBjb3JyZXNwb25kaW5nIGJhc2UgZnVuY3Rpb25zLCByZWFkciBmdW5jdGlvbnM6DQoNClVzZSBhIGNvbnNpc3RlbnQgbmFtaW5nIHNjaGVtZSBmb3IgdGhlIHBhcmFtZXRlcnMgKGUuZy4gY29sX25hbWVzIGFuZCBjb2xfdHlwZXMgbm90IGhlYWRlciBhbmQgY29sQ2xhc3NlcykuDQoNCkFyZSBtdWNoIGZhc3RlciAodXAgdG8gMTB4KS4NCg0KTGVhdmUgc3RyaW5ncyBhcyBpcyBieSBkZWZhdWx0LCBhbmQgYXV0b21hdGljYWxseSBwYXJzZSBjb21tb24gZGF0ZS90aW1lIGZvcm1hdHMuDQoNCkhhdmUgYSBoZWxwZnVsIHByb2dyZXNzIGJhciBpZiBsb2FkaW5nIGlzIGdvaW5nIHRvIHRha2UgYSB3aGlsZS4NCg0KQWxsIGZ1bmN0aW9ucyB3b3JrIGV4YWN0bHkgdGhlIHNhbWUgd2F5IHJlZ2FyZGxlc3Mgb2YgdGhlIGN1cnJlbnQgbG9jYWxlLiBUbyBvdmVycmlkZSB0aGUgVVMtY2VudHJpYyBkZWZhdWx0cywgdXNlIGxvY2FsZSgpLg0KDQpUaGV5IHByb2R1Y2UgdGliYmxlcy4gQmFzZSBSIGZ1bmN0aW9uIGluaGVyaXQgc29tZSBiZWhhdmlvciBmcm9tIHlvdXIgT1MgYW5kIGVudmlyb25tZW50IHZhcmlhYmxlcywgc28gaW1wb3J0IGNvZGUgdGhhdCB3b3JrcyBvbiBvbmUgY29tcHV0ZXIgbWlnaHQgbm90IHdvcmsgb24gb3RoZXIgY29tcHV0ZXJzLg0KDQoNCjxoMz5kYXRhLnRhYmxlIGFuZCBmcmVhZCgpPC9oMz4NCg0KZGF0YS50YWJsZSBoYXMgYSBmdW5jdGlvbiBzaW1pbGFyIHRvIHJlYWRfY3N2KCkgY2FsbGVkIGZyZWFkLiBDb21wYXJlZCB0byBmcmVhZCwgcmVhZHIgZnVuY3Rpb25zOg0KDQpBcmUgc2xvd2VyIChjdXJyZW50bHkgfjEuMi0yeCBzbG93ZXIuIElmIHlvdSB3YW50IGFic29sdXRlbHkgdGhlIGJlc3QgcGVyZm9ybWFuY2UsIHVzZSBkYXRhLnRhYmxlOjpmcmVhZCgpLg0KDQpVc2UgYSBzbGlnaHRseSBtb3JlIHNvcGhpc3RpY2F0ZWQgcGFyc2VyLCByZWNvZ25pc2luZyBib3RoIGRvdWJsZWQgKCIiIiIpIGFuZCBiYWNrc2xhc2ggZXNjYXBlcyAoIlwiIiksIGFuZCBjYW4gcHJvZHVjZSBmYWN0b3JzIGFuZCBkYXRlL3RpbWVzIGRpcmVjdGx5Lg0KDQpGb3JjZXMgeW91IHRvIHN1cHBseSBhbGwgcGFyYW1ldGVycywgd2hlcmUgZnJlYWQoKSBzYXZlcyB5b3Ugd29yayBieSBhdXRvbWF0aWNhbGx5IGd1ZXNzaW5nIHRoZSBkZWxpbWl0ZXIsIHdoZXRoZXIgb3Igbm90IHRoZSBmaWxlIGhhcyBhIGhlYWRlciwgYW5kIGhvdyBtYW55IGxpbmVzIHRvIHNraXAuDQoNCkFyZSBidWlsdCBvbiBhIGRpZmZlcmVudCB1bmRlcmx5aW5nIGluZnJhc3RydWN0dXJlLiBSZWFkciBmdW5jdGlvbnMgYXJlIGRlc2lnbmVkIHRvIGJlIHF1aXRlIGdlbmVyYWwsIHdoaWNoIG1ha2VzIGl0IGVhc2llciB0byBhZGQgc3VwcG9ydCBmb3IgbmV3IHJlY3Rhbmd1bGFyIGRhdGEgZm9ybWF0cy4gZnJlYWQoKSBpcyBkZXNpZ25lZCB0byBiZSBhcyBmYXN0IGFzIHBvc3NpYmxlLg0KDQo8aDM+IEV4ZXJjaXNlcyA8L2gzPg0KDQpPdGhlciBhcmd1bWVudHM6PC9icj4NCnJlYWRfY3N2KGZpbGUsIGNvbF9uYW1lcyA9IFRSVUUsIGNvbF90eXBlcyA9IE5VTEwsDQogIGxvY2FsZSA9IGRlZmF1bHRfbG9jYWxlKCksIG5hID0gYygiIiwgIk5BIiksIHF1b3RlZF9uYSA9IFRSVUUsDQogIGNvbW1lbnQgPSAiIiwgdHJpbV93cyA9IFRSVUUsIHNraXAgPSAwLCBuX21heCA9IEluZiwNCiAgZ3Vlc3NfbWF4ID0gbWluKDEwMDAsIG5fbWF4KSwgcHJvZ3Jlc3MgPSBpbnRlcmFjdGl2ZSgpKTwvcD4NCiAgDQoNCg0KPGgyPiBQYXJzaW5nIGEgVmVjdG9yIDwvaDI+DQoNClRoZSBwYXJzZV8qICgpIGZ1bmN0aW9ucyB0YWtlIGEgY2hhcmFjdGVyIHZlY3RvciBhbmQgcmV0dXJuIGEgbW9yZSBzcGVjaWFsaXplZCB2ZWN0b3IgbGlrZSBhIGxvZ2ljYWwsIGludGVnZXIsIG9yIGRhdGUuIDwvcD4NCmBgYHtyfQ0Kc3RyKHBhcnNlX2xvZ2ljYWwoYygiVFJVRSIsIkZBTFNFIiwiTkEiKSkpDQpgYGANCg0KYGBge3J9DQpzdHIocGFyc2VfaW50ZWdlcihjKCIxIiwiMiIsIjMiKSkpDQpgYGANCg0KYGBge3J9DQpzdHIocGFyc2VfZGF0ZShjKCIyMDAxLTAxLTAxIiwiMTk3OS0xMC0xNCIpKSkNCmBgYA0KDQpUaGUgbmEgYXJndW1lbnQgc3BlY2lmaWVzIHdoaWNoIHN0cmluZ3Mgc2hvdWxkIGJlIHRyZWF0ZWQgYXMgbWlzc2luZy4NCg0KYGBge3J9DQpwYXJzZV9pbnRlZ2VyKGMoIjEiLCIyIiwiLiIsIjQiKSwgbmE9Ii4iKQ0KDQpgYGANCg0KDQpJZiBwYXJzaW5nIGZhaWxzLCB5b3Ugd2lsbCBnZXQgYSB3YXJuaW5nLiBQbHVzLCB0aGUgZmFpbHVyZXMgd2lsbCBiZSBtaXNzaW5nIGluIHRoZSBvdXRwdXQuIElmIHRoZXJlIGFyZSBtYW55IGZhaWx1cmVzLCBiZXR0ZXIgdXNlIHByb2JsZW1zKCkgdG8gZ2V0IHRoZSBjb21wbGV0ZSBzZXQuIA0KDQpgYGB7cn0NCnggPC0gIHBhcnNlX2ludGVnZXIoYygiMTIzIiwiMzQ1IiwiYWJjIiwiMTIzLjQ1IikpDQoNCg0KYGBgDQoNCmBgYHtyfQ0KeA0KDQpgYGANCg0KYGBge3J9DQpwcm9ibGVtcyh4KQ0KYGBgDQoNCjxoMz4gRWlnaHQgcGFydGljdWFsYXJseSBpbXBvcnRhbnQgcGFyc2VyczogPC9oMz4NCg0KPGxpPnBhcnNlX2xvZ2ljYWwoKSBmb3IgdHJ1ZSBvciBmYWxzZSB2YWx1ZXMgPC9saT4NCjxsaT5wYXJzZV9pbnRlZ2VyKCkgZm9yIHdob2xlIG51bWJlcnMgPC9saT4NCjxsaT5wYXJzZV9kb3VibGUoKSBzdHJpY3QgbnVtZXJpYyBwYXJzZXIgPC9saT4NCjxsaT5wYXJzZV9udW1iZXIoKSA8L2xpPg0KPGxpPnBhcnNlX2NoYXJhY3RlcigpIGJlIGF3YXJlIG9mIGNoYXJhY3RlciBlbmNvZGluZ3M8L2xpPg0KPGxpPnBhcnNlX2ZhY3RvcigpIGNyZWF0ZXMgZmFjdG9ycyBmb3IgY2F0ZWdvcmljYWwgdmFsdWVzIDwvbGk+DQo8bGk+UGFyc2VfZGF0ZXRpbWUoKTwvbGk+DQo8bGk+cGFyc2VfZGF0ZSgpPC9saT4NCjxsaT5wYXJzZV90aW1lIDwvbGk+DQoNCmBgYHtyfQ0KeCA8LSBwYXJzZV9udW1iZXIoIiQxMCwwMDAiKQ0KeSA8LSBwYXJzZV9udW1iZXIoIjEwJSIpDQp6IDwtIHBhcnNlX251bWJlcigiSXQgY29zdCAkMTIzLjU2IikNCngNCnkNCnoNCg0KDQpgYGANCg0KVXNpbmcgbG9jYWxlIGFyZ3VtZW50IHRvIHNwZWNpZnkgLCBhcyB0aGUgZGVjaW1hbCBwb2ludCANCg0KYGBge3J9DQpwYXJzZV9kb3VibGUoIjEuMjMiKQ0KcGFyc2VfZG91YmxlKCIxLDIzIiwgbG9jYWxlID0gbG9jYWxlKGRlY2ltYWxfbWFyayA9ICIsIikpDQpgYGANCg0KVXNpbmcgZ3JvdXBpbmcgbWFyay4NCmBgYHtyfQ0KIyBVUw0KcGFyc2VfbnVtYmVyKCIkMTIzLDQ1Niw3ODkuMDAiKQ0KIyBFdXJvcGUNCnBhcnNlX251bWJlcigiJDEyMy40NTYuNzg5LDAwIiwNCiAgICAgICAgICAgICBsb2NhbGUgPSBsb2NhbGUoZ3JvdXBpbmdfbWFyayA9ICIuIikpDQoNCiMgU3dpdHplcmxhbmQNCnBhcnNlX251bWJlcigiJDEyMyc0NTYnNzg5LjAwIiwNCiAgICAgICAgICAgICBsb2NhbGUgPSBsb2NhbGUoZ3JvdXBpbmdfbWFyayA9ICInIikpDQpgYGANCg0KUmVhZHIncyBkZWZhdWx0IGxvY2FsZSBpcyBVUyBjZW50cmljLiANCg0KPGgyPiBTdHJpbmdzIDwvaDI+DQoNCg0KYGBge3J9DQojIHJlYWRlciB1c2VzIFVURi04IGVuY29kaW5nIA0KY2hhclRvUmF3KCJXaWxzb24gd2FzIGhlcmUiKQ0KeDEgPC0gICJFbCBOaVx4ZjFvIHdhcyBwYXJ0aWN1bGFybHkgYmFkIHRoaXMgeWVhciINCnBhcnNlX2NoYXJhY3Rlcih4MSwgbG9jYWxlID0gbG9jYWxlKGVuY29kaW5nID0gIkxhdGluMSIpKQ0KYGBgDQoNCklmIHlvdSBkb250IGtub3cgdGhlIGVuY29kaW5nIG1ldGhvZCB0aGF0IHdhcyB1c2VkLCB5b3UgY2FuIHRyeSBndWVzc19lbmNvZGluZygpIGZ1bmN0aW9uIA0KDQpgYGB7cn0NCmd1ZXNzX2VuY29kaW5nKGNoYXJUb1Jhdyh4MSkpDQpgYGANCg0KPGgyPiBGYWN0b3JzIDwvaDI+DQoNClBhcnNlX2ZhY3RvcigpIHRvIGdlbmVyYXRlIGNhdGVvcmljYWwgdmFyaWFibGVzIHRoYXQgaGF2ZSBhIGtub3duIHNldCBvZiBwb3NzaWJsZSB2YWx1ZXMuIA0KYGBge3J9DQpmcnVpdCA8LSAgYygiYXBwbGUiLCJiYW5hbmEiKQ0KcGFyc2VfZmFjdG9yKGMoImFwcGxlIiwiYmFuYW5hIiwgImJhbmFuYW5hIiksIGxldmVscyA9IGZydWl0KQ0KDQpgYGANCg0KDQo8aDI+IERhdGVzLCBEYXRlLXRpbWVzIGFuZCBUaW1lcyA8L2gyPg0KICANCldlIGNhbiB1c2UgMyBkaWZmZXJlbnQgcGFyc2VycyBkZXBlbmRpbmcgb24gd2hldGhlciB3ZSB3YW50IGRhdGUsIHRpbWUgb3IgZGF0ZS10aW1lLiANCg0KUGFyc2VfZGF0ZSgpIGV4cGVjdHMgYW4gSVNPODYwMSBkYXRlLXRpbWUuIA0KYGBge3J9DQpwYXJzZV9kYXRldGltZSgiMjAxMC0xMC0wMVQyMDEwIikNCiMgaWYgdGltZSBpcyBvbWl0dGVkLCBpdCB3aWxsIGJlIHNldCB0byBtaWRuaWdodA0KcGFyc2VfZGF0ZXRpbWUoIjIwMTAtMTAtMDEiKQ0KYGBgDQoNCg0KDQpQYXJzZV9kYXRlIGV4cGVjdHMgYSBmb3VyIGRpZ2l0IHllYXIsIGEgLSBvciAvLCB0aGUgbW9udGgsIGEgLSBvciAvIHRoZW4gdGhlIGRheS4NCg0KYGBge3J9DQpwYXJzZV9kYXRlKCIyMDEwLTEwLTAxIikNCnBhcnNlX2RhdGUoIjIwMTAvMTAvMDEiKQ0KcGFyc2VfZGF0ZSgiMjAxMC0xMC8wMSIpDQpwYXJzZV9kYXRlKCIxMC0wMS0yMDEwIikNCg0KYGBgDQoNCg0KUGFyc2VfdGltZSgpIGV4cGVjdCB0aGUgaG91ciwgOiBtaW51dGVzLCBvcHRpb25hbGx5IDogYW5kIHNlY29uZHMgYW5kIGFuIG9wdGlvbmFsIGFtL3BtIHNwZWNpZmllcg0KDQpgYGB7cn0NCiMgUiBkb2VzbnQgaGF2ZSBhIGdyZWF0IGIgdWlsdC1pbiBjbGFzcyBmb3IgdGltZSBkYXRhLCBzbyB3ZSB1c2UgdGhlIG9uZSBwcm92aWRlZCBpbiB0aGUgaG1zIHBhY2thZ2UNCmxpYnJhcnkoaG1zKQ0KcGFyc2VfdGltZSgiMToxMCBhbSIpDQpwYXJzZV90aW1lKCIwMjoxMCBhbSIpDQpwYXJzZV90aW1lKCIyMDoxMDowMSIpDQpwYXJzZV90aW1lKCI4OjEwOjAxIHBtIikNCmBgYA0KDQoNCllvdSBjYW4gc3VwcGx5IHlvdXIgb3duIGRhdGEgZm9ybWF0cy48L2JyPg0KDQpZZWFyOiAiJVkiICg0IGRpZ2l0cykuICIleSIgKDIgZGlnaXRzKTsgMDAtNjkgLT4gMjAwMC0yMDY5LCA3MC05OSAtPiAxOTcwLTE5OTkuPC9icj4NCg0KTW9udGg6ICIlbSIgKDIgZGlnaXRzKSwgIiViIiAoYWJicmV2aWF0ZWQgbmFtZSBpbiBjdXJyZW50IGxvY2FsZSksICIlQiIgKGZ1bGwgbmFtZSBpbiBjdXJyZW50IGxvY2FsZSkuPC9icj4NCg0KRGF5OiAiJWQiICgyIGRpZ2l0cyksICIlZSIgKG9wdGlvbmFsIGxlYWRpbmcgc3BhY2UpPC9icj4NCg0KSG91cjogIiVIIjwvYnI+DQoNCk1pbnV0ZXM6ICIlTSI8L2JyPg0KDQpTZWNvbmRzOiAiJVMiIChpbnRlZ2VyIHNlY29uZHMpLCAiJU9TIiAocGFydGlhbCBzZWNvbmRzKTwvYnI+DQoNClRpbWUgem9uZTogIiVaIiAoYXMgbmFtZSwgZS5nLiAiQW1lcmljYS9DaGljYWdvIiksICIleiIgKGFzIG9mZnNldCBmcm9tIFVUQywgZS5nLiAiKzA4MDAiKTwvYnI+DQoNCkFNL1BNIGluZGljYXRvcjogIiVwIi48L2JyPg0KDQpOb24tZGlnaXRzOiAiJS4iIHNraXBzIG9uZSBub24tZGlnaXQgY2hhcmFjdGVyLCAiJSsiIHNraXBzIG9uZSBvciBtb3JlIG5vbi1kaWdpdCBjaGFyYWN0ZXJzLCAiJSoiIHNraXBzIGFueSBudW1iZXIgb2Ygbm9uLWRpZ2l0cyBjaGFyYWN0ZXJzLjwvYnI+DQoNCkF1dG9tYXRpYyBwYXJzZXJzOiAiJUFEIiBwYXJzZXMgd2l0aCBhIGZsZXhpYmxlIFlNRCBwYXJzZXIsICIlQVQiIHBhcnNlcyB3aXRoIGEgZmxleGlibGUgSE1TIHBhcnNlci48L2JyPg0KDQpTaG9ydGN1dHM6ICIlRCIgPSAiJW0vJWQvJXkiLCAiJUYiID0gIiVZLSVtLSVkIiwgIiVSIiA9ICIlSDolTSIsICIlVCIgPSAiJUg6JU06JVMiLCAiJXgiID0gIiV5LyVtLyVkIi48L2JyPg0KDQoNCmBgYHtyfQ0KcGFyc2VfZGF0ZSgiMDEvMDIvMTUiLCAiJW0vJWQvJXkiKQ0KcGFyc2VfZGF0ZSgiMDEvMDIvMTUiLCAiJWQvJW0vJXkiKQ0KcGFyc2VfZGF0ZSgiMDEvMDIvMTUiLCAiJXkvJW0vJXkiKQ0KcGFyc2VfZGF0ZSgiMDEvMDIvMTUiLCAiJUQiKQ0KcGFyc2VfZGF0ZSgiMDEvMDIvMTUiLCAiJXgiKQ0KDQpwYXJzZV9kYXRlKCIxIEphbnVhcnkgMjAxNSIsICIlZCAlQiAlWSIpDQpwYXJzZV9kYXRlKCIxIEphbiAyMDE1IiwgIiVkICViICVZIikNCg0KYGBgDQoNCg0KDQo8aDI+IFBhcnNpbmcgYSBGaWxlIDwvaDI+DQoNCnJlYWRyIHVzZXMgYSBoZXVyaXN0aWMgdG8gZmlndXJlIG91dCB0aGUgdHlwZSBvZiBlYWNoIGNvbHVtbjogaXQgcmVhZHMgdGhlIGZpcnN0IDEwMDAgcm93cyBhbmQgdXNlcyBzb21lIG1vZGVyYXRlbHkgY29uc2VydmF0aXZlIGhldXJpc3RpY3MgdG8gZmlndXJlIG91dCB0aGUgdHlwZSBvZiBlYWNoIGNvbHVtbi4gVGhlIGhldXJpc3RpYyB0cmllcyBlYWNoIG9mIHRoZSBmb2xsb3dpbmcgdHlwZXMsIHN0b3BwaW5nIHdoZW4gaXQgZmluZHMgYSBtYXRjaC4gTG9naWNhbCwgaW50ZWdlciwgZG91YmxlLCBudW1iZXIsIHRpbWUsIGRhdGUsIGRhdGUtdGltZS4gQ2F0Y2ggYWxsIGlzIHN0cmluZy4NCg0KYGBge3J9DQpjaGFsbGVuZ2UgPC0gIHJlYWRfY3N2KHJlYWRyX2V4YW1wbGUoImNoYWxsZW5nZS5jc3YiKSkNCnByb2JsZW1zKGNoYWxsZW5nZSkNCmBgYA0KDQpUaGUgZXJyb3IgbWVzc2FnZSBzaG93cyB0aGF0IHRoZSBpbnRlZ2VyIGNvbHVtbiBjb250YWlucyBkYXRhIHdpdGggZGVjaW1hbHMgaW4gaXQuIA0KU28gd2UgdXNlIHBhcnNlX2RvdWJsZS4gDQoNCg0KYGBge3J9DQpjaGFsbGVuZ2UgPC0gIHJlYWRfY3N2KHJlYWRyX2V4YW1wbGUoImNoYWxsZW5nZS5jc3YiKSwNCmNvbF90eXBlcyA9IGNvbHMoDQogIHggPSBjb2xfZG91YmxlKCksDQogIHkgPSBjb2xfY2hhcmFjdGVyKCkNCikNCikNCg0KIyBsb29rIGF0IHRoZSBsYXN0IGZldyByb3dzDQp0YWlsKGNoYWxsZW5nZSkNCmBgYA0KDQpBaGEgWSBpcyBhIGRhdGUgZmllbGQsIG5vdCBhIGNoYXJhY3RlciBmaWVsZC4gDQoNCmBgYHtyfQ0KY2hhbGxlbmdlIDwtICByZWFkX2NzdihyZWFkcl9leGFtcGxlKCJjaGFsbGVuZ2UuY3N2IiksDQpjb2xfdHlwZXMgPSBjb2xzKA0KICB4ID0gY29sX2RvdWJsZSgpLA0KICB5ID0gY29sX2RhdGUoKQ0KKQ0KKQ0KIyBsb29rIGF0IHRoZSBsYXN0IGZldyByb3dzDQp0YWlsKGNoYWxsZW5nZSkNCmBgYA0KDQoNCkl0IGlzIGJlc3QgcHJhY3RpY2UgdG8gdXNlIGNvbF90eXBlcyAgdG8gZW5zdXJlIGNvbnNpc3RlbnQgYW5kIHJlcHJvZHVjaWJsZSBkYXRhIGltcG9ydCBzY3JpcHQuIFRvIGJlIHJlYWxseSBzdHJpY3QsIHdlIGNhbiBhbHNvIHVzZSBzdG9wX2Zvcl9wcm9ibGVtcygpIHdoaWNoIHdpbGwgdGhyb3cgYW4gZXJyb3IgYW5kIHN0b3AgdGhlIHNjcmlwdCBpZiB0aGVyZSBhcmUgYW55IGVycm9ycy4NCg0KPGgyPiBPdGhlciBTdHJhdGVnaWVzIDwvaDI+DQoNClVzaW5nIGd1ZXNzX21heCBhcmd1bWVudA0KDQpgYGB7cn0NCmNoYWxsZW5nZTIgPC0gIHJlYWRfY3N2KA0KICByZWFkcl9leGFtcGxlKCJjaGFsbGVuZ2UuY3N2IiksDQogIGd1ZXNzX21heCA9IDEwMDENCiAgKQ0KDQpjaGFsbGVuZ2UyDQpgYGANCg0KDQpTb21ldGltZXMgaXQgaXMgZWFzaWVyIHRvIGRpYWdub3NlIHByb2JsZW1zIGlmIHlvdSBqdXN0IHJlYWQgaW4gYWxsIHRoZSBjb2x1bW5zIGFzIGNoYXJhY3RlciB2ZWN0b3JzLiANCg0KYGBge3J9DQpjaGFsbGVuZ2UyIDwtICByZWFkX2NzdigNCiAgcmVhZHJfZXhhbXBsZSgiY2hhbGxlbmdlLmNzdiIpLA0KICBjb2xfdHlwZXMgPSBjb2xzKC5kZWZhdWx0ID0gY29sX2NoYXJhY3RlcigpKQ0KICApDQpjaGFsbGVuZ2UyDQpgYGANCg0KDQpUaGlzIGlzIHVzZWZ1bCBpbiBjb25qdW5jdGlvbiB3aXRoIHRoZSB0eXBlX2NvbnZlcnQoKSB3aGljaCBhcHBsaWVzIHRoZSBwYXJzaW5nIGhldXJpc3RpY3MgdG8gdGhlIGNoYXJhY3RlciBjb2x1bW5zIGluIGEgZGF0YSBmcmFtZS4gDQoNCmBgYHtyfQ0KZGYgPC0gIHRyaWJibGUoDQogIH54LCB+IHksIA0KICAiMSIsICIxLjIxIiwNCiAgIjIiLCIyLjMyIiwNCiAgIjMiLCAiNC41NiINCikNCg0KZGYNCg0KYGBgDQoNCg0KYGBge3J9DQojIE5vdGUgdGhlIGNvbHVtbiB0eXBlcyBhZnRlciB0aGUgY29udmVydGlvbg0KdHlwZV9jb252ZXJ0KGRmKQ0KDQpgYGANCg0KDQpOb3RlOiBmb3IgdmVyeSBsYXJnZSBmaWxlcywgeW91IG1pZ2h0IHdhbnQgdG8gc2V0IG5fbWF4IHRvIGEgc21hbGxpc2ggbnVtYmVyIGxpa2UgMTAsMDAwIG9yIDEwMCwwMDAuIA0KDQo8SDI+IFdyaXRpbmcgdG8gYSBGaWxlIDwvaDI+DQoNClR3byBmdW5jdGlvbnMgd3JpdGVfY3N2IGFuZCB3cml0ZV90c3YoKS4NCkFsd2F5cyB1c2VzIFVURi04IGVuY29kaW5nDQpTYXZpbmcgZGF0ZXMgYW5kIGRhdGUtdGltZXMgaW4gSVNPODYwMSBmb3JtYXQgc28gdGhleSBhcmUgZXNpbHkgcGFyc2VkIGVsc2V3aGVyZS4gDQoNCklmIHlvdSB3YW50IHRvIGV4cG9ydCBhIENTViBmaWxlIHRvIEV4Y2VsLCB1c2Ugd3JpdGVfZXhjZWxfY3N2KCkgdGhpcyB3cml0ZXMgYSBzcGVjaWFsIGNoYXJhY3RlciAoYSAnYnl0ZSBvcmRlciBtYXJrIikgYXQgdGhlIHN0YXJ0IG9mIHRoZSBmaWxlLCB3aGljaCB0ZWxscyBFeGNlbCB0aGF0IHlvdSdyZSB1c2luZyB0aGUgVVRGLTggZW5jb2RpbmcuIDwvcD4NCg0KYGBge3J9DQojIHRvIGFwcGVuZCB0byBhbiBleGlzdGluZyBmaWxlLiANCiMgTm90ZTogdHlwZSBpbmZvcm1hdGlvbiBpcyBsb3N0IHdoZW4geW91IHNhdmUgdG8gY3N2IGZvcm1hdA0KDQp3cml0ZV9jc3YoY2hhbGxlbmdlLCAiQ2hhbGxlbmdlLmNzdiIpDQpgYGANCg0KQnV0IHNhdmluZyBpbiBDU1YgZm9ybWF0IGlzIGEgbGl0dGxlIHVucmVsaWFibGUgZm9yIGNhY2hpbmcgaW50ZXJpbSByZXN1bHRzLiBZb3UgbmVlZCB0byByZS1jcmVhdGUgdGhlIGNvdW1uIHNwZWNpZmljYXRpb24gZXZlcnkgdGltZSB5b3UgbG9hZCBpbi4gVGhlcmUgYXJlIHR3byBhbHRlcm5hdGl2ZXM6IHdyaXRlcmRzKCkgYW5kIHJlYWRfcmRzKCkgd2lsbCBzdG9yZSBpbiBSJ3MgY3VzdG9tIGJpbmFyeSBmb3JtYXQgY2FsbGVkIFJEUy4gDQoNCmBgYHtyfQ0Kd3JpdGVfcmRzKGNoYWxsZW5nZSwgImNoYWxsZW5nZS5yZHMiKQ0KcmVhZF9yZHMoImNoYWxsZW5nZS5yZHMiKQ0KYGBgDQoNCg0KWW91IGNhbiBhbHNvIHVzZSB0aGUgZmVhdGhlciBwYWNrYWdlIHRvIGltcGxlbWVudCBhIGZhc3QgYmluYXJ5IGZpbGUgZm9ybWF0IHRoYSB0Y2FuIGJlIHNoYXJlZCBhY3Jvc3MgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2VzLg0KDQpgYGB7cn0NCmxpYnJhcnkoZmVhdGhlcikNCndyaXRlX2ZlYXRoZXIoY2hhbGxlbmdlLCAiY2hhbGxlbmdlLmZlYXRoZXIiKQ0KcmVhZF9mZWF0aGVyKCJjaGFsbGVuZ2UuZmVhdGhlciIpDQpgYGANCg0KTm90ZXM6IEZlYXRoZXIgaXMgZmFzdGVyIHRoYW4gUkRTIGFuZCBjYW4gYmUgdXNlZCBvdXRzaWRlIG9mIFIgYnV0IFJEUyBzdXBwb3NydHMgbGlzdC1jb2x1bW5zIHdoaWNoIGZlYXRoZXIgZG9lcyBub3QuIA0KDQo8aDI+IE90aGVyIFR5cGVzIG9mIERhdGE8L2gyPg0KaGF2ZW4gcmVhZHMgU1BTUywgU3RhdGEgYW5kIFNBUyBmaWxlczwvYnI+DQpyZWFkeGwgcmVhZHMgRXhjZWwgZmlsZXMgKGJvdGggeGxzLiBhbmQgeGxzeCk8L2JyPg0KREJJIGFsbG93cyB5b3UgdG8gcnVuIFNRTCBxdWVyaWVzIGFnYWluc3QgYSBkYXRhYmFzZSBhbmQgcmV0dXJuIGEgZGF0YSBmcmFtZS4gPC9icj4NCmpzb25saXRlIGZvciBoaWVyYXJjaGljYWwgZGF0YSA8L2JyPg0KeG1sMiBmb3IgWE1MIGRhdGEgPC9icj4NClNlZSBhbHNvIEplbm55IEJyeWFuIGF0IGh0dHBzOi8vamVubnliYy5naXRodWIuaW8vcHVycnItdHV0b3JpYWwgPC9icj4NCg0K