Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

build: Add a GHA Linter #79

Merged
merged 8 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
.git
.github
misc
^\.github$
.lintr
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lintr is the R linting library and the file .lintr is its configuration.

1 change: 1 addition & 0 deletions .github/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.html
32 changes: 32 additions & 0 deletions .github/workflows/lint-project.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is autogenerated by using the tool usethis::use_github_action("lint-project")

# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
on:
push:
branches: [main]
pull_request:

name: lint-project.yaml

permissions: read-all

jobs:
lint-project:
runs-on: ubuntu-latest
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4

- uses: r-lib/actions/setup-r@v2
with:
use-public-rspm: true

- name: Install lintr
run: install.packages("lintr")
shell: Rscript {0}

- name: Lint root directory
run: lintr::lint_dir()
shell: Rscript {0}
env:
LINTR_ERROR_ON_LINT: true
6 changes: 6 additions & 0 deletions .lintr
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
linters: linters_with_defaults(
line_length_linter(120),
commented_code_linter = NULL,
object_usage_linter = NULL
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This last one is useful, but there is a bug in the way lintr works where it views internal functions and references to them as undeclared globals. I have too many internal functions to ignore each line, so disabling this for now. We could circumvent this by installing the package before linting, but that would requiring installing seurat and hdf5r which takes forever to install.

See here for more information

)

17 changes: 8 additions & 9 deletions R/cmd.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
#'
#' @noRd
louper_create_cloupe <- function(
h5path,
output_dir = NULL,
output_name = NULL,
executable_path = NULL,
force = FALSE
) {
h5path,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code was auto formatted with styler

output_dir = NULL,
output_name = NULL,
executable_path = NULL,
force = FALSE) {
# default loupe name to `converted.cloupe`
if (is.null(output_name)) {
output_name <- "converted"
Expand Down Expand Up @@ -54,10 +53,10 @@ louper_create_cloupe <- function(
return(err(sprintf("cannot find Louper executable at path: '%s'", executable_path)))
}

cmd_msg <- sprintf('running command: "%s"', paste(c(executable_path, args), collapse=" "))
logMsg(cmd_msg)
cmd_msg <- sprintf('running command: "%s"', paste(c(executable_path, args), collapse = " "))
log_msg(cmd_msg)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated the name to log_msg to satisfy the linter.


status <- system2(command=executable_path, args=args)
status <- system2(command = executable_path, args = args)

if (status == 0) {
return(SUCCESS)
Expand Down
51 changes: 33 additions & 18 deletions R/err.R
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,32 @@ err <- function(msg) {

#' The non-error variant of above
#' @noRd
SUCCESS <- list(success = TRUE, msg = NULL)
SUCCESS <- list(success = TRUE, msg = NULL) # nolint

#' validation error with link to 10x support
#' @noRd
validation_err <- function(msg, name) {
sprintf("\nIt looks like the formatting of your %s does not match the required formatting for LoupeR. For further information, please see the documentation: 10xgen.com/louper\n\n%s", name, msg)
sprintf(
paste(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use paste to fix lines that are too long

"\nIt looks like the formatting of your %s does not match the required formatting for LoupeR.",
"For further information, please see the documentation: 10xgen.com/louper\n\n%s"
),
name,
msg
)
}

#' general error with link to 10x support
#' @noRd
general_err <- function(msg, name) {
sprintf("\nIt looks like there was an issue with %s. For further information, please see the documentation: 10xgen.com/louper\n\n%s", name, msg)
sprintf(
paste(
"\nIt looks like there was an issue with %s. For further information,",
"please see the documentation: 10xgen.com/louper\n\n%s",
),
name,
msg
)
}

#' Create a Bugreport from a Seurat Object
Expand Down Expand Up @@ -49,15 +63,15 @@ create_bugreport_from_seurat <- function(obj) {
}

# overview
namedAssay <- select_assay(obj)
if (is.null(namedAssay)) {
named_assay <- select_assay(obj)
if (is.null(named_assay)) {
cat("\nSeurat:\n\n")
cat("No assay found\n")
return(invisible())
}

assay_name <- names(namedAssay)
assay <- namedAssay[[1]]
assay_name <- names(named_assay)
assay <- named_assay[[1]]
clusters <- select_clusters(obj)
projections <- select_projections(obj)
counts <- counts_matrix_from_assay(assay)
Expand All @@ -78,24 +92,25 @@ create_bugreport_from_seurat <- function(obj) {
#' This bugreport can then be included when reaching out to 10xGenomics Support or when filing
#' a Github ticket. This information should be included along with any other output when creating a Loupe file.
#'
#' @param count_mat A sparse dgCMatrix as is generated via Matrix::rsparsematrix. Rows are features, Columns are barcodes.
#' @param count_mat A sparse dgCMatrix as is generated via Matrix::rsparsematrix.
#' Rows are features, Columns are barcodes.
#' @param clusters list of factors that hold information for each barcode
#' @param projections list of matrices, all with dimensions (barcodeCount x 2)
#' @param assay_name optional string that holds the Seurat Object assay name.
#' @param seurat_obj_version optional string that holds the Seurat Object version. It is useful for debugging compatibility issues.
#' @param seurat_obj_version optional string that holds the Seurat Object version.
#' It is useful for debugging compatibility issues.
#' @param skip_metadata optional logical which skips printing metadata
#'
#' @importFrom methods is
#'
#' @export
create_bugreport <- function(
count_mat,
clusters,
projections,
assay_name = NULL,
seurat_obj_version = NULL,
skip_metadata = FALSE
) {
count_mat,
clusters,
projections,
assay_name = NULL,
seurat_obj_version = NULL,
skip_metadata = FALSE) {
# metadata
if (!skip_metadata) {
cat("\nMetadata:\n\n")
Expand All @@ -118,8 +133,8 @@ create_bugreport <- function(
cat("\nMatrix Sampling:\n\n")
all_features <- rownames(count_mat)
all_barcodes <- colnames(count_mat)
features <- sample(rownames(count_mat), size=min(10, length(all_features)))
barcodes <- sample(colnames(count_mat), size=min(10, length(all_barcodes)))
features <- sample(rownames(count_mat), size = min(10, length(all_features)))
barcodes <- sample(colnames(count_mat), size = min(10, length(all_barcodes)))
cat(sprintf("feature count: %d\n", length(all_features)))
cat(sprintf("barcode count: %d\n", length(all_barcodes)))
cat(sprintf("feature sampling:\n"))
Expand Down
2 changes: 1 addition & 1 deletion R/eula.R
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
AUTO_ACCEPT_ENV_VAR <- "AUTO_ACCEPT_EULA"
AUTO_ACCEPT_ENV_VAR <- "AUTO_ACCEPT_EULA" # nolint

#' Present EULA and Record agreement
#'
Expand Down
68 changes: 37 additions & 31 deletions R/hdf5.R
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
#' Create an hdf5 interchange file
#'
#' @param count_mat A sparse dgCMatrix as is generated via Matrix::rsparsematrix. Rows are features, Columns are barcodes.
#' @param count_mat A sparse dgCMatrix as is generated via Matrix::rsparsematrix.
#' Rows are features, Columns are barcodes.
#' @param clusters list of factors that hold information for each barcode
#' @param projections list of matrices, all with dimensions (barcodeCount x 2)
#' @param h5path path to h5 file
#' @param feature_ids optional character vector that specifies the feature ids of the count matrix. Typically, these are the ensemble ids.
#' @param seurat_obj_version optional string that holds the Seurat Object version. It is useful for debugging compatibility issues.
#' @param feature_ids optional character vector that specifies the feature ids of the count matrix.
#' Typically, these are the ensemble ids.
#' @param seurat_obj_version optional string that holds the Seurat Object version.
#' It is useful for debugging compatibility issues.
#'
#' @importFrom hdf5r H5File
#'
#' @return TRUE on success, FALSE on error
#'
#' @noRd
create_hdf5 <- function(
count_mat,
clusters,
projections,
h5path,
feature_ids,
seurat_obj_version
) {
count_mat,
clusters,
projections,
h5path,
feature_ids,
seurat_obj_version) {
if (file.exists(h5path)) {
return(err(sprintf("cannot create h5 file %s", h5path)))
}

# create hdf5 file and matrix groups
f <- hdf5r::H5File$new(h5path, mode="w")
f <- hdf5r::H5File$new(h5path, mode = "w")

write_mat(f, count_mat, feature_ids)
write_clusters(f, clusters)
Expand All @@ -40,8 +42,10 @@ create_hdf5 <- function(
#' Writes the matrix to the H5 file
#'
#' @param f An open H5File
#' @param count_mat A sparse dgCMatrix as is generated via Matrix::rsparsematrix. Rows are features, Columns are barcodes.
#' @param feature_ids optional character vector that specifies the feature ids of the count matrix. Typically, these are the ensemble ids.
#' @param count_mat A sparse dgCMatrix as is generated via Matrix::rsparsematrix.
#' Rows are features, Columns are barcodes.
#' @param feature_ids optional character vector that specifies the feature ids of the count matrix.
#' Typically, these are the ensemble ids.
#'
#' @noRd
write_mat <- function(f, count_mat, feature_ids) {
Expand All @@ -62,14 +66,16 @@ write_mat <- function(f, count_mat, feature_ids) {
matrix_group$close()

if (is.null(feature_ids)) {
feature_ids <- lapply(1:length(features), function(x) {return(sprintf("feature_%d", x))})
feature_ids <- lapply(seq_along(features), function(x) {
return(sprintf("feature_%d", x))
})
}

create_str_dataset(features_group, "name", features)
create_str_dataset(features_group, "id", as.character(feature_ids))
create_str_dataset(features_group, "id", as.character(feature_ids))
create_str_dataset(features_group, "feature_type", rep("Gene Expression", length(features)))
create_str_dataset(features_group, "_all_tag_keys", as.character()) # required features

features_group$close()
}

Expand All @@ -79,7 +85,7 @@ write_mat <- function(f, count_mat, feature_ids) {
#' @param prefix What to prefix each line
#'
#' @noRd
print_metadata <- function(metadata, prefix="") {
print_metadata <- function(metadata, prefix = "") {
for (name in names(metadata)) {
val <- metadata[[name]]

Expand Down Expand Up @@ -179,27 +185,28 @@ create_metadata <- function(seurat_obj_version = NULL) {
}

meta <- list()
meta["tool"] <- "loupeR"
meta["tool_version"] <- as.character(utils::packageVersion("loupeR"))
meta["os"] <- ifelse(is.null(sinfo$running), "Unknown", sinfo$running)
meta["system"] <- sinfo$platform
meta["language"] <- rversion$language
meta["tool"] <- "loupeR"
meta["tool_version"] <- as.character(utils::packageVersion("loupeR"))
meta["os"] <- ifelse(is.null(sinfo$running), "Unknown", sinfo$running)
meta["system"] <- sinfo$platform
meta["language"] <- rversion$language
meta["language_version"] <- language_version

extra = list()
extra["loupeR_seurat_version"] <- as.character(utils::packageVersion("Seurat"))
extra <- list()
extra["loupeR_seurat_version"] <- as.character(utils::packageVersion("Seurat"))
extra["loupeR_seurat_object_version"] <- ifelse(is.null(seurat_obj_version), "n/a", seurat_obj_version)
extra["loupeR_hdf5r_version"] <- as.character(utils::packageVersion("hdf5r"))
extra["loupeR_hdf5_version"] <- hdf5r::h5version(FALSE)
meta[["extra"]] <- extra
extra["loupeR_hdf5r_version"] <- as.character(utils::packageVersion("hdf5r"))
extra["loupeR_hdf5_version"] <- hdf5r::h5version(FALSE)
meta[["extra"]] <- extra

meta
}

#' Writes the metadata
#'
#' @param f An open H5File
#' @param seurat_obj_version optional string that holds the Seurat Object version. It is useful for debugging compatibility issues.
#' @param seurat_obj_version optional string that holds the Seurat Object version.
#' It is useful for debugging compatibility issues.
#'
#' @noRd
write_metadata <- function(f, seurat_obj_version) {
Expand Down Expand Up @@ -254,8 +261,7 @@ create_str_dataset <- function(obj, key, strs, ...) {
max_len <- max(as.numeric(lapply(strs, nchar)))
}

dtype <- hdf5r::H5T_STRING$new(size=max_len)
dtype <- hdf5r::H5T_STRING$new(size = max_len)

create_dataset(obj, key, strs, dtype=dtype, ...)
create_dataset(obj, key, strs, dtype = dtype, ...)
}

Loading
Loading