Nothing
# Tests for R/core.R. All CDS access is mocked; nothing touches the network.
test_that("estimate_cds_fields counts variables x timesteps x levels", {
expect_equal(estimate_cds_fields(2, "2023-01-01", "2023-01-10", "hourly"), 2L * 10L * 24L)
expect_equal(estimate_cds_fields(3, "2023-01-01", "2023-01-10", "daily"), 30L)
expect_equal(estimate_cds_fields(1, "2023-01-01", "2023-01-01", "daily", 5), 5L)
# Nov 2022 .. Feb 2024 = 16 months
expect_equal(estimate_cds_fields(2, "2022-11-15", "2024-02-01", "monthly"), 32L)
expect_equal(estimate_cds_fields(1, "2023-03-01", "2023-03-31", "monthly", 3L), 3L)
expect_type(estimate_cds_fields(1, "2023-01-01", "2023-01-02", "hourly"), "integer")
})
test_that("compute_optimal_chunk_size respects CDS limits", {
# hourly: 120000 %/% (1 * 24) = 5000 days, capped at 3650
expect_equal(compute_optimal_chunk_size(1, "hourly"), 3650L)
expect_equal(compute_optimal_chunk_size(5, "hourly"), 1000L)
expect_equal(compute_optimal_chunk_size(2, "hourly", n_pressure_levels = 10), 250L)
expect_equal(compute_optimal_chunk_size(1, "daily"), 3650L)
expect_equal(compute_optimal_chunk_size(100, "daily"), 1200L)
# never below one day
expect_equal(compute_optimal_chunk_size(10000, "hourly"), 1L)
# monthly: 9000 %/% (n * 12) years, capped to [1, 30]
expect_equal(compute_optimal_chunk_size(1, "monthly"), 30L)
expect_equal(compute_optimal_chunk_size(50, "monthly"), 15L)
expect_equal(compute_optimal_chunk_size(1000, "monthly"), 1L)
# chunk size times fields per day never exceeds the limit
for (n in c(1, 3, 7, 25)) {
days <- compute_optimal_chunk_size(n, "hourly")
expect_lte(n * 24 * days, CDS_FIELD_LIMIT_HOURLY)
}
})
test_that("convert_era5_units converts K to C and m to mm only where needed", {
df <- data.frame(
variable = c("t2m", "2m_temperature", "tp", "total_precipitation", "sp", "mx2t"),
value = c(273.15, 300, 0.001, 0.0025, 101325, 310.15),
stringsAsFactors = FALSE
)
out <- convert_era5_units(df)
expect_equal(out$value, c(0, 26.85, 1, 2.5, 101325, 37))
# untouched when columns are absent
x <- data.frame(a = 1)
expect_identical(convert_era5_units(x), x)
})
test_that("convert_era5_units strips units class", {
skip_if_not_installed("units")
df <- data.frame(variable = "t2m", stringsAsFactors = FALSE)
df$value <- units::set_units(300, "K")
out <- convert_era5_units(df)
expect_false(inherits(out$value, "units"))
expect_equal(out$value, 300 - 273.15)
})
test_that("validate_era5_inputs normalises and rejects bad inputs", {
v <- validate_era5_inputs("SINGLE", "2m_temperature", "2023-01-01", "2023-01-05")
expect_equal(v$dataset_type, "single")
expect_equal(v$start_dt, as.Date("2023-01-01"))
expect_equal(v$end_dt, as.Date("2023-01-05"))
expect_error(validate_era5_inputs("surface", "x", "2023-01-01", "2023-01-02"), "Invalid dataset_type: surface")
expect_error(
validate_era5_inputs("single", c("2m_temperature", "bogus"), "2023-01-01", "2023-01-02"),
"Incorrect variables for single dataset: bogus"
)
expect_error(
validate_era5_inputs("single", "2m_temperature", "2023-02-01", "2023-01-01"),
"The start date must be before the end date"
)
})
test_that("era5ify_bbox validates coordinates before downloading", {
local_mocked_bindings(download_era5_data = function(...) stop("must not download"))
call_bbox <- function(n, s, e, w) {
era5ify_bbox("r", "2m_temperature", "2023-01-01", "2023-01-02",
north = n, south = s, east = e, west = w, use_cache = FALSE
)
}
expect_error(call_bbox(10, 20, 80, 70), "The north boundary must be more")
expect_error(call_bbox(20, 10, 70, 80), "The east boundary must be more")
expect_error(call_bbox(95, 10, 80, 70), "Incorrect coordinates")
expect_error(call_bbox(20, 10, 190, 70), "Incorrect coordinates")
})
test_that("era5ify_point validates coordinates", {
expect_error(era5ify_point("r", "2m_temperature", "2023-01-01", "2023-01-02", lat = 91, lon = 0), "Latitude")
expect_error(era5ify_point("r", "2m_temperature", "2023-01-01", "2023-01-02", lat = 0, lon = -181), "Longitude")
})
test_that("era5ify_geojson errors for a missing GeoJSON file", {
expect_error(
era5ify_geojson("r", "2m_temperature", "2023-01-01", "2023-01-02",
json_file = file.path(tempdir(), "missing.geojson")
),
"Cannot find the GeoJSON file"
)
})
# fixture time step is the chunk start date, exposing each requested range
era5_fake_downloader <- function(log, too_large_if = function(s, e) FALSE, ...) {
log$calls <- list()
extra <- list(...)
function(variables, start_date, end_date, area = NULL, resolution = 0.25,
frequency = "hourly", output_file, ...) {
log$calls[[length(log$calls) + 1L]] <- list(
start = as.Date(start_date), end = as.Date(end_date),
area = area, output_file = output_file
)
if (too_large_if(as.Date(start_date), as.Date(end_date))) {
stop(structure(
class = c("cds_request_too_large", "error", "condition"),
list(message = "CDS request too large", call = NULL)
))
}
do.call(era5_make_nc, c(list(
path = output_file,
times = as.POSIXct(as.character(as.Date(start_date)), tz = "UTC")
), extra))
output_file
}
}
test_that("download_era5_data returns a single file when one chunk suffices", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
log <- new.env()
local_mocked_bindings(download_era5_single = era5_fake_downloader(log))
res <- suppressMessages(download_era5_data(
"single", "2m_temperature", as.Date("2023-01-01"), as.Date("2023-01-10"),
c(20, 77, 19.75, 77.5), 0.25, "hourly", d, "req"
))
expect_named(res, "file")
expect_true(file.exists(res$file))
expect_length(log$calls, 1)
expect_match(basename(log$calls[[1]]$output_file), "^req_chunk1_\\d{8}\\.nc$")
})
test_that("download_era5_data auto-chunks and combines multiple files", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
log <- new.env()
local_mocked_bindings(
download_era5_single = era5_fake_downloader(log),
compute_optimal_chunk_size = function(...) 4L
)
res <- NULL
capture.output(res <- suppressMessages(download_era5_data(
"single", "2m_temperature", as.Date("2023-01-01"), as.Date("2023-01-10"),
NULL, 0.25, "daily", d, "req",
verbose = TRUE
)))
expect_length(log$calls, 3)
starts <- do.call(c, lapply(log$calls, `[[`, "start"))
expect_equal(starts, as.Date(c("2023-01-01", "2023-01-05", "2023-01-09")))
expect_named(res, c("data", "files"))
expect_equal(nrow(res$data), 3 * 6)
expect_equal(sort(unique(as.Date(res$data$datetime))), starts)
# raw files removed unless save_raw = TRUE
expect_false(any(file.exists(res$files)))
})
test_that("download_era5_data keeps raw chunk files with save_raw", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
log <- new.env()
local_mocked_bindings(
download_era5_single = era5_fake_downloader(log),
compute_optimal_chunk_size = function(...) 5L
)
capture.output(res <- suppressMessages(download_era5_data(
"single", "2m_temperature", as.Date("2023-01-01"), as.Date("2023-01-10"),
NULL, 0.25, "daily", d, "req",
save_raw = TRUE
)))
expect_length(res$files, 2)
expect_true(all(file.exists(res$files)))
})
test_that("download_era5_data splits chunks that CDS reports as too large", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
log <- new.env()
local_mocked_bindings(download_era5_single = era5_fake_downloader(
log,
too_large_if = function(s, e) as.numeric(e - s) >= 30
))
res <- suppressMessages(download_era5_data(
"single", "2m_temperature", as.Date("2023-01-01"), as.Date("2023-03-01"),
NULL, 0.25, "hourly", d, "req"
))
ok <- Filter(function(x) as.numeric(x$end - x$start) < 30, log$calls)
ranges <- vapply(ok, function(x) paste(x$start, x$end), character(1))
expect_equal(ranges, c(
"2023-01-01 2023-01-29", "2023-01-30 2023-02-13", "2023-02-14 2023-03-01"
))
expect_match(basename(ok[[3]]$output_file), "^req_chunk1bb_")
expect_equal(nrow(res$data), 3 * 6)
})
test_that("download_era5_data gives up when splitting cannot help", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
log <- new.env()
local_mocked_bindings(download_era5_single = era5_fake_downloader(
log,
too_large_if = function(s, e) TRUE
))
expect_error(
suppressMessages(download_era5_data(
"single", "2m_temperature", as.Date("2023-01-01"), as.Date("2023-01-20"),
NULL, 0.25, "hourly", d, "req"
)),
"The request is still too large after the split to 19 days"
)
})
test_that("download_era5_data uses year-aligned chunks for monthly data", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
log <- new.env()
local_mocked_bindings(download_era5_single = era5_fake_downloader(log))
res <- suppressMessages(download_era5_data(
"single", "2m_temperature", as.Date("2020-03-01"), as.Date("2021-05-31"),
NULL, 0.25, "monthly", d, "req"
))
expect_length(log$calls, 1)
expect_equal(log$calls[[1]]$start, as.Date("2020-01-01"))
expect_equal(log$calls[[1]]$end, as.Date("2021-12-31"))
# Force multi-year chunking
local_mocked_bindings(compute_optimal_chunk_size = function(...) 2L)
capture.output(res <- suppressMessages(download_era5_data(
"single", "2m_temperature", as.Date("2015-01-01"), as.Date("2019-12-31"),
NULL, 0.25, "monthly", d, "req2"
)))
starts <- vapply(log$calls[-1], function(x) format(x$start), character(1))
ends <- vapply(log$calls[-1], function(x) format(x$end), character(1))
expect_equal(starts, c("2015-01-01", "2017-01-01", "2019-01-01"))
expect_equal(ends, c("2016-12-31", "2018-12-31", "2019-12-31"))
})
test_that("download_era5_data splits too-large monthly requests at year boundaries", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
log <- new.env()
local_mocked_bindings(download_era5_single = era5_fake_downloader(
log,
too_large_if = function(s, e) as.numeric(e - s) > 400
))
capture.output(res <- suppressMessages(download_era5_data(
"single", "2m_temperature", as.Date("2018-01-01"), as.Date("2021-12-31"),
NULL, 0.25, "monthly", d, "req"
)))
ok <- Filter(function(x) as.numeric(x$end - x$start) <= 400, log$calls)
starts <- vapply(ok, function(x) format(x$start), character(1))
expect_true(all(grepl("-01-01$", starts)))
expect_equal(length(ok), 4)
})
test_that("download_era5_data routes pressure requests", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
log <- new.env()
local_mocked_bindings(
download_era5_pressure = era5_fake_downloader(log),
download_era5_single = function(...) stop("wrong downloader")
)
res <- suppressMessages(download_era5_data(
"pressure", "temperature", as.Date("2023-01-01"), as.Date("2023-01-02"),
NULL, 0.25, "hourly", d, "req",
pressure_levels = c("500", "850")
))
expect_true(file.exists(res$file))
expect_length(log$calls, 1)
})
era5_mock_cds <- function(log = new.env(), ...) {
local_mocked_bindings(
get_cds_credentials = function(...) TRUE,
wf_request = era5_fake_wf_request(log, ...),
download_era5_timeseries = function(...) stop("network disabled in tests"),
.env = parent.frame()
)
log
}
era5_two_vars <- list(
t2m = era5_default_value,
tp = function(lon, lat, tidx) tidx / 1000
)
test_that("era5ify_bbox (hourly) returns converted, bbox-filtered data", {
era5_local_cache()
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
log <- era5_mock_cds(vars = era5_two_vars)
out <- suppressMessages(era5ify_bbox(
"bbox_req", c("2m_temperature", "total_precipitation"),
"2023-01-01", "2023-01-01",
north = 20, south = 19.9, east = 77.6, west = 77,
output_dir = d, use_cache = FALSE
))
expect_equal(nrow(out), 3 * 1 * 2 * 2)
expect_true(all(out$latitude == 20))
t1 <- out[out$variable == "t2m" & out$longitude == 77 &
format(out$datetime, "%H", tz = "UTC") == "00", "value"]
expect_equal(t1, 280 + 20 + 7.7 + 1 - 273.15)
expect_equal(sort(unique(out$value[out$variable == "tp"])), c(1, 2))
# raw download removed by default
expect_length(list.files(d, pattern = "\\.nc$"), 0)
req <- log$requests[[1]]
expect_equal(req$area, c(20, 77, 19.75, 77.75)) # snapped outward to the 0.25 degree grid
expect_equal(unlist(req$variable), c("2m_temperature", "total_precipitation"))
})
test_that("era5ify_bbox keeps raw units and raw files when asked", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
era5_mock_cds()
out <- suppressMessages(era5ify_bbox(
"raw_req", "2m_temperature", "2023-01-01", "2023-01-01",
north = 21, south = 19, east = 78, west = 76,
output_dir = d, use_cache = FALSE, convert_units = FALSE, save_raw = TRUE,
verbose = TRUE
))
expect_equal(max(out$value), 280 + 20 + 7.75 + 2)
expect_length(list.files(d, pattern = "\\.nc$"), 1)
})
test_that("era5ify_bbox (daily) aggregates to daily means in Celsius", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
era5_mock_cds()
out <- suppressMessages(era5ify_bbox(
"daily_req", "2m_temperature", "2023-01-01", "2023-01-01",
north = 21, south = 19, east = 78, west = 76,
frequency = "daily", output_dir = d, use_cache = FALSE
))
expect_equal(nrow(out), 6)
v <- out$value[out$latitude == 19.75 & out$longitude == 77.25]
expect_equal(v, mean(280 + 19.75 + 7.725 + c(1, 2)) - 273.15)
})
test_that("era5ify_bbox (monthly) uses monthly means and adds year/month", {
era5_local_cache()
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
times <- as.POSIXct(c("2023-01-01", "2023-02-01"), tz = "UTC")
log <- era5_mock_cds(times = times)
out <- suppressMessages(era5ify_bbox(
"monthly_req", "2m_temperature", "2023-01-01", "2023-02-28",
north = 21, south = 19, east = 78, west = 76,
frequency = "monthly", output_dir = d, use_cache = TRUE
))
expect_true(all(c("year", "month") %in% names(out)))
expect_setequal(unique(out$month), c(1L, 2L))
expect_equal(unique(out$year), 2023L)
expect_equal(log$requests[[1]]$dataset_short_name, "reanalysis-era5-single-levels-monthly-means")
# result was cached in the temp cache
expect_equal(nrow(suppressMessages(list_cache("era5"))), 1)
})
test_that("era5ify_bbox combines multi-chunk downloads", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
log <- new.env()
local_mocked_bindings(
download_era5_single = era5_fake_downloader(log),
compute_optimal_chunk_size = function(...) 1L
)
capture.output(out <- suppressMessages(era5ify_bbox(
"chunked", "2m_temperature", "2023-01-01", "2023-01-03",
north = 21, south = 19, east = 78, west = 76,
frequency = "daily", output_dir = d, use_cache = FALSE
)))
expect_length(log$calls, 3)
expect_equal(nrow(out), 3 * 6)
expect_equal(sort(unique(as.Date(out$datetime))), as.Date("2023-01-01") + 0:2)
})
test_that("era5ify_geojson filters to the polygon", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
gj <- era5_make_geojson(77.1, 77.6, 19.9, 20.1)
on.exit(unlink(gj), add = TRUE)
log <- era5_mock_cds()
out <- suppressMessages(era5ify_geojson(
"gj_req", "2m_temperature", "2023-01-01", "2023-01-01",
json_file = gj, output_dir = d, use_cache = FALSE, verbose = TRUE
))
expect_equal(nrow(out), 4)
expect_setequal(unique(out$longitude), c(77.25, 77.5))
expect_equal(log$requests[[1]]$area, c(20.25, 77, 19.75, 77.75)) # snapped outward to the 0.25 degree grid
expect_length(list.files(d, pattern = "\\.nc$"), 0)
daily <- suppressMessages(era5ify_geojson(
"gj_req", "2m_temperature", "2023-01-01", "2023-01-01",
json_file = gj, frequency = "daily", output_dir = d, use_cache = FALSE,
convert_units = FALSE
))
expect_equal(nrow(daily), 2)
expect_equal(daily$value[daily$longitude == 77.5], 280 + 20 + 7.75 + 1.5)
})
test_that("era5ify_geojson level = 'feature' returns one row per feature", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
box <- function(xmin, xmax, ymin, ymax) {
sf::st_as_sfc(sf::st_bbox(c(xmin = xmin, ymin = ymin, xmax = xmax, ymax = ymax)))
}
# west holds cell 77.25, east holds 77.5, tiny holds none
regions <- sf::st_sf(
name = c("west", "east", "tiny"),
geometry = c(box(77.1, 77.4, 19.9, 20.1), box(77.4, 77.6, 19.9, 20.1), box(77.30, 77.32, 19.95, 19.97)),
crs = 4326
)
gj <- tempfile(fileext = ".geojson")
on.exit(unlink(gj), add = TRUE)
sf::st_write(regions, gj, quiet = TRUE)
era5_mock_cds()
out <- suppressMessages(era5ify_geojson(
"gj_feat", "2m_temperature", "2023-01-01", "2023-01-01",
json_file = gj, frequency = "daily", output_dir = d, use_cache = FALSE,
convert_units = FALSE, level = "feature"
))
expect_equal(nrow(out), 3)
expect_false(any(c("latitude", "longitude") %in% names(out)))
out <- out[order(out$feature_id), ]
expect_equal(out$name, c("west", "east", "tiny"))
expect_equal(out$value[1:2], 280 + 20 + c(7.725, 7.75) + 1.5)
expect_equal(out$value[3], out$value[1]) # nearest cell is 77.25
})
test_that("era5ify_geojson filters combined multi-chunk data", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
gj <- era5_make_geojson(76.9, 77.1, 19.5, 20.5)
on.exit(unlink(gj), add = TRUE)
log <- new.env()
local_mocked_bindings(
download_era5_single = era5_fake_downloader(log),
compute_optimal_chunk_size = function(...) 1L
)
capture.output(out <- suppressMessages(era5ify_geojson(
"gj_chunks", "2m_temperature", "2023-01-01", "2023-01-02",
json_file = gj, frequency = "daily", output_dir = d, use_cache = FALSE
)))
expect_length(log$calls, 2)
expect_equal(nrow(out), 2 * 2) # lon 77 x 2 lats x 2 days
expect_true(all(out$longitude == 77))
})
test_that("era5ify_point (pressure) returns the nearest cell", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
log <- era5_mock_cds(vars = list(t = era5_default_value), plevel = 500)
out <- suppressMessages(era5ify_point(
"pt", "temperature", "2023-01-01", "2023-01-01",
lat = 19.8, lon = 77.3, dataset_type = "pressure", pressure_levels = "500",
output_dir = d, use_cache = FALSE
))
expect_equal(nrow(out), 2)
expect_true(all(out$latitude == 19.75 & out$longitude == 77.25))
expect_equal(sort(out$value), 280 + 19.75 + 7.725 + c(1, 2) - 273.15)
req <- log$requests[[1]]
# lat/lon +- 0.5, snapped outward to the 0.25 degree grid
expect_equal(req$area, c(20.5, 76.75, 19.25, 78))
expect_length(list.files(d, pattern = "\\.nc$"), 0)
})
test_that("era5ify_point (pressure) handles multi-chunk ranges", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
log <- new.env()
local_mocked_bindings(download_era5_pressure = era5_fake_downloader(log))
out <- suppressMessages(era5ify_point(
"pt", "temperature", "2010-01-01", "2020-02-15",
lat = 20, lon = 77.5, dataset_type = "pressure", pressure_levels = "850",
frequency = "daily", output_dir = d, use_cache = FALSE, convert_units = FALSE
))
expect_length(log$calls, 2)
expect_equal(log$calls[[2]]$start, as.Date("2010-01-01") + 3650)
expect_equal(nrow(out), 2)
expect_true(all(out$latitude == 20 & out$longitude == 77.5))
expect_equal(out$value, rep(280 + 20 + 7.75 + 1, 2))
expect_equal(as.Date(out$datetime), as.Date("2010-01-01") + c(0, 3650))
})
test_that("download_era5_data reports auto-chunking when over the field limit", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
log <- new.env()
local_mocked_bindings(download_era5_single = era5_fake_downloader(log))
capture.output(msgs <- testthat::capture_messages(download_era5_data(
"single", "2m_temperature", as.Date("2010-01-01"), as.Date("2023-12-31"),
NULL, 0.25, "hourly", d, "big",
verbose = TRUE
)))
expect_true(any(grepl("The package divides it into 3650-day chunks", msgs)))
expect_length(log$calls, 2)
expect_equal(log$calls[[2]]$start, as.Date("2010-01-01") + 3650)
vars12 <- paste0("v", 1:12)
capture.output(msgs <- testthat::capture_messages(download_era5_data(
"single", vars12, as.Date("1940-01-01"), as.Date("2023-12-31"),
NULL, 0.25, "monthly", d, "bigm",
verbose = TRUE
)))
expect_true(any(grepl("The package divides it into 3 chunk\\(s\\) of a maximum of 30 year", msgs)))
})
test_that("era5ify_point works for single-level data (default dataset_type)", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
log <- era5_mock_cds(vars = list(t2m = era5_default_value))
out <- suppressMessages(era5ify_point(
"pt", "2m_temperature", "2023-01-01", "2023-01-01",
lat = 19.8, lon = 77.3, output_dir = d, use_cache = FALSE
))
expect_gt(nrow(out), 0)
expect_true(all(out$latitude == 19.75 & out$longitude == 77.25))
expect_length(log$requests, 1)
})
test_that("era5ify_point single-level handles multi-chunk ranges", {
d <- era5_tempdir()
on.exit(unlink(d, recursive = TRUE), add = TRUE)
log <- new.env()
local_mocked_bindings(download_era5_single = era5_fake_downloader(log))
out <- suppressMessages(era5ify_point(
"pt", "2m_temperature", "2010-01-01", "2020-02-15",
lat = 20, lon = 77.5, frequency = "daily", output_dir = d,
use_cache = FALSE, convert_units = FALSE
))
expect_length(log$calls, 2)
expect_true(all(out$latitude == 20 & out$longitude == 77.5))
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.