This is how to insert new soil moisture measurements data into the database. Click “Code” at the top of this page to download an rmarkdown version of this file!
First, load the necessary libraries
library(RPostgreSQL)
library(readr)
library(dplyr)
library(tidyr)
library(uuid)
library(magrittr)
library(kableExtra)
library(DT)
Load up the rodm2 package!
library(devtools)
install_github("khondula/rodm2")
library(rodm2)
Then connect to the database
password <- scan(".pgpass", what="")
db <- dbConnect(PostgreSQL(),
host = "sesync-postgis01.research.sesync.org",
dbname = "choptank",
user = "palmergroup",
password = password)
Upload and prepare data
Upload the spreadsheet with your new soil moisture data. Either use a file browser:
new_measurements <- file.choose() %>% read_csv()
Or supply the file path:
new_measurements <- read_csv("data/Field_measurements - SOIL_field_measurements.csv")
The new_measurements
data frame should look something like this:
new_measurements %>% head() %>% kable(format = "markdown")
Data column names
Make sure the column names for data match the template. This chunk should return TRUE
.
all(c("VWC_percent", "soil_EC_mscm", "soil_temp_C") %in% names(new_measurements))
Site codes
Make a new column with site code names called samplingfeaturecode if it isn’t there already. If you have the wetland site code and the transect point in 2 separate columns, you can combine them using paste.
new_measurements$samplingfeaturecode <- paste(new_measurements$Site, new_measurements$Location)
new_measurements$samplingfeaturecode <- gsub("SC ", "SC-", new_measurements$samplingfeaturecode)
Make sure all the sites in the new data are in the database by reading in the samplingfeatures table and checking all the site code names in the new data. The function check_samplingfeaturecodes (defined in helpers.R
) returns the names of any sites not in the database compared to a given vector.
samplingfeatures <- dbGetQuery(db, "SELECT samplingfeaturecode FROM odm2.samplingfeatures")
check_samplingfeaturecodes(new_codes = new_measurements$samplingfeaturecode) %>% head()
If you need help figuring out the exact site names, look for site names in the database with a given pattern using the function get_site_names_like()
get_site_names_like("QB SC")
Fix site code names if needed. For example, soil chamber sites might need hyphens added.
new_measurements$samplingfeaturecode <- gsub(pattern = "SC ", "SC-", new_measurements$samplingfeaturecode)
Check site names again if needed. If there are no rows returned then all the site names match sampling feature codes in the database.
check_samplingfeaturecodes(new_measurements$samplingfeaturecode)
Method code
There needs to be a defined method in the methods table about how samples were collected. Make sure that sample collection method exists by checking the methods table online or reading it directly from the database using the dbReadTable()
function. Note the methodcode or make a new one with the db_add_method()
function.
dbReadTable(db, c("odm2", "methods")) %>% kable()
# db_add_method(methodname = "Soil moisture measurement",
# methodcode = "soilmoistureTDR",
# methodtypecv = "Instrument deployment",
# methoddescription = "Point measurement of soil moisture using TDR probe with 1.5 inch probes")
Save the methodcode as a variable
methodcode = "soilmoistureTDR"
Annotations
Coming soon!
Add an annotation to an action or result
# dbReadTable(db, c("odm2", "cv_annotationtype"))
# db_add_annotation(annotationtypecv = "Action annotation",
# annotationtext = "measured less than 24 hours after rain")
Insert measurements data
This is where the magic happens! The function db_insert_measurements_sm()
writes and runs SQL code to upload data from one row of the new_measurements data frame at a time. So db_insert_measurements_sm(1)
would insert data from the first row. Use an apply statement to upload all of the data at once. The eval=FALSE
argument means that this code will not run if you knit the whole document.
# db_insert_measurements_sm(1) # for just one measurement
sapply(1:nrow(new_measurements),
function(x) db_insert_measurements_sm(x))
Query database
Use this chunk to make a data frame called sm_db with all the soil data in the database
get_sm_data <- function(){
sql <- paste0("SELECT mrv.datavalue, mrv.valuedatetime, sf.samplingfeaturecode, r.featureactionid, v.variablecode, u.unitsname
FROM odm2.measurementresultvalues mrv, odm2.results r, odm2.variables v, odm2.units u, odm2.samplingfeatures sf, odm2.featureactions fa
WHERE r.variableid = v.variableid
AND r.featureactionid = fa.featureactionid
AND fa.samplingfeatureid = sf.samplingfeatureid
AND r.unitsid = u.unitsid
AND mrv.resultid = r.resultid
AND r.sampledmediumcv = 'Soil'")
sql <- gsub("\n", "", sql)
dbGetQuery(db, sql)
}
sm_db <- get_sm_data()
organize back to the original format using the spread function in the tidyr package
sm_db_spread <- sm_db %>% group_by(samplingfeaturecode) %>%
dplyr::select(-unitsname) %>%
spread(variablecode, datavalue)
sm_db_spread %>% datatable()
LS0tCnRpdGxlOiAiSW5zZXJ0IHNvaWwgbW9pc3R1cmUgbWVhc3VyZW1lbnRzIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGRmX3ByaW50OiBwYWdlZAplZGl0b3Jfb3B0aW9uczoKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQoKVGhpcyBpcyBob3cgdG8gaW5zZXJ0IG5ldyBzb2lsIG1vaXN0dXJlIG1lYXN1cmVtZW50cyBkYXRhIGludG8gdGhlIGRhdGFiYXNlLiBDbGljayAiQ29kZSIgYXQgdGhlIHRvcCBvZiB0aGlzIHBhZ2UgdG8gZG93bmxvYWQgYW4gcm1hcmtkb3duIHZlcnNpb24gb2YgdGhpcyBmaWxlIQoKRmlyc3QsIGxvYWQgdGhlIG5lY2Vzc2FyeSBsaWJyYXJpZXMKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KFJQb3N0Z3JlU1FMKQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KHV1aWQpCmxpYnJhcnkobWFncml0dHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeShEVCkKYGBgCgpMb2FkIHVwIHRoZSByb2RtMiBwYWNrYWdlIQoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoZGV2dG9vbHMpCmluc3RhbGxfZ2l0aHViKCJraG9uZHVsYS9yb2RtMiIpCmxpYnJhcnkocm9kbTIpCmBgYAoKVGhlbiBjb25uZWN0IHRvIHRoZSBkYXRhYmFzZQoKYGBge3J9CnBhc3N3b3JkIDwtIHNjYW4oIi5wZ3Bhc3MiLCB3aGF0PSIiKQoKZGIgPC0gZGJDb25uZWN0KFBvc3RncmVTUUwoKSwgCiAgICAgICAgICAgICAgICBob3N0ID0gInNlc3luYy1wb3N0Z2lzMDEucmVzZWFyY2guc2VzeW5jLm9yZyIsCiAgICAgICAgICAgICAgICBkYm5hbWUgPSAiY2hvcHRhbmsiLCAKICAgICAgICAgICAgICAgIHVzZXIgPSAicGFsbWVyZ3JvdXAiLAogICAgICAgICAgICAgICAgcGFzc3dvcmQgPSBwYXNzd29yZCkKYGBgCgoKIyBVcGxvYWQgYW5kIHByZXBhcmUgZGF0YQoKVXBsb2FkIHRoZSBzcHJlYWRzaGVldCB3aXRoIHlvdXIgbmV3IHNvaWwgbW9pc3R1cmUgZGF0YS4gRWl0aGVyIHVzZSBhIGZpbGUgYnJvd3NlcjoKCmBgYHtyLCBldmFsID0gRkFMU0V9Cm5ld19tZWFzdXJlbWVudHMgPC0gZmlsZS5jaG9vc2UoKSAlPiUgcmVhZF9jc3YoKQpgYGAKCk9yIHN1cHBseSB0aGUgZmlsZSBwYXRoOiAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpuZXdfbWVhc3VyZW1lbnRzIDwtIHJlYWRfY3N2KCJkYXRhL0ZpZWxkX21lYXN1cmVtZW50cyAtIFNPSUxfZmllbGRfbWVhc3VyZW1lbnRzLmNzdiIpCmBgYAoKVGhlIGBuZXdfbWVhc3VyZW1lbnRzYCBkYXRhIGZyYW1lIHNob3VsZCBsb29rIHNvbWV0aGluZyBsaWtlIHRoaXM6IAoKYGBge3J9Cm5ld19tZWFzdXJlbWVudHMgJT4lIGhlYWQoKSAlPiUga2FibGUoZm9ybWF0ID0gIm1hcmtkb3duIikKYGBgCgojIyBEYXRhIGNvbHVtbiBuYW1lcwoKTWFrZSBzdXJlIHRoZSBjb2x1bW4gbmFtZXMgZm9yIGRhdGEgbWF0Y2ggdGhlIHRlbXBsYXRlLiBUaGlzIGNodW5rIHNob3VsZCByZXR1cm4gYFRSVUVgLiAKCmBgYHtyfQphbGwoYygiVldDX3BlcmNlbnQiLCAic29pbF9FQ19tc2NtIiwgInNvaWxfdGVtcF9DIikgJWluJSBuYW1lcyhuZXdfbWVhc3VyZW1lbnRzKSkKYGBgCgojIyBTaXRlIGNvZGVzIAoKTWFrZSBhIG5ldyBjb2x1bW4gd2l0aCBzaXRlIGNvZGUgbmFtZXMgY2FsbGVkIHNhbXBsaW5nZmVhdHVyZWNvZGUgaWYgaXQgaXNuJ3QgdGhlcmUgYWxyZWFkeS4gSWYgeW91IGhhdmUgdGhlIHdldGxhbmQgc2l0ZSBjb2RlIGFuZCB0aGUgdHJhbnNlY3QgcG9pbnQgaW4gMiBzZXBhcmF0ZSBjb2x1bW5zLCB5b3UgY2FuIGNvbWJpbmUgdGhlbSB1c2luZyBwYXN0ZS4gCgpgYGB7cn0KbmV3X21lYXN1cmVtZW50cyRzYW1wbGluZ2ZlYXR1cmVjb2RlIDwtIHBhc3RlKG5ld19tZWFzdXJlbWVudHMkU2l0ZSwgbmV3X21lYXN1cmVtZW50cyRMb2NhdGlvbikKbmV3X21lYXN1cmVtZW50cyRzYW1wbGluZ2ZlYXR1cmVjb2RlIDwtIGdzdWIoIlNDICIsICJTQy0iLCBuZXdfbWVhc3VyZW1lbnRzJHNhbXBsaW5nZmVhdHVyZWNvZGUpCmBgYAoKTWFrZSBzdXJlIGFsbCB0aGUgc2l0ZXMgaW4gdGhlIG5ldyBkYXRhIGFyZSBpbiB0aGUgZGF0YWJhc2UgYnkgcmVhZGluZyBpbiB0aGUgc2FtcGxpbmdmZWF0dXJlcyB0YWJsZSBhbmQgY2hlY2tpbmcgYWxsIHRoZSBzaXRlIGNvZGUgbmFtZXMgaW4gdGhlIG5ldyBkYXRhLiBUaGUgZnVuY3Rpb24gY2hlY2tfc2FtcGxpbmdmZWF0dXJlY29kZXMgKGRlZmluZWQgaW4gYGhlbHBlcnMuUmApIHJldHVybnMgdGhlIG5hbWVzIG9mIGFueSBzaXRlcyBub3QgaW4gdGhlIGRhdGFiYXNlIGNvbXBhcmVkIHRvIGEgZ2l2ZW4gdmVjdG9yLgoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CnNhbXBsaW5nZmVhdHVyZXMgPC0gZGJHZXRRdWVyeShkYiwgIlNFTEVDVCBzYW1wbGluZ2ZlYXR1cmVjb2RlIEZST00gb2RtMi5zYW1wbGluZ2ZlYXR1cmVzIikKY2hlY2tfc2FtcGxpbmdmZWF0dXJlY29kZXMobmV3X2NvZGVzID0gbmV3X21lYXN1cmVtZW50cyRzYW1wbGluZ2ZlYXR1cmVjb2RlKSAlPiUgaGVhZCgpCmBgYAoKSWYgeW91IG5lZWQgaGVscCBmaWd1cmluZyBvdXQgdGhlIGV4YWN0IHNpdGUgbmFtZXMsIGxvb2sgZm9yIHNpdGUgbmFtZXMgaW4gdGhlIGRhdGFiYXNlIHdpdGggYSBnaXZlbiBwYXR0ZXJuIHVzaW5nIHRoZSBmdW5jdGlvbiBgZ2V0X3NpdGVfbmFtZXNfbGlrZSgpYAoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmdldF9zaXRlX25hbWVzX2xpa2UoIlFCIFNDIikKYGBgCgpGaXggc2l0ZSBjb2RlIG5hbWVzIGlmIG5lZWRlZC4gRm9yIGV4YW1wbGUsIHNvaWwgY2hhbWJlciBzaXRlcyBtaWdodCBuZWVkIGh5cGhlbnMgYWRkZWQuIAoKYGBge3J9Cm5ld19tZWFzdXJlbWVudHMkc2FtcGxpbmdmZWF0dXJlY29kZSA8LSBnc3ViKHBhdHRlcm4gPSAiU0MgIiwgIlNDLSIsIG5ld19tZWFzdXJlbWVudHMkc2FtcGxpbmdmZWF0dXJlY29kZSkKYGBgCgpDaGVjayBzaXRlIG5hbWVzIGFnYWluIGlmIG5lZWRlZC4gSWYgdGhlcmUgYXJlIG5vIHJvd3MgcmV0dXJuZWQgdGhlbiBhbGwgdGhlIHNpdGUgbmFtZXMgbWF0Y2ggc2FtcGxpbmcgZmVhdHVyZSBjb2RlcyBpbiB0aGUgZGF0YWJhc2UuIAoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmNoZWNrX3NhbXBsaW5nZmVhdHVyZWNvZGVzKG5ld19tZWFzdXJlbWVudHMkc2FtcGxpbmdmZWF0dXJlY29kZSkKYGBgCgojIyBNZXRob2QgY29kZSAKClRoZXJlIG5lZWRzIHRvIGJlIGEgZGVmaW5lZCBtZXRob2QgaW4gdGhlIG1ldGhvZHMgdGFibGUgYWJvdXQgaG93IHNhbXBsZXMgd2VyZSBjb2xsZWN0ZWQuIE1ha2Ugc3VyZSB0aGF0IHNhbXBsZSBjb2xsZWN0aW9uIG1ldGhvZCBleGlzdHMgYnkgY2hlY2tpbmcgdGhlIFttZXRob2RzIHRhYmxlIG9ubGluZV0oaHR0cHM6Ly9wYWxtZXJsYWIudW1kLmVkdS9jaG9wdGFuay1kYi9tZXRob2RzLmh0bWwpIG9yIHJlYWRpbmcgaXQgZGlyZWN0bHkgZnJvbSB0aGUgZGF0YWJhc2UgdXNpbmcgdGhlIGBkYlJlYWRUYWJsZSgpYCBmdW5jdGlvbi4gTm90ZSB0aGUgKiptZXRob2Rjb2RlKiogb3IgbWFrZSBhIG5ldyBvbmUgd2l0aCB0aGUgYGRiX2FkZF9tZXRob2QoKWAgZnVuY3Rpb24uIAoKYGBge3J9CmRiUmVhZFRhYmxlKGRiLCBjKCJvZG0yIiwgIm1ldGhvZHMiKSkgJT4lIGthYmxlKCkKYGBgCgpgYGB7ciwgZXZhbD1GQUxTRX0KIyBkYl9hZGRfbWV0aG9kKG1ldGhvZG5hbWUgPSAiU29pbCBtb2lzdHVyZSBtZWFzdXJlbWVudCIsCiMgICAgICAgICAgICAgICBtZXRob2Rjb2RlID0gInNvaWxtb2lzdHVyZVREUiIsCiMgICAgICAgICAgICAgICBtZXRob2R0eXBlY3YgPSAiSW5zdHJ1bWVudCBkZXBsb3ltZW50IiwKIyAgICAgICAgICAgICAgIG1ldGhvZGRlc2NyaXB0aW9uID0gIlBvaW50IG1lYXN1cmVtZW50IG9mIHNvaWwgbW9pc3R1cmUgdXNpbmcgVERSIHByb2JlIHdpdGggMS41IGluY2ggcHJvYmVzIikKYGBgCgpTYXZlIHRoZSBtZXRob2Rjb2RlIGFzIGEgdmFyaWFibGUKCmBgYHtyfQptZXRob2Rjb2RlID0gInNvaWxtb2lzdHVyZVREUiIKYGBgCgojIyBEYXRlIHRpbWUgZm9ybWF0dGluZwoKQWN0aW9ucyBuZWVkIGEgY29ycmVjdGx5IGZvcm1hdHRlZCBkYXRlIGFuZCB0aW1lLiBGb3JtYXQgdGhlIGRhdGUgYW5kIHRpbWUuIElmIG5vIHRpbWUgaXMgZ2l2ZW4sIGFzc3VtZSBtaWRuaWdodCAodG8ga25vdyB0aGF0IGl0IGlzbid0IGEgc3BlY2lmaWMgdGltZSkuIEFsc28gZGVmaW5lIHRoZSBVVEMgb2Zmc2V0IHZhcmlhYmxlIChhc3N1bWluZyBpdCBpcyB0aGUgc2FtZSBhcyB5b3VyIGNvbXB1dGVyJ3MgdGltZXpvbmUpLiBVc2UgYD9zdHJwdGltZWAgdG8gZmluZCB0aGUgY29ycmVjdCBjaGFyYWN0ZXIgc3RyaW5nIGZvciB0aGUgaW5wdXQgZGF0YSBvciByZWZlciB0byBbc3RydGltZS5vcmddKGh0dHA6Ly9zdHJmdGltZS5vcmcvKQoKYGBge3J9Cm5ld19tZWFzdXJlbWVudHMkZGF0ZXRpbWUgPC0gc3RycHRpbWUoCiAgcGFzdGUobmV3X21lYXN1cmVtZW50cyREYXRlLCBuZXdfbWVhc3VyZW1lbnRzJFRpbWUpLAogIGZvcm1hdCA9ICIlQiAlZCwgJVkgJUg6JU06JVMiLCB0eiA9ICIiKQpgYGAKCkRlZmluZSBVVEMgb2Zmc2V0IGFzIGEgdmFyaWFibGUuIFRoaXMgd2lsbCBiZSBlaXRoZXIgLTQgb3IgLTUgZGVwZW5kaW5nIG9uIHdoZXRoZXIgaXRzIGRheWxpZ2h0IHNhdmluZ3MgdGltZSBvciBub3QuCgpgYGB7cn0KdXRjb2Zmc2V0IDwtIGZvcm1hdChTeXMudGltZSgpLCAiJXoiKSAlPiUgc3Vic3RyKDEsIDMpICU+JSBhcy5pbnRlZ2VyKCkKYGBgCgojIyBBbm5vdGF0aW9ucyAKCipDb21pbmcgc29vbiEqIAoKQWRkIGFuIGFubm90YXRpb24gdG8gYW4gYWN0aW9uIG9yIHJlc3VsdAoKYGBge3IsIGV2YWw9RkFMU0V9CiMgZGJSZWFkVGFibGUoZGIsIGMoIm9kbTIiLCAiY3ZfYW5ub3RhdGlvbnR5cGUiKSkKIyBkYl9hZGRfYW5ub3RhdGlvbihhbm5vdGF0aW9udHlwZWN2ID0gIkFjdGlvbiBhbm5vdGF0aW9uIiwKIyAgICAgICAgICAgICAgICAgICBhbm5vdGF0aW9udGV4dCA9ICJtZWFzdXJlZCBsZXNzIHRoYW4gMjQgaG91cnMgYWZ0ZXIgcmFpbiIpCmBgYAoKIyMgTW9yZSBtZXRhZGF0YSAKClNwZWNpZnkgc29tZSBuZWNlc3NhcnkgbWV0YWRhdGEgcGFyYW1ldGVycy4gWW91IHdpbGwgbGlrZWx5IG5vdCBuZWVkIHRvIGNoYW5nZSB0aGVzZS4gS2VsbHkgc2hvdWxkIHByb2JhYmx5IGFkZCB0aGVzZSBhcyBkZWZhdWx0IGZ1bmN0aW9uIGFyZ3VtZW50cyBzbyBpdHMgbm90IGNsdXR0ZXJpbmcgdXAgdGhpcyBkb2N1bWVudCBhcyBtdWNoLiAKCmBgYHtyfQphY3Rpb250eXBlY3YgPSAiSW5zdHJ1bWVudCBkZXBsb3ltZW50IgpyZXN1bHR0eXBlY3YgPSAiTWVhc3VyZW1lbnQiCmNlbnNvcmNvZGVjdiA9ICJOb3QgY2Vuc29yZWQiCnF1YWxpdHljb2RlY3YgPSAiVW5rbm93biIKYWdncmVnYXRpb25zdGF0aXN0aWNjdiA9ICJTcG9yYWRpYyIKdGltZWFnZ3JlZ2F0aW9uaW50ZXJ2YWwgPSAxICMgYXNzdW1lIDEgbWludXRlCnRpbWVhZ2dyZWdhdGlvbmludGVydmFsdW5pdHNpZCA9IGRiR2V0UXVlcnkoZGIsICJTRUxFQ1QgdW5pdHNpZCBGUk9NIG9kbTIudW5pdHMgV0hFUkUgdW5pdHNuYW1lID0gJ01pbnV0ZSciKSAjIG1pbnV0ZQpwcm9jZXNzbGluZ2xldmVsaWQgPSAxICMgZGVmaW5pdGlvbiA9ICJSYXcgZGF0YSIgIyBwcm9jZXNzaW5nbGV2ZWxjb2RlID0gMAp2YWx1ZWNvdW50ID0gMQpzYW1wbGVkbWVkaXVtY3YgPSAiU29pbCIKYGBgCgojIEluc2VydCBtZWFzdXJlbWVudHMgZGF0YQoKVGhpcyBpcyB3aGVyZSB0aGUgbWFnaWMgaGFwcGVucyEgVGhlIGZ1bmN0aW9uIGBkYl9pbnNlcnRfbWVhc3VyZW1lbnRzX3NtKClgIHdyaXRlcyBhbmQgcnVucyBTUUwgY29kZSB0byB1cGxvYWQgZGF0YSBmcm9tIG9uZSByb3cgb2YgdGhlIG5ld19tZWFzdXJlbWVudHMgZGF0YSBmcmFtZSBhdCBhIHRpbWUuIFNvIGBkYl9pbnNlcnRfbWVhc3VyZW1lbnRzX3NtKDEpYCB3b3VsZCBpbnNlcnQgZGF0YSBmcm9tIHRoZSBmaXJzdCByb3cuIFVzZSBhbiBhcHBseSBzdGF0ZW1lbnQgdG8gdXBsb2FkIGFsbCBvZiB0aGUgZGF0YSBhdCBvbmNlLiBUaGUgYGV2YWw9RkFMU0VgIGFyZ3VtZW50IG1lYW5zIHRoYXQgdGhpcyBjb2RlIHdpbGwgbm90IHJ1biBpZiB5b3Uga25pdCB0aGUgd2hvbGUgZG9jdW1lbnQuIAoKYGBge3IsIGV2YWw9RkFMU0V9CiMgZGJfaW5zZXJ0X21lYXN1cmVtZW50c19zbSgxKSAjIGZvciBqdXN0IG9uZSBtZWFzdXJlbWVudAoKc2FwcGx5KDE6bnJvdyhuZXdfbWVhc3VyZW1lbnRzKSwKICAgICAgIGZ1bmN0aW9uKHgpIGRiX2luc2VydF9tZWFzdXJlbWVudHNfc20oeCkpCmBgYAoKIyBRdWVyeSBkYXRhYmFzZSAKClVzZSB0aGlzIGNodW5rIHRvIG1ha2UgYSBkYXRhIGZyYW1lIGNhbGxlZCBzbV9kYiB3aXRoIGFsbCB0aGUgc29pbCBkYXRhIGluIHRoZSBkYXRhYmFzZQoKYGBge3J9CmdldF9zbV9kYXRhIDwtIGZ1bmN0aW9uKCl7CiAgCiAgc3FsIDwtIHBhc3RlMCgiU0VMRUNUIG1ydi5kYXRhdmFsdWUsIG1ydi52YWx1ZWRhdGV0aW1lLCBzZi5zYW1wbGluZ2ZlYXR1cmVjb2RlLCByLmZlYXR1cmVhY3Rpb25pZCwgdi52YXJpYWJsZWNvZGUsIHUudW5pdHNuYW1lCiBGUk9NIG9kbTIubWVhc3VyZW1lbnRyZXN1bHR2YWx1ZXMgbXJ2LCBvZG0yLnJlc3VsdHMgciwgb2RtMi52YXJpYWJsZXMgdiwgb2RtMi51bml0cyB1LCBvZG0yLnNhbXBsaW5nZmVhdHVyZXMgc2YsIG9kbTIuZmVhdHVyZWFjdGlvbnMgZmEKIFdIRVJFIHIudmFyaWFibGVpZCA9IHYudmFyaWFibGVpZCAKIEFORCByLmZlYXR1cmVhY3Rpb25pZCA9IGZhLmZlYXR1cmVhY3Rpb25pZAogQU5EIGZhLnNhbXBsaW5nZmVhdHVyZWlkID0gc2Yuc2FtcGxpbmdmZWF0dXJlaWQKIEFORCByLnVuaXRzaWQgPSB1LnVuaXRzaWQKIEFORCBtcnYucmVzdWx0aWQgPSByLnJlc3VsdGlkIAogQU5EIHIuc2FtcGxlZG1lZGl1bWN2ID0gJ1NvaWwnIikKCiAgc3FsIDwtIGdzdWIoIlxuIiwgIiIsIHNxbCkKICBkYkdldFF1ZXJ5KGRiLCBzcWwpCn0KCnNtX2RiIDwtIGdldF9zbV9kYXRhKCkKYGBgCgpvcmdhbml6ZSBiYWNrIHRvIHRoZSBvcmlnaW5hbCBmb3JtYXQgdXNpbmcgdGhlIHNwcmVhZCBmdW5jdGlvbiBpbiB0aGUgdGlkeXIgcGFja2FnZQoKYGBge3J9CnNtX2RiX3NwcmVhZCA8LSBzbV9kYiAlPiUgZ3JvdXBfYnkoc2FtcGxpbmdmZWF0dXJlY29kZSkgJT4lCiAgZHBseXI6OnNlbGVjdCgtdW5pdHNuYW1lKSAlPiUKICBzcHJlYWQodmFyaWFibGVjb2RlLCBkYXRhdmFsdWUpCmBgYAoKYGBge3J9CnNtX2RiX3NwcmVhZCAlPiUgZGF0YXRhYmxlKCkKYGBgCgoK