File: cause.R

package info (click to toggle)
r-cran-assertive.base 0.0-9-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, trixie
  • size: 476 kB
  • sloc: sh: 13; makefile: 2
file content (176 lines) | stat: -rw-r--r-- 4,812 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#' Get or set the \code{"cause"} attribute
#'
#' Gets or sets the \code{"cause"} (of failure) attribute of a variable.
#'
#' @param x Any variable.
#' @param value Passed to \code{gettextf} and stored in the \code{"cause"}
#' attribute.
#' @return The get method returns the \code{"cause"} attribute.
#' @seealso \code{\link{set_cause}}
#' @examples
#' # Scalar case
#' yn <- is_identical_to_true(FALSE)
#' cause(yn)
#' 
#' # Vector case
#' yn <- is_true(c(TRUE, FALSE, NA))
#' cause(yn)
#' @export
cause <- function(x)
{
  y <- attr(x, "cause")
  if(is.null(y))
  {
    return(noquote(character(length(x))))
  }
  y
}

#' @rdname cause
#' @export
`cause<-` <- function(x, value)
{
  # Can't use is_scalar here due to dependency on this
  if(length(value) != 1 && length(value) != length(x))
  {
    stop(
      sprintf(
        "The length of value should be 1 or the length of x (%d) but is %d.", 
        length(x),
        length(value)
      )
    )
  }
  attr(x, "cause") <- noquote(as.character(value))
  x
}

#' Set a cause and return the input
#' 
#' Sets the cause attribute of an object and returns that object.
#' @param x A variable.
#' @param false_value A character vector to set the cause to, where \code{x} is
#' \code{FALSE}.
#' @param missing_value A character vector to set the cause to, where \code{x} is
#' \code{NA}.
#' @details If \code{x} is \code{TRUE} everywhere, this returns the input 
#' without setting a cause.  Otherwise, the cause is an empty string where 
#' \code{x} is \code{TRUE}, \code{false_value} where it is \code{FALSE}, and
#' \code{missing_value} where it is \code{NA}.
#' @return \code{x}, with a new cause attribute.
#' @seealso \code{\link{cause}}, \code{\link[stats]{setNames}}
#' @export
set_cause <- function(x, false_value, missing_value = "missing")
{
  if(!anyNA(x) && all(x, na.rm = TRUE)) # fast version of all(!is.na(x) & x)
  {
    return(x)
  }
  is_na_x <- is.na(x)
  len_x <- length(x)
  # TRUEs
  cause_value <- character(len_x)
  # NAs
  if(length(missing_value) == 1)
  {
    cause_value[is_na_x] <- missing_value
  } else
  {
    missing_value <- rep_len(missing_value, len_x)
    cause_value[is_na_x] <- missing_value[is_na_x]
  }
  # FALSEs
  false_index <- !(x | is_na_x) # more efficient to calc than !x & !is_na_x
  if(length(false_value) == 1)
  {
    cause_value[false_index] <- false_value
  } else
  {
    false_value <- rep_len(false_value, len_x)
    cause_value[false_index] <- false_value[false_index]
  }
  cause(x) <- cause_value
  class(x) <- c("vector_with_cause", "logical")
  x
}

#' @rdname print.vector_with_cause
#' @method print scalar_with_cause
#' @export
print.scalar_with_cause <- function(x, ...)
{
  if(length(x) != 1L)
  {
    stop("x is malformed; it should have length 1.", domain = NA) 
  }
  print(x[1])
  cat("Cause of failure: ", cause(x), "\n")
}    

#' Print methods for objects with a cause attribute
#' 
#' Prints objects of class \code{scalar_with_cause} and 
#' \code{vector_with_cause}.
#' @param x an object of class \code{scalar_with_cause} or
#' \code{vector_with_cause}.
#' @param na_ignore A logical value.  If \code{FALSE}, \code{NA} values
#' are printed; otherwise they do not.  Like \code{na.rm} in many
#' stats package functions, except that the position of the failing
#' values does not change.
#' @param n_to_show A natural number.  The maximum number of failures 
#' to show. 
#' @param ... Currently unused.
#' @method print vector_with_cause
#' @importFrom utils head
#' @export
print.vector_with_cause <- function(x, na_ignore = FALSE, n_to_show = 10, ...)
{
  cause_x <- cause(x)
  names_x <- names(x)
  if(is.null(names_x))
  {
    names_x <- character(length(x))
  }
  x <- strip_attributes(x)
  ok <- if(na_ignore)
  {
    # ok can be TRUE or NA; FALSE is bad
    x | is.na(x)
  } else
  {
    # ok can be TRUE; FALSE or NA is bad
    x & !is.na(x)
  }
  
  # Append first few failure values and positions to the error message.
  fail_index <- which(!ok)
  n <- length(fail_index)
  fail_index <- head(fail_index, n_to_show)
  failures <- data.frame(
    Position = fail_index,
    Value    = truncate(names_x[fail_index]),
    Cause    = unclass(cause_x[fail_index]), # See bug 15997
    row.names = seq_along(fail_index)
  )
  # Slightly convoluted way of creating message done to ensure that xgettext
  # creates all the translation strings
  msg_showing_first <- if(nrow(failures) < n) 
  {
    paste0(
      " ", 
      gettextf(
        "(showing the first %d)", 
        nrow(failures), 
        domain = "R-assertive.base"
      )
    )
  } else ""
  msg_n_failures <- ngettext(
    n,
    "There was %d failure%s:\n",
    "There were %d failures%s:\n",
    domain = "R-assertive.base"
  )
  cat(enc2utf8(sprintf(msg_n_failures, n, msg_showing_first)))
  print(failures)
}