Skip to content

Commit 068ca98

Browse files
committed
adopted improvements from copilot
1 parent a78209e commit 068ca98

File tree

8 files changed

+187
-70
lines changed

8 files changed

+187
-70
lines changed

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Imports:
2323
glue,
2424
readxl,
2525
rlang,
26+
stats,
2627
stringr,
2728
tibble,
2829
yaml

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ export(short_to_longnames)
1111
export(star_to_number)
1212
export(well_from_rowcol)
1313
importFrom(rlang,.data)
14+
importFrom(stats,setNames)

R/excelDataGuide-package.R

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#' @keywords internal
2+
"_PACKAGE"
3+
4+
## usethis namespace: start
5+
#' @importFrom stats setNames
6+
## usethis namespace: end
7+
NULL

R/guide.R

Lines changed: 108 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,43 @@ read_guide <- function(path) {
1414
guide <- yaml::read_yaml(path)
1515
check_guide(guide)
1616

17-
if ('translations' %in% names(guide)) {
18-
17+
if ("translations" %in% names(guide)) {
1918
guide$translations <- dplyr::bind_rows(guide$translations)
2019

21-
# translation long and short names must be unique
22-
if (anyDuplicated(guide$translations$long)) {
23-
dup <- unique(guide$translations$long[duplicated(guide$translations$long)])
24-
rlang::abort("Duplicate keys in long names of the translations: {paste0(dup, collapse=', ')}")
25-
}
26-
27-
if (anyDuplicated(guide$translations$short)) {
28-
dup <- unique(guide$translations$short[duplicated(guide$translations$short)])
29-
rlang::abort("Duplicate keys in short names of the translations: {paste0(dup, collapse=', ')}")
30-
}
20+
# Ensure unique long and short names in translations
21+
validate_unique_names(guide$translations$long, "long")
22+
validate_unique_names(guide$translations$short, "short")
3123

24+
# Ensure reserved 'File path' and '.sourcefile' mapping
3225
if (!(".sourcefile" %in% guide$translations$short)) {
33-
if ('File path' %in% guide$translations$long) {
26+
if ("File path" %in% guide$translations$long) {
3427
rlang::abort("The 'long' variable name 'File path' is reserved for the 'short' name '.sourcefile' exclusively.")
3528
}
36-
guide$translations <- dplyr::bind_rows(guide$translations, list(long='File path', short='.sourcefile'))
29+
guide$translations <- dplyr::bind_rows(
30+
guide$translations,
31+
list(long = "File path", short = ".sourcefile")
32+
)
3733
}
3834
}
3935

4036
structure(guide, class = "guide")
4137
}
4238

39+
#' Helper function to validate unique names in translations
40+
#' @param names_vector A vector of names to check for uniqueness
41+
#' @param name_type The type of names to check (e.g., "long" or "short")
42+
#' @return An error message or nothing
43+
#' @noRd
44+
#'
45+
validate_unique_names <- function(names_vector, name_type) {
46+
if (anyDuplicated(names_vector)) {
47+
duplicates <- unique(names_vector[duplicated(names_vector)])
48+
rlang::abort(glue::glue(
49+
"Duplicate keys in {name_type} names of the translations: {paste0(duplicates, collapse = ', ')}"
50+
))
51+
}
52+
}
53+
4354
#' Abort if the guide does not contain all required elements
4455
#' @param guide A spreadsheet guide object
4556
#' @return An error message or nothing
@@ -48,40 +59,74 @@ read_guide <- function(path) {
4859
check_guide <- function(guide) {
4960
## NOTE: Most of the validation of a guide should be performed using the JSON schema
5061

51-
# TODO: make translations optional
62+
# Ensure translations are optional
63+
if (!"translations" %in% names(guide)) {
64+
guide$translations <- NULL
65+
}
5266

53-
# Conditionally required element plate.format in case we have platedata
54-
types <- unique(sapply(guide$locations, function(x) x$type))
55-
if ('platedata' %in% types) {
56-
if (!('plate.format' %in% names(guide))) {
57-
rlang::abort("The spreadsheet guide must contain the 'plate.format' element when 'platedata' is present in the locations.")
58-
}
59-
if (!(as.character(guide$plate.format) %in% names(.plateformats))) {
60-
rlang::abort(glue::glue("The plate format in the spreadsheet guide is not valid. It must be one of '24', '48', '96' or '384'."))
61-
}
67+
# Validate plate.format if platedata is present
68+
if ("platedata" %in% unique(sapply(guide$locations, `[[`, "type"))) {
69+
validate_plate_format(guide)
6270
}
6371

64-
# Check content of locations
65-
for (i in seq_along(guide$locations)) {
66-
if (guide$locations[[i]]$type == 'platedata') {
67-
for (range in guide$locations[[i]]$ranges) {
68-
check_dim(
69-
range,
70-
required_rows = .plateformats[[as.character(guide$plate.format)]]$rows + 1,
71-
required_cols = .plateformats[[as.character(guide$plate.format)]]$cols + 1
72-
)
73-
}
74-
} else {
75-
if (guide$locations[[i]]$type == 'cells') {
76-
dims <- dim(cellranger::as.cell_limits(guide$locations[[i]]$ranges[1]))
77-
# if (range.dim[range.dim > 1] != length(varnames)) {
78-
# rlang::abort(glue::glue("The length of the range ({range.dim[range.dim > 1]}) differs from the number of variables given ({length(varnames)})."))
79-
# }
80-
}
81-
}
72+
# Validate each location in the guide
73+
lapply(guide$locations, validate_location, guide)
74+
}
75+
76+
#' Helper function to validate plate.format
77+
#' @param guide A spreadsheet guide object
78+
#' @return An error message or nothing
79+
#' @noRd
80+
#'
81+
validate_plate_format <- function(guide) {
82+
if (!"plate.format" %in% names(guide)) {
83+
rlang::abort("The spreadsheet guide must contain the 'plate.format' element when 'platedata' is present in the locations.")
84+
}
85+
if (!(as.character(guide$plate.format) %in% names(.plateformats))) {
86+
rlang::abort(glue::glue("The plate format in the spreadsheet guide is not valid. It must be one of '24', '48', '96', or '384'."))
8287
}
8388
}
8489

90+
#' Helper function to validate a single location
91+
#' @param location A location object from the guide
92+
#' @param guide A spreadsheet guide object
93+
#' @return An error message or nothing
94+
#' @noRd
95+
#'
96+
validate_location <- function(location, guide) {
97+
if (location$type == "platedata") {
98+
validate_platedata_ranges(location$ranges, guide$plate.format)
99+
} else if (location$type == "cells") {
100+
validate_cells(location$ranges)
101+
}
102+
}
103+
104+
#' Helper function to validate platedata ranges
105+
#' @param ranges A list of ranges for platedata
106+
#' @param plate_format The plate format specified in the guide
107+
#' @return An error message or nothing
108+
#' @noRd
109+
#'
110+
validate_platedata_ranges <- function(ranges, plate_format) {
111+
for (range in ranges) {
112+
check_dim(
113+
range,
114+
required_rows = .plateformats[[as.character(plate_format)]]$rows + 1,
115+
required_cols = .plateformats[[as.character(plate_format)]]$cols + 1
116+
)
117+
}
118+
}
119+
120+
#' Helper function to validate cell ranges
121+
#' @param ranges A list of ranges for cells
122+
#' @return An error message or nothing
123+
#' @noRd
124+
#'
125+
validate_cells <- function(ranges) {
126+
dims <- dim(cellranger::as.cell_limits(ranges[1]))
127+
# TODO: add additional validation logic for cells if needed
128+
}
129+
85130
#' Function to check the dimensions of a range
86131
#' @param range A range object
87132
#' @param required_rows The required number of rows
@@ -91,16 +136,32 @@ check_guide <- function(guide) {
91136
#'
92137
check_dim <- function(range, required_rows = NA, required_cols = NA) {
93138
if (is.na(required_rows) && is.na(required_cols)) {
94-
rlang::abort("You must specify at least one of 'required_rows' or 'required_cols'")
139+
rlang::abort("You must specify at least one of 'required_rows' or 'required_cols'.")
95140
}
141+
96142
dims <- dim(cellranger::as.cell_limits(range))
97-
if (all(!is.na(c(required_rows, required_cols))) && any(dims != c(required_rows, required_cols))) {
98-
rlang::abort(glue::glue("The range {range} does not have the required dimensions. Expected: {required_rows} rows and {required_cols} columns"))
143+
144+
# Validate both rows and columns if both are specified
145+
if (!is.na(required_rows) && !is.na(required_cols)) {
146+
if (!all(dims == c(required_rows, required_cols))) {
147+
rlang::abort(glue::glue(
148+
"The range {range} does not have the required dimensions. Expected: {required_rows} rows and {required_cols} columns."
149+
))
150+
}
151+
return() # Exit early if both dimensions are validated
99152
}
153+
154+
# Validate rows if specified
100155
if (!is.na(required_rows) && dims[1] != required_rows) {
101-
rlang::abort(glue::glue("The range {range} does not have the required number of rows. Expected: {required_rows}"))
156+
rlang::abort(glue::glue(
157+
"The range {range} does not have the required number of rows. Expected: {required_rows}."
158+
))
102159
}
160+
161+
# Validate columns if specified
103162
if (!is.na(required_cols) && dims[2] != required_cols) {
104-
rlang::abort(glue::glue("The range {range} does not have the required number of columns. Expected: {required_cols}"))
163+
rlang::abort(glue::glue(
164+
"The range {range} does not have the required number of columns. Expected: {required_cols}."
165+
))
105166
}
106167
}

R/wells.R

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,20 @@
1515
#' normalize_wells(c("a01", "A 2", "0", " A 4 ", "A05", "H012", "K12"), 96)
1616
#' normalize_wells(c("a01", "A 2", "0", " A 4 ", "A05", "H012", "K12"))
1717
#'
18-
#'
1918
normalize_wells <- function(v, format = NULL) {
19+
# Normalize well names
2020
v <- as.character(v) |>
2121
stringr::str_remove_all(" ") |>
2222
stringr::str_to_upper() |>
2323
stringr::str_replace("(?<=[A-Z])0+", "")
24+
25+
# Validate well names if format is provided
2426
if (!is.null(format)) {
25-
v <- check_wells(v , format, returnerror = FALSE)
27+
v <- check_wells(v, format, returnerror = FALSE)
2628
}
27-
v[!stringr::str_detect(v, "^[A-Z]+\\d++$")] <- NA
29+
30+
# Mark invalid well names as NA
31+
v[!stringr::str_detect(v, "^[A-Z]+\\d+$")] <- NA
2832
v
2933
}
3034

@@ -38,24 +42,33 @@ normalize_wells <- function(v, format = NULL) {
3842
#' @param format A single element character or numeric vector with the format of the plate
3943
#' @param returnerror A logical value indicating if the function should throw an error
4044
#' @return A vector with well names or `NA` values
41-
#'
4245
#' @noRd
46+
#'
4347
check_wells <- function(wells, format, returnerror = TRUE) {
4448
format <- as.character(format)
49+
50+
# Validate plate format
4551
if (!format %in% names(.plateformats)) {
46-
rlang::abort("format must be one of '24', '48', '96', or '384'")
52+
rlang::abort("Invalid plate format. Must be one of '24', '48', '96', or '384'.")
4753
}
54+
55+
# Ensure wells is a character vector
4856
if (!is.character(wells)) {
49-
rlang::abort("wells must be a character vector")
57+
rlang::abort("The 'wells' parameter must be a character vector.")
5058
}
51-
wrongwells <- !(wells %in% .plateformats[[format]]$wellnames)
52-
if (any(wrongwells)) {
59+
60+
# Identify invalid wells
61+
invalid_wells <- !(wells %in% .plateformats[[format]]$wellnames)
62+
63+
# Handle invalid wells
64+
if (any(invalid_wells)) {
5365
if (returnerror) {
54-
rlang::abort(glue::glue("wells not present in {format}-wells format"))
66+
rlang::abort(glue::glue("The following wells are invalid for the {format}-well format: {paste(wells[invalid_wells], collapse = ', ')}"))
5567
} else {
56-
wells[wrongwells] <- NA
68+
wells[invalid_wells] <- NA
5769
}
5870
}
71+
5972
wells
6073
}
6174

@@ -70,13 +83,16 @@ check_wells <- function(wells, format, returnerror = TRUE) {
7083
#' @export
7184
#'
7285
well_from_rowcol <- function(row, col) {
86+
# Validate inputs
7387
if (any(is.na(row)) || any(is.na(col))) {
74-
rlang::abort("row and col must not contain NA values")
88+
rlang::abort("Both 'row' and 'col' must not contain NA values.")
7589
}
7690
if (length(row) != length(col)) {
77-
rlang::abort("The length of row and col must be the same")
91+
rlang::abort("The lengths of 'row' and 'col' must be the same.")
7892
}
79-
paste0(row, sprintf("%02.f", as.numeric(col)))
93+
94+
# Generate well names
95+
paste0(row, sprintf("%02d", as.numeric(col)))
8096
}
8197

8298
#' Calculate row and column from well name.
@@ -89,21 +105,29 @@ well_from_rowcol <- function(row, col) {
89105
#' rowcol_from_well(c("A1", "B2", "C3", NA), 48)
90106
#' # The order is preserved
91107
#' rowcol_from_well(c("H12", "A1"), 96)
108+
#'
92109
rowcol_from_well <- function(well, format) {
93110
format <- as.character(format)
94-
if (!length(format) == 1) {
95-
rlang::abort("Plate format must be a single element character vector")
111+
112+
# Validate plate format
113+
if (length(format) != 1) {
114+
rlang::abort("Plate format must be a single-element character vector.")
96115
}
97-
plfs <- names(.plateformats)
98-
if (!as.character(format) %in% plfs) {
99-
rlang::abort(glue::glue("Plate format must be one of ", paste0("'", plfs, "'", collapse = ", "), "."))
116+
if (!format %in% names(.plateformats)) {
117+
valid_formats <- paste(names(.plateformats), collapse = ", ")
118+
rlang::abort(glue::glue("Invalid plate format. Must be one of: {valid_formats}."))
100119
}
120+
121+
# Validate well names
101122
if (!is.character(well)) {
102-
rlang::abort("Well must be a character vector")
123+
rlang::abort("The 'well' parameter must be a character vector.")
103124
}
104-
if (any(!well[!is.na(well)] %in% .plateformats[[format]]$wellnames)) {
105-
rlang::abort(glue::glue("Wells not present in {format}-wells format"))
125+
invalid_wells <- well[!is.na(well) & !(well %in% .plateformats[[format]]$wellnames)]
126+
if (length(invalid_wells) > 0) {
127+
rlang::abort(glue::glue("The following wells are invalid for the {format}-well format: {paste(invalid_wells, collapse = ', ')}"))
106128
}
129+
130+
# Map wells to rows and columns
107131
indices <- match(well, .plateformats[[format]]$map$well)
108-
.plateformats[[format]]$map[indices, c('row', 'col')]
132+
.plateformats[[format]]$map[indices, c("row", "col")]
109133
}

man/excelDataGuide-package.Rd

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/normalize_wells.Rd

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/rowcol_from_well.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)