# Copyright (C) Brodie Gaslam
#
# This file is part of "unitizer - Interactive R Unit Tests"
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# Go to <https://www.r-project.org/Licenses/GPL-2> for a copy of the license.
# - Required Code -------------------------------------------------------------
#' @include conditions.R
#' @include class_unions.R
#' @include list.R
#' @include global.R
NULL
# - Validity ------------------------------------------------------------------
# Used to run "validity" methods
#
# This is to avoid having to automatically run validity on object
# instantiation as is the case with actual built-in validity objects. We can
# create a bunch of objects, and then only run validity when
#
# @param object object to validate
# @param ... additional arguments
# @return TRUE on success, character vector explaining failure otherwise
setGeneric(
# nocov start
"isValid", function(object, ...) standardGeneric("isValid")
# nocov end
)
# - Classes -------------------------------------------------------------------
# Data Produced During Evaluation of \code{`unitizer`} Test
#
# Kept separate from the \code{`\link{unitizerItem-class}`} because these
# are the slots that get compared from the new item to the reference items
# which means there are a whole bunch of other classes that need to have the
# same structure as this and by definining it we let those other classes
# confirm they have the correct structure.
setClass(
"unitizerItemData",
representation(
value="list",
conditions="conditionList",
output="character",
message="character",
aborted="logical" # more generally, should this be a withRestarts slot?
) )
# Full Representation Of an Evaluated \code{`unitizer`} Test
#
# Note we have both a `call` and `call.dep` object due to the
#
# @slot call the call that is tested
# @slot call.dep deparsed version of the call
# @slot reference whether this is a reference or new \code{`unitizerItem`}
# @slot ignore whether this test should be treated as a test or just a step
# towards compiling a test
# @slot environment the environment the test was evaluated in, should contain
# all relevant objects
# @slot data the container for the results of the evaluation of \code{`call`}
# @slot the \code{`unitizer_sect`} from the newly added items this test item
# corresponds to; can be NA if section not known; this is used primarily to
# keep track of original sections when storing reference tests.
setClass(
"unitizerItem",
representation(
call="ANY",
call.dep="character",
id="integer",
reference="logical",
env="environmentOrNULL",
ignore="logical",
ls="data.frame",
comment="characterOrNULL",
trace="list",
data="unitizerItemData",
section.id="integer",
section.name="character",
glob.indices="unitizerGlobalIndices",
state="unitizerGlobalState"
),
prototype(
reference=FALSE, ignore=FALSE, id=1L,
ls=data.frame(names=character(), status=character()),
section.id=NA_integer_
) )
unitizerItemSlotNames <- slotNames("unitizerItem") # cache names
setMethod("isValid", "unitizerItem",
function(object) {
if(!identical(length(object@reference), 1L))
return("Slot `@reference` must be length 1")
if(!identical(length(object@id), 1L) || !isTRUE(object@id > 0))
return("Slot `@id` must be length 1 and greater than zero.")
if(!identical(names(object@ls), c("names", "status")) ||
!identical(unname(vapply(object@ls, class, "")), rep("character", 2L))) {
return("Slot `@ls` has incorrect data structure") # nocov
}
if(
length(object@section.id) != 1L || (
!is.na(object@section.id) && object@section.id < 1L # not 100% about allowing NA section ids, seems required for sectionless reference tests
)
) {
return("Slot `@section.id` must be integer(1L) >= 1L") # nocov
}
if(!identical(length(object@section.name), 1L))
return("Slot `@section.name` must be character(1L)")
TRUE
} )
setClassUnion("unitizerItemOrNULL", c("unitizerItem", "NULL"))
# Initialize A \code{`\link{unitizerItem-class}`}
#
# Makes the fact that most of the data needs to be part of a
# \code{`\link{unitizerItemData-class}`} object transparent to the user.
#' @rdname unitizer_s4method_doc
setMethod("initialize", "unitizerItem", function(.Object, ...) {
dots.all <- list(...)
dots.names <- names(dots.all)
if("call" %in% dots.names) {
.Object@call <- dots.all$call
.Object@call.dep <- deparse_call(dots.all$call)
} else .Object@call <- NULL
if("env" %in% dots.names) .Object@env <- dots.all$env
if("comment" %in% dots.names) .Object@comment <- dots.all$comment
if("trace" %in% dots.names) .Object@trace <- dots.all$trace
if("reference" %in% dots.names) .Object@reference <- dots.all$reference
if("glob.indices" %in% dots.names)
.Object@glob.indices <- dots.all$glob.indices
dots <- dots.all[!(dots.names %in% unitizerItemSlotNames)]
if("ignore" %in% dots.names) {
.Object@ignore <- dots.all$ignore
if(.Object@ignore) dots[["value"]] <- new("unitizerDummy")
}
dots[["value"]] <- list(dots[["value"]]) # to avoid S3 validity issues
.Object@data <- do.call("new", c(list("unitizerItemData"), dots), quote=TRUE)
.Object
} )
# Collection of \code{\link{unitizerItem-class}} Objects
setClass("unitizerItems", contains="unitizerList",
representation(
base.env="environment" # should be enclosure of first item
),
validity=function(object) {
if(!all(vapply(object@.items, is, logical(1L), "unitizerItem")))
return("slot `items` may only contain objects \"unitizerItem\"")
if(!(obj.len <- length(object))) return(TRUE)
# Need to test items in addition to what the unitizerList validity does
# because we cannot actually have a validity method attached to
# each unitizer item (way too slow)
idx.to.test <- unique(
c(1L, max(1L, as.integer(floor(obj.len / 2))), obj.len))
test <- lapply(as.list(object[idx.to.test]), isValid)
success <- vapply(test, isTRUE, logical(1L))
if(all(success)) TRUE else unlist(test[!success])
} )
setClassUnion("unitizerItemsOrNULL", c("unitizerItems", "NULL"))
# - Single Object Methods -----------------------------------------------------
# Display a \code{`\link{unitizerItem-class}`} Object
#
# Highly summarized view of the unitizer object.
#' @rdname unitizer_s4method_doc
setMethod("show", "unitizerItem",
function(object) {
cat("~~~ ")
if(object@reference) cat("Reference") else cat("New")
cat(" Test ~~~\n")
cat(object@call.dep, sep="\n")
cat(
"* value:",
paste0(
desc(object@data@value[[1L]], limit=getOption("width") - 7L), "\n"
) )
if(out.len <- sum(nchar(object@data@output)))
cat("* output:", out.len, "chars\n")
if(err.len <- sum(nchar(object@data@message)))
cat("* message:", err.len, "chars\n")
if(cond.len <- length(object@data@conditions)) {
cond.types <- vapply(
as.list(object@data@conditions),
function(x) {
if(inherits(x, "error")) {
"error"
} else if (inherits(x, "warning")) {
"warning"
} else if (inherits(x, "message")) {
"message"
} else {
"other"
} },
character(1L)
)
cond.types.summ <- Filter(
Negate(is.na),
tapply(
cond.types,
factor(
cond.types,
levels=c("error", "warning", "message", "other")),
length
) )
cat(
"* conditions:",
paste0(
cond.types.summ, " ",
paste0(
names(cond.types.summ),
ifelse(cond.types.summ > 1L, "s", "")
), "\n", collapse=", "
) )
}
cat(
"\nAccess components with `$`, e.g.",
paste0("`", if(object@reference) ".REF" else ".NEW", "$value`;"),
"see `help(\"$\", \"unitizer\")`\n"
)
} )
# Methods to Track Whether a \code{\link{unitizerItem-class}} Object is New Or Reference
#
# Necessitated due to the awkward structure around
# \code{\link{reviewNext,unitizerBrowse-method}}, where the only return value is
# a \code{\link{unitizerItems-class}} object and there is no easy way to tell
# which objects have been kept from reference vs which ones are
# new.
# nocov start
setGeneric("itemType", function(x, ...) standardGeneric("itemType"))
setGeneric("itemType<-", function(x, value) standardGeneric("itemType<-"))
setGeneric("itemsType<-", function(x, value) standardGeneric("itemsType<-"))
setGeneric("itemsType", function(x, ...) standardGeneric("itemsType"))
# nocov end
setMethod(
"itemType", "unitizerItem",
function(x) if(x@reference) "reference" else "new"
)
setReplaceMethod("itemType", c("unitizerItem", "character"),
function(x, value) {
if(!(value %in% c("new", "reference")))
stop("Argument `value` must be in ", deparse(c("new", "reference")))
x@reference <- identical(value, "reference")
x
} )
setMethod("itemsType", "unitizerItems",
function(x) {
vapply(
as.list(x),
function(y) if(y@reference) "reference" else "new", character(1L)
)
} )
setReplaceMethod("itemsType", c("unitizerItems", "character"),
function(x, value) {
if(length(value) != 1L & !identical(length(x), length(value))) {
stop(
"Argument `value` must be length 1L or have same length as argument `x`")
}
if(!all(value %in% c("reference", "new")))
stop("Argument `value` may only contain ", deparse(c("new", "reference")))
if(length(x)) {
x@.items <- mapply(function(y, z) {
y@reference <- identical(z, "reference")
y
},
x@.items, value, SIMPLIFY=F
)
}
x
} )
setGeneric("ignored", function(x, ...) standardGeneric("ignored"))
# Determines Which Items In \code{\link{unitizerItems-class}} Are Not Full Tests
#
# In order to simplify user interaction, some statements are not considered
# to be tests, rather, they are set up steps for the actual test. At the
# time of this writing, top level assignments are included in this group.
setMethod(
"ignored", "unitizerItems",
function(x, ...) vapply(as.list(x), function(y) y@ignore, logical(1L))
)
setMethod("ignored", "unitizerItem", function(x, ...) x@ignore)
# - Multi Object Methods -------------------------------------------------------
# Add a \code{\link{unitizerItem-class}} to a \code{\link{unitizerItems-class}}
#' @rdname unitizer_s4method_doc
setMethod("+", c("unitizerItems", "unitizerItemOrNULL"),
function(e1, e2) {
if(is.null(e2)) return(e1)
e1 <- append(e1, list(e2))
e1
} )
# Add a \code{\link{unitizerItem-class}} to a \code{\link{unitizerItems-class}}
#' @rdname unitizer_s4method_doc
setMethod("+",
c("unitizerItems", "unitizerItems"),
function(e1, e2) append(e1, e2)
)
#' Retrieve Test Contents From Test Item
#'
#' Intended for use within the \code{unitizer} interactive environment, allows
#' user to retrieve whatever portions of tests are stored by \code{unitizer}.
#'
#' Currently the following elements are available:
#'
#' \itemize{
#' \item \code{call} the call that was tested as an unevaluated call,
#' but keep in mind that if you intend to evaluate this for a reference
#' item the environment may not be the same so you could get different
#' results (\code{ls} will provide more details)
#' \item \code{value} the value that results from evaluating the test, note
#' this is equivalent to using \code{.new} or \code{.ref}; note that the
#' value is displayed using \code{\link{desc}} when viewing all of
#' \code{.NEW} or \code{.REF}
#' \item \code{output} the screen output (i.e. anything produced by cat/print,
#' or any visible evaluation output) as a character vector
#' \item \code{message} anything that was output to \code{stderr}, mostly
#' this is all contained in the conditions as well, though there could be
#' other output here, as a character vector
#' \item \code{conditions} a \code{\link{conditionList}} containing all
#' the conditions produced during test evaluation
#' \item \code{aborted} whether the test call issues a restart call to the
#' `abort` restart, as `stop` does.
#' }
#' @export
#' @aliases $,unitizerItem-method
#' @name $.unitizerItem
#' @rdname extract-unitizerItem-method
#' @param x a \code{unitizerItem} object, typically \code{.NEW} or \code{.REF}
#' at the \code{unitizer} interactive prompt
#' @param name a valid test sub-component
#' @param i a valid test sub-component as a character string, or a sub-component
#' index
#' @param j missing for compatibility with generic
#' @param ... missing for compatibility with generic
#' @param exact unused, always matches exact
#'
#' @return the test component requested
#' @examples
#' ## From the unitizer> prompt:
#' .NEW <- mock_item() # .NEW is normally available at unitizer prompt
#' .NEW$call
#' .NEW$conditions
#' .NEW$value # equivalent to `.new`
setMethod("$", c("unitizerItem"),
function(x, name) {
what <- substitute(name)
what <- if(is.symbol(what)) as.character(what) else name
x[[what]]
} )
#' @export
#' @rdname extract-unitizerItem-method
setMethod(
"[[", signature=c(x="unitizerItem"),
function(x, i, j, ..., exact=TRUE) {
what <- i
data.slots <- slotNames(x@data)
extras <- c("call", "state")
valid <- c(extras, data.slots)
if(identical(what, "call")) return(parse(text=x@call.dep)[[1L]])
if(identical(what, "state")) return(x@state)
if(length(what) != 1L || !what %in% data.slots) {
stop(
"Argument `name` must be in ",
paste0(deparse(valid, width.cutoff=500L), collapse="")
) }
if(identical(what, "value")) return(x@data@value[[1L]])
slot(x@data, what)
}
)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.